a close-up of a building

Get Microsoft 365 Unified Audit Logs data

Microsoft 365 Unified Audit Logs are a centralized log of all activities performed within a Microsoft 365 tenant. These logs provide a detailed record of user, administrator and system activities, including information about when specific actions were performed, who performed them and from what location. The Unified Audit Logs help organizations to monitor and track changes to their Microsoft 365 environment, and assist with compliance, security, and troubleshooting efforts.



The logs can be used to track a wide range of activities, including:


  1. Changes to user accounts and settings
  2. Login activity, including successful and failed sign-ins
  3. Mailbox activity, such as email sends, receives and deletes
  4. SharePoint and OneDrive activity, including document and folder updates
  5. Exchange Online activity, including message tracking and changes to mailboxes
  6. Teams activity, including team and channel creation, updates, and deletions
  7. Power Platform activity, including flow and PowerApps creation and updates
  8. The Unified Audit Logs are stored in a central location, making it easy to access and search logs from multiple services in one place. They can be searched and filtered using a range of parameters, such as date range, user, IP address, and action type.


In conclusion, Microsoft 365 Unified Audit Logs provide a valuable tool for organizations to monitor and track changes to their Microsoft 365 environment, assist with compliance and security efforts, and provide a centralized location for security event information.

Enable and search for logs using the ExchangeOnline PowerShell


Open the PowerShell ISE as administrator and type:


Install-Module -Name ExchangeOnlineManagement -Force

Import-Module ExchangeOnlineManagement

Connect-ExchangeOnline

# This is optional as this setting is likely enabled for your org

Enable-OrganizationCustomization

# Check if the logs are enabled

Get-AdminAuditLogConfig

# The response looks like this
#AdminAuditLogEnabled : True
#LogLevel : None
#TestCmdletLoggingEnabled : False
#AdminAuditLogCmdlets : {*}
#AdminAuditLogParameters : {*}
#AdminAuditLogExcludedCmdlets : {}
#AdminAuditLogAgeLimit : 90.00:00:00
#LoadBalancerCount : 3
#RefreshInterval : 10
#PartitionInfo : {}
#AdminAuditLogMailbox :
#UnifiedAuditLogIngestionEnabled : False
#UnifiedAuditLogFirstOptInDate :
#AdminDisplayName :
#ExchangeVersion : 0.10 (14.0.100.0)
#Name : Admin Audit Log Settings
#DistinguishedName : CN=Admin Audit Log Settings,CN=Global

# If UnifiedAuditLogIngestionEnabled : False then execute set-adminauditlogconfig to enable it

Set-AdminAuditLogConfig -UnifiedAuditLogIngestionEnabled $true

#You will then be able to Search the logs. Bear in mind if you just enabled them, they will be empty
Search-UnifiedAuditLog -StartDate 1/30/2023 -EndDate 1/31/2023

# Response looks like
#RecordType : ExchangeItemAggregated
#CreationDate : 1/30/2023 11:15:01 PM
#UserIds : xxx@xxx.onmicrosoft.com
#Operations : MailItemsAccessed
#AuditData : ...


Something to keep in mind that by default the Search-UnifiedAuditLog cmdlet will return Exchange related logs.


You need to specify the RecordType to get SharePoint specific logs for example:

Search-UnifiedAuditLog -StartDate 1/30/2023 -EndDate 1/31/2023 -RecordType SharePoint


Here is the list with Record Types here: https://learn.microsoft.com/en-us/microsoft-365/compliance/audit-log-search?view=o365-worldwide#microsoft-365-services-that-support-auditing


Bear in mind SharePoint record type can be narrowed down to: SharePointFileOperation,SharePointSharingOperation, SharePointListOperation, SharePointCommentOperation.


Another handy feature of this cmdlet is the -Operations parameter allowing you to narrow down the search scope even more:

Search-UnifiedAuditLog -StartDate 1/30/2023 -EndDate 1/31/2023 -RecordType SharePointFileOperation -Operations FileAccessed,FilePreviewed


Having the option to specify the operations, is really handy as you don't have to query big logs set later.

A list with all operations is available here: https://learn.microsoft.com/en-us/microsoft-365/compliance/audit-log-activities?view=o365-worldwide

Search for SharePoint Online audit logs using PowerShell


This can be done by using PnP PowerShell. Open the PowerShell ISE as administrator and type:


Install-Module PnP.PowerShell

Import-Module PnP.PowerShell

Register-PnPManagementShellAccess

# Note instructions on how to get ClientId and ClientSecret below
Connect-PnPOnline https://xxx-admin.sharepoint.com -ClientId xxx-xxx-xxx -ClientSecret xxxxxx

Get-PnPUnifiedAuditLog -ContentType SharePoint -StartTime (Get-Date).AddDays(-1) -EndTime (Get-Date)

