Private/Rules/Get-TcComparisonRule.ps1
|
function Get-TcComparisonRule { <# .SYNOPSIS Returns the rules that compare one source with the target, as script blocks that take the source role. .DESCRIPTION A finding here belongs to the source: it is what stands between that source and the target. Each block names its workload first, so a table it misses counts against that workload of that source. #> [CmdletBinding()] [OutputType([scriptblock])] param() { # Cross-tenant access and guests that collide with migrated accounts param($Source) $script:TcRuleRun['Workload'] = 'Entra ID' $sourceId = [string](Get-TcProperty $script:TcRuleRun['Tenants'][$Source] 'tenantId') if (-not $sourceId) { $sourceId = Get-TcRuleSetting $Source 'organizationSettings' 'Tenant ID' } if ((Test-TcRuleTable 'Target' 'crossTenantPartners') -and $sourceId -and -not @(Get-TcRuleRow 'Target' 'crossTenantPartners' | Where-Object { (Get-TcProperty $_ 'Tenant') -eq $sourceId }).Count) { Add-TcFinding 'CMP-CROSS-TENANT' 'Both' } $domains = @(Get-TcSourceDomain $Source) if ($domains.Count) { $pattern = '(' + (@($domains | ForEach-Object { [regex]::Escape($_) }) -join '|') + ')' $guests = @(Get-TcRuleRow 'Target' 'users' | Where-Object { (Get-TcProperty $_ 'Type') -eq 'Guest' -and ("$(Get-TcProperty $_ 'UPN') $(Get-TcProperty $_ 'Email')").ToLowerInvariant() -match $pattern }) if ($guests.Count) { Add-TcFinding 'CMP-CROSS-GUESTS' 'Both' -Values @{ count = $guests.Count } -Items ($guests | ForEach-Object { '{0} - {1}' -f (Get-TcProperty $_ 'UPN'), (Get-TcProperty $_ 'Name') }) } } } { # Licenses for the accounts of this source param($Source) $script:TcRuleRun['Workload'] = 'Entra ID' $needed = @(Get-TcRuleRow $Source 'users' | Where-Object { (Get-TcProperty $_ 'Type') -eq 'Member' -and (Test-TcRowTrue $_ 'Enabled') -and (Get-TcRowInteger $_ 'License count') -gt 0 }).Count $sku = Get-TcRuleLicenseSku $license = Get-TcRuleRow 'Target' 'licenses' | Where-Object { (Get-TcProperty $_ 'SKU') -eq $sku } | Select-Object -First 1 if ($needed -and (Test-TcRuleTable 'Target' 'licenses')) { $available = if ($license) { [int](Get-TcProperty $license 'Available') } else { 0 } if ($available -lt $needed) { Add-TcFinding 'CMP-LICENSES' 'Both' -Values @{ sku = $sku; available = $available; needed = $needed; missing = $needed - $available } } } } { # Teams Phone licenses for the voice users of this source param($Source) $script:TcRuleRun['Workload'] = 'Teams Phone' $voice = @(Get-TcRuleRow $Source 'teamsUsers' | Where-Object { Test-TcRowTrue $_ 'Enterprise Voice' }).Count if ($voice -and (Test-TcRuleTable 'Target' 'licenses')) { $available = (@(Get-TcRuleRow 'Target' 'licenses' | Where-Object { (Get-TcProperty $_ 'Plans') -match 'MCOEV' -and (Get-TcProperty $_ 'SKU') -notmatch 'VIRTUALUSER' } | ForEach-Object { [int](Get-TcProperty $_ 'Available') }) | Measure-Object -Sum).Sum if ($available -lt $voice) { Add-TcFinding 'CMP-VOICE-LICENSES' 'Both' -Values @{ available = [int]$available; needed = $voice } } } } { # Site addresses and storage param($Source) $script:TcRuleRun['Workload'] = 'SharePoint and OneDrive' $sourceSites = @(Get-TcRuleRow $Source 'sites') $targetSites = @(Get-TcRuleRow 'Target' 'sites') if (-not $sourceSites.Count -or -not $targetSites.Count) { return } $paths = @{} foreach ($site in $targetSites) { $paths[[string](Get-TcProperty $site 'Path')] = Get-TcProperty $site 'Url' } $prefix = [string]$script:TcRuleRun['SourcePrefix'][$Source] $taken = @($sourceSites | Where-Object { (Get-TcProperty $_ 'Path') -match '^/(sites|teams)/' -and $paths.ContainsKey([string](Get-TcProperty $_ 'Path')) }) $takenPrefixed = @($sourceSites | Where-Object { (Get-TcProperty $_ 'Path') -match '^/(sites|teams)/([^/]+)$' -and $prefix -and $paths.ContainsKey(('/{0}/{1}-{2}' -f $Matches[1], $prefix.ToLowerInvariant(), $Matches[2])) }) if ($taken.Count -or $takenPrefixed.Count) { $items = @($taken | ForEach-Object { '{0} - already exists: {1}' -f (Get-TcProperty $_ 'Url'), $paths[[string](Get-TcProperty $_ 'Path')] }) + @($takenPrefixed | ForEach-Object { '{0} - the prefixed form already exists' -f (Get-TcProperty $_ 'Url') }) Add-TcFinding 'CMP-SITE-URLS' 'Both' -Values @{ count = $taken.Count + $takenPrefixed.Count } -Items $items } $quota = Get-TcRowNumber @{ q = Get-TcRuleSetting 'Target' 'sharePointSettings' 'StorageQuota' } 'q' $usedTarget = (@($targetSites | ForEach-Object { [double](Get-TcProperty $_ 'Storage MB') }) | Measure-Object -Sum).Sum $usedSource = (@($sourceSites | ForEach-Object { [double](Get-TcProperty $_ 'Storage MB') }) | Measure-Object -Sum).Sum $estimated = $false if (-not $quota) { $licensed = @(Get-TcRuleRow 'Target' 'users' | Where-Object { (Get-TcRowInteger $_ 'License count') -gt 0 }).Count if ($licensed) { $quota = (1024 * 1024) + (10 * 1024 * $licensed); $estimated = $true } } if ($quota) { $margin = [double](Get-TcRuleThreshold 'storageMarginPercent') $total = $usedTarget + $usedSource $severity = if ($total -gt $quota) { 'Critical' } elseif ($total -gt $quota * (1 - $margin / 100)) { 'High' } else { 'Info' } $values = @{ total = Format-TcNumber ($total / 1024); quota = Format-TcNumber ($quota / 1024); used = Format-TcNumber ($usedTarget / 1024) source = Format-TcNumber ($usedSource / 1024); margin = $margin; estimated = $(if ($estimated) { Get-TcRulePart 'CMP-CAPACITY' 'estimated' } else { '' }) } $variant = if ($severity -eq 'Info') { '' } else { 'short' } Add-TcFinding 'CMP-CAPACITY' 'Both' -Severity $severity -Variant $variant -Values $values } } { # Team names param($Source) $script:TcRuleRun['Workload'] = 'Teams' $sourceTeams = @(Get-TcRuleRow $Source 'teams') $targetTeams = @(Get-TcRuleRow 'Target' 'teams') if (-not $sourceTeams.Count -or -not $targetTeams.Count) { return } $prefix = ([string]$script:TcRuleRun['SourcePrefix'][$Source]).ToLowerInvariant() $names = @{} foreach ($team in $targetTeams) { $names[([string](Get-TcProperty $team 'Name')).ToLowerInvariant()] = 1 } $clashes = @($sourceTeams | Where-Object { $key = ([string](Get-TcProperty $_ 'Name')).ToLowerInvariant() $names.ContainsKey($key) -or ($prefix -and ($names.ContainsKey("$prefix-$key") -or $names.ContainsKey("$prefix - $key"))) }) if ($clashes.Count) { Add-TcFinding 'CMP-TEAM-NAMES' 'Both' -Values @{ count = $clashes.Count } -Items ($clashes | ForEach-Object { Get-TcProperty $_ 'Name' }) } } { # Teams federation during coexistence param($Source) $script:TcRuleRun['Workload'] = 'Teams' if (-not (Test-TcRuleTable 'Target' 'teamsSettings')) { return } $federation = Get-TcRuleSetting 'Target' 'teamsSettings' 'External access.AllowFederatedUsers' $allowed = [string](Get-TcRuleSetting 'Target' 'teamsSettings' 'External access.AllowedDomains') $domains = @(Get-TcSourceDomain $Source) $closed = ($null -ne $federation -and -not (Test-TcTrue $federation)) -or ($allowed -and $allowed -notmatch '(?i)AllowAllKnownDomains' -and @($domains | Where-Object { $allowed.ToLowerInvariant().Contains($_) }).Count -lt $domains.Count) if ($closed) { Add-TcFinding 'CMP-TEAMS-FEDERATION' 'Both' -Values @{ federation = $federation; domains = $(if ($allowed) { $allowed } else { Get-TcRulePart 'CMP-TEAMS-FEDERATION' 'allDomains' }) } } } { # Free/busy during coexistence param($Source) $script:TcRuleRun['Workload'] = 'Exchange Online' if (-not (Test-TcRuleTable 'Target' 'organizationRelationships')) { return } $domains = @(Get-TcSourceDomain $Source) $sharing = @(Get-TcRuleRow 'Target' 'organizationRelationships' | Where-Object { $names = ([string](Get-TcProperty $_ 'Domains')).ToLowerInvariant() (Test-TcRowTrue $_ 'Enabled') -and @($domains | Where-Object { $names.Contains($_) }).Count }) if (-not $sharing.Count) { Add-TcFinding 'CMP-FREE-BUSY' 'Both' } } { # Shared mailboxes with a counterpart in the target param($Source) $script:TcRuleRun['Workload'] = 'Exchange Online' $sourceBoxes = @(Get-TcRuleRow $Source 'mailboxes' | Where-Object { (Get-TcProperty $_ 'Type') -in 'SharedMailbox', 'RoomMailbox', 'EquipmentMailbox' }) $targetBoxes = @(Get-TcRuleRow 'Target' 'mailboxes') if (-not $sourceBoxes.Count -or -not $targetBoxes.Count) { return } $locals = @{} foreach ($box in $targetBoxes) { $part = Get-TcAddressPrefix ([string](Get-TcProperty $box 'Primary address')) if ($part) { $locals[$part.ToLowerInvariant()] = Get-TcProperty $box 'Primary address' } } $pairs = @($sourceBoxes | Where-Object { $locals.ContainsKey((Get-TcAddressPrefix ([string](Get-TcProperty $_ 'Primary address'))).ToLowerInvariant()) }) if ($pairs.Count) { Add-TcFinding 'CMP-SHARED-MAILBOXES' 'Both' -Values @{ count = $pairs.Count } -Items ($pairs | ForEach-Object { '{0} and {1}' -f (Get-TcProperty $_ 'Primary address'), $locals[(Get-TcAddressPrefix ([string](Get-TcProperty $_ 'Primary address'))).ToLowerInvariant()] }) } } { # Autopilot in the target param($Source) $script:TcRuleRun['Workload'] = 'Intune' if ((Test-TcRuleTable $Source 'autopilotDevices') -and @(Get-TcRuleRow $Source 'autopilotDevices').Count -and (Test-TcRuleTable 'Target' 'autopilotProfiles') -and -not @(Get-TcRuleRow 'Target' 'autopilotProfiles').Count) { Add-TcFinding 'CMP-AUTOPILOT-TARGET' 'Both' -Values @{ count = @(Get-TcRuleRow $Source 'autopilotDevices').Count } } } } |