Private/Trusts/Collector.ps1
|
# Reads local-forest TDO observations only; does not contact a trust partner. $script:PSITrustCollector={ param([string]$Domain,[pscredential]$Credential) $ErrorActionPreference='Stop';Import-Module ActiveDirectory -ErrorAction Stop function Parameters([string]$dc){$p=@{Server=$dc;ErrorAction='Stop'};if($null -ne $Credential){$p.Credential=$Credential};return $p} function ParentDN([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 Property($o,$p){if($null -ne $o.PSObject.Properties[$p]){return $o.$p};return $null} $p=Parameters $Domain;$r=Get-ADRootDSE @p;$seed=[string]$r.dnsHostName if(-not $seed){throw 'Trust discovery did not return a source DC.'} $p=Parameters $seed;$r=Get-ADRootDSE @p;$initial=Get-ADDomain @p if([string]$initial.DNSRoot -ine $Domain){throw 'Connection domain does not match discovery.'} $forest=Get-ADForest @p;$names=@($forest.Domains|ForEach-Object{[string]$_}|Sort-Object -Unique) if(-not $names.Count -or $names -notcontains $Domain){throw 'Connection domain is not in the returned local forest membership.'} [pscustomobject]@{Kind='Context';Forest=[string]$forest.Name;RootDomain=[string]$forest.RootDomain;Domain=$Domain;Server=$seed;Domains=$names;ConfigurationNC=[string]$r.configurationNamingContext} # Never request Properties * or trust authentication secret attributes. $ldap=@('trustAttributes','trustDirection','trustPartner','flatName','securityIdentifier','msDS-SupportedEncryptionTypes','whenCreated','whenChanged') $safe=@('Name','DistinguishedName','ObjectGUID','Source','Target','Direction','IntraForest','ForestTransitive','DisallowTransivity','SelectiveAuthentication','SIDFilteringForestAware','SIDFilteringQuarantined','TGTDelegation','UsesAESKeys','UsesRC4Encryption','SID')+$ldap foreach($localDomain in $names){ $clock=[diagnostics.stopwatch]::StartNew();$rows=@();$source=$null;$status='Succeeded';$id=$null;$message=$null try{ if($localDomain -ieq $Domain){$source=$seed;$info=$initial} else{$connect=Parameters $localDomain;$rd=Get-ADRootDSE @connect;$source=[string]$rd.dnsHostName;if(-not $source){throw 'Member-domain discovery did not return a DC.'};$connect=Parameters $source;$info=Get-ADDomain @connect} if([string]$info.DNSRoot -ine $localDomain -or [string]$info.Forest -ine [string]$forest.Name){throw 'Member domain/forest verification failed.'} $connect=Parameters $source;$systemDN='CN=System,'+[string]$info.DistinguishedName $rows=@(Get-ADTrust -Filter * -Properties $ldap @connect | ForEach-Object{ if((ParentDN ([string]$_.DistinguishedName)) -ine $systemDN){throw 'Get-ADTrust returned a TDO outside the verified domain System container.'} $record=[ordered]@{} foreach($name in $safe){ $v=Property $_ $name if($null -eq $v){$record[$name]=$null} elseif($v -is [bool]){$record[$name]=$v} elseif($v -is [datetime]){$record[$name]=([datetimeoffset]$v).ToString('o')} elseif($v -is [byte[]]){$record[$name]=[convert]::ToBase64String($v)} else{$record[$name]=[string]$v} } $record['NativeProtocolType']=[string](Property $_ 'TrustType') [pscustomobject]$record }) }catch{$status='Failed';$id=$_.FullyQualifiedErrorId;$message=$_.Exception.Message;$rows=@()} $clock.Stop() [pscustomobject]@{Kind='Section';Key='trust:'+$localDomain;Target=$localDomain;SourceDC=$source;Status=$status;Records=$rows;Method='ADWS / Get-ADTrust on verified local-forest domain';CollectedAt=[datetimeoffset]::Now.ToString('o');DurationMs=$clock.ElapsedMilliseconds;ErrorId=$id;ErrorMessage=$message} } } |