All scripts
Microsoft 365 447

Microsoft 365 Copilot Usage Report

Pulls the Microsoft 365 Copilot usage user detail report from the Microsoft Graph reporting API and breaks out which Copilot enabled apps each user actually opened, so you can see who has a license but is not using it.

Get-CopilotUsageReport.ps1
<#
.SYNOPSIS
    Reports on Microsoft 365 Copilot usage per user using the Microsoft Graph reporting API.

.DESCRIPTION
    Connects to Microsoft Graph and pulls the Microsoft 365 Copilot usage user detail
    report for a chosen reporting period. Breaks out which Copilot enabled apps each
    user actually opened, Word, Excel, Outlook, Teams, PowerPoint, so you can see who
    has a license but is not using it, and exports the result to CSV.

.AUTHOR
    Shehryar Hassan

.EXAMPLE
    .\Get-CopilotUsageReport.ps1 -Period D30 -OutputPath C:\Reports\copilot-usage.csv
    Pulls the last 30 days of Copilot usage and saves it to the given CSV path.
#>

[CmdletBinding()]
param(
    [ValidateSet('D7','D30','D90','D180')]
    [string]$Period = 'D30',

    [string]$OutputPath = ".\CopilotUsageReport_$(Get-Date -Format 'yyyyMMdd').csv"
)

if (-not (Get-Module -ListAvailable -Name Microsoft.Graph.Reports)) {
    Write-Host "Installing Microsoft.Graph.Reports module..." -ForegroundColor Yellow
    Install-Module Microsoft.Graph.Reports -Scope CurrentUser -Force
}

Import-Module Microsoft.Graph.Reports
Import-Module Microsoft.Graph.Authentication

Connect-MgGraph -Scopes "Reports.Read.All" -NoWelcome

Write-Host "Pulling Copilot usage for period $Period..." -ForegroundColor Cyan

$uri = "https://graph.microsoft.com/beta/reports/getMicrosoft365CopilotUsageUserDetail(period='$Period')?`$format=text/csv"

try {
    $rawCsv = Invoke-MgGraphRequest -Method GET -Uri $uri -OutputType String
}
catch {
    Write-Error "Could not pull the Copilot usage report. Check that the account has Reports.Read.All and that Copilot reporting is enabled for the tenant. $_"
    return
}

$rows = $rawCsv | ConvertFrom-Csv

if (-not $rows) {
    Write-Warning "No rows came back. This usually means Copilot usage data is not available yet for this tenant, or the period has no activity."
    return
}

$summary = foreach ($row in $rows) {
    $lastActivity = $row.'Last Activity Date'
    $daysSinceActive = if ($lastActivity) {
        (New-TimeSpan -Start ([datetime]$lastActivity) -End (Get-Date)).Days
    } else {
        $null
    }

    [PSCustomObject]@{
        DisplayName        = $row.'Display Name'
        UserPrincipalName  = $row.'User Principal Name'
        LastActivityDate   = $lastActivity
        DaysSinceActive    = $daysSinceActive
        UsedWord           = $row.'Copilot in Word Last Activity Date' -ne ''
        UsedExcel          = $row.'Copilot in Excel Last Activity Date' -ne ''
        UsedOutlook        = $row.'Copilot in Outlook Last Activity Date' -ne ''
        UsedTeams          = $row.'Copilot in Teams Last Activity Date' -ne ''
        UsedPowerPoint     = $row.'Copilot in PowerPoint Last Activity Date' -ne ''
    }
}

$summary | Sort-Object DaysSinceActive -Descending | Export-Csv -Path $OutputPath -NoTypeInformation

$neverActive = ($summary | Where-Object { -not $_.LastActivityDate }).Count
$totalUsers = $summary.Count

Write-Host ""
Write-Host "Report saved to $OutputPath" -ForegroundColor Green
Write-Host "$totalUsers licensed users found, $neverActive with no recorded Copilot activity in the $Period window." -ForegroundColor Cyan

Disconnect-MgGraph | Out-Null

Read it before you run it, and test in a safe tenant first.