Private/Matrix/ConvertTo-TcIdentityMatrix.ps1
|
function ConvertTo-TcIdentityMatrix { <# .SYNOPSIS Drafts the identity matrix of one source from a snapshot, and flags likely duplicates in the target. .DESCRIPTION Each member account of the source gets one row, with a proposed decision: Create, Duplicate or To review. Two accounts are likely the same person when their display names match once accents, case and punctuation are set aside. This is a draft for a person to confirm, not a decision. When the accounts of the target were not read, no duplicate can be ruled out, so nothing reads Create. .PARAMETER IgnoreNameSuffix Words to remove from the end of a display name before comparing, for a tenant that marks its accounts that way. #> [CmdletBinding()] [OutputType([System.Collections.Specialized.OrderedDictionary])] param( [Parameter(Mandatory)][System.Collections.IDictionary]$Snapshot, [Parameter(Mandatory)][string]$SourceRole, [AllowEmptyCollection()][string[]]$IgnoreNameSuffix = @(), [int]$InactiveDays = 90 ) $tenants = $Snapshot['tenants'] if (-not $tenants.Contains($SourceRole)) { throw "This snapshot has no source $SourceRole." } $sourceTables = Get-TcProperty $tenants[$SourceRole] 'tables' $targetTables = Get-TcProperty $tenants['Target'] 'tables' # Dates are relative to the run, so a saved snapshot always gives the same draft. $reference = Get-Date $started = [string](Get-TcProperty (Get-TcProperty $Snapshot 'meta') 'started') if ($started) { $parsed = [datetime]::MinValue if ([datetime]::TryParse($started, [Globalization.CultureInfo]::InvariantCulture, [Globalization.DateTimeStyles]::None, [ref]$parsed)) { $reference = $parsed } } $limit = $reference.AddDays(-$InactiveDays) $targetRead = (Get-TcTableState -Snapshot $Snapshot -Role 'Target' -Table 'users').State -notin 'NotCollected', 'NotFullyRead' $targetUsers = @(Get-TcProperty $targetTables 'users' @()) | Where-Object { (Get-TcProperty $_ 'Type') -eq 'Member' } $index = @{} foreach ($user in $targetUsers) { $index[(ConvertTo-TcMatchName -Name (Get-TcProperty $user 'Name') -IgnoreSuffix $IgnoreNameSuffix)] = $user } $initial = @(Get-TcProperty $targetTables 'domains' @()) | Where-Object { Get-TcProperty $_ 'Initial' $false } | Select-Object -First 1 $targetDomain = Get-TcProperty $initial 'Domain' (Get-TcProperty $tenants['Target'] 'primaryDomain') $mailboxes = @(Get-TcProperty $sourceTables 'mailboxes' @()) $sourceUsers = @(Get-TcProperty $sourceTables 'users' @()) | Where-Object { (Get-TcProperty $_ 'Type') -eq 'Member' } foreach ($user in @($sourceUsers | Sort-Object { Get-TcProperty $_ 'Name' })) { $upn = [string](Get-TcProperty $user 'UPN') $mailbox = $mailboxes | Where-Object { (Get-TcProperty $_ 'UPN') -eq $upn } | Select-Object -First 1 $mailboxType = [string](Get-TcProperty $mailbox 'Type') # A Bookings page is a mailbox with no license, like a shared mailbox. $type = switch ($mailboxType) { { $_ -in 'SharedMailbox', 'SchedulingMailbox' } { 'Shared' } { $_ -in 'RoomMailbox', 'EquipmentMailbox' } { 'Resource' } default { 'User' } } $duplicate = $index[(ConvertTo-TcMatchName -Name (Get-TcProperty $user 'Name') -IgnoreSuffix $IgnoreNameSuffix)] $decision = if ($duplicate) { 'Duplicate' } else { 'Create' } $notes = [System.Collections.Generic.List[string]]::new() $enabled = [bool](Get-TcProperty $user 'Enabled') $lastText = [string](Get-TcProperty $user 'Last sign-in') $last = $null if ($lastText) { $parsed = [datetime]::MinValue if ([datetime]::TryParse($lastText, [Globalization.CultureInfo]::InvariantCulture, [Globalization.DateTimeStyles]::None, [ref]$parsed)) { $last = $parsed } } $licenseCount = [int](Get-TcProperty $user 'License count' 0) if ($type -eq 'User' -and (-not $enabled -or ($last -and $last -lt $limit) -or (-not $last -and -not $licenseCount))) { $decision = 'To review' if (-not $enabled) { $notes.Add('disabled account') } elseif ($last) { $notes.Add("inactive since $($last.ToString('yyyy-MM-dd'))") } else { $notes.Add('no license and no known sign-in') } } if ($mailboxType -eq 'SchedulingMailbox') { # Bookings cannot be moved: the page is recreated in the target or dropped. $decision = 'To review' $notes.Add('Bookings page: recreate it in the target or drop it (Bookings cannot be moved)') } if ($duplicate) { $notes.Add("likely duplicate of $(Get-TcProperty $duplicate 'UPN')") } if (-not $targetRead) { $decision = 'To review' $notes.Add('the accounts of the target were not read, so duplicates were not looked for') } $alias = (Get-TcAddressPrefix $upn).ToLowerInvariant() [ordered]@{ 'Source UPN' = $upn 'Display name' = Get-TcProperty $user 'Name' 'First name' = Get-TcProperty $user 'First name' 'Last name' = Get-TcProperty $user 'Last name' 'Type' = $type 'Decision' = $decision 'Target UPN' = $(if ($duplicate) { Get-TcProperty $duplicate 'UPN' } else { $upn }) 'Temporary UPN' = $(if ($duplicate) { '' } else { '{0}.{1}@{2}' -f $SourceRole.ToLowerInvariant(), $alias, $targetDomain }) 'License' = '' 'Groups' = '' 'Notes' = @($notes) -join '; ' } } } |