One Script, Dozens of M365 Reports: Your “Download Everything” Graph Reporting Toolkit

 


If you’ve ever needed to pull Microsoft 365 usage data and found yourself bouncing between portals, exporting one report at a time, renaming files, and trying to remember which timeframe you used last, this PowerShell script is for you.

At a high level, it does three really useful things:

  1. Connects to Microsoft Graph Reports using the Graph PowerShell SDK
  2. Builds a clean, timestamped folder so your exports stay organized
  3. Loops through a curated list of report endpoints and saves each one as a CSV in one pass

It’s simple, repeatable, and it scales well. If you are responsible for adoption reporting, governance, or executive dashboards, this is the kind of “set it and forget it” foundation you want in your toolkit.


What the Script Does (In Plain English)

Here’s the flow:

  • You set a reporting period (D7, D30, D90, D180).
  • The script creates a folder in your Downloads like:
    Downloads\M365Reports2026-04-27\
  • It connects to Microsoft Graph with Reports.Read.All.
  • It iterates through a list of report names (Teams, Exchange, OneDrive, SharePoint, Apps usage, activations, active users, plus Copilot reporting endpoints).
  • For each report:
    • It constructs the Graph URL
    • Calls the endpoint
    • Saves the output to a uniquely named CSV file

End result: you run the script once, and you get a complete report bundle that you can hand to a stakeholder, feed into Power BI, or archive for trend analysis.


Why This Matters

This script solves a problem that shows up everywhere in IT:

1) Consistency beats “one-off” reporting

Manual exports are fine until the third time someone asks, “Can you run that again for last month?” This script helps you standardize your reporting process and keep the period and file naming consistent.

2) Better auditability and traceability

The timestamped folder makes it easy to answer questions like:

  • “Which report set did we use for the Q1 adoption review?”
  • “What did Teams usage look like 90 days ago compared to now?”

3) It’s designed for scale

The report list is just an array. Add or remove endpoints without rewriting the logic. That makes it easy to tailor to your environment, your stakeholders, and your governance goals.


What You Get: Report Categories Included

Your $Reports list pulls across several big areas:

  • Copilot reports (usage and trend style reporting)
  • Teams usage and activity (device usage, user activity, team activity, counts and distribution)
  • Exchange / email activity (activity, app usage, mailbox usage)
  • M365 activations and active users
  • M365 Apps usage
  • OneDrive activity and storage
  • SharePoint activity and site usage

That’s a strong baseline set for most enterprise reporting needs.


How to Use It (Step-by-Step)

Step 1: Install the Graph PowerShell modules (if needed)

If you already use Graph PowerShell, you’re probably set. If not, you’ll need the module that includes Reports:

Install-Module Microsoft.Graph -Scope CurrentUser

Step 2: Paste the script into PowerShell (or save as a .ps1)

Save it somewhere like:

  • C:\Scripts\Export-M365Reports.ps1
Import-Module Microsoft.Graph.Reports

# -------------------------------
# CONFIGURATION
# -------------------------------
$ReportPeriod = "D30"      # Change to D7, D30, D90, D180 as needed
$FormatType = "text/csv"
$Timestamp    = Get-Date -Format "yyyy-MM-dd"
$folderPath = [Environment]::GetFolderPath('UserProfile') + '\Downloads\M365Reports' +$Timestamp +"\"

try {
    # Check if the folder exists
    if (-not (Test-Path -Path $folderPath -PathType Container)) {
        # Create the folder
        New-Item -Path $folderPath -ItemType Directory -Force | Out-Null
        Write-Host "Folder created at: $folderPath" -ForegroundColor Green
    }
    else {
        Write-Host "Folder already exists: $folderPath" -ForegroundColor Yellow
    }
}
catch {
    Write-Host "Error creating folder: $($_.Exception.Message)" -ForegroundColor Red
}

# -------------------------------
# CONNECT TO GRAPH
# https://learn.microsoft.com/en-us/graph/api/resources/reportroot?view=graph-rest-beta
# -------------------------------
Connect-MgGraph -Scopes "Reports.Read.All"

# Ensure correct Graph profile
Select-MgProfile v1.0