# Result looks like this
#CreationTime : 1/30/2023 1:10:44 PM
#Id : 40a0852e-4ca1-4a1e-cb6c-08db02c36576
#Operation : FileModifiedExtended
#OrganizationId : 224b4d39-fd4d-4cc7-9718-3e71361e062e
#OrganizationName :
#OriginatingServer :
#Parameters :
#ExtendedProperties :
#RecordType : 6
#UserKey : i:0h.f|membership|xxx@live.com
#UserType : 0
#Version : 1
#Workload : SharePoint
#ClientIP : 112.206.1.87
#ActorIpAddress :
#ResultStatus :
#ObjectId : https://xxx.sharepoint.com/sites/xxx/SitePages/Search-Result.aspx
#UserId : xxx@xxx.onmicrosoft.com
#CorrelationId : 9f4f91a0-00c8-3000-4607-39f72ef2113d
#EventSource : SharePoint
#ItemType : File
#UserAgent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) #Chrome/109.0.0.0 Safari/537.36
#EventData :
#WebId : 2cebccfa-00e7-4c92-a8c6-0734a972177d
#ListId : 7e9c6d4e-0f84-432b-9d51-6772b1688b10
#ListItemId :
#SiteUrl : https://xxx.sharepoint.com/sites/xxx/
#SourceFileExtension : aspx
#SourceFileName : Search-Result.aspx
#SourceRelativeUrl : SitePages

Connect to the Microsoft 365 Management APIs using Azure AD app


You will need a Azure AD and a Client Id and Secret to connect to the APIs. So you will have to create a new Azure Ad App


  1. Register an App: To start working with the Microsoft 365 Management API, you will need to register an app in Azure Active Directory. This app will be used to authenticate and authorize your API calls.
  2. Go to API Permissions and add Office 365 Management APIs -> Application -> ActivityFeed.Read permission
  3. Once you close the window find and click on the Grant admin consent button
  4. Then go to the certificates and secrets and create a new client secret
  5. Copy the secret value so you can use it with powershell or rest api
  6. Go to the Overview panel and copy the Client Id so you can use it with powershell or rest api
Grant Azure AD permissions

Search for unified audit logs by using the Rest API and your Azure AD app Client Id and Secret


  1. To start working with the Microsoft 365 Management API, you will need the registered app in Azure Active Directory. This app will be used to authenticate and authorize your API calls.
  2. Get an Access Token: Next, you will need to obtain an access token from Azure Active Directory. You can do this using the OAuth 2.0 Client Credentials flow, where you provide your app’s client ID and secret to the Azure AD token endpoint. The token endpoint will then return an access token that you can use to make API calls.
  3. Make an API Call: Once you have obtained an access token, you can make an API call to the Microsoft 365 Management API. For example, to retrieve a list of all audit logs, you can make a GET request to the following endpoint: https://manage.office.com/api/v1.0/<tenant-id>/activity/feed/subscriptions/content.
  4. Handle the Response: The API will return a JSON response containing the data you requested. You can parse this response and use the data in your application as needed.


Here is an example of a code snippet in Python to get the list of audit logs in a Microsoft 365 tenant:


Python code

import requests
import json

# Azure AD App credentials
client_id = "<app_client_id>"
client_secret = "<app_client_secret>"
tenant_id = "<tenant_id>"

# Request an access token
def get_access_token():
url = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"
headers = {
"Content-Type": "application/x-www-form-urlencoded"
}
data = {
"client_id": client_id,
"client_secret": client_secret,
"grant_type": "client_credentials",
"resource": "https://manage.office.com"
}
response = requests.post(url, headers=headers, data=data)
if response.status_code == 200:
return response.json()["access_token"]
else:
raise Exception("Failed to get access token")

# Make an API call to retrieve the list of audit logs
def get_audit_logs():
access_token = get_access_token()
url = f"https://manage.office.com/api/v1.0/{tenant_id}/activity/feed/subscriptions/content"
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
return response.json()
else:
raise Exception("Failed to get audit logs")

# Print the list of audit logs
audit_logs = get_audit_logs()
for log in audit_logs["value"]:
print(log["operation"])


JavaScript Code


async function getAccessToken() {
const clientId = "<app_client_id>";
const clientSecret = "<app_client_secret>";
const tenantId = "<tenant_id>";
const url = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
const headers = {
"Content-Type": "application/x-www-form-urlencoded"
};
const data = {
client_id: clientId,
client_secret: clientSecret,
grant_type: "client_credentials",
resource: "https://manage.office.com"
};
try {
const response = await fetch(url, {
method: "POST",
headers,
body: new URLSearchParams(data)
});
const json = await response.json();
return json.access_token;
} catch (error) {
throw new Error("Failed to get access token");
}
}

async function getAuditLogs() {
const accessToken = await getAccessToken();
const tenantId = "<tenant_id>";
const url = `https://manage.office.com/api/v1.0/${tenantId}/activity/feed/subscriptions/content`;
const headers = {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json"
};
try {
const response = await fetch(url, {
method: "GET",
headers
});
const json = await response.json();
return json;
} catch (error) {
throw new Error("Failed to get audit logs");
}
}

(async function main() {
const auditLogs = await getAuditLogs();
for (const log of auditLogs.value) {
console.log(log.operation);
}
})();

