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

ScriptingGuy1

 

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. l am anxious to complete our discussion of the Compare-GPO.ps1 script. As you may recall, yesterday we looked at the portion of the script that determined if the GroupPolicy module was available and loaded, and we also looked at the function that downloaded the two GPOs to be compared. Because the Compare-GPO.ps1 script is more than 125 lines long, I have posted it to our Script Repository rather than pasting it into this post.

Note: This is part two of a two-part article. Part one was posted yesterday.

The Compare-XMLGPO function is used to compare two XML reports of GPOs. It accepts an array of strings that point to the two XML files that will be used for comparison. It does not matter if more than two GPO reports are supplied because only the first two will be compared. The two Boolean values, $user and $computer, control which portion of the GPO is compared. If both values are present, both the computer and the user portion of the GPO will be compared. The [xml] type accelerator is used to ensure the contents of the two GPO reports are interpreted as XML. Reading the XML GPO reports is really easy—the Get-Content cmdlet is used. The $xml1 and $xml2 variables contain a system.xml.xmldocument .NET Framework object. This section of the script is shown here:

 

Function Compare-XMLGPO

{

Param([string[]]$gpoReports, [bool]$user, [bool]$computer)

[xml]$xml1 = Get-Content -Path $gpoReports[0]

[xml]$xml2 = Get-Content -Path $gpoReports[1]

 

To effectively parse the GPO XML report, you must know the structure of the XML document. I like to use XML Notepad. As shown in the next image, the path to our GPO policy settings is GPO/User/extensiondata/extension. Each policy setting is a node under the extension folder. This particular GPO setting is in an XML namespace (XMLNS) called q1. Therefore, each policy setting is in a node called q1:Policy. At times you will see an XML namespace called q2 or even something else. But each policy node will have the word policy in it. The breakdown between user and computer in the structure of the XML document corresponds with the Group Policy computer settings and user settings.

Image of path to GPO policy settings

To compare the two GPOs, the childnodes property from the System.Xml.XmlElement .NET Framework class is used to obtain a collection of all the policy nodes under the extension element. There are many attributes associated with the policy element, but to make the comparison easier, only two attributes—the name and the state of the GPO—are selected. This selection is made for both the computer GPO and the user GPO from both XML documents. This section of the script is shown here:

 

$regpolicyComputerNodes1 = $xml1.gpo.Computer.extensiondata.extension.ChildNodes | 

Select-Object name, state

$regpolicyComputerNodes2 = $xml2.gpo.Computer.extensiondata.extension.ChildNodes | 

Select-Object name, state

$regpolicyUserNodes1 = $xml1.gpo.User.extensiondata.extension.ChildNodes | 

Select-Object name, state

$regpolicyUserNodes2 = $xml2.gpo.User.extensiondata.extension.ChildNodes | 

Select-Object name, state

 

It is quite possible; in fact, it is a probability that one or more of the GPOs will not contain both the computer section and the user section of the GPO. An error will occur if you try to compare an empty GPO. Therefore, the if statement is used to determine if there is anything in the computer section or the user section of the GPO. If the variable is not null, a Try/Catch block is used to perform the comparison using the Compare-Object cmdlet. When using the compare-object cmdlet, it is important to specify the property that will form the basis of the comparison. Otherwise, Windows PowerShell will pick a property that may or may not work well for your particular application.

I am interested in GPOs that contain similar settings, and therefore I told the Compare-Object cmdlet to include equal settings that exist in the reference object and the difference object. If you are only looking for duplicate settings that exist in GPOs, you may wish to use the –excludedifferent switch so that your report will only contain duplicate settings. The order in which the GPOs are listed could make a difference depending on what you are looking for. It definitely will impact how you read the report that is generated. This section of the script is shown here:

 

if($computer)

{

Try {

“Comparing Computer GPO’s $($gpoReports[0]) to $($gpoReports[1])`r`n”

Compare-Object -ReferenceObject $regpolicyComputerNodes1 `

-DifferenceObject $regpolicyComputerNodes2 -IncludeEqual `

-property name}

 

On the other hand, if one of the portions of the GPO is empty, the settings of the GPO that is not empty are listed on the Windows PowerShell console. This is shown here:

 

Catch [system.exception]

{

if($regPolicyComputerNodes1) 

{

“Computer GPO $($gpoReports[0]) settings `r`f”

$regPolicyComputerNodes1

}

 

If there are no settings to display from a particular GPO, a message to that effect is displayed on the Windows PowerShell console. This section of the script is shown here:

 

else { “Computer GPO $($gpoReports[0]) not set” }

The same code is repeated for the other computer node:

 

if($regPolicyComputerNodes2) 

{

“Computer GPO $($gpoReports[1]) settings `r`f”

$regPolicyComputerNodes2

}

else { “Computer GPO $($gpoReports[1]) not set”}

} #end catch

} #end if computer

 

If a user section of the GPO exists, a Try/Catch is used to attempt to compare the two user portions of the GPO. If an error occurs, the Catch block is used to determine which portion of the GPO is empty, and it will display a message indicating that the GPO is empty. If a GPO has settings, the settings will be displayed. This code, shown here, is nearly the same as that for the computer portion of the script:

 

if($user)

{

Try {

“Comparing User GPO’s $($gpoReports[0]) to $($gpoReports[1])`r`n”

Compare-Object -ReferenceObject $regpolicyUserNodes1 `

-DifferenceObject $regpolicyUserNodes2 -SyncWindow 5 -IncludeEqual `

-property name}

Catch [system.exception]

{

if($regPolicyUserNodes1) 

{

“User GPO $($gpoReports[0]) settings `r`f”

$regPolicyUserNodes1

}

else { “User GPO $($gpoReports[0]) not set” }

if($regPolicyUserNodes2) 

{

“User GPO $($gpoReports[1]) settings `r`f”

$regPolicyUserNodes2

}

else { “User GPO $($gpoReports[1]) not set”}

}

} #end catch

} #end function compare-XMLGPO

 

