Sunday, September 27, 2026

Connect to SharePoint Online with Connect-PnPOnline

Goal: Starting from a fresh Windows PC, finish by running one PowerShell command that reads the title of a SharePoint site.
Who it's for: Anyone. You don't need PowerShell experience, just copy, paste and follow the screenshots.


At a glance

ItemDetails
⏱️ Time needed15–20 minutes (first time)
💻 ComputerWindows 10 / 11
👤 AccountA Microsoft 365 work account with access to the SharePoint site
🔑 One-time admin helpSomeone with Global Administrator (or an admin role that can grant consent) for Step 3
🎯 End result(Get-PnPWeb).Title prints your site title

The workflow

Workflow: install PowerShell 7, install PnP.PowerShell, register the Entra ID app, connect, read the site title
Workflow: install PowerShell 7, install PnP.PowerShell, register the Entra ID app, connect, read the site title

Key terms (30-second glossary)

TermWhat it means in plain English
PowerShell 7The modern command window from Microsoft. Not the same as the built-in "Windows PowerShell 5.1".
PnP.PowerShellA free add-on (module) with 700+ SharePoint and Microsoft 365 commands.
Connect-PnPOnlineThe command that signs you in to a SharePoint site. Every other PnP command needs it first.
Entra ID app registrationAn "ID card" for PnP PowerShell in your tenant. Since September 2024, every tenant needs its own.
Client IDThe ID number of that app registration. You pass it to Connect-PnPOnline.
Admin consentA one-time approval by an admin that allows the app to access SharePoint.

Step 1: Install PowerShell 7

Why not the built-in PowerShell?

Windows PowerShell 5.1PowerShell 7
Comes with Windows✅ Yes❌ Install once
Runs PnP.PowerShell 3.x❌ No✅ Yes (needs 7.4.0 or later)
Window title"Windows PowerShell""PowerShell" / "PowerShell 7"
Start commandpowershellpwsh

If you run PnP commands in the old one, you get this error:

Windows PowerShell 5.1 does not recognise Connect-PnPOnline
Windows PowerShell 5.1 does not recognise Connect-PnPOnline

Install it

#Action
1Open Start → type PowerShell → open Windows PowerShell (any window works for this step)
2Check the latest version: winget search --id Microsoft.PowerShell --exact
3Install: winget install --id Microsoft.PowerShell --source winget
4Close the window. Open Start → type PowerShell 7 → open it
5Confirm the version: $PSVersionTable.PSVersion → Major must be 7
winget search --id Microsoft.PowerShell --exact
winget install --id Microsoft.PowerShell --source winget
winget search and install PowerShell 7
winget search and install PowerShell 7
pwsh
$PSVersionTable.PSVersion
Switch to PowerShell 7 and confirm the version
Switch to PowerShell 7 and confirm the version

💡 Tip: Already inside Windows PowerShell? Just type pwsh and press Enter to switch to PowerShell 7 in the same window.


Step 2: Install the PnP.PowerShell module

Run this inside PowerShell 7:

Install-Module PnP.PowerShell -Scope CurrentUser -Force
Get-Module PnP.PowerShell -ListAvailable | Select-Object Name, Version
PartMeaning
-Scope CurrentUserInstalls for you only, so no admin rights are needed
-ForceSkips the "untrusted repository" prompt
Get-Module ... -ListAvailableProves it installed and shows the version
PnP.PowerShell installed
PnP.PowerShell installed

⚠️ Common mistake: Installing the module from Windows PowerShell 5.1 puts it in a different folder. PowerShell 7 then says Connect-PnPOnline is not recognized. Always install from PowerShell 7.


Step 3: Create the Entra ID app registration (one time per tenant)

Pick one option.

Option A: Azure portalOption B: One command
Best forVisual learners, full controlSpeed
Clicks~151 command + sign-in
NeedsAdmin who can grant consentSame

Option A: Azure portal (click-by-click)

A1. Open App registrations

#Action
1Go to https://portal.azure.com and sign in
2In the top search bar type App registrations → open it
3Click + New registration
App registrations → New registration
App registrations → New registration

A2. Fill in the registration form

FieldValue
NamePnP.PowerShell (any name works)
Supported account typesSingle tenant only (default)
Redirect URI → platformPublic client/native (mobile & desktop)
Redirect URI → valuehttp://localhost

Click Register.

Register an application form
Register an application form

A3. Copy the Client ID

On the Overview page, copy Application (client) ID and save it in Notepad. You'll need it in Step 4. The Directory (tenant) ID is here too.

Overview page: Application (client) ID
Overview page: Application (client) ID

A4. Add the SharePoint permission

#Action
1Left menu → API permissions → + Add a permission
2Choose SharePoint
3Choose Delegated permissions (you sign in as yourself)
4Search AllSites → tick the permission you need (see table below)
5Click Add permissions
SharePoint → Delegated → AllSites permissions
SharePoint → Delegated → AllSites permissions
PermissionWhat you can doAdmin consent?
AllSites.ReadRead sites, lists, items (enough for this guide)No
AllSites.WriteRead + write list itemsNo
AllSites.ManageRead + write items and listsNo
AllSites.FullControlEverything, including site settingsYes

🔒 Least privilege: Start with AllSites.Read. Add more only when you need it.

A5. Grant admin consent

Click ✓ Grant admin consent for <your org> → Yes. The Status column turns green: Granted for …

API permissions with admin consent granted
API permissions with admin consent granted

A6. Check the Authentication settings

WhereWhat to check
Authentication → Redirect URI configurationMobile and desktop applications → http://localhost
Authentication → SettingsAllow public client flows = Enabled (needed for -DeviceLogin)
Redirect URI http://localhost
Redirect URI http://localhost
Allow public client flows enabled
Allow public client flows enabled

Option B: One command (PnP does it for you)

Run in PowerShell 7 and sign in with an admin account when the browser opens:

Register-PnPEntraIDAppForInteractiveLogin -ApplicationName "PnP.PowerShell" -Tenant "contoso.onmicrosoft.com"
What it does automatically
Creates the app registration✅
Adds redirect URI + default permissions✅
Prompts you to grant consent✅
Prints the Client ID at the end✅ Copy it

💡 Add -SharePointDelegatePermissions "AllSites.Read" to choose the exact permission.
Add -DeviceLogin if no browser pops up (sign in with a code instead).


Step 4: Connect to the SharePoint site

Replace the two values, then run:

Connect-PnPOnline -Url "https://contoso.sharepoint.com/sites/pub" -ClientId "<your-client-id>" -Interactive
ParameterValue to use
-UrlFull site address from your browser, e.g. https://contoso.sharepoint.com/sites/pub
-ClientIdThe Application (client) ID from Step 3
-InteractiveOpens a Microsoft sign-in window → sign in with your work account

No output = success. PowerShell just returns to the prompt.

No browser pop-up? Use a device code instead

Connect-PnPOnline -Url "https://contoso.sharepoint.com/sites/pub" -ClientId "<your-client-id>" -Tenant "contoso.onmicrosoft.com" -DeviceLogin
#Action
1PowerShell shows a code, e.g. ABCD1234
2Open https://microsoft.com/devicelogin in any browser
3Enter the code → sign in → return to PowerShell

Step 5: Read the site title 🎯

(Get-PnPWeb).Title
Connect-PnPOnline and site title output
Connect-PnPOnline and site title output

✅ You did it. You're connected, and PowerShell printed the site title.

Bonus: a few more read-only commands to try

CommandShows
Get-PnPWeb -Includes Created | Select Title, Url, CreatedTitle, address, creation date
Get-PnPList | Select Title, ItemCountAll lists and libraries with item counts
Get-PnPSiteCollectionAdminSite collection admins
Disconnect-PnPOnlineSigns out of the session

Save it as a reusable script

Save as Get-SiteTitle.ps1, then run .\Get-SiteTitle.ps1 from PowerShell 7:

param(
    [string]$SiteUrl  = "https://contoso.sharepoint.com/sites/pub",
    [string]$ClientId = "<your-client-id>",
    [string]$Tenant   = "contoso.onmicrosoft.com",
    [switch]$DeviceLogin
)

if (-not (Get-Module -ListAvailable PnP.PowerShell)) {
    Install-Module PnP.PowerShell -Scope CurrentUser -Force
}
Import-Module PnP.PowerShell

if ($DeviceLogin) {
    Connect-PnPOnline -Url $SiteUrl -ClientId $ClientId -Tenant $Tenant -DeviceLogin
} else {
    Connect-PnPOnline -Url $SiteUrl -ClientId $ClientId -Interactive
}

$web = Get-PnPWeb -Includes Title, Url, Created
Write-Host "Title   : $($web.Title)"
Write-Host "Url     : $($web.Url)"
Write-Host "Created : $($web.Created)"

Disconnect-PnPOnline

💡 Skip typing -ClientId every time. Save it once as an environment variable, then open a new PowerShell 7 window:

[Environment]::SetEnvironmentVariable("ENTRAID_APP_ID", "<your-client-id>", "User")

Troubleshooting

SymptomCauseFix
Connect-PnPOnline is not recognizedYou're in Windows PowerShell 5.1, or the module was installed thereType pwsh, then re-run Install-Module PnP.PowerShell -Scope CurrentUser -Force
pwsh is not recognized right after installPATH not refreshedClose and reopen the window, or open PowerShell 7 from Start
AADSTS700016: Application ... not foundWrong Client ID or wrong tenantCopy the Client ID again from Overview
AADSTS65001: ... has not consentedAdmin consent missingAPI permissions → Grant admin consent
AADSTS7000218: ... client_assertion or client_secretPublic client flows disabled (device login)Authentication → Settings → Allow public client flows = Enabled
403 Forbidden / Access deniedYour account has no access to that siteAsk the site owner to add you as a member/visitor
running scripts is disabled on this systemExecution policySet-ExecutionPolicy -Scope CurrentUser RemoteSigned
Sign-in window never appearsPop-up blocked / remote sessionUse -DeviceLogin

Cheat sheet

TaskCommand
Install PowerShell 7winget install --id Microsoft.PowerShell --source winget
Switch to PowerShell 7pwsh
Check version$PSVersionTable.PSVersion
Install PnPInstall-Module PnP.PowerShell -Scope CurrentUser -Force
Update PnPUpdate-Module PnP.PowerShell
Create the app (one command)Register-PnPEntraIDAppForInteractiveLogin -ApplicationName "PnP.PowerShell" -Tenant "contoso.onmicrosoft.com"
Connect (browser)Connect-PnPOnline -Url "<site-url>" -ClientId "<client-id>" -Interactive
Connect (device code)Connect-PnPOnline -Url "<site-url>" -ClientId "<client-id>" -Tenant "<tenant>.onmicrosoft.com" -DeviceLogin
Site title(Get-PnPWeb).Title
Sign outDisconnect-PnPOnline

References

SourceLink
Install PowerShell 7 on Windows (Microsoft Learn)https://learn.microsoft.com/powershell/scripting/install/install-powershell-on-windows
PnP PowerShell: Installationhttps://pnp.github.io/powershell/articles/installation.html
PnP PowerShell: Register an Entra ID applicationhttps://pnp.github.io/powershell/articles/registerapplication.html
Connect-PnPOnline referencehttps://pnp.github.io/powershell/cmdlets/Connect-PnPOnline.html
Grant tenant-wide admin consent (Microsoft Learn)https://learn.microsoft.com/entra/identity/enterprise-apps/grant-admin-consent

Tags: #SharePointOnline #PnPPowerShell #PowerShell7 #EntraID #Microsoft365 #M365Admin

Friday, September 25, 2026

Assigning Microsoft Graph Permissions to a Managed Identity: Application vs Delegated

Assigning Microsoft Graph Permissions to a Managed Identity: Application vs Delegated (with ServiceMessage Examples)

You've enabled a system-assigned managed identity on an Azure Automation Account, and the runbook needs to read Microsoft 365 Message Center posts through Microsoft Graph. You open Entra ID → Enterprise applications → your identity → Permissions and there's no Add a permission button, and Grant admin consent is greyed out.

This article explains why, how to assign the permission with PowerShell, and why one permission people commonly ask for (ServiceMessageViewpoint.Write) fails with a confusing Edm.Guid error.


1. Why the portal can't do it

A managed identity is a service principal with no app registration behind it. In the portal, the API permissions blade (where you click Add a permission → Microsoft Graph) exists only on app registrations. The enterprise app Permissions blade just shows what has already been granted.

To give a managed identity Graph permissions, you create the grant directly on its service principal:

Permission typeGraph object createdPowerShell cmdletWorks for managed identity at runtime?
Application (app role)appRoleAssignmentNew-MgServicePrincipalAppRoleAssignment✅ Yes
Delegated (scope)oauth2PermissionGrantNew-MgOauth2PermissionGrant❌ No, a managed identity has no signed-in user

