Remediation/ExchangeOnline/EXO-LARGE-MAILBOXES.ps1

# UNTESTED: this script has never been run on a real tenant. Read it, try it on a test tenant first, and use it at your own risk.
<#
.SYNOPSIS
    Finds the large mailboxes, checks the target capacity and enables the archives (procedure EXO-LARGE-MAILBOXES).
 
.DESCRIPTION
    Target tenant (BUILD-02, by default), or a source with -Tenant S1 (PREP-02).
 
    Data read: mailboxUsage, mailboxes, archives, users and matrices. The threshold comes from the LargeMailboxGb setting.
    exports\large-mailboxes-<tenant>.csv gives each mailbox, its size, its quota, its archive and the conclusion.
    In a source, names are concealed in the usage statistics: in an -Apply run, a direct read of the mailboxes produces
    exports\large-mailboxes-measured-<tenant>.csv with the real names.
 
    Alerts: user mailbox over 50 GB with a 100 GB quota, shared mailbox over 50 GB with no license.
    Actions: in a source, turning on the archive on the target account when the source mailbox has one. If the target mailbox does not
    exist yet (PREP-02), the action is postponed.
    Decision: decisions\EXO-LARGE-MAILBOXES.csv, one row per mailbox, Enable the archive or Keep. It needs a license and cannot be
    undone: with no decision, no archive is turned on. The direct measure only reads, and runs without one.
 
    Limits: the license stays a decision of PREP-02 and the pre-copy starts in the copy tool. Turning on an archive cannot be undone by the
    script: disabling it can lose its content.
#>

$c = Open-TcRemediation

$Threshold = [double](Get-TcRemediationOption 'LargeMailboxGb')
$StandardLimit = 50
$TargetQuota = 100
$Targets = @{}
$archives = [System.Collections.Generic.List[object]]::new()

function ConvertTo-Gigabyte {
    <# 12.3 GB (13,207,024,435 bytes): the number of bytes, in GB. #>
    param([string]$SizeValue)
    if ($SizeValue -match '\(([\d,\s]+) bytes\)') { return [math]::Round([double](($Matches[1] -replace '[^\d]', '')) / 1GB, 2) }
    return 0
}

foreach ($t in $c.Tenants) {
    Write-TcRemediationTenant $t
    $usage = @(Get-TcRemediationTable $t 'mailboxUsage')
    if (-not $usage.Count -and -not (Test-TcCollectionComplete $t 'mailboxUsage')) { Write-TcRemediationAlert 'Mailbox usage collection incomplete: run Invoke-TcAssessment again'; continue }
    $largeItems = @($usage | Where-Object { [double]$_['Size GB'] -ge $Threshold } | Sort-Object { [double]$_['Size GB'] } -Descending)
    $totalCount = (Get-TcMember (@($usage | ForEach-Object { [double]$_['Size GB'] }) | Measure-Object -Sum) 'Sum')
    Write-TcRemediationNote ("{0} mailbox(es) of {1} GB or more; total volume {2:N1} GB" -f $largeItems.Count, $Threshold, $totalCount)

    $noQuota = 0; $noLicense = 0
    $rows = foreach ($u in $largeItems) {
        $b = Get-TcRemediationTable $t 'mailboxes' | Where-Object { $_['UPN'] -eq $u['UPN'] } | Select-Object -First 1
        $a = Get-TcRemediationTable $t 'archives' | Where-Object { $_['UPN'] -eq $u['UPN'] } | Select-Object -First 1
        $account = Get-TcRemediationTable $t 'users' | Where-Object { $_['UPN'] -eq $u['UPN'] } | Select-Object -First 1
        $sizeValue = [double]$u['Size GB']
        $type = if ($b) { $b['Type'] } else { '' }
        $conclusion = 'Pre-copy first'
        if ($sizeValue -gt $StandardLimit -and $type -eq 'SharedMailbox' -and $account -and [int]$account['License count'] -eq 0) { $conclusion = 'License required'; $noLicense++ }
        elseif ($sizeValue -gt $StandardLimit -and [double]$u['Quota GB'] -lt $TargetQuota) { $conclusion = '100 GB quota required'; $noQuota++ }
        [pscustomobject][ordered]@{ 'Mailbox' = $u['UPN']; Type = $type; 'Size GB' = $sizeValue; 'Quota GB' = $u['Quota GB']; Archive = $u['Archive']; 'Archive GB' = $(if ($a) { $a['Size GB'] }); Conclusion = $conclusion }
    }
    if (@($rows).Count) { Export-TcRemediationReport -Name "large-mailboxes-$t" -Rows @($rows) | Out-Null }
    if ($noQuota) { Write-TcRemediationAlert ("{0} mailbox(es) over 50 GB with a quota under 100 GB" -f $noQuota) }
    if ($noLicense) { Write-TcRemediationAlert ("{0} shared mailbox(es) over 50 GB with no license: an Exchange Online Plan 2 license or equivalent is required" -f $noLicense) }

    if ($t -eq 'Target') { continue }

    $concealedOnes = @($largeItems | Where-Object { $_['UPN'] -notmatch '@' }).Count
    if ($concealedOnes) { Write-TcRemediationNote ("{0} mailbox(es) have a concealed name in the usage statistics: the direct measure gives their name" -f $concealedOnes) }
    Add-TcBlockAction -Tenant $t -Service Exo -Object 'Measure' -Data @{ Mailboxes = @(Get-TcRemediationTable $t 'mailboxes' | ForEach-Object { $_['UPN'] }) } -Description ("Measure the mailboxes of {0} directly" -f $t) -Reason 'Read only: the report stays in the log folder.' `
        -Block {
            param($a)
            $measures = foreach ($upn in $a.data.Mailboxes) {
                $s = Invoke-TcServiceCommand -Tenant $a.tenant -Service Exo -Command 'Get-EXOMailboxStatistics' -Parameters @{ UserPrincipalName = $upn }
                $go = ConvertTo-Gigabyte ((Get-TcText $s 'TotalItemSize'))
                if ($go -ge $Threshold) { [pscustomobject][ordered]@{ 'Mailbox' = $upn; 'Size GB' = $go; 'Items' = (Get-TcMember $s 'ItemCount') } }
            }
            Export-TcRemediationReport -Name ("large-mailboxes-measured-{0}" -f $a.tenant) -Rows @($measures | Sort-Object 'Size GB' -Descending) | Out-Null
        }

    foreach ($b in Get-TcRemediationTable $t 'mailboxes' | Where-Object { ConvertTo-TcBoolean $_['Archive'] }) {
        $l = @((Get-TcMember (Get-TcRemediationIdentity $t) 'rows')) | Where-Object { $_['Source UPN'] -eq $b['UPN'] } | Select-Object -First 1
        if (-not $l -or $l['Decision'] -notin 'Create', 'Duplicate') { Write-TcRemediationAlert ("{0} has an archive but no target account decided in the matrix" -f $b['UPN']); continue }
        $target = if ($l['Decision'] -eq 'Create') { $l['Temporary UPN'] } else { $l['Target UPN'] }
        $Targets[$target] = @($l['Temporary UPN'], $l['Target UPN'])
        $archives.Add([ordered]@{ Tenant = $t; Mailbox = $b['UPN']; 'Target account' = $target; Proposal = 'Enable the archive' })
    }

    Add-TcManualAction -Tenant $t -Object 'Pre-copy' -Description ("Start the pre-copy of the largest mailboxes of {0} first" -f $t) `
        -Instruction @("Confirm the target license: over 50 GB, a 100 GB quota is needed.", "A shared mailbox over 50 GB needs an Exchange Online Plan 2 license or equivalent, otherwise the copy can stop halfway.",
                    "Start these mailboxes first in the copy tool: a late pre-copy can overflow into the cutover window.")
}

$decisions = @(Get-TcDecision -Name 'EXO-LARGE-MAILBOXES' -Proposal @($archives) -Key 'Tenant', 'Mailbox' -Choice 'Enable the archive', 'Keep')
foreach ($d in $decisions | Where-Object { (Get-TcMember $_ 'Decision') -eq 'Enable the archive' }) {
    $target = (Get-TcText $d 'Target account')
    Add-TcCommandAction -Tenant 'Target' -Service Exo -Command 'Enable-Mailbox' -Parameters @{ Identity = $target; Archive = $true } -Object $target -Description ("Enable the archive of {0}" -f $target) `
        -Reason "Disabling an archive can lose its content: going back is decided separately." `
        -Test {
            param($a)
            foreach ($i in @($Targets[$a.parameters.Identity]) | Where-Object { $_ }) {
                try { $m = Invoke-TcServiceCommand -Tenant 'Target' -Service Exo -Command 'Get-Mailbox' -Parameters @{ Identity = $i } } catch { continue }
                $a.parameters.Identity = $i
                return (Get-TcText $m 'ArchiveStatus') -eq 'Enabled'
            }
            return Test-TcPostponed $a ("{0} has no mailbox in the target yet (PREP-02)" -f $a.parameters.Identity)
        }
}

Invoke-TcRemediationPlan
Close-TcRemediation