Active Directory Operations Test

‘Sup PSHomies,

Last blog I demonstrated how to create a HTML report from the Active Directory configuration snapshot. Here’s yet another way to get more use out of the Active Directory configuration snapshot.

I started out with the intention of reporting, then it hit me, why not use the snapshot for Operation readiness? Let’s dive in, I’ll explain as we go along…

Before we get started, you’ll need to have your Active Directory specification at hand. Modify $ADConfiguration according to your specifications.

#region Active Directory configuration as you expect it to be. Modify to reflect your Active Directory
$ADConfiguration = @{
    Forest = @{
        FQDN = 'pshirwin.local'
        ForestMode = 'Windows2012R2Forest'
        GlobalCatalogs = @(
            'DC-DSC-01.pshirwin.local'
        )
        SchemaMaster = 'DC-DSC-01.pshirwin.local'
        DomainNamingMaster = 'DC-DSC-01.pshirwin.local'

    }
    Domain = @{
        NetBIOSName = 'PSHIRWIN'
        DomainMode = 'Windows2012R2Domain'
        RIDMaster = 'DC-DSC-01.pshirwin.local'
        PDCEmulator = 'DC-DSC-01.pshirwin.local'
        InfrastructureMaster = 'DC-DSC-01.pshirwin.local'
        DistinguishedName = 'DC=pshirwin,DC=local'
        DNSRoot = 'pshirwin.local'
        DomainControllers = @(
            'DC-DSC-01'
        )
    }
    PasswordPolicy = @{
        PasswordHistoryCount = 24
        LockoutThreshold = 0
        LockoutDuration = '00:30:00'
        LockoutObservationWindow = '00:30:00'
        MaxPasswordAge = '42.00:00:00'
        MinPasswordAge = '1.00:00:00'
        MinPasswordLength = 8
        ComplexityEnabled = $true
    }
    Sites = @('Default-First-Site-Name')
    SiteLinks = @(
       @{
            Name = 'DEFAULTIPSITELINK'
            Cost = 100
            ReplicationFrequencyInMinutes = 180
        }
    )
    SubNets = @()
}
#endregion

Quick sidestep, we’re in the middle of implementing a new Infrastructure for a customer. Some post configuration had to be done, FSMO roles rearranged, Global catalogs etc. etc., you know the drill. I got my hand on the Active Directory specifications and filled it in. I did a AD configuration snapshot and was now ready to compare. My colleagues were in the middle of post configuring Active Directory. I noticed that the FSMO roles weren’t as expected. I was missing a Domain Controller and some Sites, subnets and sitelinks. I did a AD snapshot the next day, ran my operation readiness test and surprise, everything was as expected! It wasn’t my intention to supervise my colleagues, but I could give them the good news that the Active Directory is configured as specified.

To give you an idea of what to expect, I did the operation readiness test on my lab. Here’s the script:

