Documenting with PowerShell: Documenting Office365 mailbox permissions

I like being able to report to our clients exactly what the permissions on mailboxes are. The only issue with reporting via the Office 365 admin portal is that we don’t get a history. I’ve decided to make sure that I can do this by documenting the permissions daily.

To do this, I have two versions of the script; one for IT-Glue and one for general HTML file usage. Both scripts connect to the Office365 Exchange PowerShell using the Secure App Model. For more information you can check this blog post.

IT-Glue version

The IT-Glue version gets all contacts in your IT-Glue environment, compares these to the domains found in each of your partner tenants and documents the permissions for the right tenant into the correct IT-Glue client. The script creates a Flexible Asset for you if it does not yet exist.

$key = "YOUR IT Glue Key here"
$ApplicationId         = 'Application ID'
$ApplicationSecret     = 'Application Secret' | Convertto-SecureString -AsPlainText -Force
$TenantID              = 'YourTenantID'
$RefreshToken          = 'YourRefreshTokens'
$ExchangeRefreshToken  = 'YourExchangeRefresherToken'
$upn                   = 'UPN-That-Generated-Tokens'
$APIEndpoint           = "https://api.eu.itglue.com"
$FlexAssetName         = "Office365 Permissions - Automatic Documentation"
#######################################################################
write-output "Getting Module"
If (Get-Module -ListAvailable -Name "ITGlueAPI") { Import-module ITGlueAPI } Else { install-module ITGlueAPI -Force; import-module ITGlueAPI }
If (Get-Module -ListAvailable -Name "MsOnline") { Import-module "Msonline" } Else { install-module "MsOnline" -Force; import-module "Msonline" }
If (Get-Module -ListAvailable -Name "PartnerCenter") { Import-module "PartnerCenter" } Else { install-module "PartnerCenter" -Force; import-module "PartnerCenter" }
Write-Output "Generating tokens to login"
$credential = New-Object System.Management.Automation.PSCredential($ApplicationId, $ApplicationSecret)
$aadGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.windows.net/.default' -ServicePrincipal -Tenant $tenantID
$graphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.microsoft.com/.default' -ServicePrincipal -Tenant $tenantID
Add-ITGlueBaseURI -base_uri $APIEndpoint
Add-ITGlueAPIKey $key
write-output "Getting IT-Glue contact list"
$i = 0
do {
    $AllITGlueContacts += (Get-ITGlueContacts -page_size 1000 -page_number $i).data.attributes
    $i++
    Write-Host "Retrieved $($AllITGlueContacts.count) Contacts" -ForegroundColor Yellow
}while ($AllITGlueContacts.count % 1000 -eq 0 -and $AllITGlueContacts.count -ne 0)
Write-Output "Checking if flexible asset exists."
$FilterID = (Get-ITGlueFlexibleAssetTypes -filter_name $FlexAssetName).data
if (!$FilterID) {
Write-Output "No Flexible Asset found. Creating new one"
    $NewFlexAssetData =
@{
    type = 'flexible-asset-types'
    attributes = @{
            name = $FlexAssetName
            icon = 'sitemap'
            description = $description
    }
    relationships = @{
        "flexible-asset-fields" = @{
            data = @(
                @{
                    type       = "flexible_asset_fields"
                    attributes = @{
                        order           = 1
                        name            = "tenantid"
                        kind            = "Text"
                        required        = $true
                        "show-in-list"  = $true
                        "use-for-title" = $true
                    }
                },
                @{
                    type       = "flexible_asset_fields"
                    attributes = @{
                        order          = 2
                        name           = "permissions"
                        kind           = "Textbox"
                        required       = $false
                        "show-in-list" = $true
                    }
                },
                @{
                    type       = "flexible_asset_fields"
                    attributes = @{
                        order          = 3
                        name           = "tenant-name"
                        kind           = "Text"
                        required       = $false
                        "show-in-list" = $false
                    }
                }
            )
            }
        }

   }
    New-ITGlueFlexibleAssetTypes -Data $NewFlexAssetData
    $FilterID = (Get-ITGlueFlexibleAssetTypes -filter_name $FlexAssetName).data
}

write-output "Logging in."
Connect-MsolService -AdGraphAccessToken $aadGraphToken.AccessToken -MsGraphAccessToken $graphToken.AccessToken
$customers = Get-MsolPartnerContract -All

