Remediation/ExchangeOnline/EXO-PROTOCOLS.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 Lists POP, IMAP and authenticated SMTP, then turns on in the target only what is needed (procedure EXO-PROTOCOLS). .DESCRIPTION Data read: the mailboxProtocols and legacySignIns tables of each tenant, the SMTP setting of the organization, matrices. exports\protocols-<tenant>.csv gives each mailbox, its protocols and the observed use. A protocol that is on does not prove a use: POP and IMAP are on by default. The Unknown client of the sign-ins does not count as use. Decision: decisions\EXO-PROTOCOLS.csv, with Keep or Disable. one row per mailbox with an observed use or authenticated SMTP on, in each tenant. Source: Keep carries the access over to the target account (after the domains are assigned); Disable carries nothing over. Target: Keep keeps the access; Disable cuts POP, IMAP and authenticated SMTP of this mailbox. three grouped rows for the target: (CAS plan) : POP and IMAP off by default on new mailboxes, including migrated ones; (other mailboxes) : POP and IMAP off on all mailboxes with no row of their own; (organization) : authenticated SMTP off for the organization, after the exceptions, and only if no target mailbox still waits for its decision. A mailbox kept with an SMTP use gets its exception, even if it inherited the setting of the organization. Emergency accounts, migration team accounts and kept accounts are never touched. Limits: only the sign-ins of the log retention period are seen. Reconfiguring devices and the send test stay to be done by a person, after the cutover. #> $c = Open-TcRemediation $Targets = @{} $CasRow = '(CAS plan)' $OtherRow = '(other mailboxes)' $OrganizationRow = '(organization)' function Get-ProtocolUsage { <# What the legacy sign-ins show: SMTP, POP, IMAP. #> param([object[]]$SignIns) $n = @($SignIns | ForEach-Object { [string]$_['Client'] }) return @(if ($n -match 'SMTP') { 'SMTP' }; if ($n -match 'POP') { 'POP' }; if ($n -match 'IMAP') { 'IMAP' }) } function Add-CaseUndo { <# Reads the mailbox in the -Test and prepares the return to the earlier values. #> param($Action) $b = Invoke-TcServiceCommand -Tenant 'Target' -Service Exo -Command 'Get-CASMailbox' -Parameters @{ Identity = (Get-TcMember (Get-TcMember $Action 'parameters') 'Identity') } $Action.undo = @{ command = 'Set-CASMailbox'; parameters = @{ Identity = (Get-TcMember (Get-TcMember $Action 'parameters') 'Identity'); PopEnabled = [bool](Get-TcMember $b 'PopEnabled'); ImapEnabled = [bool](Get-TcMember $b 'ImapEnabled'); SmtpClientAuthenticationDisabled = (Get-TcMember $b 'SmtpClientAuthenticationDisabled') } } return $b } function Test-AlreadyDone { <# True if the mailbox already has the values asked for. #> param($Action, $Mailbox) foreach ($k in @((Get-TcMember $Action 'parameters').Keys | Where-Object { $_ -ne 'Identity' })) { if ((Get-TcMember $Mailbox $k) -ne (Get-TcMember $Action 'parameters')[$k]) { return $false } } return $true } $ReadItems = @{} $proposals = foreach ($t in $c.Tenants) { Write-TcRemediationTenant $t $i = Get-TcRemediationTenant $t $table = @(Get-TcRemediationTable $t 'mailboxProtocols') if (-not $table.Count -and -not (Test-TcCollectionComplete $t 'mailboxProtocols')) { Write-TcRemediationAlert 'Incomplete protocol collection: run Invoke-TcAssessment again'; continue } $legacy = @(Get-TcRemediationTable $t 'legacySignIns' | Where-Object { $_['Client'] -match 'SMTP|POP|IMAP' }) $rows = foreach ($b in $table) { $u = @($legacy | Where-Object { $_['UPN'] -eq $b['Mailbox'] }) [pscustomobject][ordered]@{ Tenant = $t; Mailbox = $b['Mailbox']; POP = [bool]$b['POP']; IMAP = [bool]$b['IMAP']; SMTP = (ConvertTo-TcBoolean $b['Authenticated SMTP']); 'SMTP per'= [string]$b['Authenticated SMTP'] 'Observed use' = (@($u | ForEach-Object { '{0} ({1})' -f $_['Client'], $_['Sign-ins'] }) -join ', ') } } $ReadItems[$t] = @($rows) Write-TcRemediationNote ("POP: {0} - IMAP: {1} - authenticated SMTP: {2} - observed use: {3} mailbox(es)" -f @($rows | Where-Object POP).Count, @($rows | Where-Object IMAP).Count, @($rows | Where-Object SMTP).Count, @($rows | Where-Object 'Observed use').Count) Export-TcRemediationReport -Name "protocols-$t" -Rows @($rows) | Out-Null foreach ($l in $rows | Where-Object { ((Get-TcMember $_ 'Observed use') -or (Get-TcMember $_ 'SMTP')) -and -not (Test-TcProtectedAccount (Get-TcMember $_ 'Mailbox') $i) }) { [ordered]@{ Tenant = $t; Mailbox = (Get-TcMember $l 'Mailbox'); Protocols = (@(if ((Get-TcMember $l 'POP')) { 'POP' }; if ((Get-TcMember $l 'IMAP')) { 'IMAP' }; if ((Get-TcMember $l 'SMTP')) { 'SMTP' }) -join ' '); 'Observed use' = (Get-TcMember $l 'Observed use'); Proposal = '' } } if ($t -eq 'Target') { $n = @($rows | Where-Object { ((Get-TcMember $_ 'POP') -or (Get-TcMember $_ 'IMAP')) -and -not (Get-TcMember $_ 'Observed use') -and -not (Get-TcMember $_ 'SMTP') }).Count [ordered]@{ Tenant = $t; Mailbox = $CasRow; Protocols = 'POP IMAP'; 'Observed use' = 'New mailboxes, including migrated mailboxes'; Proposal = 'Disable' } [ordered]@{ Tenant = $t; Mailbox = $OtherRow; Protocols = 'POP IMAP'; 'Observed use' = ("No use observed on {0} mailbox(es)" -f $n); Proposal = 'Disable' } if (-not (ConvertTo-TcBoolean (Get-TcRemediationValue $t 'exchangeSettings' 'Transport.SmtpClientAuthenticationDisabled'))) { [ordered]@{ Tenant = $t; Mailbox = $OrganizationRow; Protocols = 'SMTP'; 'Observed use' = 'Authenticated SMTP open for the whole organization'; Proposal = 'Disable' } } } } $decisions = @(Get-TcDecision -Name 'EXO-PROTOCOLS' -Proposal @($proposals) -Key 'Tenant', 'Mailbox' -Choice 'Keep', 'Disable') $group = @{} foreach ($d in $decisions | Where-Object { (Get-TcMember $_ 'Tenant') -eq 'Target' -and (Get-TcMember $_ 'Mailbox') -like '(*' }) { $group[$d.Mailbox] = (Get-TcMember $d 'Decision') } if ('Target' -in $c.Tenants) { $targetInfoRow = Get-TcRemediationTenant 'Target' if ($group[$CasRow] -eq 'Disable') { Add-TcBlockAction -Tenant 'Target' -Service Exo -Object 'CAS plans' -Description 'Turn off POP and IMAP by default on new mailboxes (CAS plans)' ` -Test { -not @(Invoke-TcServiceCommand -Tenant 'Target' -Service Exo -Command 'Get-CASMailboxPlan' | Where-Object { (Get-TcMember $_ 'PopEnabled') -or (Get-TcMember $_ 'ImapEnabled') }).Count } ` -Block { param($a) $undo = [System.Collections.Generic.List[object]]::new() foreach ($p in @(Invoke-TcServiceCommand -Tenant 'Target' -Service Exo -Command 'Get-CASMailboxPlan' | Where-Object { (Get-TcMember $_ 'PopEnabled') -or (Get-TcMember $_ 'ImapEnabled') })) { $undo.Add(@{ command = 'Set-CASMailboxPlan'; parameters = @{ Identity = (Get-TcText $p 'Identity'); PopEnabled = [bool](Get-TcMember $p 'PopEnabled'); ImapEnabled = [bool](Get-TcMember $p 'ImapEnabled') } }) Invoke-TcServiceCommand -Tenant 'Target' -Service Exo -Command 'Set-CASMailboxPlan' -Parameters @{ Identity = (Get-TcText $p 'Identity'); PopEnabled = $false; ImapEnabled = $false } | Out-Null $a.undo = @($undo) } } } $mailboxesWithRow = @(@($proposals | Where-Object { (Get-TcMember $_ 'Tenant') -eq 'Target' -and (Get-TcMember $_ 'Mailbox') -notlike '(*' }) | ForEach-Object { (Get-TcMember $_ 'Mailbox') }) foreach ($d in $decisions | Where-Object { (Get-TcMember $_ 'Tenant') -eq 'Target' -and (Get-TcMember $_ 'Mailbox') -notlike '(*' }) { if (Test-TcProtectedAccount (Get-TcMember $d 'Mailbox') $targetInfoRow) { continue } $r = $ReadItems['Target'] | Where-Object Mailbox -eq (Get-TcMember $d 'Mailbox') | Select-Object -First 1 if ((Get-TcMember $d 'Decision') -eq 'Disable') { $p = @{ Identity = (Get-TcMember $d 'Mailbox'); PopEnabled = $false; ImapEnabled = $false; SmtpClientAuthenticationDisabled = $true }; $text = 'Turn off POP, IMAP and authenticated SMTP for {0}' } elseif ($r -and ((Get-TcMember $r 'SMTP') -or (Get-TcMember $r 'Observed use') -match 'SMTP')) { $p = @{ Identity = (Get-TcMember $d 'Mailbox'); SmtpClientAuthenticationDisabled = $false }; $text = 'Keep the authenticated SMTP of {0} as an exception' } else { continue } # an exception that fails must stop the plan before the organization is cut Add-TcCommandAction -Tenant 'Target' -Service Exo -Command 'Set-CASMailbox' -Parameters $p -Object (Get-TcMember $d 'Mailbox') -Description ($text -f (Get-TcMember $d 'Mailbox')) -Critical:((Get-TcMember $d 'Decision') -ne 'Disable') ` -Test { param($a) Test-AlreadyDone $a (Add-CaseUndo $a) } } if ($group[$OtherRow] -eq 'Disable') { foreach ($l in @($ReadItems['Target']) | Where-Object { ((Get-TcMember $_ 'POP') -or (Get-TcMember $_ 'IMAP')) -and (Get-TcMember $_ 'Mailbox') -notin $mailboxesWithRow -and -not (Test-TcProtectedAccount (Get-TcMember $_ 'Mailbox') $targetInfoRow) }) { Add-TcCommandAction -Tenant 'Target' -Service Exo -Command 'Set-CASMailbox' -Parameters @{ Identity = (Get-TcMember $l 'Mailbox'); PopEnabled = $false; ImapEnabled = $false } -Object (Get-TcMember $l 'Mailbox') ` -Description ("Turn off POP and IMAP for {0}" -f (Get-TcMember $l 'Mailbox')) ` -Test { param($a) Test-AlreadyDone $a (Add-CaseUndo $a) } } } } $carriedOver = @($decisions | Where-Object { (Get-TcMember $_ 'Tenant') -ne 'Target' -and (Get-TcMember $_ 'Tenant') -in $c.Tenants -and (Get-TcMember $_ 'Decision') -eq 'Keep' }) foreach ($d in $carriedOver) { $t = (Get-TcMember $d 'Tenant') $row = Find-TcMatrixRow $t (Get-TcMember $d 'Mailbox') if (-not $row -or $row['Decision'] -notin 'Create', 'Duplicate') { Write-TcRemediationAlert ("{0}: no target account decided in the matrix, access not carried over" -f (Get-TcMember $d 'Mailbox')); continue } $r = $ReadItems[$t] | Where-Object Mailbox -eq (Get-TcMember $d 'Mailbox') | Select-Object -First 1 $seen = @(Get-ProtocolUsage @(Get-TcRemediationTable $t 'legacySignIns' | Where-Object { $_['UPN'] -eq (Get-TcMember $d 'Mailbox') })) $p = @{ Identity = $row['Target UPN'] } if ('POP' -in $seen) { $p.PopEnabled = $true } if ('IMAP' -in $seen) { $p.ImapEnabled = $true } if ('SMTP' -in $seen -or ($r -and (Get-TcMember $r 'SMTP'))) { $p.SmtpClientAuthenticationDisabled = $false } if ($p.Count -lt 2) { continue } $Targets[$row['Target UPN']] = $row['Source UPN'] Add-TcCommandAction -Tenant 'Target' -Service Exo -Command 'Set-CASMailbox' -Parameters $p -Object $row['Target UPN'] -Description ("Carry over the access of {0} onto {1}" -f (Get-TcMember $d 'Mailbox'), $row['Target UPN']) ` -Test { param($a) try { return (Test-AlreadyDone $a (Add-CaseUndo $a)) } catch { return (Test-TcPostponed $a ("{0} does not have its own mailbox in the target yet: run again after the domains are assigned to target accounts" -f $a.parameters.Identity)) } } } $pendingMailboxes = @($proposals | Where-Object { (Get-TcMember $_ 'Tenant') -eq 'Target' -and (Get-TcMember $_ 'Mailbox') -notlike '(*' -and (Get-TcMember $_ 'Mailbox') -notin @($decisions | Where-Object { (Get-TcMember $_ 'Tenant') -eq 'Target' } | ForEach-Object { (Get-TcMember $_ 'Mailbox') }) }) if ($group[$OrganizationRow] -eq 'Disable' -and $pendingMailboxes.Count) { Write-TcRemediationAlert ("Turning off authenticated SMTP for the organization is postponed: {0} mailbox(es) of the target are waiting for their decision." -f $pendingMailboxes.Count) } elseif ($group[$OrganizationRow] -eq 'Disable') { Add-TcCommandAction -Tenant 'Target' -Service Exo -Command 'Set-TransportConfig' -Parameters @{ SmtpClientAuthenticationDisabled = $true } -Object 'Organization' ` -Description "Turn off authenticated SMTP for the organization, except the per-mailbox exceptions" ` -Test { param($a) $x = Invoke-TcServiceCommand -Tenant 'Target' -Service Exo -Command 'Get-TransportConfig' $a.undo = @{ command = 'Set-TransportConfig'; parameters = @{ SmtpClientAuthenticationDisabled = [bool](Get-TcMember $x 'SmtpClientAuthenticationDisabled') } } return [bool](Get-TcMember $x 'SmtpClientAuthenticationDisabled') } } foreach ($t in @($carriedOver | ForEach-Object { (Get-TcMember $_ 'Tenant') } | Select-Object -Unique)) { Add-TcManualAction -Tenant $t -Object 'Devices' -Description ("Reconfigure the devices and applications of {0} after the cutover" -f $t) ` -Instruction @("Choose for each device or application the sending method: authenticated SMTP, direct send or relay through a connector.", "After the cutover, reconfigure toward the target and test a send: a forgotten scanner stops sending on the Monday of the cutover.") } Invoke-TcRemediationPlan -Verify { param($t) if ($t -ne 'Target') { return } $org = Invoke-TcServiceCommand -Tenant 'Target' -Service Exo -Command 'Get-TransportConfig' if ($group[$OrganizationRow] -eq 'Disable' -and -not $pendingMailboxes.Count -and -not (Get-TcMember $org 'SmtpClientAuthenticationDisabled')) { New-TcRemediationResult Blocking 'Authenticated SMTP still open for the organization' } else { New-TcRemediationResult OK ("Authenticated SMTP of the organization turned off: {0}" -f [bool](Get-TcMember $org 'SmtpClientAuthenticationDisabled')) } $openOnes = @(Invoke-TcServiceCommand -Tenant 'Target' -Service Exo -Command 'Get-EXOCasMailbox' -Parameters @{ ResultSize = 'Unlimited'; Properties = @('PopEnabled', 'ImapEnabled', 'SmtpClientAuthenticationDisabled') } | Where-Object { (Get-TcMember $_ 'PopEnabled') -or (Get-TcMember $_ 'ImapEnabled') -or (Get-TcMember $_ 'SmtpClientAuthenticationDisabled') -eq $false }) New-TcRemediationResult Info ("{0} mailbox(es) keep POP, IMAP or authenticated SMTP" -f $openOnes.Count) } Close-TcRemediation |