So here’s another spin on using Pester to validate operational readiness… 😉
Group policies can be pretty tricky! Troubleshooting can be a challenge. There might be even times that you start doubting yourself. Depending on the link order of your Policies, you might not get what you expected…
Operations is dynamic, things get moved around, enabled/disabled, blocked, name it and it’s bound to happen.
How about… some way to validate your GPOs Scope of Management! Once everything is working as it should, create a validation set you can verify later on. Trust me, I’ve been there… Using Pester will definitely give you that edge…
So I improvised a little on Ashley’s McGlone’s GPO Report and made a function Get-GPOsSoM. Just be sure to save it in the same folder as Domain-GPOSoM.Tests.ps1
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
<# | |
Author: I.C.A. Strachan | |
Version: 1.0 | |
Version History: Based on Ashley McGlone's Get-GPOReport. Here's a shortlink to it: http://tinyurl.com/ofpfnf4 | |
Purpose: Get all GPOs that are linked to Domain, Sites and/or OUs | |
#> | |
Function Get-GPOsSoM { | |
BEGIN{ | |
Import-Module GroupPolicy –Verbose:$false | |
Import-Module ActiveDirectory –Verbose:$false | |
#region Get a list of all GPOs | |
$GPOs = Get-GPO –All | | |
Select-Object ID, Path, DisplayName, GPOStatus, WMIFilter | |
#endregion | |
#Array for GPLinks results | |
$gPLinks = @() | |
#region GPO Linked to the Domain | |
$domainGPO = @{ | |
Identity = ((Get-ADDomain).distinguishedName) | |
Properties = @('name', 'distinguishedName', 'gPLink', 'gPOptions', 'canonicalname') | |
} | |
$gPlinks += Get-ADObject @domainGPO | | |
Select-Object 'name', 'distinguishedName', 'gPLink', 'gPOptions', 'canonicalname', | |
@{name='Depth';expression={0}} | |
#endregion | |
#region GPO Linked to OUs | |
$ouGPOs = @{ | |
Filter = '*' | |
Properties = @('name', 'distinguishedName', 'gPLink', 'gPOptions', 'canonicalname') | |
} | |
$gPLinks += Get-ADOrganizationalUnit @ouGPOs | | |
Select-Object name, distinguishedName, gPLink, gPOptions ,canonicalname , | |
@{name='Depth';expression={($_.distinguishedName -split 'OU=').count – 1}} | |
#endregion | |
#region GPOs linked to sites | |
$siteGPOs = @{ | |
LDAPFilter = '(objectClass=site)' | |
SearchBase = "CN=Sites,$((Get-ADRootDSE).configurationNamingContext)" | |
SearchScope = 'Onelevel' | |
Properties = @('name', 'distinguishedName', 'gPLink', 'gPOptions', 'canonicalname') | |
} | |
$gPLinks += Get-ADObject @siteGPOs | | |
Select-Object name, distinguishedName, gPLink, gPOptions ,canonicalname, | |
@{name='Depth';expression={0}} | |
#Hashtable to lookup GPOs | |
$lookupGPO = $GPOs | Group-Object –AsHashTable –Property 'Path' | |
} | |
PROCESS{ | |
#Get the Scope of Management of each gPLink | |
ForEach ($SOM in $gPLinks) { | |
if ($SOM.gPLink) { | |
If ($SOM.gPLink.length -gt 1) { | |
$links = @($SOM.gPLink -split {$_ -eq '[' -or $_ -eq ']'} | Where-Object {$_}) | |
For ( $i = $links.count – 1 ; $i -ge 0 ; $i— ) { | |
$GPOData = $links[$i] -split {$_ -eq '/' -or $_ -eq ';'} | |
[PSCustomObject]@{ | |
Depth = $SOM.Depth; | |
Name = $SOM.Name; | |
DistinguishedName = $SOM.distinguishedName; | |
canonicalName = $SOM.canonicalname; | |
PolicyDN = $GPOData[2]; | |
LinkOrderNr = $links.count – $i | |
GUID = $lookupGPO.$($GPOData[2]).ID; | |
DisplayName = $lookupGPO.$($GPOData[2]).DisplayName; | |
GPOStatus = $lookupGPO.$($GPOData[2]).GPOStatus; | |
WMIFilter = $lookupGPO.$($GPOData[2]).WMIFilter.Name; | |
Config = $GPOData[3]; | |
LinkEnabled = [bool](!([int]$GPOData[3] -band 1)); | |
Enforced = [bool]([int]$GPOData[3] -band 2); | |
BlockInheritance = [bool]($SOM.gPOptions -band 1) | |
} | |
} | |
} | |
} | |
} | |
} | |
END{} | |
} |
Now for the fun part! 🙂
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
<# | |
Author: I.C.A. Strachan | |
Version: | |
Version History: | |
Purpose: Pester script to validate Group Polcies status and Link on Domain,Sites and OUs | |
#> | |
[CmdletBinding()] | |
Param() | |
Describe 'Group Policies Scope of Management validation' { | |
BeforeAll { | |
#region Get GPOs Producution Validation set | |
$gpoValidationSet = @' | |
DisplayName,DistinguishedName,GPOStatus,BlockInheritance,LinkEnabled,Enforced,LinkOrderNr | |
Default Domain Policy,"DC=pshirwin,DC=local",AllSettingsEnabled,FALSE,TRUE,FALSE,1 | |
Default Domain Controllers Policy,"OU=Domain Controllers,DC=pshirwin,DC=local",AllSettingsEnabled,FALSE,TRUE,FALSE,1 | |
WinRM Listeners,"OU=Servers,DC=pshirwin,DC=local",AllSettingsEnabled,FALSE,TRUE,FALSE,1 | |
RemoteDesktop,"OU=Servers,DC=pshirwin,DC=local",AllSettingsEnabled,FALSE,TRUE,FALSE,2 | |
Firewall,"OU=Servers,DC=pshirwin,DC=local",UserSettingsDisabled,FALSE,TRUE,TRUE,3 | |
'@ | ConvertFrom-Csv –Delimiter ',' | |
#endregion | |
#Dot source Function Get-GPOsSOM.ps1 | |
. $PSScriptRoot\Get-GPOsSOM.ps1 | |
#Create hashtable for lookup | |
$lookupGPOInReport = Get-GPOsSOM | Group-Object –AsHashTable –Property 'DisplayName' | |
} | |
It 'GPOs Scope of Managment has been retrieved' { | |
$lookupGPOInReport | should not BeNullOrEmpty | |
} | |
It 'GPO validation set has been retrieved' { | |
$gpoValidationSet | Should not BeNullOrEmpty | |
} | |
foreach($set in $gpoValidationSet){ | |
Context "GPO: $($set.DisplayName)" { | |
it "GPO $($set.DisplayName) exists" { | |
$lookupGPOInReport.$($set.DisplayName) | Should Not BeNullOrEmpty | |
} | |
it "GPO is linked to $($set.DistinguishedName)"{ | |
$lookupGPOInReport.$($set.DisplayName).DistinguishedName | Should be $set.DistinguishedName | |
} | |
it "BlockInheritance: $($set.BlockInheritance)" { | |
$lookupGPOInReport.$($set.DisplayName).BlockInheritance | Should be $set.BlockInheritance | |
} | |
it "LinkEnabled: $($set.LinkEnabled)" { | |
$lookupGPOInReport.$($set.DisplayName).LinkEnabled | Should be $set.LinkEnabled | |
} | |
it "Group policy Enforced: $($set.Enforced)" { | |
$lookupGPOInReport.$($set.DisplayName).Enforced | Should be $set.Enforced | |
} | |
it "Group policy LinkOrder nr: $($set.LinkOrderNr)" { | |
$lookupGPOInReport.$($set.DisplayName).LinkOrderNr | Should be $set.LinkOrderNr | |
} | |
it "Group policy status: $($set.GPOStatus)" { | |
$lookupGPOInReport.$($set.DisplayName).GPOStatus | Should be $set.GPOStatus | |
} | |
} | |
} | |
} |
So here’s the result:
Now Imagine someone changed your GPO link order:
Run Pester test script again:
No more doubt! The link order has been tampered with! This is definitely a game changer for Operations!
My new motto : “If you can automate it, you should test it” 😛
Pester for everyone!
Hope it’s worth something to you
Ttyl,
Urv
Nice work on the Pester articles Irwin, always good to see more real-world examples on how to apply Pester.
LikeLike
Hi Jaap, I saw the demo scripts by Kevin Marquette and that really resonated! You can use Pester for Operations… Imagine the possibilities… 😉 Hey I’ll be seeing you next week @ExpertsLive.nl!
LikeLike