Azure Functions, PnP PowerShell, Managed Identity and SharePoint Online
Tags: pnp, sharepoint, managedidentity, azure
Published: 2022-10-14 10:41:09

> _EDIT 2002.11.13_: What a time to be alive! :upside_down_face: Since version 1.11.95-nightly, Managed Identities are both supported against SharePoint Online as well as Microsoft Graph cmdlets. :tada: :partying_face: :confetti_ball:
>
>[Decide how you want to authenticate in your Azure Function: By using a Managed Identity](https://pnp.github.io/powershell/articles/azurefunctions.html#by-using-a-managed-identity)
>
> Most of this blog post is still valid. In the **profile.ps1** however, simply use `Connect-PnPOnline -ManagedIdentity` from now on. 

If you followed [Using PnP PowerShell in Azure Functions](https://pnp.github.io/powershell/articles/azurefunctions.html) and now try to connect to your SPO site using 
```bash
Connect-PnPOnline -ManagedIdentity
```
you will get _The current connection holds no SharePoint context._

## Access token
You can still use the managed identity though. You just need additional step, to [connect to Azure services in app code](https://learn.microsoft.com/en-us/azure/app-service/overview-managed-identity?tabs=portal%2Cpowershell#connect-to-azure-services-in-app-code):

###profile.ps1
```bash
$resourceURI ="https://<yourtenant>.sharepoint.com"
$siteUrl="https://<yourtenant>.sharepoint.com/sites/<yoursite>"

if ($env:MSI_SECRET) {
    $tokenAuthURI = $env:MSI_ENDPOINT + "?resource=$resourceURI&api-version=2017-09-01"
    $tokenResponse = Invoke-RestMethod -Method Get -Headers @{"Secret" = "$env:MSI_SECRET" } -Uri $tokenAuthURI
    $accessToken = $tokenResponse.access_token

    Connect-PnPOnline -Url  $siteUrl -AccessToken $accessToken
    #Invoke Get-PnPSite to test
}
```

The `$env:MSI_SECRET` and `$env:IDENTITY_HEADER` return the same values. And so do `$env:MSI_ENDPOINT` and `$env:IDENTITY_ENDPOINT`.

###run.ps1
```bash
$listTitle = "<yourlistname>"
try{
    $connection = Get-PnPConnection
    $list = Get-PnPList  -Identity $listTitle -Connection $connection
    $item = Add-PnPListItem -List $list -Values @{"Title" = "item 4" }  -Connection $connection
    Write-Host "Item created"
}
catch{
    Write-Host "ERROR CREATING ITEM"
    Get-Error
}
```

## Minimum required permissions
And since I believe in assigning minimum required permissions, the managed identity has only `Sites.Selected` API permissions for Graph API and SPO. 
### Setup.ps1
```bash
function Set-AzureADPermissions_SitesSelected {
    param(
        $tenantId,
        $appDisplayName
    )
    Connect-AzureAD -TenantId $tenantId

    $GraphAppId = "00000003-0000-0000-c000-000000000000"  # Microsoft Graph
    $SPOAppId = "00000003-0000-0ff1-ce00-000000000000" # SharePoint Online

    #Retrieve the Azure AD Service Principal instance for the Microsoft Graph (00000003-0000-0000-c000-000000000000) or SharePoint Online (00000003-0000-0ff1-ce00-000000000000).
    $servicePrincipal_Graph = Get-AzureADServicePrincipal -Filter "appId eq '$GraphAppId'"
    $servicePrincipal_SPO = Get-AzureADServicePrincipal -Filter "appId eq '$SPOAppId'"
    $permissionName = "Sites.Selected"

    $SPN = Get-AzADServicePrincipal -Filter "displayName eq '$appDisplayName'"
    Start-Sleep -Seconds 10

    # Use application permissions. Delegate permissions cannot be utilized using a Managed Identity.
    # $servicePrincipal_Graph.AppRole | Where-Object { $_.AllowedMemberType -eq "Application" -and $_.Value -eq "Sites.Selected"}

    $appRole_Graph = $servicePrincipal_Graph.AppRoles | Where-Object { $_.AllowedMemberTypes -eq "Application" -and $_.Value -eq $permissionName }
    $appRole_SPO = $servicePrincipal_SPO.AppRoles | Where-Object { $_.AllowedMemberTypes -eq "Application" -and $_.Value -eq $permissionName }

    # Grant API Permissions
    New-AzureAdServiceAppRoleAssignment -ObjectId $SPN.Id -PrincipalId $SPN.Id -ResourceId $servicePrincipal_Graph.ObjectId -Id $appRole_Graph.Id
    New-AzureAdServiceAppRoleAssignment -ObjectId $SPN.Id -PrincipalId $SPN.Id -ResourceId $servicePrincipal_SPO.ObjectId -Id $appRole_SPO.Id

    # Write-Host "Now grant access to SPO site using the following:"
    # Write-Host "App Id: $($SPN.AppId)"
    # Write-Host "App Name: $($SPN.DisplayName)"

    return $SPN.AppId
}
function Set-PnPSiteAccess{
    param(
        $siteUrl,
        $appId,
        $appName,
        $permission

    )
    Connect-PnPOnline -Url $siteUrl -Interactive
    Grant-PnPAzureADAppSitePermission -AppId $appId -DisplayName $appName -Site $siteUrl -Permissions $permission
}

$appId= Set-APIPermissions -tenantId $tenantId -appDisplayName $appName
Set-PnPSiteAccess -siteUrl $siteUrl -appId $appId -appName $appName -permission Write
```