Private/OrganizationalUnits/Helpers.ps1

function Get-PSIOuSchema {
 [ordered]@{
  ous=@('Organizational units',@('Name','OU ID','DN','Parent DN','Canonical path','Description','Managed by','Protected from accidental deletion','Created','Changed'))
  counts=@('Object counts',@('Name','OU ID','DN','Direct users','Descendant users','Direct computers','Descendant computers','Direct groups','Descendant groups','Direct OUs','Descendant OUs','Count status'))
  links=@('Direct GPO links',@('Name','OU ID','OU DN','GPO ID','GPO DN','Link order','Enabled','Enforced','Options raw'))
  inheritance=@('Inheritance configuration',@('Name','OU ID','DN','Block GPO inheritance','GPO options raw','DACL inheritance protected'))
  delegations=@('Delegation ACEs',@('Name','OU ID','OU DN','Trustee SID','Access type','AD rights','Object type GUID','Inherited object type GUID','Inheritance type','Inherited','Inheritance flags','Propagation flags'))
  objectQueries=@('OU collection',@('Name','OU ID','Target','Operation','Status','Record count','Duration ms','Error ID','Message','Collected at'))
  collection=@('Category collection',@('Name','Status','Record count','Method','Collected at','Duration ms','Error ID','Message'))
 }
}
function Get-PSIOuParentDN([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-PSIOuAcl([string]$Server,[string]$DN){
 $entry=$null
 try{
  $entry=New-Object DirectoryServices.DirectoryEntry(('LDAP://'+$Server+'/'+$DN.Replace('/','\/')))
  $entry.Options.SecurityMasks=[DirectoryServices.SecurityMasks]::Dacl
  $entry.RefreshCache(@('nTSecurityDescriptor'))
  $security=$entry.ObjectSecurity
  if($null -eq $security){throw 'DACL was not returned.'}
  [pscustomobject]@{Protected=$security.AreAccessRulesProtected;Rules=@($security.GetAccessRules($true,$true,[Security.Principal.SecurityIdentifier]))}
 }finally{if($null -ne $entry){$entry.Dispose()}}
}
function ConvertTo-PSIOuModel($Context,[object[]]$Envelopes){
 $schema=Get-PSIOuSchema;$data=[ordered]@{};$coverage=[ordered]@{};$seen=@{}
 foreach($k in $schema.Keys){$data[$k]=@()}
 foreach($e in $Envelopes){
  $k=[string]$e.Key
  if(-not $schema.Contains($k) -or $k -eq 'collection' -or $seen.ContainsKey($k)){throw "Unknown/duplicate OU category: $k"}
  if($e.Status -notin @('Succeeded','Partial','Failed','TimedOut','NotAttempted')){throw "Invalid OU status: $k"}
  $seen[$k]=$true;$coverage[$k]=$e.Status -eq 'Succeeded'
  if($e.Status -in @('Succeeded','Partial')){$data[$k]=@($e.Records|Where-Object{$null -ne $_}|ForEach-Object{$row=[ordered]@{};foreach($c in $schema[$k][1]){$row[$c]=Get-PSISiteValue $_ $c};[pscustomobject]$row})}
  $data.collection+=[pscustomobject]@{Name=$schema[$k][0];Status=$e.Status;'Record count'=$data[$k].Count;Method='Pinned DC / ADWS and LDAP DACL reads';'Collected at'=$e.CollectedAt;'Duration ms'=$e.DurationMs;'Error ID'=$e.ErrorId;Message=$e.ErrorMessage}
 }
 foreach($k in $schema.Keys){if($k -ne 'collection' -and -not $seen.ContainsKey($k)){throw "Missing OU category: $k"}}
 # An unfinished count category still retains OU identity with unknown counts.
 if(-not $coverage.counts -and $data.counts.Count -eq 0){$data.counts=@($data.ous|ForEach-Object{$row=[ordered]@{};foreach($c in $schema.counts[1]){$row[$c]=$null};$row.Name=$_.Name;$row.'OU ID'=$_.'OU ID';$row.DN=$_.DN;$row.'Count status'='Unavailable';[pscustomobject]$row})}
 $coverage.collection=$true
 [pscustomobject]@{Schema=$schema;Data=$data;Coverage=$coverage;Raw=[ordered]@{}}
}