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:
- 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:
$GpmSearchCriteria.Add($Constants.SearchPropertySOMLinks,$Constants.SearchOpContains,$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.
"$SomDN:$SomInheritance:$LinkEnabled:$LinkOrder:$LinkEnforced"
For example:
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:
The path is then split at the quotation marks to get the filter name:
$WMiFilter = ($WmiFilter -split "`"")[1]
For example:
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:
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!
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:
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) {
$MigrationTable.Add($Constants.ProcessSecurity,$BackedUpGPO)
} #End of ForEach ($BackedUpGPO in $BackedUpGPOs)...
The file is then saved as “MigrationTable.migtable,” and it is defined in $MigrationFile:
$MigrationTable.Save($MigrationFile)
Note After the migration table is created, the Destination names have to be manually updated, for example:
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.
~Ian
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 scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy