Using PowerShell to Back Up Group Policy Objects

Summary: Guest blogger, Ian Farr, talks about using Windows PowerShell to back up Group Policy Objects.

Microsoft Scripting Guy, Ed Wilson, is here. Today I am happy to welcome back a recent new guest blogger, Ian Farr. Here is what Ian had to say about himself:

I started out writing UNIX shell scripts to automate simple tasks. Then as a Windows IT pro, I discovered VBScript, and it ignited a passion for automation. Over the years, I've used batch files, KiXtart, JScript, HTAs, Perl, JavaScript, and Python. I love solving problems with scripts, and I've written code for several large enterprise environments. I now work as a premier field engineer at Microsoft, teaching Windows PowerShell and helping my customers with their own scripts.

The challenge

A customer recently asked about backing up Group Policies Objects (GPOs), “Can the Windows PowerShell Group Policy cmdlets mirror our production GPOs to our test environment?“

Almost. The cmdlets aren’t enough, however.  Information has to be collected by using several techniques. I decided to produce a demonstration Windows PowerShell script to share with the community. In fact, I wrote two code samples—one to back up and one to import. Keeping the script functions separate increases their reuse potential.

The backup                           

“To begin at the beginning…”

I work out my script flow before I write any code. Understanding the structure and content saves time and effort. I started by listing what GPO information to capture:

  • Group Policy settings
  • Delegation
  • Security filtering
  • Scope-of-management (SOM)
  • Block inheritance
  • Enforced
  • Link enabled
  • Link order
  • WMI filters
  • IP security policies

Let’s look at the relevant sections of the script to see how to get the information.

Group Policy settings, delegation, security filtering

The Backup-GPO cmdlet, from the Group Policy PowerShell module, captures GPO settings, delegation, and security filtering information. The script assigns objects that are returned by the cmdlet to $Backups for later use:

$Backups = Backup-GPO -All -Path $SubBackupFolder -Domain $DomainFQDN -Comment "Scripted backup created by $env:userdomain\$env:username on $(Get-Date -format d)"

The Backup-GPO parameters:

  • All does what you’d expect. It tells the cmdlet to back up all GPOs in the domain.
  • Path determines where the backup is saved. The $SubBackupFolder variable is made up of the backup folder name (in the format Year_Month_Day_HourMinuteSecond) and the backup target path (a parameter passed at script execution), for example:

Image of folder

  • Domain is also a script parameter (the target domain), and it must be a fully qualified domain name (FQDN).
  • Comment is a string that is associated with the backup. In this instance, it combines output from Windows environment variables and the Get-Date cmdlet. It shows who created the backup and when, for example:

“Scripted backup created by CONTOSO\FarrI on 22/10/2013”

Scope-of-management and block inheritance

So what’s scope-of-management (SOM)? SOM refers to a site, domain, or organizational unit where a GPO is linked. The Group Policy cmdlets won’t capture the SOM details needed, but the Group Policy Management (GPM) COM interfaces can. The Group Policy Management Console (GPMC) provides access to these interfaces.

A GPM COM object has the ability to automate many GPMC functions. The backup API that is exposed is also used by the Backup-GPO cmdlet, and it shares the same limitations. A different interface is needed for SOM details.

Here’s how to create a GPM COM object:

$GPM = New-Object -ComObject GPMgmt.GPM

GPM constants provide easy access to incredibly useful functionality. To obtain the GPM constants:

$Constants = $GPM.getConstants()

For more information about constants, see:

Now tell the GPM object to reference the target domain. Notice the constants:

$GpmDomain = $GPM.GetDomain($DomainFQDN,$Null,$Constants.UseAnyDc)

Start a parent loop and process each backed-up GPO by using the GPO GUID to instantiate an object as $GPO:

ForEach ($Backup in $Backups) {


        #Get the GPO GUID for our target GPO

        $GpoGuid = $Backup.GpoId


        #Instantiate an object for the relevant GPO using GPM

        $GPO = $GpmDomain.GetGPO("{$GpoGuid}")

Next, some SOM-specific search criteria for the current $GPO:


Put that criteria to use:

$SOMs = $GpmDomain.SearchSOMs($GpmSearchCriteria)

Open a child loop to process the SOMs for the current GPO and assign the distinguished name and inheritance status of the SOM to variables:

  ForEach ($SOM in $SOMs) {


                #Capture the SOM Distinguished Name

                $SomDN = $SOM.Path


                #Capture Block Inheritance state

                $SomInheritance = $SOM.GPOInheritanceBlocked

Enforced, link enabled, link order

As part of the child loop, the Get-GPInheritance cmdlet obtains enforced, link enabled, and link order details:

            $GpoLinks = (Get-GPInheritance -Target $SomDN).GpoLinks

Because the cmdlet and its parameter are in parenthesis, they are evaluated first by Windows PowerShell. A Microsoft.GroupPolicy.Som object is returned. The contents of the GpoLinks property of this object is then stored in $GpoLinks.

The next loop is the grandchild of the parent loop. We cycle through each of the potential values in $GpoLinks and check that the display name associated with the value matches that of the current GPO. This check is necessary because there could be other GPOs associated with the SOM.

            $GpoName = $GPO.DisplayName

                ForEach ($GpoLink in $GpoLinks) {

                    If ($GpoLink.DisplayName -eq $GpoName) {

                        #Capture the GP link status

                        $LinkEnabled = $GpoLink.Enabled


                        #Capture the GP precedence order

                        $LinkOrder = $GpoLink.Order


                        #Capture Enforced state

                        $LinkEnforced = $GpoLink.Enforced


                    }   #End of If ($GpoLink.DisplayName -eq $GpoName)

                }   #End of ForEach ($GpoLink in $GpoLinks)

Enforced, link enabled, and link order details are now assigned to variables. Before closing the child loop, add the newly populated variables for each SOM to a string, and add the string to an array:

[Array]$SomInfo += "$SomDN`:$SomInheritance`:$LinkEnabled`:$LinkOrder`:$LinkEnforced"

}   #End of ForEach ($SOM in $SOMs)…

The format of the strings in the $SOMInfo array will aid reporting and importing.


For example:

Image of command output

WMI: GPOs with filters

A WMI filter refines the GPO scope by using computer attributes. If the filter returns True, the policy is applied. Each GPO can have only one WMI filter. Capturing WMI filter information is a two part process. Get-GPO is used to obtain the path of the filter:

$WmiFilter = (Get-GPO -Guid $GpoGuid).WMiFilter.Path

For example:

Image of command output

The path is then split at the quotation marks to get the filter name:

$WMiFilter = ($WmiFilter -split "`"")[1]

For example:

Image of command output

One object to rule them all

One incredibly useful feature of Windows PowerShell is the ability to create custom objects. Here a new object is created for the current GPO by using the [PSCustomObject] type declaration and a hash table of properties. The $SOMInfo array is assigned to the SOMs property of the hash table.

        $GpoInfo = [PSCustomObject]@{

                BackupGuid = $BackupGuid

                Name = $GpoName

                GpoGuid = $GpoGuid

                SOMs = $SomInfo

                DomainDN = $DomainDN

   WmiFilter = $WmiFilter


        }   #End of $Properties…       

The new object is then added to a parent array. When the parent loop is finished, $TotalGPOs contains objects for all backed-up GPOs.

 [Array]$TotalGPOs += $GpoInfo

}   #End of ForEach ($Backup in $Backups)…

WMI: The filters

The custom objects in $TotalGPOs have details of any linked WMI filters. To pull the WMI filters out of Active Directory, use Get-ADObject:

     $WmiFilters = Get-ADObject -Filter 'objectClass -eq "msWMI-Som"'

                            -Properties msWMI-Author, msWMI-ID, msWMI-Name, msWMI-Parm1, msWMI-Parm2

The -Filter parameter targets objects that match the WMI filter object class ‘msWMI-Som’. The –Properties parameter asks for attributes that are not returned by default. The attributes returned have key WMI filter details that are used by the import script, for example:

 Image of command output

The properties are:

  • msWMI-Author: The account that created the filter.
  • msWMI-ID: The filter ID (corresponds to the Active Directory object name).
  • msWMI-Name: The “human-readable” filter name.
  • msWMI-Parm1: The filter description.
  • msWMI-Parm2: The WQL statement used by  the filter.
    For more information, see Querying with WQL.

IP security policies

Here’s a self-imposed limitation— IP security policies aren’t backed up. However, just like WMI filters and the Active Directory Group Policy container, IP security policies are stored in the system container of a domain partition. This time, the schema object class to filter on is ipsecPolicy. Over to you, my capable friends!

Image of menu

Export and report

Time to export and report. First, use the Export-CliXML cmdlet to export the custom objects to GpoDetails.xml ($CustomGpoXML):

            $TotalGPOs | Export-Clixml -Path $CustomGpoXML

Why? The exported (serialized) objects are easily imported (deserialized) from the XML file, so we instantly get our objects and properties back, albeit without the original methods. The import script in Part 2 of this series uses the XML file.

This process is repeated for any WMI filters that are retrieved from Active Directory ($WmiXML points to WMiFIlters.xml):

$WmiFilters | Export-Clixml -Path $WmiXML

Next, we need a “human-readable” CSV report. Each report line contains the GPO name, the GPO GUID, and a cell for each string in the SOMs property, for example:

Image of report

Here’s how the report is populated. Start a parent loop to process each object contained in the $TotalGPOs array, and start building a string to add to the CSV report:

ForEach ($CustomGPO in $TotalGPOs) {    

        $CSVLine = "`"$($CustomGPO.Name)`",`"{$($CustomGPO.GPOGuid)}`","

Expand the SOMs property and loop through any values returned. Each value found is appended to the CSV line:

        $CustomSOMs = $CustomGPO | Select-Object -ExpandProperty SOMs

        ForEach ($CustomSOM in $CustomSOMs) {

            #Append the SOM path to our CSV line

            $CSVLine += "`"$CustomSOM`","

       }   #End of ForEach ($CustomSOM in $CustomSOMs)…

When the SOMs loop is finished, write the fully constructed CSV line to the report:

                        Add-Content -Path $SOMReportCSV -Value $CSVLine

            }   #End of ForEach ($CustomGPO in $TotalGPOs)…

The parent loop is closed when the CSV report has an entry for every custom GPO object.

Migration tables

The script has a –MigTable switch. What’s it for?

Domain-specific data, such as UNC paths, users, or groups, might need translating for the domain import.  The GPMC allows you to create a Migration Table, where a source value can be matched to a destination value. Can you automate the creation of a migration table? Of course!

The Group Policy cmdlets can’t be used, so over to the GPMC COM interfaces. We can access the CreateMigrationTable method of the GPM COM object:

$MigrationTable = $GPM.CreateMigrationTable()

Domain-specific information from each GPO is added to the migration table as part of a loop. The ProcessSecurity constant evaluates security on the backed-up GPO:

       ForEach ($BackedUpGPO in $BackedUpGPOs) {


       }   #End of ForEach ($BackedUpGPO in $BackedUpGPOs)…

The file is then saved as “MigrationTable.migtable,” and it is defined in $MigrationFile:


Note  After the migration table is created, the Destination names have to be manually updated, for example:

Image of report

And that concludes Part 1. We have a backup of all the GPOs in the production domain. We’ve captured additional, important GPO information. We’ve produced a “human-readable” CSV report, and we exported our custom GPO objects and WMI filters to XML. We may have even created a migration table.

You can see the entire script in the Script Center Repository: Comprehensive Group Policy Backup Script.

Please join me tomorrow when I’ll discuss how to mirror this information to a test environment.


Thank you, Ian, for sharing your time and knowledge.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

Comments (9)

  1. The script now processes gPLink info on site objects – BIG thanks to Mark Renoden! I’ve a number of other suggestions from the community to implement… watch this space.

  2. Here’s a couple of companion scripts to ensure we have a matching OU structure in the test environment:

    Dump AD OU Structure Script

    Mirror AD OU Structure Script

  3. Jean-Philippe says:

    Thanks Ian – Great job here !

  4. Mark says:

    This is almost everything I want. It just doesn’t capture gpLinks to sites.

  5. Mauro Rita says:

    Fantastic work, Ian ! Kudos !

  6. Rob says:

    Brilliant script but how are you dealing with GPO preferences? The migration table doesn’t work.

  7. Carsten Krüger says:

    I get an error:
    The overall error was: The library, drive, or media pool is empty. (Exception from HRESULT: 0x800710d2)

    I can’t read a single gpo, backup-gpo -all fails completly at the time of reading this gpo.
    If I replace backup-gpo -all with loop single read, only this one is missing

    Change the script this way:
    #Backup all the GPOs found in the domain
    # $Backups = Backup-GPO -All -Path $SubBackupFolder -Domain $DomainFQDN -Comment "Scripted backup created by $env:userdomain$env:username on $(Get-Date -format d)"
    $GPO=Get-GPO -All
    $backups = @()

    foreach ($Entry in $GPO) {
    Backup-GPO -Guid $ -Path $SubBackupFolder -Domain $DomainFQDN -Comment "Scripted backup created by $env:userdomain$env:username on $(Get-Date -format d)"
    $backups = $backups + $backup

  8. Carsten Krüger says:

    forgot $backup=

    here is the correct line

    $backup=Backup-GPO -Guid $ -Path $SubBackupFolder -Domain $DomainFQDN -Comment "Scripted backup created by $env:userdomain$env:username on $(Get-Date -format d)"

  9. #Version: 2.4
    – 2.1 – 19/08/2014
    * the script now processes gPLink info on site objects
    * thanks to Mark Renoden [MSFT]

    – 2.2 – 08/07/2015
    * updates to allow backup from one trusted forest to another

    – 2.3 – 12/01/2016
    * added ability to backup GPOs modified within the last X days
    * added ability to create html report of settings per GPO
    * thanks to Marcus Carvalho [MSFT]

    – 2.4 – 15/01/2016
    * added ability to backup a single GPO
    * added parameter sets to prevent -GpoGuid and -ModifiedDate being used together

Skip to main content