Public/Invoke-TcRuleSet.ps1
|
function Invoke-TcRuleSet { <# .SYNOPSIS Runs the rules on a snapshot and returns findings, measures and setting gaps. .DESCRIPTION Works from the snapshot alone, with no connection. Rules run on each tenant, then each source is compared with the target, then all the tenants are read together, for 1, 2 or 3 sources. The result has five keys. findings holds what the rules found, most severe first. measures holds one line per indicator. settingGaps holds the settings that differ between a source and the target. unread lists, by tenant and workload, the tables the rules needed and could not read. errors lists the rules, measures and setting comparisons that stopped, and it is empty in a normal run. One bad value stops only its own block, and a workload with an error counts as not fully read. .PARAMETER Snapshot The snapshot, as read from snapshot.json. .PARAMETER Threshold Replaces thresholds: inactiveDays, secretExpiryDays, mailboxThresholdGb, siteThresholdGb, oneDriveThresholdGb, maxTtlSeconds, storageMarginPercent. .PARAMETER LicenseSku The SKU migrated accounts are expected to get. The default is the most used SKU of the target. .PARAMETER SourcePrefix The prefix planned for each source, by role, to look for names already taken once prefixed. .PARAMETER HomeCountry The countries sign-ins are expected from. The default is the country of each tenant. .EXAMPLE $ruleSet = Invoke-TcRuleSet -Snapshot (Get-Content ./snapshot.json -Raw | ConvertFrom-Json -AsHashtable) Runs the rules on a saved snapshot. #> [CmdletBinding()] [OutputType([System.Collections.Specialized.OrderedDictionary])] param( [Parameter(Mandatory)][System.Collections.IDictionary]$Snapshot, [hashtable]$Threshold = @{}, [string]$LicenseSku, [hashtable]$SourcePrefix = @{}, [string[]]$HomeCountry = @() ) $tenants = Get-TcProperty $Snapshot 'tenants' if ($null -eq $tenants) { throw 'This snapshot has no tenants.' } $thresholds = @{ inactiveDays = 90; secretExpiryDays = 60; mailboxThresholdGb = 45; siteThresholdGb = 100; oneDriveThresholdGb = 100; maxTtlSeconds = 300; storageMarginPercent = 15 } foreach ($key in $Threshold.Keys) { if (-not $thresholds.ContainsKey($key)) { throw "'$key' is not a rule threshold. Use one of: $(@($thresholds.Keys | Sort-Object) -join ', ')." } $thresholds[$key] = $Threshold[$key] } $roles = @('Target', 'S1', 'S2', 'S3' | Where-Object { $tenants.Contains($_) }) $sources = @($roles | Where-Object { $_ -ne 'Target' }) $script:TcRuleRun = @{ Snapshot = $Snapshot; Tenants = $tenants; Roles = $roles; Sources = $sources; Thresholds = $thresholds LicenseSku = $LicenseSku; SourcePrefix = $SourcePrefix; HomeCountry = @($HomeCountry) Role = $null; Workload = $null; Source = $null Findings = [System.Collections.Generic.List[object]]::new() Unread = [System.Collections.Generic.List[object]]::new() UnreadKeys = [System.Collections.Generic.HashSet[string]]::new() Errors = [System.Collections.Generic.List[object]]::new() } $run = $script:TcRuleRun $invoke = { param([scriptblock]$Rule, [string]$Role, [object[]]$Arguments) try { & $Rule @Arguments } catch { Add-TcRuleError -Role $Role -Workload $run['Workload'] -ErrorRecord $_ } } try { $workloads = [ordered]@{ 'Entra ID' = 'Get-TcEntraIdRule'; 'Exchange Online' = 'Get-TcExchangeRule'; 'SharePoint and OneDrive' = 'Get-TcSharePointRule'; 'Teams' = 'Get-TcTeamsRule' 'Teams Phone' = 'Get-TcTeamsPhoneRule'; 'Intune' = 'Get-TcIntuneRule'; 'Purview' = 'Get-TcPurviewRule'; 'Power Platform' = 'Get-TcPowerPlatformRule' 'Azure' = 'Get-TcAzureRule'; 'Domains and DNS' = 'Get-TcDnsRule'; 'Other services' = 'Get-TcOtherServiceRule'; 'Coverage' = 'Get-TcCoverageRule' } foreach ($role in $roles) { $run['Role'] = $role foreach ($workload in $workloads.Keys) { $run['Workload'] = $workload foreach ($rule in & $workloads[$workload]) { & $invoke $rule $role @($role) } } } if ($tenants.Contains('Target')) { foreach ($source in $sources) { $run['Role'] = $source $run['Source'] = $source foreach ($rule in Get-TcComparisonRule) { & $invoke $rule $source @($source) } } } if ($roles.Count -ge 2) { $run['Role'] = 'All' $run['Workload'] = 'Consolidation' foreach ($rule in Get-TcConsolidationRule) { & $invoke $rule 'All' @() } } $run['Role'] = $null $run['Workload'] = $null $gaps = @(Get-TcSettingGap) foreach ($source in $sources) { $run['Source'] = $source $important = @($gaps | Where-Object { $_['important'] -and $_['different'] -contains $source }) if ($important.Count) { Add-TcFinding 'CMP-SETTINGS' 'Both' -Values @{ count = $important.Count } -Items ($important | ForEach-Object { '{0} - {1}: {2} to {3}' -f $_['workload'], $_['setting'], $_['values'][$source], $_['values']['Target'] }) } } if ($sources.Count -ge 2) { $between = @($gaps | Where-Object { $gap = $_; $gap['important'] -and @($sources | Where-Object { $gap['values'].Contains($_) } | ForEach-Object { $gap['values'][$_] } | Sort-Object -Unique).Count -ge 2 }) if ($between.Count) { Add-TcFinding 'CON-SETTINGS' 'All' -Values @{ count = $between.Count } -Items ($between | ForEach-Object { $gap = $_ '{0} - {1}: {2}' -f $gap['workload'], $gap['setting'], (@($gap['values'].Keys | ForEach-Object { '{0} {1}' -f (Get-TcRuleTenantName $_), $gap['values'][$_] }) -join ' - ') }) } } [ordered]@{ findings = @($run['Findings'] | Sort-Object -Stable { $_['order'] }, { $_['workload'] }, { $_['title'] }) measures = @(Get-TcMeasure) settingGaps = $gaps unread = @($run['Unread']) errors = @($run['Errors']) } } finally { $script:TcRuleRun = $null } } |