One of the things I commonly do is grant an Azure AD app registration permissions to a specific SharePoint site. I think I blogged about this in the past, but essentially it boils down to:
1) Granting the App Registration Sites.Selected
permissions for Graph and ShaerPoint. You don’t need to do both, it depends on how you are accessing the site. If you app uses the Graph API then the former is needed; if your app uses the SharePoint API (e.g. PnP.PowerShell) then the latter is needed. I usually do both so the requestor can choose whichever method they want.
2) Running a bit of PowerShell to give the App Id permissions to this Site. Very generically, this is the bit of code I run:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
$adminCenterURL = "https://${tenantName}-admin.sharepoint.com" Connect-PnPOnline -ClientId $appIdSPO -CertificatePath $appCertSPO -Tenant $appTenant -Url $adminCenterUrl # The App Registration that needs access $appId = "xxxx" $appName = "YYYY" # I define an array of sites here so I can loop over them below $listOfSites = "https://${tenantName}.sharepoint.com/sites/mySite1/" $permissions = "Read","Write" # <== Upto here ==> foreach ($siteUrl in $listOfSites) { Write-Host "Processing $siteUrl" try { Grant-PnPAzureADAppSitePermission -AppId $appId -DisplayName $appName -Permissions $permissions -Site $siteUrl } catch { "Oops! $(($_.Exception.Message | ConvertFrom-Json -Depth 5).error.message)" } } |
This has always worked, until recently when I stumbled upon a site from a different geo. I did the usual, taking care to use the geo specific site Url (basically https://${tenantName}${region}
) but it failed:
1 |
{"error":{"code":"itemNotFound","message":"Item not found","innerError":{"date":"2022-06-02T17:10:54","request-id":"452df367-1dcf-45cd-92c2-39ed14dcf8aa","client-request-id":"454da367-1dcf-45cd-93c2-39ed34dcf8aa"}}} |
I tried changing the Admin Url too with a region specific one… but nope, no luck! We have many geos and none of them worked either.
The alternative is to use Graph API directly.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
# Connect to Graph API with an app registration that has Sites.FullControl.All Connect-MgGraph -ClientId $appIdSPO -Certificate $appCert -TenantId $appTenant # The App Registration that needs access $appId = "xxxx" $appName = "YYYY" # This is the geo specific name # Example, for https://contosogbr.sharepoint.com/xxx, this would be contosogbr. $spoTenantName = "${tenantName}${region}" # In the case of this Graph snippet I am doing a single site. But one can extend this to loop over multiple sites if needed. # Get the site Id of the site. This is what Graph uses to refer to SharePoint sites. $siteId = ((Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/sites/$spoTenantName.sharepoint.com:/sites/$siteName/").id -split ',')[1] # Define the paramters for the cmdlet $mgSiteParams = [ordered]@{ "roles" = @("write") "grantedToIdentities" = @( @{ "application" = @{ "id" = $appId "displayName" = $appName } } ) } # Assign the permissions New-MgSitePermission -SiteId $siteId -BodyParameter $mgSiteParams # In my testing the above cmdlet sometimes failed. Maybe I was doing something wrong, I am not sure. Try this one in that case. Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/sites/$siteId/permissions" ` -Method 'POST' -ContentType "application/json" ` -Body (ConvertTo-Json -Depth 5 $mgSiteParams) |
Thanks to the official docs for this.
Update: The above doesn’t always work. It keeps erroring: New-MgSitePermission_Create1: Item not found
. Ditto with Invoke-MgGraphRequest
.
In my testing I also found that even getting the permissions errors out:
1 2 |
Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/sites/$siteId/lists" ` -Method 'GET' -ContentType "application/json" |
What I finally stumbled upon is that if I were to get the lists in a site, then both getting and setting permissions works! So I do the following now:
1 2 3 4 5 6 7 8 9 10 11 12 |
# Get the list in a site. Doing this somehow makes Graph aware of this site for the subsequent commands. Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/sites/$siteId/lists" ` -Method 'GET' -ContentType "application/json" # Get the permissions assigned to a site. If this works then the next command will definitely work. Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/sites/$siteId/permissions" ` -Method 'GET' -ContentType "application/json" # Assign permissions. Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/sites/$siteId/permissions" ` -Method 'POST' -ContentType "application/json" ` -Body (ConvertTo-Json -Depth 5 $mgSiteParams) |
Update (23 Jan 2023):
What I use currently, with a try/catch
block to fail back as needed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
try { New-MgSitePermission -SiteId $siteId -BodyParameter $mgSiteParams -ErrorAction Stop } catch { if ($_.Exception.Message -eq "Item not found") { # Alternatively Write-Host -ForegroundColor Yellow "Ran into the expected error, so failing back" Write-Host -ForegroundColor Yellow "Getting the lists via API" # Get the lists in a site. Doing this somehow makes Graph aware of this site for the subsequent commands. Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/sites/$siteId/lists" ` -Method 'GET' -ContentType "application/json" Write-Host -ForegroundColor Yellow "Getting the current permissions via API" # Get the permissions assigned to a site. If this works then the next command will definitely work. Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/sites/$siteId/permissions" ` -Method 'GET' -ContentType "application/json" Write-Host -ForegroundColor Yellow "Assign new permissions via API" # Assign permissions. Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/sites/$siteId/permissions" ` -Method 'POST' -ContentType "application/json" ` -Body (ConvertTo-Json -Depth 5 $mgSiteParams) } else { $_.Exception.Message } } |