You can do this with PowerShell, the Azure CLI (az rest), or Graph Explorer. All three call the same Graph API.


2. Know your permission before you assign it

The service message permissions are a good example of how application and delegated permissions differ. From the Microsoft Graph permissions reference:

PermissionApplicationDelegatedPurpose
ServiceMessage.Read.All✅ 1b620472-6534-4fe6-9df2-4680e8aa28ec✅Read Message Center posts
ServiceHealth.Read.All✅ 79c261e0-fe76-4144-aad5-bdc68fbe4037✅Read service health and incidents
ServiceMessageViewpoint.Write❌ none✅Mark posts read, archived or favourite for the signed-in user

Two things to note:

  • There is no ServiceMessage.ReadWrite.All. For app-only automation, ServiceMessage.Read.All is the highest service message permission available.
  • ServiceMessageViewpoint.Write is delegated only. Read, archived and favourite status is stored per user, so Graph doesn't offer it for app-only access.

Tip: Permission names are the same in both lists, but their GUIDs differ. Always look up the ID from the correct collection: AppRoles for application permissions, Oauth2PermissionScopes for delegated ones.


3. Prerequisites

ItemRequirement
RoleGlobal Administrator or Privileged Role Administrator
ModulesMicrosoft.Graph.Applications, Microsoft.Graph.Identity.SignIns
IdentityManaged identity enabled on the resource (e.g. Automation Account → Identity → System assigned → On)

4. Assign an application permission (the one your runbook can use)

Run these blocks one at a time. Replace aa-automation-demo with your resource name.

# 1. Install and import the module (first time only)
Install-Module Microsoft.Graph.Applications -Scope CurrentUser -Force -AllowClobber
Import-Module Microsoft.Graph.Applications

# 2. Sign in
Connect-MgGraph -Scopes "Application.Read.All","AppRoleAssignment.ReadWrite.All" -NoWelcome

# 3. Get the managed identity's service principal
$miName = "aa-automation-demo"
$miSp = Get-MgServicePrincipal -Filter "displayName eq '$miName'"
$miSp | Select-Object DisplayName, Id, ServicePrincipalType     # expect: ManagedIdentity

# 4. Get the Microsoft Graph service principal
$graphSp = Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'"

# 5. Find the application permission (app role)
$role = $graphSp.AppRoles | Where-Object { $_.Value -eq "ServiceMessage.Read.All" -and $_.AllowedMemberTypes -contains "Application" }
$role | Select-Object Value, Id                                  # must return a row

# 6. Check whether it's already assigned
$existing = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $miSp.Id -All |
    Where-Object { $_.ResourceId -eq $graphSp.Id -and $_.AppRoleId -eq $role.Id }

# 7. Assign it
if (-not $role) {
    Write-Warning "Not an application permission. Check the name, or use the delegated section."
} elseif ($existing) {
    Write-Host "Already assigned" -ForegroundColor Yellow
} else {
    New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $miSp.Id -PrincipalId $miSp.Id -ResourceId $graphSp.Id -AppRoleId $role.Id
    Write-Host "Assigned $($role.Value)" -ForegroundColor Green
}

# 8. Check the result
Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $miSp.Id -All |
    Where-Object { $_.ResourceId -eq $graphSp.Id } |
    ForEach-Object { $rid = $_.AppRoleId; ($graphSp.AppRoles | Where-Object { $_.Id -eq $rid }).Value }

Disconnect-MgGraph

Refresh Enterprise applications → your identity → Permissions in the portal. The permission shows with type Application.


5. The Edm.Guid error explained

If you put ServiceMessageViewpoint.Write into step 5 above, you'll get:

New-MgServicePrincipalAppRoleAssignment : Cannot convert the literal '' to the expected type 'Edm.Guid'.
Status: 400 (BadRequest)
ErrorCode: Request_BadRequest
SymptomCause
Step 5 returns nothingThe permission isn't in $graphSp.AppRoles because it's delegated-only
$role.Id is emptyGraph receives appRoleId: ""
Edm.Guid 400 errorAn empty string can't be converted to a GUID

To confirm which list a permission belongs to:

# Application permissions
$graphSp.AppRoles | Where-Object { $_.Value -like "ServiceMessage*" } | Select-Object Value, Id

