Creating a Composite Microsoft 365 User Activity Report

Bringing Microsoft 365 User Activity Data Together from Multiple Workloads

I’ve been dabbling with the Microsoft Graph usage report API for a couple of years. This is the API that powers the activity reports available in the Microsoft 365 admin center, Teams admin center, and SharePoint Online admin center, so it’s a good source of hard information.

The output of my labor is the Microsoft 365 user activity report, a PowerShell script that assembles data from SharePoint Online, Exchange Online, Teams, OneDrive for Business, and Yammer activity to build a picture of how active a user account is, with the intention of removing underused or unused accounts to save on licensing costs. The 2020 version of the script introduced a bunch of performance fixes to make it possible to retrieve data quickly and efficiently.

New Version Extends the Usage History to 180 Days

Recently, a reader pointed out that the usage report API now supports a lookback period of 180 days, doubling the previous 90 days. In other words, you can fetch information about the activities performed by an account inside Microsoft 365 for the last 180 days. I don’t know when Microsoft made this change, but it’s a good one.

The usage report API doesn’t capture data about every possible user activity, nor does it cover all workloads. For instance, there’s no usage API covering Stream and Planner activity. However, in the case of Stream, once the transition to OneDrive for Business and SharePoint Online, video activities will show up in the data for those workloads.

In any case, the usage data is sufficient to make a good assessment of just how active an account is. After all, if little or no trace of activity exists over 180 days, the account probably isn’t too active and is a candidate for removal. Measuring usage over 90 days is also a good yardstick of activity but doubling the measurement period makes the assessment even more accurate because it accommodates long absences such as sabbaticals and parental leave. This underlines the need to assess data in a wider context when deciding whether accounts really are inactive.

Example Microsoft 365 User Activity Data

The script works by extracting usage data for the supported workloads and combining them into an overall record per user. Here’s an example of a combined record. Note that usage data is always a couple of days behind real time.

UPN                     : Tony.Redmond@office365itpros.com
DisplayName             : Tony Redmond
Status                  : Account in use
LastSignIn              : 03/08/2022 18:08
DaysSinceSignIn         : 0
EXOLastActive           : 31-Jul-2022
EXODaysSinceActive      : 3
EXOQuotaUsed            : 5.91
EXOItems                : 34324
EXOSendCount            : 2572
EXOReadCount            : 4661
EXOReceiveCount         : 11158
TeamsLastActive         : 01-Aug-2022
TeamsDaysSinceActive    : 2
TeamsChannelChat        : 362
TeamsPrivateChat        : 493
TeamsMeetings           : 22
TeamsCalls              : 2
SPOLastActive           : 31-Jul-2022
SPODaysSinceActive      : 3
SPOViewedEditedFiles    : 798
SPOSyncedFiles          : 575
SPOSharedExtFiles       : 17
SPOSharedIntFiles       : 34
SPOVisitedPages         : 92
OneDriveLastActive      : 31-Jul-2022
OneDriveDaysSinceActive : 3
OneDriveFiles           : 6183
OneDriveStorage         : 27.2147
OneDriveQuota           : 1024
YammerLastActive        : 19-Jul-2022
YammerDaysSinceActive   : 15
YammerPosts             : 104
YammerReads             : 238
YammerLikes             : 1
License                 : POWER BI (FREE)+ENTERPRISE MOBILITY + SECURITY E5+BUSINESS APPS
 (FREE)+MICROSOFT POWER AUTOMATE FREE+MICROSOFT VIVA TOPICS+OFFICE 365 E5
OneDriveSite            : https://redmondassociates-my.sharepoint.com/personal/tony_redmond_office365itpros_com
IsDeleted               : False
EXOReportDate           : 31-Jul-2022
TeamsReportDate         : 01-Aug-2022
UsageFigure             : 5.2

Very importantly, if your organization chooses to obfuscate usage data (Figure 1), it isn’t possible to generate the report because user principal names provide the match for usage data from the workloads, and the routine that generates the obscured data creates different values for the user principal name in each workload.

Concealed data setting for reports in the Microsoft 365 admin center
Figure 1: Concealed data setting for reports in the Microsoft 365 admin center

Microsoft 365 User Activity Report Output

The output generated by the script is a PowerShell list which can be exported in different formats. Figure 2 shows the output as viewed through the Out-GridView cmdlet. The script also generates a CSV file, but you could also use the ImportExcel module to create a nicely-formatted Excel worksheet or the PSWriteHTML module to generate a HTML report.

Example of Microsoft 365 user activity report data
Figure 2: Example of Microsoft 365 user activity report data

You can download the updated Microsoft 365 user activity report script from GitHub. If you find an enhancement (aka a bug fix), please suggest it in GitHub. It’s always good to have extra eyes review and improve code.


Insight like this doesn’t come easily. You’ve got to know the technology and understand how to look behind the scenes. Benefit from the knowledge and experience of the Office 365 for IT Pros team by subscribing to the ultimate eBook covering Office 365 and the wider Microsoft 365 ecosystem.

13 Replies to “Creating a Composite Microsoft 365 User Activity Report”

  1. I’ve been trying to run this script but I am getting 403 forbiden errors. Yet I should have all the right permissions. App registration has the following application permissions for MsGraph :
    AuditLog.Read.All
    Directory.Read.All
    User.Read.All

    The default delegation User.Read was kept

    Is there a config I need to edit in the script apart from the CSV output TenantID AppID and Secret? Or I am missing something in my tenant in order to access the data?

    Thank you

      1. Yup, missed that after passing initial configurations and never re-read the script comments. Works perfectly now and thank you for this wonderfull script

      2. I’ve added all these permissions but I’m still getting 403 forbidden errors… what could be the problem?

  2. That’s exactly what I needed today! Everything worked great, thanks for the work! Greetings from Germany

  3. This is exactly what I have been looking for when it comes to lastsignin data. The script is running successfully, but the license data does not seem to populate for the majority of my licensed users.

    1. You mean the license data returned by the usage API? If you look at records in the $TeamUserData array, do you see license data there?

      Here’s an example:

      $TeamsUserData[4]

      Report Refresh Date : 2022-12-14
      User Id : c814b6e2-d4c2-431d-b82c-e059b152c96c
      User Principal Name : Jeff.Brown@office365itpros.com
      Last Activity Date :
      Is Deleted : False
      Deleted Date :
      Assigned Products : OFFICE 365 E3
      Team Chat Message Count : 0
      Private Chat Message Count : 0
      Call Count : 0
      Meeting Count : 0
      Meetings Organized Count : 0
      Meetings Attended Count : 0
      Ad Hoc Meetings Organized Count : 0
      Ad Hoc Meetings Attended Count : 0
      Scheduled One-time Meetings Organized Count : 0
      Scheduled One-time Meetings Attended Count : 0
      Scheduled Recurring Meetings Organized Count : 0
      Scheduled Recurring Meetings Attended Count : 0
      Audio Duration : PT0S
      Video Duration : PT0S
      Screen Share Duration : PT0S
      Audio Duration In Seconds : 0
      Video Duration In Seconds : 0
      Screen Share Duration In Seconds : 0
      Has Other Action : No
      Urgent Messages : 0
      Post Messages : 0
      Tenant Display Name : REDMOND & ASSOCIATES
      Shared Channel Tenant Display Names :
      Reply Messages : 0
      Is Licensed : Yes
      Report Period : 180

  4. For anyone looking for the option for “Display concealed user, group, and site names in all reports”

    Go to the Microsoft 365 admin center. Go to Settings > Org Settings > Services. Select Reports. Clear Display concealed user, group, and site names in all reports, and then select Save

    1. *Edit* For anyone looking for the option for “Display concealed user, group, and site names in all reports”

      Go to the Microsoft 365 admin center. Go to Settings > Org Settings > Services. Select Reports.

  5. fantastic report! Thanks for this, wondering if anyone has found a way to turn this into reporting days of the week, meaning M-T-W-th-f-sa-su rather than date ranges etc. End goal would be to show that users WFH/office,etc in a hybrid are statistically as busy, more busy, or less busy certain days of the week than others.

    1. I think you might get into difficulties with unions/worker councils if you were to do such a thing…

      In any case, the minimum period for the usage reports API is 7 days. Without 1-day granularity, it’s not feasible to use this data for such a purpose.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.