Skip to content

Instantly share code, notes, and snippets.

@pandieme
Last active October 30, 2024 11:40
Show Gist options
  • Save pandieme/7a424f4074f419e8eb84d9ce461eafa0 to your computer and use it in GitHub Desktop.
Save pandieme/7a424f4074f419e8eb84d9ce461eafa0 to your computer and use it in GitHub Desktop.
<#
.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