<#
Author: I.C.A. Strachan
Version: 1.1
Version History:
08-04-2016 1.0 - First Release
12-05-2016 1.1 - Fixed issues with Sitelinks & Subnets.
Purpose: Pester script to validate Active Directory configuration.
#>
[CmdletBinding()]
Param(
$xmlFile = 'ADReport-12052016.xml'
)
#region Active Directory configuration as you expect it to be. Modify to reflect your AD
$ADConfiguration = @{
Forest = @{
FQDN = 'pshirwin.local'
ForestMode = 'Windows2012R2Forest'
GlobalCatalogs = @(
'DC-DSC-01.pshirwin.local'
)
SchemaMaster = 'DC-DSC-01.pshirwin.local'
DomainNamingMaster = 'DC-DSC-01.pshirwin.local'
}
Domain = @{
NetBIOSName = 'PSHIRWIN'
DomainMode = 'Windows2012R2Domain'
RIDMaster = 'DC-DSC-01.pshirwin.local'
PDCEmulator = 'DC-DSC-01.pshirwin.local'
InfrastructureMaster = 'DC-DSC-01.pshirwin.local'
DistinguishedName = 'DC=pshirwin,DC=local'
DNSRoot = 'pshirwin.local'
DomainControllers = @('DC-DSC-01')
}
PasswordPolicy = @{
PasswordHistoryCount = 24
LockoutThreshold = 0
LockoutDuration = '00:30:00'
LockoutObservationWindow = '00:30:00'
MaxPasswordAge = '42.00:00:00'
MinPasswordAge = '1.00:00:00'
MinPasswordLength = 8
ComplexityEnabled = $true
}
Sites = @('Default-First-Site-Name','Branch01')
SiteLinks = @(
[PSCustomObject]@{
Name = 'DEFAULTIPSITELINK'
Cost = 100
ReplicationFrequencyInMinutes = 180
}
)
SubNets = @(
[PSCustomObject]@{
Name = '192.168.0.0/24'
Site = 'CN=Branch01,CN=Sites,CN=Configuration,DC=pshirwin,DC=local'
}
)
}
#endregion
#Import saved AD snapshot
$SavedADReport = Import-Clixml .\export\dsa\$xmlFile
Describe 'Active Directory configuration operational readiness' {
Context 'Verifying Forest Configuration'{
it "Forest FQDN $($ADConfiguration.Forest.FQDN)" {
$ADConfiguration.Forest.FQDN |
Should be $SavedADReport.ForestInformation.RootDomain
}
it "ForestMode $($ADConfiguration.Forest.ForestMode)"{
$ADConfiguration.Forest.ForestMode |
Should be $SavedADReport.ForestInformation.ForestMode.ToString()
}
}
Context 'Verifying GlobalCatalogs'{
$ADConfiguration.Forest.GlobalCatalogs |
ForEach-Object{
it "Server $($_) is a GlobalCatalog"{
$SavedADReport.ForestInformation.GlobalCatalogs.Contains($_) |
Should be $true
}
}
}
Context 'Verifying Domain Configuration'{
it "Total Domain Controllers $($ADConfiguration.Domain.DomainControllers.Count)" {
$ADConfiguration.Domain.DomainControllers.Count |
Should be @($SavedADReport.DomainControllers).Count
}
$ADConfiguration.Domain.DomainControllers |
ForEach-Object{
it "DomainController $($_) exists"{
$SavedADReport.DomainControllers.Name.Contains($_) |
Should be $true
}
}
it "DNSRoot $($ADConfiguration.Domain.DNSRoot)"{
$ADConfiguration.Domain.DNSRoot |
Should be $SavedADReport.DomainInformation.DNSRoot
}
it "NetBIOSName $($ADConfiguration.Domain.NetBIOSName)"{
$ADConfiguration.Domain.NetBIOSName |
Should be $SavedADReport.DomainInformation.NetBIOSName
}
it "DomainMode $($ADConfiguration.Domain.DomainMode)"{
$ADConfiguration.Domain.DomainMode |
Should be $SavedADReport.DomainInformation.DomainMode.ToString()
}
it "DistinguishedName $($ADConfiguration.Domain.DistinguishedName)"{
$ADConfiguration.Domain.DistinguishedName |
Should be $SavedADReport.DomainInformation.DistinguishedName
}
it "Server $($ADConfiguration.Domain.RIDMaster) is RIDMaster"{
$ADConfiguration.Domain.RIDMaster |
Should be $SavedADReport.DomainInformation.RIDMaster
}
it "Server $($ADConfiguration.Domain.PDCEmulator) is PDCEmulator"{
$ADConfiguration.Domain.PDCEmulator |
Should be $SavedADReport.DomainInformation.PDCEmulator
}
it "Server $($ADConfiguration.Domain.InfrastructureMaster) is InfrastructureMaster"{
$ADConfiguration.Domain.InfrastructureMaster |
Should be $SavedADReport.DomainInformation.InfrastructureMaster
}
}
Context 'Verifying Default Password Policy'{
it 'ComplexityEnabled'{
$ADConfiguration.PasswordPolicy.ComplexityEnabled |
Should be $SavedADReport.DefaultPassWordPoLicy.ComplexityEnabled
}
it 'Password History count'{
$ADConfiguration.PasswordPolicy.PasswordHistoryCount |
Should be $SavedADReport.DefaultPassWordPoLicy.PasswordHistoryCount
}
it "Lockout Threshold equals $($ADConfiguration.PasswordPolicy.LockoutThreshold)"{
$ADConfiguration.PasswordPolicy.LockoutThreshold |
Should be $SavedADReport.DefaultPassWordPoLicy.LockoutThreshold
}
it "Lockout duration equals $($ADConfiguration.PasswordPolicy.LockoutDuration)"{
$ADConfiguration.PasswordPolicy.LockoutDuration |
Should be $SavedADReport.DefaultPassWordPoLicy.LockoutDuration.ToString()
}
it "Lockout observation window equals $($ADConfiguration.PasswordPolicy.LockoutObservationWindow)"{
$ADConfiguration.PasswordPolicy.LockoutObservationWindow |
Should be $SavedADReport.DefaultPassWordPoLicy.LockoutObservationWindow.ToString()
}
it "Min password age equals $($ADConfiguration.PasswordPolicy.MinPasswordAge)"{
$ADConfiguration.PasswordPolicy.MinPasswordAge |
Should be $SavedADReport.DefaultPassWordPoLicy.MinPasswordAge.ToString()
}
it "Max password age equals $($ADConfiguration.PasswordPolicy.MaxPasswordAge)"{
$ADConfiguration.PasswordPolicy.MaxPasswordAge |
Should be $SavedADReport.DefaultPassWordPoLicy.MaxPasswordAge.ToString()
}
}
Context 'Verifying Active Directory Sites'{
$ADConfiguration.Sites |
ForEach-Object{
it "Site $($_)" {
$SavedADReport.Sites.Name.Contains($_) |
Should be $true
}
}
}
Context 'Verifying Active Directory Sitelinks'{
$lookupSiteLinks = $SavedADReport.Sitelinks | Group-Object -AsHashTable -Property Name
$ADConfiguration.Sitelinks |
ForEach-Object{
it "Sitelink $($_.Name)" {
$_.Name |
Should be $($lookupSiteLinks.$($_.Name).Name)
}
it "Sitelink $($_.Name) costs $($_.Cost)" {
$_.Cost |
Should be $lookupSiteLinks.$($_.Name).Cost
}
it "Sitelink $($_.Name) replication interval $($_.ReplicationFrequencyInMinutes)" {
$_.ReplicationFrequencyInMinutes |
Should be $lookupSiteLinks.$($_.Name).ReplicationFrequencyInMinutes
}
}
}
Context 'Verifying Active Directory Subnets'{
$lookupSubnets = $SavedADReport.SubNets | Group-Object -AsHashTable -Property Name
$ADConfiguration.Subnets |
ForEach-Object{
it "Subnet $($_.Name)" {
$_.Name |
Should be $lookupSubnets.$($_.Name).Name
}
it "Site $($_.Site)" {
$_.Site |
Should be $lookupSubnets.$($_.Name).Site
}
}
}
}