foreach ($customer in $customers) {
    $MSOLPrimaryDomain = (get-msoldomain -TenantId $customer.tenantid | Where-Object { $_.IsInitial -eq $false }).name
    $customerDomains = Get-MsolDomain -TenantId $customer.TenantId | Where-Object { $_.status -contains "Verified" }
    $MSOLtentantID = $customer.tenantid
    #Connecting to the O365 tenant
    $InitialDomain = Get-MsolDomain -TenantId $customer.TenantId | Where-Object { $_.IsInitial -eq $true }
    Write-host "Documenting $($Customer.Name)"
$token = New-PartnerAccessToken -ApplicationId 'a0c73c16-a7e3-4564-9a95-2bdf47383716'-RefreshToken $ExchangeRefreshToken -Scopes 'https://outlook.office365.com/.default' -Tenant $customer.TenantId
    $tokenValue = ConvertTo-SecureString "Bearer $($token.AccessToken)" -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($upn, $tokenValue)
    $customerId = $customer.DefaultDomainName
    $session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://ps.outlook.com/powershell-liveid?DelegatedOrg=$($customerId)&BasicAuthToOAuthConversion=true" -Credential $credential -Authentication Basic -AllowRedirection
Import-PSSession $session -CommandName "Get-MailboxPermission", "Get-Mailbox" -AllowClobber
$Mailboxes = Get-Mailbox -ResultSize:Unlimited | Sort-Object displayname

    foreach ($mailbox in $mailboxes) {
        $AccesPermissions = Get-MailboxPermission -Identity $mailbox.identity | Where-Object { $_.user.tostring() -ne "NT AUTHORITY\SELF" -and $_.IsInherited -eq $false } -erroraction silentlycontinue | Select-Object User, accessrights
        if ($AccesPermissions) { $HTMLPermissions += $AccesPermissions | convertto-html -frag -PreContent "

Permissions on $($mailbox.PrimarySmtpAddress)
" | Out-String }
}
Remove-PSSession $session

    $FlexAssetBody =
    @{
        type       = 'flexible-assets'
        attributes = @{
            traits = @{
                'permissions' = $HTMLPermissions
                'tenantid'    = $MSOLtentantID
                'tenant-name' = $initialdomain.name
            }
        }
    }


    write-output "             Finding $($customer.name) in IT-Glue"
    $orgID = @()
    foreach ($customerDomain in $customerdomains) {
        $orgID += ($AllITGlueContacts | Where-Object { $_.'contact-emails'.value -match $customerDomain.name }).'organization-id' | Select-Object -Unique

    }



    write-output "             Uploading Office Permission $($customer.name) into IT-Glue"
    foreach ($org in $orgID) {
        $ExistingFlexAsset = (Get-ITGlueFlexibleAssets -filter_flexible_asset_type_id $($filterID.ID) -filter_organization_id $org).data | Where-Object { $_.attributes.traits.'tenant-name' -eq $initialdomain.name }
        #If the Asset does not exist, we edit the body to be in the form of a new asset, if not, we just upload.
        if (!$ExistingFlexAsset) {
            $FlexAssetBody.attributes.add('organization-id', $org)
            $FlexAssetBody.attributes.add('flexible-asset-type-id', $($filterID.ID))
            write-output "                      Creating new Office Permission $($customer.name) into IT-Glue organisation $org"
            New-ITGlueFlexibleAssets -data $FlexAssetBody
        }
        else {
            write-output "                      Updating Office Permission $($customer.name) into IT-Glue organisation $org"
            $ExistingFlexAsset = $ExistingFlexAsset[-1]
            Set-ITGlueFlexibleAssets -id $ExistingFlexAsset.id  -data $FlexAssetBody
        }

    }
    $MSOLPrimaryDomain = $null
    $MSOLtentantID = $null
    $AccesPermissions = $null
    $HTMLPermissions = $null

}

General HTML version

The generic HTML version makes a HTML file per client in C:\Temp. It uses the name of the client as the filename.

$ApplicationId = 'ApplicationID'
$ApplicationSecret     = 'ApplicationSecret' | Convertto-SecureString -AsPlainText -Force
$TenantID              = 'YourTenantID'
$RefreshToken          = 'RefreshToken'
$ExchangeRefreshToken  = 'ExchangeRefreshToken'
$upn                   = 'YourUPN'
#######################################################################
write-output "Getting Modules"
If (Get-Module -ListAvailable -Name "MsOnline") { Import-module "Msonline" } Else { install-module "MsOnline" -Force; import-module "Msonline" }
If (Get-Module -ListAvailable -Name "PartnerCenter") { Import-module "PartnerCenter" } Else { install-module "PartnerCenter" -Force; import-module "PartnerCenter" }
Write-Output "Generating tokens to login"
$credential = New-Object System.Management.Automation.PSCredential($ApplicationId, $ApplicationSecret)
$aadGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.windows.net/.default' -ServicePrincipal -Tenant $tenantID
$graphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.microsoft.com/.default' -ServicePrincipal -Tenant $tenantID
write-output "Logging in."
Connect-MsolService -AdGraphAccessToken $aadGraphToken.AccessToken -MsGraphAccessToken $graphToken.AccessToken
$customers = Get-MsolPartnerContract -All
$head = @"
<script>
function myFunction() {
    const filter = document.querySelector('#myInput').value.toUpperCase();
    const trs = document.querySelectorAll('table tr:not(.header)');
    trs.forEach(tr => tr.style.display = [...tr.children].find(td => td.innerHTML.toUpperCase().includes(filter)) ? '' : 'none');
  }</script>
<title>Audit Log Report</title>
<style>
body { background-color:#E5E4E2;
      font-family:Monospace;
      font-size:10pt; }
td, th { border:0px solid black;
        border-collapse:collapse;
        white-space:pre; }
th { color:white;
    background-color:black; }
table, tr, td, th {
     padding: 2px;
     margin: 0px;
     white-space:pre; }
tr:nth-child(odd) {background-color: lightgray}
table { width:95%;margin-left:5px; margin-bottom:20px; }
h2 {
font-family:Tahoma;
color:#6D7B8D;
}
.footer
{ color:green;
 margin-left:10px;
 font-family:Tahoma;
 font-size:8pt;
 font-style:italic;
}
#myInput {
  background-image: url('https://www.w3schools.com/css/searchicon.png'); /* Add a search icon to input */
  background-position: 10px 12px; /* Position the search icon */
  background-repeat: no-repeat; /* Do not repeat the icon image */
  width: 50%; /* Full-width */
  font-size: 16px; /* Increase font-size */
  padding: 12px 20px 12px 40px; /* Add some padding */
  border: 1px solid #ddd; /* Add a grey border */
  margin-bottom: 12px; /* Add some space below the input */
}
</style>
"@
foreach ($customer in $customers) {
    $MSOLPrimaryDomain = (get-msoldomain -TenantId $customer.tenantid | Where-Object { $_.IsInitial -eq $false }).name
    $customerDomains = Get-MsolDomain -TenantId $customer.TenantId | Where-Object { $_.status -contains "Verified" }
    $MSOLtentantID = $customer.tenantid
    #Connecting to the O365 tenant
    $InitialDomain = Get-MsolDomain -TenantId $customer.TenantId | Where-Object { $_.IsInitial -eq $true }
    Write-host "Documenting $($Customer.Name)"
    $token = New-PartnerAccessToken -ApplicationId 'a0c73c16-a7e3-4564-9a95-2bdf47383716'-RefreshToken $ExchangeRefreshToken -Scopes 'https://outlook.office365.com/.default' -Tenant $customer.TenantId
    $tokenValue = ConvertTo-SecureString "Bearer $($token.AccessToken)" -AsPlainText -Force
    $credential = New-Object System.Management.Automation.PSCredential($upn, $tokenValue)
    $customerId = $customer.DefaultDomainName
    $session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://ps.outlook.com/powershell-liveid?DelegatedOrg=$($customerId)&BasicAuthToOAuthConversion=true" -Credential $credential -Authentication Basic -AllowRedirection
    Import-PSSession $session -CommandName "Get-MailboxPermission", "Get-Mailbox" -AllowClobber
    $Mailboxes = Get-Mailbox -ResultSize:Unlimited | Sort-Object displayname
    $MSOLPrimaryDomain = $null
    $MSOLtentantID = $null
    $AccesPermissions = $null
    $HTMLPermissions = $null
    foreach ($mailbox in $mailboxes) {
        $AccesPermissions = Get-MailboxPermission -Identity $mailbox.identity | Where-Object { $_.user.tostring() -ne "NT AUTHORITY\SELF" -and $_.IsInherited -eq $false } -erroraction silentlycontinue | Select-Object User, accessrights
        if ($AccesPermissions) { $HTMLPermissions += $AccesPermissions | convertto-html -frag -PreContent "<h4>Permissions on $($mailbox.PrimarySmtpAddress)</h4>" | Out-String }
    }
    Remove-PSSession $session
    $CompleteHTML = $head,$HTMLPermissions | Out-String | out-file "C:\Temp\$($customer.name).html"
}

