Hey, Scripting Guy! How Can I Connect Two Group Policy Objects in Active Directory and Compare Them Offline? (Part 1)



Hey, Scripting Guy! Question

Hey, Scripting Guy! I need to be able to connect to two Group Policy objects (GPOs) in Active Directory and make an offline copy of the GPOs using Windows PowerShell 2.0 so that I can compare the two objects. I know I can do this using the Group Policy Management Console, but I have several hundred GPOs in my domain and I do not have the time to go through the GUI and click-click-click. I would prefer to be able to script this operation using Windows PowerShell, if possible. Ideas?

— SV


Hey, Scripting Guy! Answer

Hello SV,

Microsoft Scripting Guy Ed Wilson here. One of the cool things about Windows PowerShell is that when one thing does not work, often a different approach can be taken that will work. Such was my experience this week while trying to answer your question. There are many graphical tools available that will allow you to compare two XML files. One such tool is XML Notepad, which is free from Microsoft. But there are relatively few programmatic tools one can use. When searching MSDN, I came across a really old package called the XML Diff and Patch Tool that looks like it might be fun. The problem is that, when attempting to install it on Windows 7, it complains the .NET Framework 1.1 is not installed; I did not feel like downloading, installing, and patching a six-year-old version of the .NET Framework.

Note: This is part one of a two-part article. Part two will be posted tomorrow.

I then began to look at Language-Integrated Query (LINQ) and LINQ to XML. LINQ builds data query language capabilities directly into the programing language (such as C# or VB.Net) and therefore it makes data query as easy as querying a WMI class in Windows PowerShell 2.0. While there are a number of LINQ .NET Framework classes that can be used in Windows PowerShell 2.0 in the System.XML.LINQ .NET Framework namespace, the ability to use or to create a LINQ query does not appear. I guess I could have used the Add-Type trick and executed native C# code, but SV, I did not want to do that for your script if I could avoid it. In the end, I decided to use Windows PowerShell directly to perform this operation.

SV, you can download all of the GPOs in your domain in a single command directly from the Windows PowerShell console. You will need to first import the Group Policy module. Then you can run your command as shown here:


PS C:\> Import-Module group*

PS C:\> Get-GPOReport -All -Path c:\fso\allgporeport.xml -ReportType xml

PS C:\>

After the Group Policy report has been saved, it can be opened in XML Notepad and viewed, as shown in the following image.

Image of viewing Group Policy report in XML Notepad

SV, because you stated you wished to create offline representations of all your GPOs so that you would be able to more easily compare them to each other, I decided to create a small script to perform this operation. (The command could also be typed on a single line in the Windows PowerShell console. In fact, this is the way I developed the “script.”) The Get-XMLForEachGPO.ps1 script is shown just below. It begins by getting a list of all the GPOs in the domain, using the Get-GPO cmdlet. The returned objects are all instances of the Microsoft.Grouppolicy.Gpo .NET Framework class. The Microsoft.GroupPolicy namespace contains a number of really cool classes that can be used from within Windows PowerShell scripts. The ForEach-Object cmdlet cmdlet is used in the pipeline to allow you to work with each GPO. The Join-Path cmdlet is used to create the path to store the outputted XML report. Inside the pipeline, the Get-GPOReport cmdlet obtains the XML report for each of the GPO objects. The Get-XMLForEachGPO.ps1 script is shown here.


$path = “c:\fso”

get-gpo -All | 

ForEach-Object { 

Get-GPOReport -Name $_.displayname -ReportType xml `

-Path (join-path -Path $path -ChildPath “$($_.displayname).xml”) }


SV, I wrote a rather long script called Compare-GPO.ps1. I will talk about the first half of the script today and the second half of the script tomorrow. Because the complete script is so long (around 125 lines), I decided to upload it to the Scripting Guys Script Repository.

The Compare-GPO.ps1 script begins by creating several command-line parameters. The first is the domain name and the second is the server that you wish to contact in order to retrieve the GPOs. The $gponame variable holds a single string that contains the name of the two GPOs you wish to compare. As shown here, a comma separates the GPO names:

[string]$gponame = “aTestOuGPO,AnotherTestOuGPO”

The GPO name you use is the one shown in the Group Policy Management Console in the following image.

Image of GPO name in GPMC

The folder parameter holds the destination for the GPOs. Two switched parameters, $computer and $user, are used to compare either the computer portion, the user portion, or both portions of the GPO. The command-line parameters are shown here:



[string]$domain =“nwtraders.com”,

[string]$server = “dc1.nwtraders.com”,

[string]$gponame = “aTestOuGPO,AnotherTestOuGPO”,

[string]$folder = “c:\fso”,





The first function that is added to the script is the Get-MyModule function that was discussed in a Weekend Scripter article called Checking for Module Dependencies in Windows PowerShell. The use of the Get-MyModule function to check for the GroupPolicy module is discussed in the Hey, Scripting Guy! Blog post, Can You Get Me Going on Windows PowerShell Cmdlets for Group Policy? The Get-MyModule function will therefore not be discussed here:


Function Get-MyModule



if(-not(Get-Module -name $name)) 


if(Get-Module -ListAvailable | 

Where-Object { $_.name -eq $name })


Import-Module -Name $name 


} #end if module available then import

else { $false } #module not available

} # end if not module

else { $true } #module already loaded

} #end function get-MyModule

You can modify the Get-XMLForEachGPO.ps1 script seen earlier to retrieve an XML report for the two specified GPOs, or you can use the Get-GPOAsXML function. The reason for the Get-GPOAsXML function is that it offers more flexibility than the Get-XMLForEachGPO.ps1 script. The Get-GPOAsXML function begins by defining the $gponame variable as an array of strings. When the script is called, the $gponame variable at the script level is a single string and is therefore not an array. However, when the Get-GPOAsXML function is called, the single string is split and therefore it becomes an array. The domain, server, and folder variables are the same as the ones from the beginning of the script. The Param section of the function is shown here:


Function Get-GPOAsXML









The $gpoReports variable is initialized and set to $null, and the ForEach statement is used to walk through the GPOs in the $gpoName variable, as shown here:


$gpoReports = $null

ForEach($gpo in $gpoName)



Next, the Join-Path cmdlet is used to create a path to store the XML GPO report. The $gpo variable holds the name of the GPO that is currently being processed, and because an expanding string is used, the value in the $gpo variable will substitute for the variable itself to create a complete path. This is shown here:


$path = Join-Path -Path $folder -ChildPath “$gpo.xml”

Next the Get-GPO cmdlet is used to retrieve an instance of the Microsoft.GroupPolicy.Gpo class. The GenerateReportToFile method of the GPO class is used to create the XML report:


(Get-GPO -Name $gpo -Domain $domain -Server $server).`



An array of paths to the GPO reports is created and the array is passed back to the calling code. This is shown here:


[array]$gpoReports + $path


Return $gpoReports

} #end get-gpoasxml



SV, that is all there is to using Windows PowerShell to retrieve an XML representation of a Group Policy object. Group Policy Week will continue tomorrow when we will continue with the Compare-GPO.ps1 script and compare the two XML files.

We would love for you to follow us on Twitter or Facebook. If you have any questions, send email to us at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.


Ed Wilson and Craig Liebendorfer, Scripting Guys


Discussion is closed.

Feedback usabilla icon