And here’s the result:
AD Operation Readiness
My testlab is quite simple.

Validating operation readiness will definitely help you keep things in check! No second guessing: “Did I configure server x as a Global catalog? With the AD Configuration snapshot you can be certain how you left things! “I know for a fact I configured the server as a Global catalog last week.” Compare your past snapshot to what you’re expecting. Create a new snaphot and compare again. If it’s different… Well… Sometimes colleagues forget to communicate changes that have been made… At least you don’t have to second guess yourself 😉 As an OPS guys Operation readiness has my vote!

Hope it’s worth something to you

Ttyl,

Urv

8 thoughts on “Active Directory Operations Test

  1. Pingback: Some Pester Tests for SQL Defaults – SQL DBA with A Beard

  2. Pingback: Continuously Testing your Infrastructure with OVF and Microsoft Operations Management Suite | PowerShell, Programming and DevOps

  3. Pingback: Active Directory Operational Testing « The Surly Admin

  4. Pingback: AD Operation Validation class | pshirwin

  5. Pingback: Episode 319 - PowerScripting Podcast - MVPs Don Jones and Adam Bertram on Pester

  6. Pingback: Episode 319 - PowerScripting Podcast - MVPs Don Jones and Adam Bertram on Pester | PowerShell.org

Leave a comment