Use PowerShell to resolve site URLs in reports

Use this method when your report contains site IDs and you need human-readable URLs for analysis.

Before you begin

You must have the following items and permissions to do the tasks in thie article:

  • A Microsoft 365 admin account
  • Access to Microsoft Entra admin center
  • Permission to register an application and grant admin consent
  • The exported CSV files from:

If you need help downloading the reports or turning off privacy settings for user details, see Microsoft 365 admin center activity reports.

How it works

Microsoft Graph API provides an API that you can use to list all the sites within an organization. To use this API, you need an application with the Sites.Read.All permission.

The script calls this API endpoint to get the mapping between site IDs and site URLs. Then, it adds the site URLs into the exported CSV reports.

Why not use delegated permissions?

  • The /sites/getAllSites API only accepts application permissions.

  • The /sites?search=* API accepts delegated permissions, but it doesn't return all the sites, even when you use an admin account.

Use PowerShell to display site URLs

Step 1. Create a Microsoft Entra ID application

  1. Go to Microsoft Entra admin center > Applications > App registrations.
  2. On the App registrations page, select New registrations.
  3. Pick a name for this application, and use the default configuration to register the app.

The app's Essentials section displays the client id and tenant id.

Screenshot of the app registration page with the client ID and tenant ID fields highlighted.

Step 2. Add Graph API permission to the app

On the new application's Request API permissions page, add the Sites.Read.All permission.

Screenshot of API permissions with Sites.Read.All selected for the app registration.

Then, grant admin consent.

Screenshot of the API permissions page showing Grant admin consent selected.

Step 3. Create a client secret

In the new application's Certificates & secrets section, create a new client secret. Then, store the secret's value in a safe and secure place.

Screenshot of the Certificates & secrets page where a new client secret is created.

Step 4. Download the reports in Microsoft 365 admin center

Download the site details report on the two report pages and put the CSV report files in a local folder.

Before downloading the reports, turn off the privacy setting for user details. For more information, see Microsoft 365 admin center activity reports.

For SharePoint site usage, go to the SharePoint site usage page in the Microsoft 365 admin center.

For OneDrive site usage, go to the OneDrive site usage page in the Microsoft 365 admin center.

Step 5. Update the reports with site URLs

To update the reports with site URLs, run the following PowerShell script.

.\Update-Report.ps1 -**tenantId** {tenant id above} -**clientId** {client id above} -**reportPaths** @("file path for report \#1", "file path for report \#2")

To view the full Update-Report PowerShell script, see Update-Report PowerShell.

The script asks you to enter the secret's value created in Step 3. Create a client secret.

Screenshot of the PowerShell prompt requesting the client secret value.

After you run the script, it creates new versions of the reports with site URLs added.

Screenshot of a usage report with resolved site URLs added to the output.

Step 6. Clean up the environment

To clean up the environment, go back to the application's Certificates & secrets page and delete the secret that you created earlier.

Tip

Use an SSD (solid-state drive) to improve the I/O performance. Run the script on a machine with enough free or unused memory. The cache uses roughly 2 GB for the 15 million sites.

Update-Report PowerShell script

The following script is the PowerShell script for Update-Report.

 param(
 [Parameter(Mandatory=$true)]
 [string]$tenantId,
 [Parameter(Mandatory=$true)]
 [string]$clientId,
 [Parameter(Mandatory=$false)]
 [string[]]$reportPaths
)

function Get-AccessToken {
 param(
     [Parameter(Mandatory=$true)]
     [string]$tenantId,
     [Parameter(Mandatory=$true)]
     [string]$clientId,
     [Parameter(Mandatory=$true)]
     [System.Security.SecureString]$clientSecret,
     [Parameter(Mandatory=$false)]
     [string]$scope = "https://graph.microsoft.com/.default"
 )

 $tokenEndpoint = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"
 $tokenRequest = @{
     client_id     = $clientId
     scope         = $scope
     client_secret = ConvertFrom-SecureString $clientSecret -AsPlainText
     grant_type    = "client_credentials"
 }

 $tokenResponse = Invoke-RestMethod -Uri $tokenEndpoint -Method Post -Body $tokenRequest
 return $tokenResponse.access_token
}

Prepare the cache and client secret

if ($reportPaths.Count -eq 0) {
  Write-Host "Please provide at least one report path" -ForegroundColor Red
  exit
}
$cache = New-Object 'System.Collections.Generic.Dictionary[[String],[String]]'
$clientSecret = Read-Host "Please enter client secret" -AsSecureString

Fetch site info from Graph API

Write-Host
Write-Host "Getting information for all the sites..." -ForegroundColor Cyan

$uri = "https://graph.microsoft.com/v1.0/sites/getAllSites?`$select=sharepointIds&`$top=10000"
while ($uri -ne $null) {

  Write-Host $uri

  $isSuccess = $false
  while (-not $isSuccess) {
      try {
          $accessToken = Get-AccessToken -tenantId $tenantId -clientId $clientId -clientSecret $clientSecret
          $restParams = @{Headers=@{Authorization="Bearer $accessToken"}}
      }
      catch {
          Write-Host "Retrying...  $($_.Exception.Message)" -ForegroundColor Yellow
          continue
      }
      try {
          $sites = Invoke-RestMethod $uri @restParams
          $isSuccess = $true
      }
      catch {
          if ($_.Exception.Response -and $_.Exception.Response.Headers['Retry-After']) {
              $retryAfter = [int]$_.Exception.Response.Headers['Retry-After']
              Write-Output "Waiting for $retryAfter seconds before retrying..." -ForegroundColor Yellow
              Start-Sleep -Seconds $retryAfter
          }
          Write-Host "Retrying...  $($_.Exception.Message)" -ForegroundColor Yellow
          continue
      }
  }

  $sites.value | ForEach-Object {
      $cache[$_.sharepointIds.siteId] = $_.sharepointIds.siteUrl
  }

  $uri = $sites."@odata.nextLink"

  Write-Host "Total sites received: $($cache.Count)"
}

Update the report using cached site info

foreach ($reportPath in $reportPaths) {
  Write-Host
  Write-Host "Updating report $($reportPath) ..." -ForegroundColor Cyan

  $outputPath = "$($reportPath)_$([Math]::Floor((Get-Date -UFormat %s))).csv"
  $writer = [System.IO.StreamWriter]::new($outputPath)
  $reader = [System.IO.StreamReader]::new($reportPath)
  $rowCount = 0

  while ($null -ne ($line = $reader.ReadLine())) {
      $rowCount++

      $columns = $line.Split(",")
      $siteId = $columns[1]

      $_guid = New-Object System.Guid
      if ([System.Guid]::TryParse($siteId, [ref]$_guid)) {
          $siteUrl = $cache[$siteId]
          $columns[2] = $siteUrl
          $line = $columns -join ","
      }
      
      $writer.WriteLine($line)

      if ($rowCount%1000 -eq 0) {
          Write-Host "Processed $($rowCount) rows"
      }
  }
  $writer.Close()
  $reader.Close()

  Write-Host "Processed $($rowCount) rows"
  Write-Host "Report updated: $($outputPath)" -ForegroundColor Cyan
}

Finalize

Write-Host
Read-Host "Press any key to exit..."

Next steps

Extra option for small-scale scenarios

For smaller scale scenarios, admins with appropriate access can use the SharePoint REST API or Microsoft Graph API to retrieve information about site IDs referenced in affected reports.