Microsoft 365 Management Audit REST APIs



Once you have access token, a GET request can be executed to identify the blob locations of the logs:

GET https://manage.office.com/api/v1.0/<tenant_id>/activity/feed/subscriptions/content?contentType=Audit.SharePoint&PublisherIdentifier=$<tenant_id>&startTime=2023-01-30T04:18:47&endTime=2023-01-31T04:18:47
Authorization: Bearer eyJ0eXAi...
Host: manage.office.com

# Response:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/10.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Tue, 31 Jan 2023 16:18:48 GMT
Content-Length: 851

[{
"contentUri": "https://manage.office.com/api/v1.0/224b4d39-fd4d-4cc7-9718-3e71361e062e/activity/feed/audit/20230130094624375096219$20230130131108554084900$audit_sharepoint$Audit_SharePoint$na9016",
"contentId": "20230130094624375096219$20230130131108554084900$audit_sharepoint$Audit_SharePoint$na9016",
"contentType": "Audit.SharePoint",
"contentCreated": "2023-01-30T13:11:08.554Z",
"contentExpiration": "2023-02-06T09:46:24.375Z"
}, {
"contentUri": "https://manage.office.com/api/v1.0/224b4d39-fd4d-4cc7-9718-3e71361e062e/activity/feed/audit/20230130131207718125819$20230130145234380038844$audit_sharepoint$Audit_SharePoint$na9016",
"contentId": "20230130131207718125819$20230130145234380038844$audit_sharepoint$Audit_SharePoint$na9016",
"contentType": "Audit.SharePoint",
"contentCreated": "2023-01-30T14:52:34.380Z",
"contentExpiration": "2023-02-06T09:46:24.375Z"
}]


  1. PublisherIdentifier is the tenant ID.
  2. contentType:
  3. Audit.AzureActiveDirectory
  4. Audit.Exchange
  5. Audit.SharePoint
  6. Audit.General (includes all other workloads not included in the previous content types)
  7. DLP.All (DLP events only for all workloads)

startTime amd endTime: Optional datetimes (UTC) indicating the time range of content to return, based on when the content became available. The time range is inclusive with respect to startTime (startTime <= contentCreated) and exclusive with respect to endTime (contentCreated < endTime), so that non-overlapping, incrementing time intervals can used to page through available content.


YYYY-MM-DD

YYYY-MM-DDTHH:MM

YYYY-MM-DDTHH:MM:SS


Both must be specified (or both omitted) and they must be no more than 24 hours apart, with the start time no more than 7 days in the past. By default, if startTime and endTime are omitted, then the content available in the last 24 hours is returned.


NOTE: Even though it is possible to specify a startTime and endTime more than 24 hours apart, this is not recommended. Furthermore, if you do get any results in response to a request for more than 24 hours, these could be partial results and should not be taken into account. The request should be issued with an interval of no more than 24 hours between the startTime and endTime.


Once you have the blob logs locations you can execute GET requests to retrieve the data:



GET https://manage.office.com/api/v1.0/224b4d39-fd4d-4cc7-9718-3e71361e062e/activity/feed/audit/20230130131207718125819$20230130145234380038844$audit_sharepoint$Audit_SharePoint$na9016
Authorization: Bearer eyJ0eXAi...
Host: manage.office.com

GET https://manage.office.com/api/v1.0/224b4d39-fd4d-4cc7-9718-3e71361e062e/activity/feed/audit/20230130131207718125819$20230130145234380038844$audit_sharepoint$Audit_SharePoint$na9016
Authorization: Bearer eyJ0eXAi...
Host: manage.office.com



The Exchange Online cmdlet is using different api


# Search-UnifiedAuditLog -StartDate 1/30/2023 -EndDate 1/31/2023 -RecordType SharePointFileOperation -Operations FileAccessed,FilePreviewed

# Request Headers
POST https://outlook.office365.com/adminapi/beta/224b4d39-fd4d-4cc7-9718-3e71361e062e/InvokeCommand HTTP/1.1
Prefer: odata.maxpagesize=1000
X-AnchorMailbox: UPN:xxx@xxx.onmicrosoft.com
X-ResponseFormat: clixml
Content-Type: application/json
connection-id: b97012fb-0a3c-4362-ab71-0be19732343d
client-request-id: 6fafde3b-f187-4f5e-93b4-6dd3cf13ed80
X-ClientApplication: ExoManagementModule
Accept: application/json
Accept-Language: en-US
Accept-Charset: UTF-8
Authorization: Bearer eyJ0eX...
WarningAction:
Accept-Encoding: gzip
X-SerializationLevel: Partial
X-ClientModuleVersion: 3.1.0
User-Agent: Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.19041.2364
Host: outlook.office365.com
Content-Length: 201

# Request Body
{"CmdletInput":{"CmdletName":"Search-UnifiedAuditLog","Parameters":{"StartDate":"1/30/2023","RecordType":"SharePointFileOperation","EndDate":"1/31/2023","Operations":["FileAccessed","FilePreviewed"]}}}