Recent Articles

The return of CyberDrain CTF

CyberDrain CTF returns! (and so do I!)

It’s been since september that I actually picked up a digital pen equivalent and wrote anything down. This was due to me being busy with life but also my side projects like CIPP. I’m trying to get back into the game of scripting and blogging about these scripts. There’s still so much to automate and so little time, right? ;)

Monitoring with PowerShell: Monitoring Acronis Backups

Intro

This is a monitoring script requested via Reddit, One of the reddit r/msp users wondered how they can monitor Acronis a little bit easier. I jumped on this because it happened pretty much at the same time that I was asked to speak at the Acronis CyberSummit so it kinda made sense to script this so I have something to demonstrate at my session there.

Monitoring with PowerShell: Monitoring VSS Snapshots

Intro

Wow! It’s been a while since I’ve blogged. I’ve just been so swamped with CIPP that I’ve just let the blogging go entirely. It’s a shame because I think out of all my hobbies it’s one I enjoy the most. It’s always nice helping others achieve their scripting target. I even got a couple of LinkedIn questions asking if I was done with blogging but I’m not. Writing always gives me some more piece of mind so I’ll try to catch up again. I know I’ve said that before but this time I’ll follow through. I’m sitting down right now and scheduling the release of 5 blogs in one go. No more whining and no more waiting.