Finding organizational units without a specific Group Policy linked
Custom properties, Where-object and Return
Summary
This is a short post explaining a solution to a problem I needed for work. We have computers for each department in a OU ending with computers. These OU’s have a GPO linked to them with a script to install some software.
I wanted to know which OU’s didn’t have that specific GPO linked to it. It took me a while to figure this out and get my head around the different objects.
Get the OU’s
The first step was to get the OU’s ending with ‘computers’. This was simple enough:
Get-ADOrganizationalUnit -Filter 'Name -like "*computers*"'
Get the GPO’s linked to an OU
Next step was to get all the GPO’s linked to the OU, this was also simple but I wanted to pipe the results from Get-ADOrganizationalUnit
to Get-GPInheritance
.
If you check the help for Get-GPInheritance
, the Target parameter accepts pipeline input, it accepts the domain or OU.
The Get-ADOrganizationalUnit
command outputs the path of the OU but the property name is DistinguishedName.
Therefore I had to output a custom value from Get-ADOrganizationalUnit
that Get-GPInheritance
would accept. I can use select-object
for that:
Get-ADOrganizationalUnit -Filter 'Name -like "*computers*"' |
Select-Object -Property @{
label = 'target'; expression = { $_.distinguishedname } }
The above would just give me the OU paths, the next code block would give me another object with the GPOLinks property.
Get-ADOrganizationalUnit -Filter 'Name -like "*computers*"' |
Select-Object -Property @{
label = 'target'; expression = { $_.distinguishedname } } | Get-GPInheritance
This would return the below, the GpoLinks property is a collection which I would need to loop through but I do that in the where-object
script block
Name : uat_computers
ContainerType : OU
Path : ou=uat_computers,ou=uat,dc=corp,dc=ordo,dc=gi
GpoInheritanceBlocked : No
GpoLinks : {Adobereader, chrome-settings,...}
InheritedGpoLinks : {Adobereader, chrome-settings,...}
Getting the OUs without the GPO link
The tricky part was figuring out how to return the OU’s without the GPO. I would use the where-object
command.
I normlly just use this command to check if a property matches, equals, is like, etc some value. But this time I had to use foreach
and if
statements within where-object
.
The first step is to loop through the gpolinks. I used a foreach
loop:
foreach ($link in $psitem.gpolinks) {
if ($link.displayname -eq 'UnicornGPO') {
return $false
} #if
} #foreach
I use return false
to exit the where-object
script block as I’m not interested in the OU which has the UnicornGPO GPO linked and I don’t need to check any further links once I find UnicornGPO. I wanted the OU’s that don’t, I couldn’t return true
if a link wasn’t the UnicornGPO because I need to go through all the links first to see if UnicornGPO existed in subsequent links, this is why I found this tricky at first.
So outside the foreach
loop I return true
because if the if
statement within the foreach
loop hadn’t returned false
(meaning that it found the UnicornGPO)
and exiting the script block it meant that this GPO didn’t have the UnicornGPO.
[CmdletBinding()]
param ()
Get-ADOrganizationalUnit -Filter 'Name -like "*computers*"' |
Select-Object -Property @{
label = 'target'; expression = { $_.distinguishedname } } |
Get-GPInheritance | Where-Object {
Write-Verbose "Checking $($psitem.path)"
Write-debug "Where-object"
foreach ($link in $psitem.gpolinks) {
Write-Verbose "$($link.displayname)"
if ($link.displayname -eq 'UnicornGPO') {
Write-Verbose "UnicornGPO found!"
return $false
} #if
} #foreach
Write-Verbose "UnicornGPO not found in $($psitem.path)"
write-verbose "Returning `$true"
return $true
}
I used verbose output and debugging to check that I was getting the right results. I had a previous version of this script which did work but was a bit longer (See below).
The main difference is that even if it found UnicornGPO it would continue looking through the rest of the links in the object, which was unneccessary. I used a $hasUnicorn
variable which was set to true
if the UnicornGPO was found. Then in the if
statement as the end I would check if $hasUnicorn
had a value of true
and then I would return $false. If $hasUnicorn
was false then I return true
because these were the objects I wanted to select in the where-object
command.
[CmdletBinding()]
param ()
Get-ADOrganizationalUnit -Filter 'Name -like "*computers*"' |
Select-Object -Property @{label = 'target'; expression = {
$_.distinguishedname } } |
Get-GPInheritance | Where-Object {
Write-Verbose "Checking $($psitem.path)"
Write-debug "Where-object"
$hasUnicorn= $false
foreach ($link in $psitem.gpolinks) {
Write-Verbose "$($link.displayname)"
if ($link.displayname -eq 'UnicornGPO') {
$hasUnicorn= $true
Write-Verbose "UnicornGPO found!"
} #if
} #foreach
if ($hasUnicorn-eq $true) {
Write-Verbose "Returning `$false"
#If the gpo exists then we don't want it listed
return $false
}
else {
Write-Verbose "Returning `$true"
return $true
} #if/else
}
I hope that I explained myself properly!
Conclusion
In the end it took me more time to figue this out than checking Active Directory manually but I was more interested in the learning process and who knows maybe this might come in handy when I have to search through hundreds or thousands of items.
Maybe there is a better way of handling this but I resisted asking in forums until I had given it my best shot. I could have taken it further and linked the missing gpo to selected OU’s using New-GPLink
but I wanted to verify that these OU’s actually needed the GPO as there ween’t that many to look through and some of them didn’t even have computer objects inside them.