# Delegated permissions
$graphSp.Oauth2PermissionScopes | Where-Object { $_.Value -like "ServiceMessage*" } | Select-Object Value, Id

6. Granting a delegated permission to a managed identity (and why it won't help)

You can grant a delegated scope to a managed identity's service principal with an admin-consent (AllPrincipals) oauth2PermissionGrant. It will then appear on the Permissions blade with type Delegated:

Import-Module Microsoft.Graph.Identity.SignIns
Connect-MgGraph -Scopes "Application.Read.All","DelegatedPermissionGrant.ReadWrite.All" -NoWelcome

$miSp    = Get-MgServicePrincipal -Filter "displayName eq 'aa-automation-demo'"
$graphSp = Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'"
$scopeName = "ServiceMessageViewpoint.Write"

$grant = Get-MgOauth2PermissionGrant -All |
    Where-Object { $_.ClientId -eq $miSp.Id -and $_.ResourceId -eq $graphSp.Id -and $_.ConsentType -eq "AllPrincipals" }

if (-not $grant) {
    New-MgOauth2PermissionGrant -BodyParameter @{
        clientId    = $miSp.Id
        consentType = "AllPrincipals"
        resourceId  = $graphSp.Id
        scope       = $scopeName
    }
} elseif (($grant.Scope -split " ") -contains $scopeName) {
    Write-Host "Already granted" -ForegroundColor Yellow
} else {
    Update-MgOauth2PermissionGrant -OAuth2PermissionGrantId $grant.Id -Scope (($grant.Scope.Trim() + " " + $scopeName).Trim())
}

Disconnect-MgGraph

But the runbook still can't use it. A managed identity always gets an app-only token, and app-only tokens contain roles (application permissions), never scp (delegated scopes). Calls to markRead or archive from the runbook will still return 403.


7. Marking Message Center posts read or archived (the delegated way)

If you need viewpoint actions, run them as a signed-in admin:

Connect-MgGraph -Scopes "ServiceMessage.Read.All","ServiceMessageViewpoint.Write" -NoWelcome

$body = @{ messageIds = @("MC000001","MC000002") } | ConvertTo-Json
$base = "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages"

Invoke-MgGraphRequest -Method POST -Uri "$base/markRead" -Body $body -ContentType "application/json"
Invoke-MgGraphRequest -Method POST -Uri "$base/archive"  -Body $body -ContentType "application/json"
ActionEndpoint
Mark read / unread/markRead, /markUnread
Archive / unarchive/archive, /unarchive
Favourite / unfavourite/favorite, /unfavorite

Status changes apply only to the signed-in user. Other admins still see the posts unchanged.


8. Auditing: who added a permission, and how?

Every assignment is logged. Go to Enterprise applications → your identity → Audit logs and look for:

ActivityMeaning
Add app role assignment to service principalAn application permission was assigned
Add delegated permission grantA delegated scope was granted

The User-Agent field shows the tool that was used. For example, AZURECLI/… cloud-shell means Azure CLI in Cloud Shell, and a Graph PowerShell SDK agent string means Microsoft.Graph PowerShell.


9. Removing a permission

# Application permission
$assignment = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $miSp.Id -All |
    Where-Object { $_.AppRoleId -eq $role.Id }
Remove-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $miSp.Id -AppRoleAssignmentId $assignment.Id

# Delegated grant (entire grant)
Remove-MgOauth2PermissionGrant -OAuth2PermissionGrantId $grant.Id

You can also use Review permissions on the enterprise app's Permissions blade.


10. Key takeaways

#Takeaway
1The portal can't add Graph permissions to a managed identity. Use PowerShell, the CLI or Graph Explorer
2Application permission → New-MgServicePrincipalAppRoleAssignment; delegated → New-MgOauth2PermissionGrant
3Look up permission IDs from AppRoles (application) or Oauth2PermissionScopes (delegated)
4Cannot convert the literal '' to 'Edm.Guid' usually means the permission isn't an application permission
5ServiceMessageViewpoint.Write is delegated only, so a managed identity can't use it at runtime
6Managed identity tokens are cached, so allow time before a new permission takes effect in a running job

References

Featured Post

Connect to SharePoint Online with Connect-PnPOnline

Goal: Starting from a fresh Windows PC, finish by running one PowerShell command that reads the title of a SharePoint site . Who it's f...

Popular posts