When testing the script to ensure it reports accurately the settings in my two GPO files, I used the compare feature in XML Notepad. The compare feature is under View/Compare XML Files. As shown in the following image, the color coding makes it easy to spot modifications, additions, and deletions.

Image of color coding

The entry point to the script first determines if the –user or–computer switch has been used. If neither is present, the script displays a message and exits. You could change this behavior and set one or the other as the default. The code to do this is shown here:

 

if(-not ($user -or $computer))

{ “using default user GPO” ; $user=$true}

 

The code as it exists in the Compare-GPO.ps1 script is shown here:

 

if(-not ($user -or $computer))

{ “Please specify either -computer or -user when running script” ; exit}

 

The Get-MyModule function is used to load the GroupPolicy module. If the module is not found on the system, the script exits:

 

If(-not (Get-MyModule -name “GroupPolicy”)) { exit }

Next, the Get-GpoAsXML function is used to retrieve the two GPOs specified in the $gponame variable. The Get-GpoAsXML function returns the path to the two GPOs. This returned array of paths is stored in the $gpoReports variable, as shown here:

 

$gpoReports = Get-GpoAsXML -gponame $gponame.split(“,”) -server $server `

-domain $domain -folder $folder

The last thing the script does is call the Compare-XMLGPO function, as shown here: 

Compare-XMLGPO -gpoReports $gpoReports -user $user -computer $computer

 

When the script runs, output is shown similar to that in the following image. When testing a script that uses switched parameters in the Windows PowerShell ISE, I will set the switched value to $true. This is also seen in the following image.

Image of script output

SV, that is all there is to using Windows PowerShell to compare two XML Group Policy objects. This also concludes Group Policy Week. Join us tomorrow for Weekend Scripter when we will talk about using Windows PowerShell to clean up a CSV file.

We would love for you to 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

0 comments

Discussion is closed.

Feedback usabilla icon