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.
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.
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.
Figure 2: Example of Microsoft 365 user activity report data
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.
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?
Yup, missed that after passing initial configurations and never re-read the script comments. Works perfectly now and thank you for this wonderfull script
Loading...
That’s exactly what I needed today! Everything worked great, thanks for the work! Greetings from Germany
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.
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
{"id":null,"mode":"button","open_style":"in_modal","currency_code":"EUR","currency_symbol":"\u20ac","currency_type":"decimal","blank_flag_url":"https:\/\/office365itpros.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/blank.gif","flag_sprite_url":"https:\/\/office365itpros.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/flags.png","default_amount":100,"top_media_type":"featured_image","featured_image_url":"https:\/\/office365itpros.com\/wp-content\/uploads\/2022\/11\/cover-141x200.jpg","featured_embed":"","header_media":null,"file_download_attachment_data":null,"recurring_options_enabled":true,"recurring_options":{"never":{"selected":true,"after_output":"One time only"},"weekly":{"selected":false,"after_output":"Every week"},"monthly":{"selected":false,"after_output":"Every month"},"yearly":{"selected":false,"after_output":"Every year"}},"strings":{"current_user_email":"","current_user_name":"","link_text":"Virtual Tip Jar","complete_payment_button_error_text":"Check info and try again","payment_verb":"Pay","payment_request_label":"Office 365 for IT Pros","form_has_an_error":"Please check and fix the errors above","general_server_error":"Something isn't working right at the moment. Please try again.","form_title":"Office 365 for IT Pros","form_subtitle":null,"currency_search_text":"Country or Currency here","other_payment_option":"Other payment option","manage_payments_button_text":"Manage your payments","thank_you_message":"Thank you for supporting the work of Office 365 for IT Pros!","payment_confirmation_title":"Office 365 for IT Pros","receipt_title":"Your Receipt","print_receipt":"Print Receipt","email_receipt":"Email Receipt","email_receipt_sending":"Sending receipt...","email_receipt_success":"Email receipt successfully sent","email_receipt_failed":"Email receipt failed to send. Please try again.","receipt_payee":"Paid to","receipt_statement_descriptor":"This will show up on your statement as","receipt_date":"Date","receipt_transaction_id":"Transaction ID","receipt_transaction_amount":"Amount","refund_payer":"Refund from","login":"Log in to manage your payments","manage_payments":"Manage Payments","transactions_title":"Your Transactions","transaction_title":"Transaction Receipt","transaction_period":"Plan Period","arrangements_title":"Your Plans","arrangement_title":"Manage Plan","arrangement_details":"Plan Details","arrangement_id_title":"Plan ID","arrangement_payment_method_title":"Payment Method","arrangement_amount_title":"Plan Amount","arrangement_renewal_title":"Next renewal date","arrangement_action_cancel":"Cancel Plan","arrangement_action_cant_cancel":"Cancelling is currently not available.","arrangement_action_cancel_double":"Are you sure you'd like to cancel?","arrangement_cancelling":"Cancelling Plan...","arrangement_cancelled":"Plan Cancelled","arrangement_failed_to_cancel":"Failed to cancel plan","back_to_plans":"\u2190 Back to Plans","update_payment_method_verb":"Update","sca_auth_description":"Your have a pending renewal payment which requires authorization.","sca_auth_verb":"Authorize renewal payment","sca_authing_verb":"Authorizing payment","sca_authed_verb":"Payment successfully authorized!","sca_auth_failed":"Unable to authorize! Please try again.","login_button_text":"Log in","login_form_has_an_error":"Please check and fix the errors above","uppercase_search":"Search","lowercase_search":"search","uppercase_page":"Page","lowercase_page":"page","uppercase_items":"Items","lowercase_items":"items","uppercase_per":"Per","lowercase_per":"per","uppercase_of":"Of","lowercase_of":"of","back":"Back to plans","zip_code_placeholder":"Zip\/Postal Code","download_file_button_text":"Download File","input_field_instructions":{"tip_amount":{"placeholder_text":"How much would you like to tip?","initial":{"instruction_type":"normal","instruction_message":"How much would you like to tip? Choose any currency."},"empty":{"instruction_type":"error","instruction_message":"How much would you like to tip? Choose any currency."},"invalid_curency":{"instruction_type":"error","instruction_message":"Please choose a valid currency."}},"recurring":{"placeholder_text":"Recurring","initial":{"instruction_type":"normal","instruction_message":"How often would you like to give this?"},"success":{"instruction_type":"success","instruction_message":"How often would you like to give this?"},"empty":{"instruction_type":"error","instruction_message":"How often would you like to give this?"}},"name":{"placeholder_text":"Name on Credit Card","initial":{"instruction_type":"normal","instruction_message":"Enter the name on your card."},"success":{"instruction_type":"success","instruction_message":"Enter the name on your card."},"empty":{"instruction_type":"error","instruction_message":"Please enter the name on your card."}},"privacy_policy":{"terms_title":"Terms and conditions","terms_body":null,"terms_show_text":"View Terms","terms_hide_text":"Hide Terms","initial":{"instruction_type":"normal","instruction_message":"I agree to the terms."},"unchecked":{"instruction_type":"error","instruction_message":"Please agree to the terms."},"checked":{"instruction_type":"success","instruction_message":"I agree to the terms."}},"email":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email address"},"success":{"instruction_type":"success","instruction_message":"Enter your email address"},"blank":{"instruction_type":"error","instruction_message":"Enter your email address"},"not_an_email_address":{"instruction_type":"error","instruction_message":"Make sure you have entered a valid email address"}},"note_with_tip":{"placeholder_text":"Your note here...","initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"empty":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"not_empty_initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"saving":{"instruction_type":"normal","instruction_message":"Saving note..."},"success":{"instruction_type":"success","instruction_message":"Note successfully saved!"},"error":{"instruction_type":"error","instruction_message":"Unable to save note note at this time. Please try again."}},"email_for_login_code":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email to log in."},"success":{"instruction_type":"success","instruction_message":"Enter your email to log in."},"blank":{"instruction_type":"error","instruction_message":"Enter your email to log in."},"empty":{"instruction_type":"error","instruction_message":"Enter your email to log in."}},"login_code":{"initial":{"instruction_type":"normal","instruction_message":"Check your email and enter the login code."},"success":{"instruction_type":"success","instruction_message":"Check your email and enter the login code."},"blank":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."},"empty":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."}},"stripe_all_in_one":{"initial":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"empty":{"instruction_type":"error","instruction_message":"Enter your credit card details here."},"success":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"invalid_number":{"instruction_type":"error","instruction_message":"The card number is not a valid credit card number."},"invalid_expiry_month":{"instruction_type":"error","instruction_message":"The card's expiration month is invalid."},"invalid_expiry_year":{"instruction_type":"error","instruction_message":"The card's expiration year is invalid."},"invalid_cvc":{"instruction_type":"error","instruction_message":"The card's security code is invalid."},"incorrect_number":{"instruction_type":"error","instruction_message":"The card number is incorrect."},"incomplete_number":{"instruction_type":"error","instruction_message":"The card number is incomplete."},"incomplete_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incomplete."},"incomplete_expiry":{"instruction_type":"error","instruction_message":"The card's expiration date is incomplete."},"incomplete_zip":{"instruction_type":"error","instruction_message":"The card's zip code is incomplete."},"expired_card":{"instruction_type":"error","instruction_message":"The card has expired."},"incorrect_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incorrect."},"incorrect_zip":{"instruction_type":"error","instruction_message":"The card's zip code failed validation."},"invalid_expiry_year_past":{"instruction_type":"error","instruction_message":"The card's expiration year is in the past"},"card_declined":{"instruction_type":"error","instruction_message":"The card was declined."},"missing":{"instruction_type":"error","instruction_message":"There is no card on a customer that is being charged."},"processing_error":{"instruction_type":"error","instruction_message":"An error occurred while processing the card."},"invalid_request_error":{"instruction_type":"error","instruction_message":"Unable to process this payment, please try again or use alternative method."},"invalid_sofort_country":{"instruction_type":"error","instruction_message":"The billing country is not accepted by SOFORT. Please try another country."}}}},"fetched_oembed_html":false}
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
The code says:
# Needs the Reports.Read.All permission to get user data
# Needs the AuditLog.Read.All and Directory.Read.All permissions to read user signin data
Yup, missed that after passing initial configurations and never re-read the script comments. Works perfectly now and thank you for this wonderfull script
That’s exactly what I needed today! Everything worked great, thanks for the work! Greetings from Germany
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.
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
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
*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.