Last active
October 30, 2024 11:40
-
-
Save pandieme/7a424f4074f419e8eb84d9ce461eafa0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.SYNOPSIS | |
AAD Config Validator | |
.DESCRIPTION | |
Configuration Validator for Azure AD Connect configuration files. | |
The should end up taking in a pre and post change config file and | |
produce report of what will change. | |
This will support change control by enabling us to predict what changes | |
to the config will have before they are made. | |
#> | |
using namespace System.Collections.Generic | |
[CmdletBinding()] | |
param ( | |
[System.IO.FileInfo] $PreConfigPath, | |
[System.IO.FileInfo] $PostConfigPath, | |
[System.IO.DirectoryInfo] $OutputDirectory | |
) | |
if (-not [bool] $PreConfigPath) { | |
[System.IO.FileInfo] $PreConfigPath = Join-Path $PSScriptRoot '..\config\pandie_local_config.json' | |
[System.IO.FileInfo] $PostConfigPath = Join-Path $PSScriptRoot '..\config\pandie_local_postConfig.json' | |
[System.IO.FileInfo] $OutputDirectory = $PSScriptRoot | |
} | |
try { | |
$PreConfig = Get-Content -Path $PreConfigPath -Raw | ConvertFrom-Json | |
$PostConfig = if ([bool] $PostConfigPath) { | |
Get-Content -Path $PostConfigPath -Raw | ConvertFrom-Json | |
} | |
} | |
catch { | |
Write-Host "Something gone happened when attempting to load config: [$_]" | |
exit 1 | |
} | |
#Requires -Modules ActiveDirectory | |
Import-Module -Name ActiveDirectory | |
class DomainPartitionFilter { | |
[string] $PolicyFriendlyName | |
[string] $PolicyFullyQualifiedDomainName | |
[string] $PolicyOnPremisesDirectoryAccount | |
[string] $FullyQualifiedDomainName | |
[string] $DistinguishedName | |
[string[]] $ContainerInclusions | |
[string[]] $ContainerExclusions | |
DomainPartitionFilter () {} | |
} | |
function Get-OuAdObjects { | |
param ([string] $SearchBase) | |
Get-ADOrganizationalUnit -Filter * -SearchBase $SearchBase -SearchScope OneLevel | |
} | |
function InterogateEidConfiguration ([psobject] $Config, [ref] $DomainObjects) { | |
[List[DomainPartitionFilter]] $DomainPartitions = @() | |
foreach ($policy in $Config.onpremisesDirectoryPolicy) { | |
foreach ($partition in $policy.partitionFilters) { | |
$DomainPartitions.Add([DomainPartitionFilter] @{ | |
PolicyFriendlyName = $policy.friendlyName | |
PolicyFullyQualifiedDomainName = $policy.fullyQualifiedDomainName | |
PolicyOnPremisesDirectoryAccount = $policy.onPremisesDirectoryAccount | |
FullyQualifiedDomainName = $partition.fullyQualifiedDomainName | |
DistinguishedName = $partition.distinguishedName | |
ContainerInclusions = $partition.containerInclusions | |
ContainerExclusions = $partition.containerExclusions | |
}) | |
} | |
} | |
foreach ($domain in $DomainPartitions) { | |
foreach ($ou in $domain.ContainerInclusions) { | |
[hashtable] $AdObjectProps = @{ | |
Server = $domain.FullyQualifiedDomainName | |
SearchBase = $ou | |
SearchScope = 'Subtree' | |
Properties = @( | |
'sAMAccountName' | |
'userPrincipalName' | |
'mS-DS-ConsistencyGuid' | |
'DisplayName' | |
'Description' | |
'whenCreated' | |
'whenChanged' | |
) | |
Filter = 'objectCategory -eq "user" -or objectCategory -eq "group"' | |
} | |
$children = Get-ADObject @AdObjectProps | Where-Object { $_.DistinguishedName -match '^CN=[^,]+,OU=' } | |
if (-not [bool] $children -or $children.Count -eq 0) { | |
continue | |
} | |
if (-not $DomainObjects.Value.ContainsKey($domain.FullyQualifiedDomainName)) { | |
$DomainObjects.Value.Add($domain.FullyQualifiedDomainName, [Dictionary[[string], [Microsoft.ActiveDirectory.Management.ADObject]]] @{}) | |
} | |
$children | ForEach-Object { | |
# Pattern groups everything from the start of the string to the first comma, and matches everything after it. | |
$dnPathMatches = Select-String -InputObject $_.DistinguishedName -Pattern '(?<=^.+?,).*' | |
if (-not [bool] $dnPathMatches) { | |
'Failed to get path from DN: {0}' -f $_.DistinguishedName | Write-Warning | |
return | |
} | |
[string] $dnPath = $dnPathMatches.Matches[0].Value | |
# Deep inspect exclusion paths | |
foreach ($exclusion in $domain.ContainerExclusions.Where({ $dnPath -match $_ })) { | |
$inclusionSplit = $ou -split ',' | |
$exclusionSplit = $exclusion -split ',' | |
# $dnPathSplit = $dnPath -split ',' | |
if ($exclusionSplit.Length -lt $inclusionSplit.Length) { | |
# There is an exclusion along the distinguished path, but at a higher level than the OU inclusion | |
continue | |
} | |
Write-Warning -Message "$($_.Name) ($($_.DistinguishedName)) is excluded for rule $exclusion" | |
return | |
} | |
if ($DomainObjects.Value[$domain.FullyQualifiedDomainName].ContainsKey($_.ObjectGUID)) { | |
Write-Warning ('{0} is already in the dictionary' -f $_.DistinguishedName) | |
return | |
} | |
$DomainObjects.Value[$domain.FullyQualifiedDomainName].Add($_.ObjectGUID, $_) | |
} | |
} | |
} | |
} | |
# TODO: Function to get and validate AAD objects | |
[Dictionary[[string], [Dictionary[[string], [Microsoft.ActiveDirectory.Management.ADObject]]]]] $PreDomainObjects = @{} | |
[Dictionary[[string], [Dictionary[[string], [Microsoft.ActiveDirectory.Management.ADObject]]]]] $PostDomainObjects = if ([bool] $PostConfig) { @{} } | |
InterogateEidConfiguration -Config $PreConfig -DomainObjects ([ref] $PreDomainObjects) | |
if ([bool] $PostDomainObjects) { | |
InterogateEidConfiguration -Config $PostConfig -DomainObjects ([ref] $PostDomainObjects) | |
[Dictionary[[string], [PSCustomObject]]] $AddsAndRemoves = @{} | |
foreach ($domainKey in $PreDomainObjects.Keys) { | |
if (-not $AddsAndRemoves.ContainsKey($domainKey)) { | |
$AddsAndRemoves.Add($domainKey, [PSCustomObject] @{ | |
Adds = [List[string]] @() | |
Removes = [List[string]] @() | |
}) | |
} | |
foreach ($domainObject in $PostDomainObjects.$($domainKey).Values) { | |
if (-not $PreDomainObjects.$($domainKey).ContainsKey($domainObject.ObjectGUID)) { | |
$AddsAndRemoves.$($domainKey).Adds.Add($domainObject.DistinguishedName) | |
} | |
} | |
foreach ($domainObject in $PreDomainObjects.$($domainKey).Values) { | |
if (-not $PostDomainObjects.$($domainKey).ContainsKey($domaiNobject.ObjectGUID)) { | |
$AddsAndRemoves.$($domainKey).Removes.Add($domainObject.DistinguishedName) | |
} | |
} | |
} | |
} | |
[List[pscustomobject]] $ReportPreObjects = @() | |
foreach ($domain in $PreDomainObjects.Keys) { | |
foreach ($obj in $PreDomainObjects.$domain.Values) { | |
$ReportPreObjects.Add([pscustomobject]@{ | |
Domain = $domain | |
Name = $obj.Name | |
SamAccountName = $obj.sAMAccountName | |
DisplayName = $obj.DisplayName | |
DistinguishedName = $obj.DistinguishedName | |
Description = $obj.Description | |
ObjectGuid = $obj.ObjectGUID | |
ObjectClass = $obj.ObjectClass | |
'ms-DS-ConsistencyGuid' = $(if ([bool] $obj.'ms-DS-ConsistencyGuid') { | |
[string][guid]$obj.'ms-DS-ConsistencyGuid' | |
}) | |
Created = $obj.whenCreated.ToString('yyyy/MM/dd') | |
Updated = $obj.whenChanged.ToString('yyyy/MM/dd') | |
}) | |
} | |
} | |
$ReportFileName = 'pre_report_{0}.csv' -f [datetime]::Now.ToString('yyyyMMdd_HHmmss') | |
# $ReportPreObjects | Export-Csv -Path "$OutputDirectory\$ReportFileName" -NoTypeInformation |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment