Retrieve customers’ Office 365 Message Center info via PowerShell and the Office 365 Management API

Occasionally Microsoft will roll out an update that requires Office 365 admins to prepare for a change by a certain date.
Office 365 Message Center PowerShell

In these situations the roll-out dates are often staggered over a period of time, and if you’re managing a bunch of Office 365 tenants it can be difficult to determine when the change will actually occur.

Recently, I had a couple of requests from other partners to help determine when the Skype for Business to Microsoft Teams upgrade would take place for their customers. This information is communicated to customers via email, however the email doesn’t include any information identifying the customer it refers to.

Fortunately, this information is recorded in the Office 365 Message Center which is now accessible via the Office 365 Management API.

Here is a script that Microsoft Partners can use to retrieve the ‘Action required by’ dates for specific updates in all customer tenants.

The script works by supplying a string of text to match against the messages in your customers’ Office 365 Message Centers. eg: “Microsoft will automatically upgrade your users to Teams”

If a matching message is found, details for the customer, message and the ‘action required by’ date are exported to a csv.

Prerequisites

  • You’ll need Global Admin permissions on your own Tenant
  • You’ll need to be a Microsoft Partner with delegate admin access to customer tenants from your own tenant
  • You’ll need to have the AzureAD PowerShell module installed on your computer

How to run the script

  1. Double click the script below to select it.
  2. Copy and paste it into a new file in Visual Studio Code
  3. Save it with a .ps1 extension
  4. Install the recommended PowerShell Module if you haven’t already
  5. Update the text in $MatchingString to match a line of text in the update you’re interested in.Update Matching String
  6. Update the $homePage and $appIdURI values to a valid domain within your own tenant. eg https://yourdomain.com/$((New-Guid).ToString())Update HomePage and AppIDUri
  7. Press F5 to run the script.
  8. Sign into a global admin account for your own tenant.
  9. Wait for it to complete.Run Script To Retrieve Messages
  10. A CSV with the relevant info will be exported to C:\temp\MessageCenterDetails.csvExported CSV Of Relevant Messages

Script to retrieve customers’ Office 365 Message Center messages via the Office 365 Management API

$MatchingString = "Microsoft will automatically upgrade your users to Teams"
$AppName = "GCITS Service Communications Reader"
Connect-AzureAd

