
Imagine you're the Power BI admin for a mid-sized financial services company. Over the past two years, your team has built 340 reports and dashboards, published them across dozens of workspaces, and congratulated yourselves on a thriving analytics culture. Then someone asks a simple question in a steering committee meeting: "Which of these reports are people actually using?"
Silence. You don't know. Nobody knows. The organization has been pouring development time and premium capacity costs into assets that may be collecting digital dust, while the three reports everyone actually relies on are running slow because they're sharing resources with 280 others that haven't been opened in six months.
This scenario plays out constantly in enterprise Power BI environments, and it's entirely preventable. Power BI provides two complementary tools for answering the "who's using what" question: the Activity Log (a detailed audit trail of every interaction across your tenant) and Usage Metrics (report-level dashboards built directly into the Power BI service). By the end of this lesson, you'll know how to access and extract both data sources, combine them into a meaningful governance picture, and build an analytical foundation for making data-driven decisions about your report portfolio.
What you'll learn:
Before you touch a single line of code, you need to understand what the Activity Log is, because it's easy to confuse it with other monitoring tools.
The Power BI Activity Log is a tenant-level audit record maintained by Microsoft. Every time someone in your organization interacts with Power BI — opens a report, exports data, shares a dashboard, refreshes a dataset, or even just logs in — that action generates an activity event. Each event is a structured record containing fields like who performed the action, what they did, which artifact was involved, and when it happened.
Think of it like a hotel key card system. Every time a guest opens a door, the system logs the card ID, the room number, and the timestamp. The Activity Log does the same thing for every Power BI interaction across your entire organization.
Key things to understand about the Activity Log:
Retention: Microsoft retains Activity Log data for 30 days. This is the critical constraint that shapes everything else. If you want historical data beyond 30 days — which you absolutely do for a meaningful adoption picture — you must extract the data regularly and store it yourself.
Access: Only Power BI administrators (users with the Power BI Admin role in your Microsoft 365 tenant) can access the Activity Log. Individual report owners cannot see tenant-wide activity.
Event types: There are dozens of event types. The most important for adoption tracking are ViewReport, ViewDashboard, ExportArtifact, ShareReport, and DeleteReport. A full reference list is available in the Microsoft documentation.
Tip: Set up an automated extraction job as soon as possible, even before you've built your analysis layer. The 30-day window means every day you wait is a day of history you can never recover.
The most practical way to extract Activity Log data is through the Power BI Management PowerShell module, which wraps Microsoft's REST API in convenient cmdlets (commands).
Open PowerShell and run:
Install-Module -Name MicrosoftPowerBIMgmt -Scope CurrentUser -Force
This installs the official Microsoft Power BI management module. The -Scope CurrentUser flag means you don't need administrator privileges on your local machine to install it.
Connect-PowerBIServiceAccount
A browser window will open asking you to sign in with your Power BI Admin account. Once authenticated, your PowerShell session has the permissions it needs.
The core cmdlet for activity data is Get-PowerBIActivityEvent. It accepts a start and end datetime and returns JSON-formatted event records.
$startDate = "2024-11-01T00:00:00.000Z"
$endDate = "2024-11-01T23:59:59.999Z"
$events = Get-PowerBIActivityEvent `
-StartDateTime $startDate `
-EndDateTime $endDate
# Convert from JSON string to PowerShell objects
$parsedEvents = $events | ConvertFrom-Json
# Check how many events came back
Write-Host "Events retrieved: $($parsedEvents.Count)"
Warning: The API only allows a maximum of one day per request. If you try to pull multiple days in a single call, it will error out. You must loop day by day.
Here's a practical script that loops through the last 30 days and saves everything to a CSV file you can load into Power BI:
Connect-PowerBIServiceAccount
$outputPath = "C:\PowerBI_Governance\ActivityLog_$(Get-Date -Format 'yyyyMMdd').csv"
$allEvents = @()
for ($i = 29; $i -ge 0; $i--) {
$date = (Get-Date).AddDays(-$i).ToString("yyyy-MM-dd")
$startTime = "${date}T00:00:00.000Z"
$endTime = "${date}T23:59:59.999Z"
Write-Host "Pulling data for $date..."
try {
$raw = Get-PowerBIActivityEvent -StartDateTime $startTime -EndDateTime $endTime
$parsed = $raw | ConvertFrom-Json
$allEvents += $parsed
} catch {
Write-Warning "Failed to retrieve data for $date : $_"
}
Start-Sleep -Seconds 2 # Be polite to the API
}
# Export to CSV
$allEvents | Export-Csv -Path $outputPath -NoTypeInformation -Encoding UTF8
Write-Host "Done. $($allEvents.Count) events saved to $outputPath"
When this completes, you'll have a CSV file containing every Power BI interaction across your tenant for the past 30 days. This file is your raw material.
While the Activity Log gives you tenant-wide coverage, Usage Metrics gives you a pre-built analytical view at the individual report or dashboard level. This is the built-in feature that report owners (not just admins) can access.
In the Power BI Service, navigate to any workspace. Hover over a report in the content list — you'll see a row of icons appear. Click the three-dot "More options" menu, then select "View usage metrics report." Power BI will open a pre-built report showing views, unique viewers, and sharing activity for that specific artifact.
The Usage Metrics report shows you:
Usage Metrics is convenient, but it has a significant limitation: you have to open it one report at a time. If you have 340 reports, clicking through each one individually is not a governance strategy. It's busywork.
This is why you need the Activity Log. The Activity Log gives you all of this data in one extractable feed that you can analyze across your entire portfolio simultaneously. Usage Metrics is most useful for report owners who want a quick snapshot of their own content, while the Activity Log is the tool for administrators doing portfolio-level governance.
Tip: Microsoft also provides an enhanced Usage Metrics experience that saves data to a dataset you can customize. In a workspace, go to Settings → Usage metrics → "Per user data" to enable it. This creates a dataset in the workspace that you can connect to from Power BI Desktop for custom analysis — a middle ground between the built-in report and full Activity Log extraction.
Now that you have a CSV file, let's bring it into Power BI Desktop and shape it for analysis.
Open Power BI Desktop. In the ribbon, click Home → Get data → Text/CSV. Navigate to your exported CSV file and click Open. Power BI will preview the data. Click Transform Data rather than Load — you have some shaping to do first.
The Activity Log contains around 30 columns. For adoption analysis, the most important are:
| Column | What it tells you |
|---|---|
Id |
Unique identifier for each event |
CreationTime |
When the event occurred (UTC) |
UserId |
Email address of the user who performed the action |
Activity |
The type of action (e.g., ViewReport, ExportArtifact) |
ArtifactName |
The name of the report or dashboard |
ArtifactId |
Unique ID of the artifact (stable even if renamed) |
WorkSpaceName |
The workspace containing the artifact |
WorkspaceId |
Unique ID of the workspace |
UserAgent |
Browser or app used to access the report |
In the Power Query Editor, apply these transformations:
1. Filter to relevant activity types. Click the dropdown arrow on the Activity column and select only the events you care about: ViewReport, ViewDashboard, ExportArtifact.
2. Parse the CreationTime column. Right-click the CreationTime column header, choose Change Type → Date/Time. Power BI needs this to be a proper datetime rather than a text string.
3. Extract the date portion. With CreationTime selected, go to Add Column → Date → Date Only. This creates a Date column you'll use for time-based filtering in your report.
4. Add a Day of Week column. Go to Add Column → Date → Day → Name of Day. This helps you spot patterns like "nobody opens this report on Fridays."
5. Remove columns you don't need. Hold Ctrl and click columns like CorrelationId, RecordType, and Operation — these are internal audit fields you won't analyze. Right-click and choose Remove Columns.
Click Close & Apply to load the shaped data into Power BI Desktop's model.
With clean data in the model, you can now create the measures and visuals that answer the real business questions.
In Power BI Desktop, go to the Data view and create a new table called Measures (this keeps your model tidy). Then add these DAX measures:
Total Views = COUNTROWS('ActivityLog')
Unique Viewers = DISTINCTCOUNT('ActivityLog'[UserId])
Unique Reports Accessed = DISTINCTCOUNT('ActivityLog'[ArtifactId])
Days Since Last View =
VAR LastViewDate = MAX('ActivityLog'[Date])
RETURN DATEDIFF(LastViewDate, TODAY(), DAY)
The Days Since Last View measure is particularly powerful. When you use it in a table visual filtered to a specific report, it tells you exactly how stale that report is.
To find unused reports, you need to cross-reference your activity data with a complete list of all reports in your tenant. This is where you run another PowerShell command:
Connect-PowerBIServiceAccount
# Get all workspaces
$workspaces = Get-PowerBIWorkspace -All -Include Reports
# Flatten report inventory into a list
$reportInventory = @()
foreach ($ws in $workspaces) {
foreach ($report in $ws.Reports) {
$reportInventory += [PSCustomObject]@{
WorkspaceId = $ws.Id
WorkspaceName = $ws.Name
ReportId = $report.Id
ReportName = $report.Name
}
}
}
$reportInventory | Export-Csv -Path "C:\PowerBI_Governance\ReportInventory.csv" `
-NoTypeInformation -Encoding UTF8
Write-Host "Inventory complete: $($reportInventory.Count) reports found."
Load this CSV into Power BI Desktop as a second table. Then create a relationship between ReportInventory[ReportId] and ActivityLog[ArtifactId]. Any report in the inventory that has no matching records in the activity log is, by definition, unused during your observation window.
Never Accessed =
CALCULATE(
COUNTROWS('ReportInventory'),
ISBLANK(RELATED('ActivityLog'[ArtifactId]))
)
Warning: A report with zero views in the last 30 days isn't necessarily worthless. It might be a quarterly compliance report that runs every three months. Always check the report's stated purpose before flagging it for deletion. The data tells you what, not why.
Create a new report page in Power BI Desktop called "Portfolio Health." Here's the layout to build using Power BI's standard visuals:
Card visuals (top row): Total Reports in Inventory, Reports Accessed in Last 30 Days, Unique Active Users, Total View Events. These give leadership the headline numbers at a glance.
Bar chart — Top 20 Reports by Views: Use ArtifactName on the Y-axis and Total Views on the X-axis. Sort descending. This immediately reveals your power users' favorite content and often surprises people — the most-viewed report is rarely the one that took the longest to build.
Table — Stale Reports (30+ Days with No Views): Create a calculated table or use a visual-level filter for Days Since Last View > 30. Columns: Report Name, Workspace Name, Days Since Last View. This is your cleanup candidate list.
Line chart — Daily View Trend: Plot Date on the X-axis and Total Views on the Y-axis. A sudden cliff in this chart usually means a report broke, was deleted, or a process changed. A gradual decline often means the report's audience found a better alternative.
Matrix — User Activity by Workspace: Rows = Workspace Name, Columns = Activity Type (View, Export, Share), Values = event counts. This surfaces which teams are most engaged and which workspaces are dormant.
Work through these steps to build your first governance snapshot:
Step 1: Install the Power BI management module and authenticate using the PowerShell commands from the "Accessing the Activity Log" section.
Step 2: Run the 30-day extraction script and save the output to a folder on your machine. Note how many events were returned.
Step 3: Run the report inventory script. How many reports exist in your tenant? Compare this number to reports that appear in your activity log — what percentage has been accessed at least once?
Step 4: Load both CSV files into Power BI Desktop. Create the relationship between them on the ID fields.
Step 5: Build a simple table visual showing every report in your inventory with columns for Report Name, Workspace Name, and Total Views (using the measure you created). Sort by Total Views ascending. The reports at the top of the list with zero views are your immediate candidates for review.
Step 6: Filter the table to reports with zero views and export the list. Share it with workspace owners and ask them to confirm whether each report is still needed.
"My PowerShell script returns zero events." First, confirm you're logged in with an account that has the Power BI Admin role. A regular Pro license user can't see tenant-wide activity. Second, check that you're using UTC datetime format in your parameters — local time offsets will cause the API to return no results.
"The ArtifactId in the activity log doesn't match the ReportId in my inventory."
This happens when you're comparing a report's ID to a dataset's ID. Some activity events (like DatasetRefresh) have a dataset ID in the artifact field, not a report ID. Filter your activity log to ViewReport events first, then do the join.
"My CSV has thousands of columns because the JSON wasn't fully parsed."
This happens when Power BI expands the JSON incorrectly during import. In Power Query, click the expand icon on any column that still shows [Record] values and select only the fields you need from the list.
"My Days Since Last View measure always shows today's date."
Check that you have a filter context applied. The measure uses MAX('ActivityLog'[Date]), so it needs a filter context (like a specific report selected in a slicer) to return a date other than the overall maximum.
"We have 500+ reports and the inventory script times out."
Add -Type "Workspace" to the Get-PowerBIWorkspace call to limit results to non-personal workspaces first. Personal workspaces ("My Workspace") generate enormous volume and are rarely relevant for enterprise governance.
You've covered a lot of ground. The Activity Log is your tenant-wide audit feed — powerful, comprehensive, but limited to 30 days and requiring consistent extraction to be useful. Usage Metrics gives report owners a quick view of their content's reach. Together, they give you the data to answer the question every analytics leader eventually faces: are we building things people actually use?
The most important habit this lesson should instill is regularity. Schedule your PowerShell extraction script to run daily using Windows Task Scheduler or Azure Automation. Every day you don't extract is data you can't recover. Even a simple CSV accumulation strategy beats nothing.
Where to go from here:
Get-PowerBIDatasetRefreshHistory to correlate reports that are expensive to refresh but never viewedGovernance isn't glamorous, but knowing which 40 reports carry 80% of your organization's analytics activity — and which 200 reports are costing you capacity and maintenance time for nothing — is one of the highest-leverage things a Power BI administrator can do.