Automating with PowerShell: Checking if you can move to M365 BP from O365 E3

So with M365 BP having nearly all the features that E3 gives you, albeit with some limitations a lot of our clients are moving their E3 licenses over. M365BP has the added benefit of Intune/Autopilot, P1 licenses, etc. So all pretty awesome stuff.

Before you can move a client over, you’ll have to check some small things. We have a fairly lengthy internal script to perform all these checks, but I figured to share a part of this script for the public at large. 😉

The Script

This script is a simplified version of our internal one; It checks the following things for you;

  • If you are currently using more than 300 licenses, because if you are you will have to Mix-and-Match all the overage of the licenses.
  • If there are mailboxes larger than 50GB. If so, these have to be licensed differently or cleaned up first.
  • If your clients connecting are all recent clients. Automatic downgrades of the licenses without reinstallation requires a recent version of the installed O365 application.

There’s some more caveats, but these are the most major ones you can tackle with this script. Licenses are tricky business so customize this script to your wishes if you want to add/remove anything. 🙂

As with most of my scripts you’ll need the secure application model. The resulting files will be written to C:\Temp.

######### Secrets #########
$ApplicationId = 'YourApplicationID'
$ApplicationSecret = 'YourAppicationSecret' | ConvertTo-SecureString -Force -AsPlainText
$TenantID = 'YourTenantID'
$RefreshToken = 'insanelylongtoken'
######### Secrets #########
install-module PSWriteHTML
$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

Connect-MsolService -AdGraphAccessToken $aadGraphToken.AccessToken -MsGraphAccessToken $graphToken.AccessToken

$customers = Get-MsolPartnerContract -All

foreach ($customer in $customers) {

    $UserLicenses = Get-MsolAccountSku -TenantId $customer.TenantId
    if ($UserLicenses.AccountSkuId -like "*ENTERPRISEPACK*") {
        write-host "$($Customer.DefaultDomainName) has ENTERPRISEPACK"
    }
    else {
        write-host "Skipping $($Customer.DefaultDomainName) as they do not have an ENTERPRISEPACK" -ForegroundColor Yellow
        continue
    }
    $LicObject = foreach ($UserLicense in $UserLicenses) {
        if ($UserLicense.ConsumedUnits -gt '300') {
            [PSCustomObject]@{
                Name          = ($UserLicense.AccountSkuID -split ':')
                ConsumedUnits = $UserLicense.ConsumedUnits
                Warning       = "Microsoft 365 BP can only be used for the first 300 users. If this license is contained in a package. Please note to buy a seperate license for the users with overage."
            }
        }
    }
    if (!$LicObject) {
        $LicObject = [PSCustomObject]@{
            Report = 'No licensing conflicts found. License migration can be performed without issues.'
        }
    }

    write-host "Generating token for $($Customer.name)" -ForegroundColor Green
    $graphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.microsoft.com/.default' -ServicePrincipal -Tenant $customer.TenantID
    $Header = @{
        Authorization = "Bearer $($graphToken.AccessToken)"
    }
    $MailboxUsage = (Invoke-RestMethod -Uri  "https://graph.microsoft.com/beta/reports/getMailboxUsageDetail(period='D7')" -Headers $Header -Method Get -ContentType "application/json") -replace "", "" | ConvertFrom-Csv
    $LargeMailboxes = foreach ($Mailbox in $MailboxUsage) {
        if ([int64]$Mailbox.'Storage Used (Byte)' -gt 45GB) {
            [PSCustomObject]@{
                Username      = $Mailbox.'User Principal Name'
                DisplayName   = $Mailbox.'DisplayName'
                StorageUsedGB = [math]::round($Mailbox.'Storage Used (Byte)' / 1gb, 2)
            }
        }
    }
    if (!$LargeMailboxes) {
        $LargeMailboxes = [PSCustomObject]@{
            Report = 'No mailbox sizing issues found. Technical mailbox migration can be performed without issues.'
        }
    }

    $VersionReport = (Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/reports/getEmailAppUsageVersionsUserCounts(period='D7')" -Headers $Header -Method get -ContentType "application/json") -replace "", "" | ConvertFrom-Csv
    $LegacyClients = if ($versionreport.'Outlook 2007' -or $versionreport.'Outlook 2010' -or $versionreport.'Outlook 2013' -or $versionreport.'Outlook 2016') {
        $VersionReport
    }
    if (!$LegacyClients) {
        $LegacyClients = [PSCustomObject]@{
            Report = 'No Legacy outlook clients found. License change can be performed without issues.'
        }
    }

    start-sleep -Milliseconds 500  #sleep to prevent CSV Throttle on Graph API

    New-HTML {
        New-HTMLTab -Name "O365 to M365 Preperation Report" {
            New-HTMLSection -Invisible {
                New-HTMLSection -HeaderText 'License Settings' {
                    New-HTMLTable -DataTable $LicObject
                }
            }
            New-HTMLSection -Invisible {
                New-HTMLSection -HeaderText "Mailbox Settings" {
                    New-HTMLTable -DataTable $LargeMailboxes
                }

                New-HTMLSection -HeaderText "O365 Client Usage" {
                    New-HTMLTable -DataTable $LegacyClients
                }
            }
        }

    } -FilePath "C:\temp\$($customer.DefaultDomainName).html" -Online

}

I hope this helps you migrate your clients over, as always, Happy PowerShelling!

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.