Function AddResourcePermission($requiredAccess, `
        $exposedPermissions, [string]$requiredAccesses, [string]$permissionType) {
    foreach ($permission in $requiredAccesses.Trim().Split("|")) {
        foreach ($exposedPermission in $exposedPermissions) {
            if ($exposedPermission.Value -eq $permission) {
                $resourceAccess = New-Object Microsoft.Open.AzureAD.Model.ResourceAccess
                $resourceAccess.Type = $permissionType 
                $resourceAccess.Id = $exposedPermission.Id
                $requiredAccess.ResourceAccess.Add($resourceAccess)
            }
        }
    }
}
Function GetRequiredPermissions($requiredDelegatedPermissions, $requiredApplicationPermissions, $reqSP) {
    $sp = $reqSP
    $appid = $sp.AppId
    $requiredAccess = New-Object Microsoft.Open.AzureAD.Model.RequiredResourceAccess
    $requiredAccess.ResourceAppId = $appid 
    $requiredAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess]
    if ($requiredDelegatedPermissions) {
        AddResourcePermission $requiredAccess -exposedPermissions $sp.Oauth2Permissions -requiredAccesses $requiredDelegatedPermissions -permissionType "Scope"
    }
    if ($requiredApplicationPermissions) {
        AddResourcePermission $requiredAccess -exposedPermissions $sp.AppRoles -requiredAccesses $requiredApplicationPermissions -permissionType "Role"
    }
    return $requiredAccess
}
Function CreateAppRole($Name, $Description) {
    $appRole = New-Object Microsoft.Open.AzureAD.Model.AppRole
    $appRole.AllowedMemberTypes = New-Object System.Collections.Generic.List[string]
    $appRole.AllowedMemberTypes.Add("User");
    $appRole.DisplayName = $Name
    $appRole.Id = New-Guid
    $appRole.IsEnabled = $true
    $appRole.Description = $Description
    $appRole.Value = $Name;
    return $appRole
}
Function CreateApplicationRole($Name, $Description) {
    $appRole = New-Object Microsoft.Open.AzureAD.Model.AppRole
    $appRole.AllowedMemberTypes = New-Object System.Collections.Generic.List[string]
    $appRole.AllowedMemberTypes.Add("Application");
    $appRole.DisplayName = $Name
    $appRole.Id = New-Guid
    $appRole.IsEnabled = $true
    $appRole.Description = $Description
    $appRole.Value = $Name;
    return $appRole
}
Function GenerateAppKey ($fromDate, $durationInYears, $pw) {

    $endDate = $fromDate.AddYears($durationInYears) 
    $keyId = (New-Guid).ToString();
    $key = New-Object Microsoft.Open.AzureAD.Model.PasswordCredential($null, $endDate, $keyId, $fromDate, $pw)
    return $key 

}

Function CreateAppKey($fromDate, $durationInYears, $pw) {

    $testKey = GenerateAppKey -fromDate $fromDate -durationInYears $durationInYears -pw $pw

    while ($testKey.Value -match "\+" -or $testKey.Value -match "/") {
        Write-Host "Secret contains + or / and may not authenticate correctly. Regenerating..." -ForegroundColor Yellow
        $pw = ComputePassword
        $testKey = GenerateAppKey -fromDate $fromDate -durationInYears $durationInYears -pw $pw
    }
    Write-Host "Secret doesn't contain + or /. Continuing..." -ForegroundColor Green
    $key = $testKey

    return $key 
}

Function ComputePassword {
    $aesManaged = New-Object "System.Security.Cryptography.AesManaged"
    $aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CBC
    $aesManaged.Padding = [System.Security.Cryptography.PaddingMode]::Zeros
    $aesManaged.BlockSize = 128
    $aesManaged.KeySize = 256
    $aesManaged.GenerateKey()
    return [System.Convert]::ToBase64String($aesManaged.Key)
}

$existingapp = $null
$existingapp = get-azureadapplication -SearchString $AppName
if ($existingapp) {
    Remove-Azureadapplication -ObjectId $existingApp.objectId
}

Write-Host "Creating application for: $((Get-AzureADTenantDetail).displayName)"

$reqSP = $null
$reqSP = Get-AzureADServicePrincipal -SearchString "Office 365 Management APIs"
if (!$reqSP) {
    $reqSP = Get-AzureADServicePrincipal -SearchString "OfficeManagePlatform"
}
if (!$reqSP) {
    Login-AzureRmAccount -TenantId $customer.customercontextid -Credential $credentials
    New-AzureRmADServicePrincipal -ApplicationId "5393580-f805-4401-95e8-94b7a6ef2fc2"
    $reqSP = Get-AzureADServicePrincipal -SearchString "Office 365 Management APIs"
}

if ($reqSP) {
    #$user = Get-AzureADUser -SearchString "gcitsreports"

    $homePage = "https://secure.gcits.com"
    $appIdURI = "https://secure.gcits.com/$((New-Guid).tostring())"
    $logoutURI = "https://portal.office.com"


    # Add Required Resources Access (Office 365 Management API)
    $requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.RequiredResourceAccess]

    $microsoftGraphRequiredPermissions = GetRequiredPermissions -reqSP $reqSP -requiredApplicationPermissions "ServiceHealth.Read"
        
    $requiredResourcesAccess.Add($microsoftGraphRequiredPermissions)

    # Get an application key
    $pw = ComputePassword
    $fromDate = [System.DateTime]::Now
    $appkey = CreateAppKey -fromDate $fromDate -durationInYears 2 $pw

    Write-Host "Creating the AAD application ($appName) and accessing Microsoft.Graph" -ForegroundColor Blue
    $aadApplication = New-AzureADApplication -DisplayName $appName `
        -HomePage $homePage `
        -ReplyUrls $homePage `
        -IdentifierUris $appIdURI `
        -LogoutUrl $logoutURI `
        -PasswordCredentials $appKey `
        -RequiredResourceAccess $requiredResourcesAccess `
        -AvailableToOtherTenants $true
                         
    $servicePrincipal = New-AzureADServicePrincipal -AppId $aadApplication.AppId

    Write-Host "Assigning Permissions" -ForegroundColor Yellow

    foreach ($resource in $requiredResourcesAccess.ResourceAccess) {
        if ($resource.Type -match "Role") {
            New-AzureADServiceAppRoleAssignment -ObjectId $serviceprincipal.ObjectId `
                -PrincipalId $serviceprincipal.ObjectId -ResourceId $reqSP.ObjectId -Id $resource.Id
        }
    }

    # This provides the application with access to your customer tenants.
    $group = Get-AzureADGroup -Filter "displayName eq 'Adminagents'"
    Add-AzureADGroupMember -ObjectId $group.ObjectId -RefObjectId $servicePrincipal.ObjectId
 
    $contracts = Get-AzureADContract -All $true

    Write-Host "App Created" -ForegroundColor Green

    # Define parameters for access token retrieval

    $client_id = $aadApplication.AppId
    $client_secret = $appkey.Value
    foreach ($contract in $contracts) {
        $tenant_id = $contract.customercontextid
        $resource = "https://manage.office.com"

        $authority = "https://login.microsoftonline.com/$tenant_id"
        $tokenEndpointUri = "$authority/oauth2/token"
        $content = "grant_type=client_credentials&client_id=$client_id&client_secret=$client_secret&resource=$resource"
    
        # Try to execute the API call 6 times for each customer
    
            
        $Stoploop = $false
        [int]$Retrycount = "0"
    
        do {
            try {
                $result = $null
                $response = Invoke-RestMethod -Uri $tokenEndpointUri -Body $content -Method Post -UseBasicParsing
                Write-Host "Retrieved Access Token for $($contract.displayname)" -ForegroundColor Green
                # Assign access token
                $access_token = $response.access_token
    
                  
                $headers = @{
                    Authorization       = "Bearer $access_token"
                    PublisherIdentifier = $tenant_id
                }
                    
                $testCall = "https://manage.office.com/api/v1.0/$tenant_id/ServiceComms/Messages"
                
                $result = Invoke-RestMethod -Uri $testCall -Headers $headers
    
                Write-Host "Retrieved Message Center Messages for $($contract.displayname)" -ForegroundColor Green
    
                if ($result) {
                    Write-Host "Found result"
                    [array]$matchingMessages = $result.value | Where-Object {$_.Messages.messagetext -match $MatchingString}
                    if ($matchingMessages) {
                        foreach($matchingMessage in $matchingMessages){
                            $ActionDate = ([datetime]$matchingMessage.ActionRequiredByDate).ToLongDateString()
                            Write-Host "Matching message found for $($contract.DisplayName) with action required by $ActionDate" -ForegroundColor Blue
                            [PSCustomObject][ordered]@{
                                CustomerName = $contract.DisplayName
                                TenantId = $contract.CustomerContextId
                                MessageID = $matchingMessage.id
                                MessageTitle = $matchingMessage.Title
                                Message = ($matchingMessage.messages.messagetext -join "`n")
                                ActionRequiredByDate = $matchingMessage.ActionRequiredByDate
                            } | Export-csv C:\temp\MessageCenterDetails.csv -NoTypeInformation -Append
                        }
                    }
                    else {
                        Write-Host "No matching message found for $($contract.DisplayName)" -ForegroundColor Yellow
                    }
                }
                $Stoploop = $true
            }
            catch {
                if ($Retrycount -gt 5) {
                    Write-Host "Could not get content after 6 retries."
                    $Stoploop = $true
                }
                else {
                    Write-Host "Could not get content. Retrying in 5 seconds..."
                    Start-Sleep -Seconds 5
                    $Retrycount ++
                }
            }
        }
        While ($Stoploop -eq $false)
    }
    
    Remove-AzureADApplication -ObjectId $aadApplication.objectId
}

Was this article helpful?

Related Articles