ZIZHUOffice365ManagementAPI.psm1
<#
.Synopsis PowerShell SDK for Office365ManageAPI .Description IT Admin can use this PowerShell module to call Office365ManagementAPI. It suppports all operations of Office365ManagementAPI. Also supports Webhook subscriptions and notifications. .Example # Installl and import this PowerShell Module https://www.powershellgallery.com/packages/ZIZHUOffice365ManagementAPI/1.0 https://github.com/APACMW/APACMWOffice365ManagementAPIModule Install-Module -Name ZIZHUOffice365ManagementAPI Import-Module -Name ZIZHUOffice365ManagementAPI .Example # Connect to ZIZHUOffice365ManagementAPI module via client secret $clientID = 'bc4db1db-b705-434a-91ff-145aa94185c8'; $tenantId = 'cff343b2-f0ff-416a-802b-28595997daa2'; $clientSecret = ''; Connect-Office365ManagementAPI -tenantID $tenantId -clientID $clientID -ClientSecret $clientSecret; # Connect to ZIZHUOffice365ManagementAPI module via client certificate $clientID = 'bc4db1db-b705-434a-91ff-145aa94185c8'; $tenantId = 'cff343b2-f0ff-416a-802b-28595997daa2'; $thumbprint = '15958E05E3E4C2E563CE9BC346B25A2D70867048'; $clientcertificate= get-item "cert:\localmachine\my\$thumbprint"; Connect-Office365ManagementAPI -tenantID $tenantId -clientID $clientID -clientcertificate $clientcertificate; # Connect to ZIZHUOffice365ManagementAPI module via user sign-in $clientID = '9b0547c4-28b1-466d-a80e-677c6dc42d42'; $tenantId = 'cff343b2-f0ff-416a-802b-28595997daa2'; $redirectUri='https://login.microsoftonline.com/common/oauth2/nativeclient' $loginHint = 'freeman@vjqg8.onmicrosoft.com'; Connect-Office365ManagementAPI -tenantID $tenantId -clientID $clientID -loginHint $loginHint -redirectUri $redirectUri; # List available content and receive audit data $startTime = "2024-04-04T00:00:00"; $endTime = "2024-04-05T00:00:00"; $blobs = Get-AvailableContent -startTime $startTime -endTime $endTime; Receive-Content -blobs $blobs; # Get current subscriptions/Stop subscriptions Get-CurrentSubscriptions; Stop-Subscription -contentType AuditSharePoint; Stop-Subscriptions; # Start thesubscriptions. If don't pass $webHookBody, no webhook for the subscription $webhookEndpoint='https://5a22-2404-f801-9000-1a-efea-00-23.ngrok-free.app/api/O365ManagementAPIHttpFunction'; $authId = 'ZIZHUOffice365ManagementAPINotification20240220'; $expiration= "2024-04-14T00:00:00"; $webHookBody= @" { "webhook" : { "address": "$($webhookEndpoint)", "authId": "$($authId)", "expiration": "$($expiration)" } } "@; Start-Subscription AuditAzureActiveDirectory $webHookBody; Start-Subscription AuditExchange $webHookBody; Start-Subscription AuditSharePoint $webHookBody; Start-Subscription AuditGeneral $webHookBody; Start-Subscription DLPAll $webHookBody; # List the notifications $startTime = "2024-04-05T00:00:00"; $endTime = "2024-04-06T00:00:00"; Get-Notifications -startTime $startTime -endTime $endTime -contentType AuditExchange; # Receive the FriendlyNames for DLP Resource Receive-ResourceFriendlyNames; # Clean after usgae Disconnect-Office365ManagementAPI; Get-Module ZIZHUOffice365ManagementAPI | Remove-Module; #> # Define the tenant environment types enum Office365SubscriptionPlanType { Enterpriseplan GCCGovernmentPlan GCCHighGovernmentPlan DoDGovernmentPlan GallatinPlan } # Define the content types enum ContentType { AuditAzureActiveDirectory AuditExchange AuditSharePoint AuditGeneral DLPAll } # Define the Blob type as an Azure storage unit to keep the audit data class Blob { [string]$contentUri [string]$contentId [string]$contentType [datetime]$contentCreated [datetime]$contentExpiration [System.Object[]]$auditRecords } class WebHook { [string]$authId [string]$address [string]$expiration [string]$status } class Subscription { [string]$contentType [string]$status [WebHook]$webhook } class Notification { [string]$contentType [string]$contentId [string]$contentUri [string]$notificationStatus [datetime]$contentCreated [datetime]$notificationSent [datetime]$contentExpiration } [string]$script:tenantID = $null; [string]$script:clientId = $null; [string]$script:clientsecret = $null; [string]$script:redirectUri = $null; [string]$script:loginHint = $null; [X509Certificate]$script:clientcertificate = $null; $script:AuthResult = $null; [string]$script:root = $null; [string]$script:scope = $null; $script:httpErrorResponse = $null; $script:maxretries = 4; $script:sleepSeconds = 2; function Show-VerboseMessage { param( [Parameter(Mandatory = $true)][string]$message ) Write-Verbose "[$((Get-Date).ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss"))]: $message"; return; } function Show-InformationalMessage { param( [Parameter(Mandatory = $true)][string]$message, [Parameter(Mandatory = $false)][System.ConsoleColor]$consoleColor = [System.ConsoleColor]::Gray ) $defaultConsoleColor = $host.UI.RawUI.ForegroundColor; $host.UI.RawUI.ForegroundColor = $consoleColor; Write-Information -InformationAction Continue -MessageData "[$((Get-Date).ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss"))]: $message"; $host.UI.RawUI.ForegroundColor = $defaultConsoleColor; return; } function Show-HttpErrorResponse { param( [Parameter(Mandatory = $true)][object]$httpErrorResponse ) $httpError = $httpErrorResponse | Format-List | Out-String; Show-InformationalMessage -message $httpError -consoleColor Red; } function Show-LastErrorDetails { param( [Parameter(Mandatory = $false)]$lastError = $Error[0] ) $lastError | Format-List -Property * -Force; $lastError.InvocationInfo | Format-List -Property *; $exception = $lastError.Exception; for ($depth = 0; $null -ne $exception; $depth++) { Show-InformationalMessage -message "$depth" * 80 -consoleColor Green; $exception | Format-List -Property * -Force; $exception = $exception.InnerException; } } function Show-AppPermissions { <# .SYNOPSIS Show the API permissions in the access token .DESCRIPTION Show the API permissions in the access token .PARAMETER jwtToken The accesstoken string .EXAMPLE Show-AppPermissions $accesstoken .NOTES Just show the API permissions. Not enforce to must have the specific permissions #> [cmdletbinding()] param( [Parameter(Mandatory = $true)][string]$jwtToken ) $decodedToken = Read-JWTtoken -token $jwtToken; if ($null -ne $decodedToken -and $null -ne $decodedToken.scp) { $permissions = $decodedToken.scp; } elseif ($null -ne $decodedToken -and $null -ne $decodedToken.roles) { $permissions = $decodedToken.roles; } else { $permissions = $null; } Show-InformationalMessage -message "API permissions in the AccessToke: $($permissions)" -consoleColor Yellow; } function Read-JWTtoken { <# .SYNOPSIS Parse the access token/ID token based on https://datatracker.ietf.org/doc/html/rfc7519 .DESCRIPTION Parse the access token/ID token based on https://datatracker.ietf.org/doc/html/rfc7519 .PARAMETER token The accesstoken/ID token string .EXAMPLE Read-JWTtoken -token $jwtToken .NOTES https://datatracker.ietf.org/doc/html/rfc7519 #> [cmdletbinding()] param( [Parameter(Mandatory = $true)][string]$token ) # Validate Access and ID tokens per RFC 7519 if (!$token.Contains(".") -or !$token.StartsWith("eyJ")) { Show-InformationalMessage -message "Invalid token" -consoleColor Red; return; } # Parse the Header $tokenheader = $token.Split(".")[0].Replace('-', '+').Replace('_', '/'); # Fix padding as needed; keep adding "=" until string length modulus 4 reaches 0 while ($tokenheader.Length % 4) { Show-VerboseMessage -message "Invalid length for a Base-64 char array or string, adding ="; $tokenheader += "="; } Show-VerboseMessage -message "Base64 encoded (padded) header:" Show-VerboseMessage -message $tokenheader; # Convert from Base64 encoded string to PSObject Show-VerboseMessage -message "Decoded header:" $headers = [System.Text.Encoding]::ASCII.GetString([system.convert]::FromBase64String($tokenheader)) | ConvertFrom-Json | Format-List | Out-String; Show-VerboseMessage -message $headers; # Payload $tokenPayload = $token.Split(".")[1].Replace('-', '+').Replace('_', '/'); # Fix padding as needed; keep adding "=" until string length modulus 4 reaches 0 while ($tokenPayload.Length % 4) { Show-VerboseMessage -message "Invalid length for a Base-64 char array or string, adding ="; $tokenPayload += "="; } Show-VerboseMessage -message "Base64 encoded (padded) payload:"; Show-VerboseMessage -message $tokenPayload; # Convert to Byte array $tokenByteArray = [System.Convert]::FromBase64String($tokenPayload); # Convert to string array $tokenArray = [System.Text.Encoding]::ASCII.GetString($tokenByteArray); Show-VerboseMessage -message "Decoded array in JSON format:" Show-VerboseMessage -message $tokenArray # Convert from JSON to PSObject $tokenObj = $tokenArray | ConvertFrom-Json; Show-VerboseMessage -message "Decoded Payload:" Write-Output $tokenObj; return; } function Set-RootString { <# .SYNOPSIS Based on the tenant type to specify Office365 management API endpoint .DESCRIPTION Based on the tenant type to specify Office365 management API endpoint .PARAMETER office365SubscriptionPlanType The tenant type (data type enum Office365SubscriptionPlanType) #> param( [Parameter(Mandatory = $true)][Office365SubscriptionPlanType]$office365SubscriptionPlanType ) switch ($office365SubscriptionPlanType) { Enterpriseplan { $script:root = 'https://manage.office.com'; Break; } GCCGovernmentPlan { $script:root = 'https://manage-gcc.office.com'; Break; } GCCHighGovernmentPlan { $script:root = 'https://manage.office365.us'; Break; } DoDGovernmentPlan { $script:root = 'https://manage.protection.apps.mil'; Break; } GallatinPlan { $script:root = 'https://manage.office365.cn'; Break; } Default { Write-Error "unknown type: $contentTypeData" -ErrorAction Stop; } } $script:scope = "$script:root/.default"; Show-VerboseMessage -message "Root of Office365 Management API endpoint: $($script:root) and scope: $($script:scope)"; return; } function Get-ContentTypeString { <# .SYNOPSIS From the contentType(enum), to generate the relevant content type script used for Http request .DESCRIPTION From the contentType(enum), to generate the relevant content type script used for Http request .PARAMETER contentTypeData Specify the contentTypeData (enum ContentType) #> param( [Parameter(Mandatory = $true)][ContentType]$contentTypeData ) [string]$result = $null; switch ($contentTypeData) { AuditAzureActiveDirectory { $result = "Audit.AzureActiveDirectory"; Break } AuditExchange { $result = "Audit.Exchange"; Break } AuditSharePoint { $result = "Audit.SharePoint"; Break } AuditGeneral { $result = "Audit.General"; Break } DLPAll { $result = "DLP.All"; Break } Default { Write-Error "unknown type: $contentTypeData" -ErrorAction Stop; } } Write-Output $result; return; } function Get-ContentTypeEnum { <# .SYNOPSIS Based on contenttype stirng to get the contentType as enum data type .DESCRIPTION Based on contenttype stirng to get the contentType as enum data type .PARAMETER contentTypeString The content type string #> param( [Parameter(Mandatory = $true)][string]$contentTypeString ) switch ($contentTypeString) { "Audit.AzureActiveDirectory" { $result = [ContentType]::AuditAzureActiveDirectory; Break } "Audit.Exchange" { $result = [ContentType]::AuditExchange; Break } "Audit.SharePoint" { $result = [ContentType]::AuditSharePoint; Break } "Audit.General" { $result = [ContentType]::AuditGeneral; Break } "DLP.All" { $result = [ContentType]::DLPAll; Break } Default { Write-Error "unknown type: $contentTypeData" -ErrorAction Stop; } } Write-Output $result; return; } Function Invoke-O365APIHttpRequest { <# .SYNOPSIS Sumbit the Http requests for all Ofice365 Management API operations with retry logic .DESCRIPTION Sumbit the Http requests for all Ofice365 Management API operations with retry logic .PARAMETER url Office365 Management API Request Url .PARAMETER httpVerb Http verb .PARAMETER requstBody The Http request body. Optional #> param ( [Parameter(Mandatory = $true)][string]$url, [Parameter(Mandatory = $true)][string]$httpVerb, [Parameter(Mandatory = $false)][string]$requstBody ) if ($null -eq $script:AuthResult) { Write-Error "Not authenticated. Stop." -ErrorAction Stop; } Show-VerboseMessage "Invoke-Webrequest $url $httpVerb"; if ($PSBoundParameters.ContainsKey('requstBody')) { Show-VerboseMessage "request body: $requstBody"; } # Used for retry logic $httpResponse = $null; $script:httpErrorResponse = $null; $retryCount = 0; # If fail, retry 4 times (but stop when response (Unauthorized,BadRequest)) do { if ($retryCount -gt 1) { $sleepSeconds = [math]::Pow($script:sleepSeconds, $retryCount); Show-VerboseMessage "Retry Invoke-O365APIHttpRequest $($httpVerb) $($url): $retryCount after $($sleepSeconds) seconds"; Start-Sleep -Seconds $sleepSeconds; } Get-OauthToken; $headerParams = @{'Authorization' = "$($script:AuthResult.tokentype) $($script:AuthResult.accesstoken)" }; try { if ($PSBoundParameters.ContainsKey("requstBody")) { $httpResponse = Invoke-WebRequest -uri $url -Headers $headerParams -Method $httpVerb -ContentType "application/json" -Body $requstBody; } else { $httpResponse = Invoke-WebRequest -uri $url -Headers $headerParams -Method $httpVerb; } } catch { Show-InformationalMessage -message "Http error: $($_.Exception.Response) Body: $($_.ErrorDetails.Message)" -consoleColor Red; $script:httpErrorResponse = $_.Exception.Response; } finally { $retryCount = $retryCount + 1; } } until ((($null -ne $httpResponse) -or ($retryCount -gt $script:maxretries)) -or (($null -ne $script:httpErrorResponse) -and ($script:httpErrorResponse.StatusCode -in @('Unauthorized', 'BadRequest')))) # If succeed, then pass the Http response in output stream to caller if (($null -ne $httpResponse) -and ($httpResponse.StatusCode -in (200, 204))) { Show-VerboseMessage "HTTP Response: $($httpResponse.RawContent)"; Write-Output $httpResponse; return; } # If fail, show the error information and stop Show-HttpErrorResponse -httpErrorResponse $script:httpErrorResponse; Write-Error "API request fails with error. Stop!" -ErrorAction Stop; } function Get-OauthToken { <# .SYNOPSIS Use the Msal.ps module to get the access token. Support client credential, Implicit auth flow .DESCRIPTION Use the Msal.ps module to get the access token. Support client credential, Implicit auth flow .NOTES Use the variables from script scope #> Show-VerboseMessage "Start to invoke Get-OauthToken"; # If the access token is valid, then use an existing token $utcNow = (get-date).ToUniversalTime().AddMinutes(1); if ($null -ne $script:AuthResult -and ($utcNow -lt $script:AuthResult.ExpiresOn.UtcDateTime)) { Show-VerboseMessage "Current accesstoken is valid before $($script:AuthResult.ExpiresOn.UtcDateTime)"; return; } # Implicit auth flow (delegated API permissions). Will try to get the access token silently. If fail, then interactive sign-in if (-not [string]::IsNullOrWhiteSpace($script:redirectUri)) { try { Show-VerboseMessage "Get-MsalToken via user sign-in"; $script:AuthResult = Get-MsalToken -ClientId $script:clientId -TenantId $script:tenantID -Silent -LoginHint $script:loginHint -RedirectUri $script:redirectUri -Scopes $script:scope; } Catch [Microsoft.Identity.Client.MsalUiRequiredException] { $script:AuthResult = Get-MsalToken -ClientId $script:clientId -TenantId $script:tenantID -Interactive -LoginHint $script:loginHint -RedirectUri $script:redirectUri -Scopes $script:scope; ; } Catch { Show-LastErrorDetails; Write-Error -Message "Can not get the access token, exit." -ErrorAction Stop; } } # Client credential auth flow. Can use the client secret or certificate else { try { if (-not [string]::IsNullOrWhiteSpace($script:clientsecret)) { Show-VerboseMessage "Get-MsalToken via client crendential auth flow"; $securedclientSecret = ConvertTo-SecureString $script:clientsecret -AsPlainText -Force $script:AuthResult = Get-MsalToken -clientID $script:clientId -ClientSecret $securedclientSecret -tenantID $script:tenantID -Scopes $script:scope; } elseif ($null -ne $script:clientcertificate) { $script:AuthResult = Get-MsalToken -clientID $script:clientId -ClientCertificate $script:clientcertificate -tenantID $script:tenantID -Scopes $script:scope; } } catch { Show-LastErrorDetails; Write-Error -Message "Can not get the access token, stop." -ErrorAction Stop; } } Show-VerboseMessage "Succeed to invoke Get-OauthToken"; } function Connect-Office365ManagementAPI { <# .SYNOPSIS Initilize the script varibles to prepare for calling APIs .DESCRIPTION Initilize the script varibles to prepare for calling APIs .PARAMETER tenantID tenant id .PARAMETER clientId Azure AD application Id .PARAMETER redirectUri The redirectUri used for implicit auth flow .PARAMETER loginHint The loginHint (user's UPN) used for implicit auth flow .PARAMETER clientsecret The clientsecret used for client credential auth flow .PARAMETER clientcertificate The clientcertificate used for client credential auth flow .PARAMETER office365SubscriptionPlanType Tenant type .EXAMPLE Connect-Office365ManagementAPI -tenantID $tenantId -clientID $clientID -ClientSecret $clientSecret; .NOTES Read how to register the app in Azure AD: https://learn.microsoft.com/en-us/office/office-365-management-api/get-started-with-office-365-management-apis #> param ( [Parameter(Mandatory = $true)][string]$tenantID, [Parameter(Mandatory = $true)][String]$clientId, [Parameter(Mandatory = $true, ParameterSetName = "authorizationcode")][String]$redirectUri, [Parameter(Mandatory = $true, ParameterSetName = "authorizationcode")][String]$loginHint, [Parameter(Mandatory = $true, ParameterSetName = "clientcredentialsSecret")][String]$clientsecret, [Parameter(Mandatory = $true, ParameterSetName = "clientcredentialsCertificate")][X509Certificate]$clientcertificate, [Parameter(Mandatory = $false)][Office365SubscriptionPlanType]$office365SubscriptionPlanType = [Office365SubscriptionPlanType]::Enterpriseplan ) $script:tenantID = $tenantID; $script:clientId = $clientId; if (-not [string]::IsNullOrWhiteSpace($clientsecret)) { $script:clientsecret = $clientsecret; } elseif ($null -ne $clientcertificate) { $script:clientcertificate = $clientcertificate; } elseif (-not [string]::IsNullOrWhiteSpace($redirectUri)) { $script:loginHint = $loginHint; $script:redirectUri = $redirectUri; } else { Write-Error "Not implement." -ErrorAction Stop; } Set-RootString $office365SubscriptionPlanType; Get-OauthToken; if ($null -eq $script:AuthResult) { Write-Error "Can not connect to Office365 Management API. Please check your app registration in AAD." -ErrorAction Stop; } Show-AppPermissions $script:AuthResult.accesstoken; Show-InformationalMessage -message "Successfuly Connect to Office365 Management API" -consoleColor Green; } function Start-Subscription { <# .SYNOPSIS Start Subscription for a content type .DESCRIPTION Start Subscription for a content type .PARAMETER contentType The mandatory content type .PARAMETER webhook The optional webhook .EXAMPLE Start-Subscription DLPAll $webHookBody; .NOTES reference: https://learn.microsoft.com/en-us/office/office-365-management-api/office-365-management-activity-api-reference#start-a-subscription #> param ( [Parameter(Mandatory = $true)][ContentType]$contentType, [Parameter(Mandatory = $false)][string]$webhook ) $contentTypestring = Get-ContentTypeString $contentType; $Subscriptions = Get-CurrentSubscriptions; $subscribedContentType = @($Subscriptions | Where-Object { $PSItem.status -eq "enabled" -and $PSItem.contentType -eq $contentTypeString } ); if ($subscribedContentType.Count -eq 0) { $subscriptionUrl = "$($script:root)/api/v1.0/$($script:tenantID)/activity/feed/subscriptions/start?contentType=$contentTypestring"; if ($PSBoundParameters.ContainsKey("webhook")) { $httpResponse = Invoke-O365APIHttpRequest -url $subscriptionUrl -httpVerb Post -requstBody $webhook; } else { $httpResponse = Invoke-O365APIHttpRequest -url $subscriptionUrl -httpVerb Post; } } else { Show-InformationalMessage -message "The subscription of $contentType has been started already, and please stop it before start with new parameters" -consoleColor Yellow; } $httpResponse | Format-List; } function Stop-Subscription { <# .SYNOPSIS Stop subscription for a content type .DESCRIPTION Stop subscription for a content type .PARAMETER contentType The mandatory contentType .EXAMPLE Stop-Subscription -contentType AuditSharePoint .NOTES reference: https://learn.microsoft.com/en-us/office/office-365-management-api/office-365-management-activity-api-reference#stop-a-subscription #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param ( [Parameter(Mandatory = $true)][ContentType]$contentType ) if ($PSCmdlet.ShouldProcess($contentType)) { $contentTypestring = Get-ContentTypeString $contentType; $subscriptionUrl = "$($script:root)/api/v1.0/$($script:tenantID)/activity/feed/subscriptions/stop?contentType=$contentTypestring"; $httpResponse = Invoke-O365APIHttpRequest -url $subscriptionUrl -httpVerb Post; $httpResponse | Format-List; } else { Show-InformationalMessage -message "The user decide to not stop subscription $contentType" -consoleColor Yellow; } } function Stop-Subscriptions { <# .SYNOPSIS Stop all subscriptions for this application .DESCRIPTION Stop all subscriptions for this application .EXAMPLE Stop-Subscriptions .NOTES General notes #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param() if ($PSCmdlet.ShouldProcess('Current subscriptions')) { $contentTypes = @(Get-CurrentSubscriptions | Where-Object { $_.status -eq "enabled" } | ForEach-Object { $psitem.contentType }); $contentTypes | ForEach-Object { $contentType = Get-ContentTypeEnum $PSItem; Stop-Subscription -contentType $contentType -Confirm:$false; } } } function Get-CurrentSubscriptions { <# .SYNOPSIS Get current subscriptions for this application .DESCRIPTION This operation returns a collection of the current subscriptions together with the associated webhooks .EXAMPLE Get-CurrentSubscriptions .NOTES reference: https://learn.microsoft.com/en-us/office/office-365-management-api/office-365-management-activity-api-reference#list-current-subscriptions #> [CmdletBinding()] param() $listSubscriptionURI = "$($script:root)/api/v1.0/$($script:tenantID)/activity/feed/subscriptions/list"; $httpResponse = Invoke-O365APIHttpRequest -url $listSubscriptionURI -httpVerb Get; $convertObjects = $httpResponse.Content | Out-String | ConvertFrom-Json; $subscriptions = New-Object Collections.Generic.List[Subscription]; $convertObjects | ForEach-Object { $subscription = New-Object Subscription; $subscription.contentType = $psitem.contentType; $subscription.status = $psitem.status; $subscription.webhook = $psitem.webhook -as [Webhook]; $subscriptions.Add($subscription); } Write-Output $subscriptions; return; } function Get-AvailableContent { <# .SYNOPSIS This operation lists the content currently available for retrieval for the specified content type .DESCRIPTION This operation lists the content currently available for retrieval for the specified content type .PARAMETER startTime Datetimes (UTC) indicating the start time of range for the audit content to return .PARAMETER endTime Datetimes (UTC) indicating the end time of range for the audit content to return .PARAMETER contentType The mandatory content type .EXAMPLE $blobs = Get-AvailableContent -startTime $startTime -endTime $endTime .NOTES reference: https://learn.microsoft.com/en-us/office/office-365-management-api/office-365-management-activity-api-reference#list-available-content #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)][datetime]$startTime, [Parameter(Mandatory = $true)][datetime]$endTime, [Parameter(Mandatory = $false)][ContentType]$contentType ) $Subscriptions = Get-CurrentSubscriptions; $contentTypes = @($Subscriptions | Where-Object { $_.status -eq "enabled" } | ForEach-Object { $psitem.contentType }); if ($PSBoundParameters.ContainsKey('contentType')) { $contentTypeString = Get-ContentTypeString $contentType; $contentTypes = @($contentTypes | Where-Object { $PSItem -eq $contentTypeString }); } if ($contentTypes.Count -eq 0) { Write-Warning "The subscription of the specified ContentType or any ContentTypes has not been started yet."; return; } $availableContent = New-Object Collections.Generic.List[Blob]; Show-VerboseMessage "Run Get-AvailableContent for the contenttypes $contentTypes"; $contentTypes | ForEach-Object { $enabledContentType = $psitem; # List available content $contentUrl = "$($script:root)/api/v1.0/$($script:tenantID)/activity/feed/subscriptions/content?contentType=$enabledContentType&startTime=" + $startTime + "&endTime=" + $endTime; While (-not [string]::IsNullOrEmpty($contentUrl)) { Show-VerboseMessage "List available content via the Url $contentUrl"; $httpResponse = Invoke-O365APIHttpRequest -url $contentUrl -httpVerb Get; $convertObjs = ConvertFrom-Json $httpResponse.Content; $convertObjs | ForEach-Object { $blob = New-Object Blob; $blob.contentUri = $psitem.contentUri; $blob.contentId = $psitem.contentId; $blob.contentType = $psitem.contentType; $blob.contentCreated = $psitem.contentCreated; $blob.contentExpiration = $psitem.contentExpiration; $availableContent.Add($blob); } # Support the paging (https://learn.microsoft.com/en-us/office/office-365-management-api/office-365-management-activity-api-reference#pagination) if ($null -ne $httpResponse.Headers.'NextPageUri') { $nextPageUri = $httpResponse.Headers.'NextPageUri'; Show-VerboseMessage "NextPageUri is $nextPageUri"; } $contentUrl = $httpResponse.Headers.'NextPageUri'; } } Write-Output $availableContent; return; } function Receive-Content { <# .SYNOPSIS To retrieve a content blob, make a GET request against the corresponding content URI that is included in the list of available content .DESCRIPTION To retrieve a content blob, make a GET request against the corresponding content URI that is included in the list of available content and in the notifications sent to a webhook. The returned content will be a collection of one more actions or events in JSON format .PARAMETER blobs The collection blobs parmater .EXAMPLE Receive-Content -blobs $blobs .NOTES reference:https://learn.microsoft.com/en-us/office/office-365-management-api/office-365-management-activity-api-reference#retrieve-content #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)][System.Object[]]$blobs ) if (($null -eq $blobs) -or ($blobs.Count -eq 0)) { Show-InformationalMessage "No available content, exit!" -consoleColor Yellow; return; } $blobs | ForEach-Object { try { $blob = $PSItem -as [Blob]; if ($null -ne $blob) { Show-VerboseMessage "Receive content from the content Url $($blob.contentUri)"; $httpResponse = Invoke-O365APIHttpRequest -url $blob.contentUri -httpVerb Get; $contents = $httpResponse; if ($null -ne $contents) { $auditRecords = $contents.Content | Out-String | ConvertFrom-Json; $blob.auditRecords = $auditRecords; } else { Write-Error "Can not receive content for $($blob)" -ErrorAction Continue; } } } catch { Show-LastErrorDetails; } } } function Receive-Notifications { <# .SYNOPSIS Not implement. Notifications are sent to the configured webhook for a subscription as new content becomes available .DESCRIPTION Not implement. Notifications are sent to the configured webhook for a subscription as new content becomes available .NOTES reference:https://learn.microsoft.com/en-us/office/office-365-management-api/office-365-management-activity-api-reference#receiving-notifications #> [CmdletBinding()] param() Show-InformationalMessage -message "The content notifications are sent to the webhook. We can not implement this operation in PowerShell Module" -consoleColor Yellow; } function Get-Notifications { <# .SYNOPSIS This operation lists all notification attempts for the specified content type .DESCRIPTION This operation lists all notification attempts for the specified content type .PARAMETER startTime Datetimes (UTC) indicating the start time of range for the notifications to return .PARAMETER endTime Datetimes (UTC) indicating the end time of range for the notifications to return .PARAMETER contentType The mandatory content type .EXAMPLE $startTime = "2024-04-05T00:00:00" $endTime = "2024-04-06T00:00:00" Get-Notifications -startTime $startTime -endTime $endTime -contentType AuditExchange .NOTES reference:https://learn.microsoft.com/en-us/office/office-365-management-api/office-365-management-activity-api-reference#list-notifications #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)][datetime]$startTime, [Parameter(Mandatory = $true)][datetime]$endTime, [Parameter(Mandatory = $true)][ContentType]$contentType ) $contentTypestring = Get-ContentTypeString $contentType; $listNotificationsUrl = "$($script:root)/api/v1.0/$($script:tenantID)/activity/feed/subscriptions/notifications?contentType=$contentTypestring&startTime=" + $startTime + "&endTime=" + $endTime; Show-VerboseMessage -message "List notifications via the Url $listNotificationsUrl"; $httpResponse = Invoke-O365APIHttpRequest -url $listNotificationsUrl -httpVerb Get; $convertObjs = ConvertFrom-Json $httpResponse.Content; $notifications = New-Object Collections.Generic.List[Notification]; $convertObjs | ForEach-Object { $notificationObj = New-Object Notification; $notificationObj.contentType = $psitem.contentType; $notificationObj.contentId = $psitem.contentId; $notificationObj.contentUri = $psitem.contentUri; $notificationObj.notificationStatus = $psitem.notificationStatus; $notificationObj.contentCreated = $psitem.contentCreated; $notificationObj.notificationSent = $psitem.notificationSent; $notificationObj.contentExpiration = $psitem.contentExpiration; $notifications.Add($notificationObj); } Write-Output $notifications; return; } function Receive-ResourceFriendlyNames { <# .SYNOPSIS This operation retrieves friendly names for objects in the data feed identified by guids. Currently "DlpSensitiveType" is the only supported object .DESCRIPTION This operation retrieves friendly names for objects in the data feed identified by guids. Currently "DlpSensitiveType" is the only supported object .EXAMPLE Receive-ResourceFriendlyNames .NOTES reference:https://learn.microsoft.com/en-us/office/office-365-management-api/office-365-management-activity-api-reference#retrieve-resource-friendly-names #> [CmdletBinding()] param() $url = "$($script:root)/api/v1.0/$($script:tenantID)/activity/feed/resources/dlpSensitiveTypes"; Show-VerboseMessage "Receive resource FriendlyNames via the Url $url"; $httpResponse = Invoke-O365APIHttpRequest -url $url -httpVerb Get; $friendlyNames = $httpResponse.Content | Out-String | ConvertFrom-Json; Write-Output $friendlyNames; return; } function Disconnect-Office365ManagementAPI { $script:tenantID = $null; $script:clientId = $null; $script:clientsecret = $null; $script:redirectUri = $null; $script:loginHint = $null; $script:clientcertificate = $null; $script:AuthResult = $null; $script:root = $null; $script:scope = $null; $script:httpErrorResponse = $null; Show-InformationalMessage -message "Successfuly Disconnect to Office365 Management API" -consoleColor Green; } Export-ModuleMember Disconnect-Office365ManagementAPI, Receive-ResourceFriendlyNames, Get-Notifications, Receive-Notifications, Receive-Content, Get-AvailableContent, Get-CurrentSubscriptions, Stop-Subscriptions, Stop-Subscription, Start-Subscription, Connect-Office365ManagementAPI; |