This post details how to find removed Active Directory (AD) Group Policy Object (GPO) System Volume (SYSVOL) folders. If a SYSVOL folder exists but no GPO does, I'm calling those SYSVOL folders "orphaned." Notice that this issue is different from GPOs no longer linked to an organizational unit (OU).

AD GPOs can get out of control if not properly maintained. Especially in large enterprises with lots of people making daily changes to hundreds or thousands of GPOs, things can soon get out of hand. One problem that commonly crops up is "orphaned" GPOs.

A GPO consists of an entry in the AD database and a few policy files on the SYSVOL shares of domain controllers (DCs). Sometimes, a GPO removed from the database might not show up in the Group Policy Management Console (GPMC) but may still exist on the SYSVOL shares. One term for this lingering SYSVOL is an "orphan."

Knowing where to look and how to compare the AD database and the SYSVOL folders will allow us to figure out if we have any of these orphaned GPOs lingering around to clean up. What better way to do this than to use PowerShell, of course!

We first need to get a list of all GPOs in the domain that exist in the AD database. To do this, we can use the Get-GPO command. The Get-GPO command returns various information, but we're only looking for the GUIDs. We need these to compare them with the SYSVOL folders later.

We can parse out the GUIDs from the Get-GPO output by casting the Id property to a string and then only returning that property. One way to do this is to use calculated properties with Select-Object as you can see below. Below I'm enumerating all of the GPOs in the domain and extracting all of the GUIDs from them into the $gpoGuids variable.

$gpoGuids = Get-GPO -All | Select-Object @{ n='GUID'; e = {$_.Id.ToString()}} | Select-Object -ExpandProperty GUID
GPO GUIDs

GPO GUIDs

Once we have the GUIDs that show up in the AD database, we now need to figure out how to get the folder path that's supposed to be associated with it. Each GPO should have a folder on each DC that is a child of the following folder:

\\<DomainName>\SYSVOL\<DomainName>\Policies

Inside this folder are folders with GUIDs representing all of the GPOs in the domain.

Since we just need the GUIDs, we can enumerate all of these folders excluding the one folder called PolicyDefinitions that also exists in the Policies folder as shown below.

$polFolders = Get-ChildItem \\<DomainName>\SYSVOL\<DomainName>\Policies ‑Exclude 'PolicyDefinitions' | Select-Object -ExpandProperty name

This will return an output like the screenshot below.

SYSVOL GPO folders

SYSVOL GPO folders

However, we need to perform a 1:1 match between the GUIDs. The folder names have curly braces on either end. Let's remove these by creating a $sysvolGuids array and adding them to it.

$sysvolGuids = @()
foreach ($folder in $polFolders) {
    $sysvolGuids += $folder -replace '{|}', ''
}

We now have two lists of GUIDs: one for the GPOs in the AD database and one for the SYSVOL folders. At this point, we just need to compare the two and see if any SYSVOL folders exist that don't exist in the AD database. One way we can do this is by using Compare-Object. Compare-Object checks to see if there are any differences between the two lists. If there are, the code below will return those differences. If this happens, you can be sure you've got some orphaned GPOs in a DC SYSVOL share!

Compare-Object -ReferenceObject $sysvolGuids -DifferenceObject $gpoGuids | Select-Object -ExpandProperty InputObject

To make this easier, I've created a simple function called Get-OrphanedGPO that uses all the concepts we went over here. This function is a little more advanced since it also takes into account multi-domain forests, but the tasks are exactly the same.

function Get-OrphanedGPO {
    
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string]$ForestName
    )
    try {
        ## Find all domains in the forest
        $domains = Get-AdForest -Identity $ForestName | Select-Object -ExpandProperty Domains

        $gpoGuids = @()
        $sysvolGuids = @()
        foreach ($domain in $Domains) {
            $gpoGuids += Get-GPO -All -Domain $domain | Select-Object @{ n='GUID'; e = {$_.Id.ToString()}} | Select-Object -ExpandProperty GUID
            foreach ($guid in $gpoGuids) {
                $polPath = "\\$domain\SYSVOL\$domain\Policies"
                $polFolders = Get-ChildItem $polPath -Exclude 'PolicyDefinitions' | Select-Object -ExpandProperty name
                foreach ($folder in $polFolders) {
                    $sysvolGuids += $folder -replace '{|}'
                }
            }
        }

        Compare-Object -ReferenceObject $sysvolGuids -DifferenceObject $gpoGuids | Select-Object -ExpandProperty InputObject
    } catch {
        $PSCmdlet.ThrowTerminatingError($_)
    }
}

To find orphaned GPOs in your forest, you'll now just need to run Get-OrphanedGPO ‑ForestName <ForestName>, and it does the compare for you and returns all of your orphaned GPOs!

avatar
5 Comments
  1. Oscar 5 years ago

    I think that your function is incomplete. Though in your explanation prior to showing the function you explains what appears missing.

    I added before last accolade the last parts:

    $sysvolGuids = @();foreach ($folder in $polFolders) {$sysvolGuids += $folder -replace ‘{|}’, ”};Compare-Object -ReferenceObject $sysvolGuids -DifferenceObject $gpoGuids | Select-Object -ExpandProperty InputObject

    avatar
    • Author
      Adam Bertram 5 years ago

      Yep. You’re right. Sorry about that! I’ll fix that.

  2. Andrey Voronin (Rank 1) 5 years ago
  3. Matt McKinney 4 years ago

    I revised your script to check a single domain instead.  Also, there is no need to foreach over the guids in gpoGuids, at least for the purpose of getting the sysvol folders.

    $domain = “yourdomainhere.local”

    $gpoGuids = @()
    $sysvolGuids = @()
    $gpoGuids = Get-GPO -All -Domain $domain | Select-Object @{ n=’GUID’; e = {$_.Id.ToString()}} | Select-Object -ExpandProperty GUID
    $polPath = “\\$domain\SYSVOL\$domain\Policies”
    $polFolders = Get-ChildItem $polPath -Exclude ‘PolicyDefinitions’ | Select-Object -ExpandProperty name
    foreach ($folder in $polFolders)
    {
    $sysvolGuids += $folder -replace ‘{|}’, “”
    }
    Compare-Object -ReferenceObject $sysvolGuids -DifferenceObject $gpoGuids | Select-Object -ExpandProperty InputObject

  4. Frank Tanner 4 years ago

    There appears to be an issue with your script. When I run it, it appears to return all of the GUIDs in the forest rather than returning the orphaned one(s). I, seriously, doubt that we have hundreds of orphaned GPOs.

Leave a reply

Your email address will not be published. Required fields are marked *

*

© 4sysops 2006 - 2023

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending

Log in with your credentials

or    

Forgot your details?

Create Account