Private/Users/Helpers.ps1
|
function Get-PSIUserSchema { [ordered]@{ users=@('Users',@('Name','User ID','SAM account name','UPN','Email','Enabled','Department','Title','Company','Manager DN','Location DN','DN','Created','Changed','Password last set','Account expires','Last logon timestamp (replicated)','Direct group links observed')) flags=@('Reported account flags',@('Name','User ID','Password never expires','Password not required','Cannot change password','Smartcard required','Account not delegated','Trusted for delegation','Trusted to authenticate for delegation')) memberships=@('Reported direct group links',@('Name','User ID','User','Group DN')) primaryGroups=@('Primary group references',@('Name','User ID','User SID','Primary group RID','Derived primary group SID')) departments=@('Departments',@('Name','Department','Users observed')) companies=@('Companies',@('Name','Company','Users observed')) locations=@('Directory locations',@('Name','Location DN','Users observed')) statuses=@('Enabled status',@('Name','Enabled','Users observed')) collection=@('Collection coverage',@('Name','Status','Record count','Method','Collected at','Duration ms','Error ID','Message')) } } function Get-PSIUserQuerySchema {[ordered]@{users=@('Directory users');collection=@('Collection coverage')}} function Get-PSIUserValue($o,[string]$p){if($null -ne $o -and $null -ne $o.PSObject.Properties[$p]){return $o.$p};return $null} function Get-PSIUserParentDN([string]$DN){$escaped=$false;for($i=0;$i -lt $DN.Length;$i++){if($escaped){$escaped=$false;continue};if($DN[$i] -eq '\'){$escaped=$true;continue};if($DN[$i] -eq ','){return $DN.Substring($i+1)}};return $null} function Get-PSIUserDomainDN([string]$DN){$part=$DN;while($part -and $part -notmatch '^(?i)DC='){$part=Get-PSIUserParentDN $part};return $part} function ConvertTo-PSIUserModel($Context,[object[]]$Envelopes){ if($Envelopes.Count -ne 1 -or $Envelopes[0].Key -ne 'users' -or $Envelopes[0].Status -notin @('Succeeded','Failed','TimedOut')){throw 'Expected one valid user query outcome.'} $e=$Envelopes[0];$schema=Get-PSIUserSchema;$data=[ordered]@{};$coverage=[ordered]@{};$seen=@{};$summaries=@{} foreach($k in $schema.Keys){$data[$k]=New-Object collections.ArrayList;$coverage[$k]=$e.Status -eq 'Succeeded'} foreach($k in @('departments','companies','locations','statuses')){$summaries[$k]=New-Object 'System.Collections.Generic.Dictionary[string,object]' -ArgumentList ([StringComparer]::Ordinal)} if($e.Status -eq 'Succeeded'){ foreach($source in @($e.Records)){ $id=([guid]$source.Id).ToString();if($seen.ContainsKey($id) -or (Get-PSIUserDomainDN $source.DN) -ine $Context.DomainDN){throw 'Duplicate/out-of-domain user record.'};$seen[$id]=$true $location=Get-PSIUserParentDN $source.DN;$groups=@($source.MemberOf|Where-Object{$_}|Sort-Object -Unique) [void]$data.users.Add([pscustomobject]@{Name=$source.Name;'User ID'=$id;'SAM account name'=$source.Sam;UPN=$source.Upn;Email=$source.Email;Enabled=$source.Enabled;Department=$source.Department;Title=$source.Title;Company=$source.Company;'Manager DN'=$source.Manager;'Location DN'=$location;DN=$source.DN;Created=$source.Created;Changed=$source.Changed;'Password last set'=$source.PasswordLastSet;'Account expires'=$source.Expires;'Last logon timestamp (replicated)'=$source.LastLogonTimestamp;'Direct group links observed'=$groups.Count}) [void]$data.flags.Add([pscustomobject]@{Name=$source.Name;'User ID'=$id;'Password never expires'=$source.PasswordNeverExpires;'Password not required'=$source.PasswordNotRequired;'Cannot change password'=$source.CannotChangePassword;'Smartcard required'=$source.SmartcardRequired;'Account not delegated'=$source.AccountNotDelegated;'Trusted for delegation'=$source.TrustedForDelegation;'Trusted to authenticate for delegation'=$source.TrustedToAuthForDelegation}) foreach($group in $groups){[void]$data.memberships.Add([pscustomobject]@{Name=$source.Name+' / '+$group;'User ID'=$id;User=$source.Name;'Group DN'=$group})} $primarySid=$null if($null -ne $source.PrimaryGroup -and [string]$source.Sid -match '^S-1-5-21-\d+-\d+-\d+-\d+$'){$primarySid=([string]$source.Sid -replace '-\d+$','')+'-'+[string]$source.PrimaryGroup} [void]$data.primaryGroups.Add([pscustomobject]@{Name=$source.Name;'User ID'=$id;'User SID'=$source.Sid;'Primary group RID'=$source.PrimaryGroup;'Derived primary group SID'=$primarySid}) foreach($dimension in @(@('departments','Department',$source.Department),@('companies','Company',$source.Company),@('locations','Location DN',$location),@('statuses','Enabled',$source.Enabled))){ $k=$dimension[0];$field=$dimension[1];$value=$dimension[2];$key=ConvertTo-Json -InputObject $value -Compress if(-not $summaries[$k].ContainsKey($key)){$row=[ordered]@{Name=if($null -eq $value){'Unknown / not returned'}elseif($value -is [string] -and $value.Length -eq 0){'Empty reported value'}else{[string]$value}};$row[$field]=$value;$row['Users observed']=0;$summaries[$k][$key]=[pscustomobject]$row} $summaries[$k][$key].'Users observed'++ } } } foreach($k in $summaries.Keys){foreach($row in @($summaries[$k].Values|Sort-Object Name)){[void]$data[$k].Add($row)}} [void]$data.collection.Add([pscustomobject]@{Name='Domain user enumeration';Status=$e.Status;'Record count'=$data.users.Count;Method='ADWS / pinned domain DC';'Collected at'=$e.CollectedAt;'Duration ms'=$e.DurationMs;'Error ID'=$e.ErrorId;Message=$e.ErrorMessage});$coverage.collection=$true $arrays=[ordered]@{};foreach($k in $schema.Keys){$arrays[$k]=@($data[$k].ToArray())};[pscustomobject]@{Schema=$schema;Data=$arrays;Coverage=$coverage;Raw=[ordered]@{}} } |