Category Archives: Microsoft Graph

Get list of new SPO sites with PowerShell

Scenario

Let say you administer Microsoft 365 SharePoint Online and you want to get a list of new SharePoint sites (e.g. sites created during last week/month).

With GUI it’s done easily: SharePoint Admin Center -> Active Sites -> sort based on “Date Created” – done.

With PowerShell – not so simple.
“Get-PnPTenantSite” cmdlet returns site objects but the object does not have “Created” field. You have to connect separately to each site and get root web object where you can check when the web was created. For small environments it is possible, for large environments it can take days… And still not nice.
-Filter option would help, but “…Currently, you can filter by these properties: Owner, Template, LockState, Url.”

Get-SPOSite – similar experience.

Solution

Microsoft Graph API helps. It returns result in seconds. There are some pros and cons for each method though.

Option #1: Microsoft Graph Search API.

Entry point: https://graph.microsoft.com/v1.0/search/query

Microsoft Graph Search API allows KQL in queries. So we can form a query with something like “created>=1/1/2021” and use entity type = ‘[“site”]’. Search should return only sites created after Jan 01, 2021.

Check PowerShell script sample here: Get-NewSites.ps1
https://github.com/VladilenK/PowerShell/blob/main/reports/SharePoint/Get-NewSites.ps1

If you are getting more than 500 results – think of paging.

Option #2: Microsoft Graph Sites API

Entry point: https://graph.microsoft.com/v1.0/sites

This option is also based on Microsoft Graph API, but sites entry point, which allows search too and sort results by property “createdDateTime”. So we will just search for everything and select how many results we need based on createdDateTime property.

Check PowerShell script sample here: Get-NewSites.ps1
https://github.com/VladilenK/PowerShell/blob/main/reports/SharePoint/Get-NewSites.ps1

References

Connect to SharePoint Online with PnP.PowerShell Interactively with Client App and msal token

Scenario

You use PnP.PowerShell and you need to connect to SharePoint Online via Connect-PnPOnline interactively (on behalf of a user).

Solution

  • register an Azure App (single tenant)
  • configure authentication blade:
    – add platform – “Mobile and Desktop app”
    select “https://login.microsoftonline.com/common/oauth2/nativeclient”
    add custom Redirect URI: “http://localhost”
  • configure API permissions blade:
    – add delegated permissions you need (refer to specific API you’ll use)
    e.g. Microsoft Graph Sites.FullControl.All and SharePoint AllSites.FullControl
  • use the following code to connect to your site:
$siteUrl = "https://contoso.sharepoint.com/teams/myTeamsSite"
$appId = "" # Client Id
$connection = Connect-PnPOnline -ClientId $appId -Url $adminUrl -Interactive -ReturnConnection # -ForceAuthentication
$connection

A pop-up window will appear to authenticate interactively. If you are already authenticated with another credentials (or single-sigh-on) – an interactive window might pop up and disappear – so you are not able to enter your admin id.
To ensure Connect-PnPOnline prompts you for your credentials – use ” -ForceAuthentication” option.

If you are a SharePoint tenant admin – you can connect to a tenant with:

$orgName = "yourTenantPrefix" 
$adminUrl = "https://$orgName-admin.sharepoint.com" 
$appId = "" # Client Id 
$connection = Connect-PnPOnline -ClientId $appId -Url $adminUrl -Interactive -ReturnConnection # -ForceAuthentication 
$connection 

The other option is to use MSAL.PS module to get an msal token. This might help with Microsoft graph-based requests:

$tenantId = ""
$clientid = ""
$url = ""
$token = Get-MsalToken -ClientId $clientid -TenantId $tenantId -Interactive
Connect-PnPOnline -AccessToken $token -Url $url 

By default token expires in ~ 1 hour. But you can refresh it silently.
This helps if you run heavy PowerShell script and it takes hours to complete.
So you can include something like this in the loop:

if ($token.ExpiresOn.LocalDateTime -lt $(get-date).AddMinutes(10)) {    
  $token = Get-MsalToken -ClientId $clientid -TenantId $tenantId -ForceRefresh -Silent    
  Write-Host "Token will expire on:" $token.ExpiresOn.LocalDateTime
}

NB: For delegated permissions, the effective permissions of your app are the intersection of the delegated permissions the app has been granted (via consent) and the privileges of the currently signed-in user. Your app can never have more privileges than the signed-in user.