$Reports = 
@(
#Copilot Reports
"getMicrosoft365CopilotUsageUserDetail",
"getMicrosoft365CopilotUserCountSummary",
"getMicrosoft365CopilotUserCountTrend",

#Teams Reports
"getTeamsDeviceUsageUserDetail",
"getTeamsDeviceUsageUserCounts",
"getTeamsDeviceUsageTotalUserCounts",
"getTeamsDeviceUsageDistributionUserCounts",
"getTeamsDeviceUsageDistributionTotalUserCounts",
"getTeamsUserActivityUserDetail",
"getTeamsUserActivityCounts",
"getTeamsUserActivityTotalCounts",
"getTeamsUserActivityUserCounts",
"getTeamsUserActivityTotalUserCounts",
"getTeamsUserActivityDistributionUserCounts",
"getTeamsUserActivityDistributionTotalUserCounts",
"getTeamsUserActivityTotalDistributionCounts",
"getTeamsTeamActivityDetail",
"getTeamsTeamActivityCounts",
"getTeamsTeamActivityDistributionCounts",
"getTeamsTeamCounts",

#Exchange Reports
"getEmailActivityUserDetail",
"getEmailActivityCounts",
"getEmailActivityUserCounts",
"getEmailAppUsageUserDetail",
"getEmailAppUsageAppsUserCounts",
"getEmailAppUsageUserCounts",
"getEmailAppUsageVersionsUserCounts",
"getMailboxUsageDetail",
"getMailboxUsageMailboxCounts",
"getMailboxUsageQuotaStatusMailboxCounts",
"getMailboxUsageStorage",

#M365 Activations
"getOffice365ActivationsUserDetail",
"getOffice365ActivationCounts",
"getOffice365ActivationsUserCounts",

#M365 Active Users
"getOffice365ActiveUserDetail",
"getOffice365ActiveUserCounts",
"getOffice365ServicesUserCounts",

#M365 Apps Usage
"getM365AppUserDetail",
"getM365AppUserCounts",
"getM365AppPlatformUserCounts",

#OneDrive Activity
"getOneDriveActivityUserDetail",
"getOneDriveActivityUserCounts",
"getOneDriveActivityFileCounts",

#OneDrive Usage
"getOneDriveUsageAccountDetail",
"getOneDriveUsageAccountCounts",
"getOneDriveUsageFileCounts",
"getOneDriveUsageStorage",

#SharePoint Activity
"getSharePointActivityUserDetail",
"getSharePointActivityFileCounts",
"getSharePointActivityUserCounts",
"getSharePointActivityPages",

#SharePoint Site Usage
"getSharePointSiteUsageDetail",
"getSharePointSiteUsageFileCounts",
"getSharePointSiteUsageSiteCounts",
"getSharePointSiteUsageStorage",
"getSharePointSiteUsagePages"
)

foreach($r in $Reports){
$outfile = $folderPath + $r +$Timestamp + ".csv"
$uri = "https://graph.microsoft.com/beta/reports/" + $r + "(period='$($ReportPeriod)')?`$format=$($FormatType)"
Write-Host $outfile
Invoke-MgGraphRequest `
    -Method GET `
    -Uri $uri `
    -OutputFilePath $outfile

}

Step 3: Update the reporting period (optional)

At the top, set:

$ReportPeriod = "D30"
``

Common values:

  • D7 for weekly
  • D30 for monthly
  • D90 for quarterly trend checks
  • D180 for longer baseline comparisons

Step 4: Run it in a PowerShell session

If your execution policy blocks it, you may need:

Set-ExecutionPolicy RemoteSigned -Scope CurrentUser

Then run:

.\Export-M365Reports.ps1

Step 5: Grab your outputs

Look in:

  • Downloads\M365ReportsYYYY-MM-DD\

Each file is named like:

  • getTeamsUserActivityCounts2026-04-27.csv

That naming convention is handy later when you automate ingestion.


A Few Useful Scenarios (Where This Script Shines)

Scenario 1: Monthly adoption scorecards

You need a predictable monthly export for Teams activity, OneDrive usage, SharePoint usage, and M365 Apps trends. Run with D30, archive the folder, and your month-over-month story basically writes itself.

Tip: Create a “Monthly Reports” scheduled task and have it run on the first business day of the month.


Scenario 2: Executive dashboard refresh

Power BI dashboards are great, but they need data. This script can be your “extract” step in an ETL pattern:

  • Export CSVs
  • Load into a staging folder
  • Power BI refresh pulls from the latest folder or a combined dataset

Tip: Keep your file naming stable, then use Power Query to “Combine Files” from a folder.


Scenario 3: License governance and reclamation

If you are tracking usage to support license reclamation conversations, these reports help you build defensible, data-driven lists. Examples:

  • Low Teams activity over 30 or 90 days
  • No M365 Apps usage
  • No OneDrive activity

Tip: Use D90 when you want to reduce noise and avoid flagging someone who was simply on vacation or in training.


Scenario 4: Service health or change impact checks

Rolling out a policy change, network change, or client update? Pull reports before and after with the same period. You can quickly spot shifts in usage patterns.

Tip: Save two folders and label them clearly:

  • PreChange_YYYY-MM-DD
  • PostChange_YYYY-MM-DD

What to Watch For (Common Gotchas)

  • Permissions: You need Reports.Read.All, and depending on your environment, this may require admin consent.
  • CSV formatting: Graph reports return a CSV payload that is easy to ingest, but a few reports may have headers or columns that evolve over time, especially in beta.
  • Copilot reports: These can be the most sensitive to licensing and rollout state. If the org is early in adoption or reporting hasn’t populated yet, don’t assume the script is broken.

Conclusion

This script is one of those “quiet heroes” that makes an admin’s life easier without making a big fuss about it. It gives you a repeatable, timestamped export of key Microsoft 365 usage reports in one run, and it does it in a way that is easy to maintain, easy to customize, and easy to explain to stakeholders. Whether you’re building adoption dashboards, preparing leadership updates, supporting license governance, or just trying to bring order to the chaos of manual exports, this approach turns reporting into a dependable routine instead of a recurring chore. Run it monthly, tune the report list to match what your organization cares about, and you’ll always have clean, ready-to-use data when the next “Can you pull the latest numbers?” request lands in your inbox.


References

https://learn.microsoft.com/en-us/graph/api/resources/reportroot?view=graph-rest-beta