Access SPO Site Programmatically via MS Graph API and SharePoint API

Scenario

You are a software developer. Your company uses Microsoft Office 365 (SharePoint, Teams etc.). The need is to work with a specific site collection programmatically (from code – Python, C#, Java, PowerShell, JavaScript etc.) – e.g. upload/download documents, update list items, search etc.

The code must run without user interaction (unattended, aka daemon app). Sometimes this is also called “SharePoint Automation”.

The solution is based on a new Graph API feature – Sites.Selected and a classic SP-Only app.

Solution

  1. Register an Azure App and configure it:
    MS Graph API permissions: add -> Microsoft Graph -> Applications Permissions -> “sites.selected
  2. Ask SharePoint/Tenant admin run PowerShell code (e.g. this one) to assign proper permissions to your azure app for a specific site collection (consider site owner consent)
  3. Provide SharePoint API permissions:
    (require Site Collection Owner/Admin account) – use
    https://YourTenant.sharepoint.com/teams/YourSite/_layouts/15/appinv.aspx
    to add SharePoint API permissions to your app. E.g. full control permissions to site collection would be
<AppPermissionRequests AllowAppOnlyPolicy="true">  
   <AppPermissionRequest Scope="http://sharepoint/content/sitecollection" 
    Right="FullControl" />
</AppPermissionRequests>

Consider minimal permissions (e.g. as per Sumit)

Problem Solved

  • you get access to one and only one site collection (“least privilege” principal)
  • you get both – SharePoint API and Microsoft Graph API permissions to SharePoint
  • you can use app secret or certificate to authenticate – depending on what are your security requirements

Note: if your scenario require authenticated user present – the solution would be a little different: Connect-PnPOnline Interactive with Client App Id

References:

Authenticate to Microsoft Graph from PowerShell Interactively

Scenario

You are a developer or power user in a company with Microsoft 365 tenant.
You need to connect to Microsoft Graph and then call Microsoft Graph API to consume some MS Graph resources on behalf of authenticated user programmatically with PowerShell – e.g. add/remove documents or list items, search for sites or documents content etc. – whatever available with Graph API.

You do not have tenant admin permissions or any tenant-level admin permissions (SharePoint, Teams, Exchange etc. ). But you can register an Azure App and request tenant admin consent.

Solution

  • register an Azure App
  • under authentication blade – add platform – “Mobile and Desktop app”
    add “http://localhost” (and select …/nativeclient Url ?)
  • under API permissions blade – add delegated permissions you need
    (refer to specific API you’ll use)
  • install MSAL.PS PowerShell module
  • use the following code to get graph access token and call graph API
$AppId = ""
$TenantId = ""
$connectionDetails = @{
    'TenantId'    = $AppId
    'ClientId'    = $TenantId
    'Interactive' = $true
}

$token = Get-MsalToken @connectionDetails
# or 
$token = Get-MsalToken -TenantId $TenantId -ClientId $appId -Interactive 

$Headers = @{
    'Authorization' = "bearer $($token.AccessToken)"
}

Invoke-RestMethod -Uri 'https://graph.microsoft.com/v1.0/me' -Headers $Headers

You can find the code sample here: https://github.com/VladilenK/

Did not work:

Az PowerShell module did not work for me:

Connect-AzAccount -Tenant ""
$azAccessToken = Get-AzAccessToken -Resource "https://graph.microsoft.com" 

$Headers = @{
  'Authorization' = "$($azAccessToken.Type) $($azAccessToken.Token)"
}

Invoke-RestMethod -Uri 'https://graph.microsoft.com/v1.0/me' -Headers $Headers

As I understand we need somehow let Azure know API permissions we want (e.g. via app registerd)…

PnP did not work for me too:

$url = "https://orgname.sharepoint.com"
Connect-PnPOnline -ClientId "" -Url $url -Interactive 
$pnpToken = Get-PnPGraphAccessToken 
$Headers = @{
    'Authorization' = "bearer $($pnpToken)"
}
Invoke-RestMethod -Uri 'https://graph.microsoft.com/v1.0/me' -Headers $Headers

# did not work as well:
$pnpToken = Get-PnPAppAuthAccessToken
$pnpToken = Get-PnPAccessToken 

the error message was (maybe I missed something – please let me know):

“code”: “InvalidAuthenticationToken”, “message”: “Access token validation failure. Invalid audience.”

References