Finally! Copy and merge GPOs! PowerShell saves the day!


This script has been updated here.


The Problem
I wish I had this script five years ago.  At the time I was searching for a way to combine or merge GPOs, but there simply wasn’t a way to do it.  And today there still isn’t a way to do it… until POWERSHELL!  Almost every environment I see (including my own former environment) has a collection of GPOs that have evolved over time and really need to be consolidated and cleaned up.
In Windows Server 2008 R2 we released a new PowerShell module for Group Policy administration.  I haven’t seen much written about it, and I don’t think many people realize it is there.
I’ve been sitting on this script since last September, and ultimately I’d like to convert it to an advanced function.  For now though I wanted to get this code published for the benefit of the community.


The Solution
You can use this PowerShell script to copy and merge GPOs.  Yee haw!  Essentially the script does a Get-GPRegistryValue from the source policy and a Set-GPRegistryValue on the destination policy.  It recursively enumerates all settings in the source policy and copies them to the destination policy.  But nothing is ever that simple.  It turns out that there is a special trick to getting and setting a policy value that is in disabled status.  After that hurdle it was smooth sailing.  See the code for the details on this part.


Testing It For Yourself
You’ll need to run the script on Windows Server 2008 R2 or Windows 7 with the RSAT for Active Directory and Group Policy Management features installed.  Here are the steps to test it in your environment:

1.       In GPMC create a new GPO called "Starter Computer" based on one of the GPO computer templates.

2.       In GPMC create a new GPO called "Starter User" based on one of the GPO user templates.

3.       Run the script without switches for syntax:  .\copy-gpregistryvalue.ps1

4.       Run the script:  .\copy-gpregistryvalue.ps1 -src "Starter Computer" -dest "Merged GPO" -newDest

5.       Run the script:  .\copy-gpregistryvalue.ps1 -src "Starter User" -dest "Merged GPO"

6.       View the new "Merged GPO" settings in GPMC.

7.       Compare these settings with the ones from the two source GPOs.  They should match everything except the comments (which are unsupported by the current cmdlets).

This demo uses starter policies that separately contain user and computer settings, but you can use it just the same with existing source and destination policies containing both user and computer settings.


Note that the Get/Set-GPRegistryValue cmdlets do not support the policy comments, therefore these will not be present in the destination GPO.
Also note that due to limitations of the GPO cmdlets we only copy registry-based settings in the policy.  Other settings such as software installations, audit policies, file system permissions, etc. cannot be copied at this time.
Preference are not copied in the current version of the script.  You could easily update the script to call similar cmdlets for Get/Set-GPPrefRegistryValue.  I’ll leave that up to you.


Now go clean up all of those policies that need to be consolidated in your environment!  (But first back them up with Backup-GPO.)


# Copy GPO Registry Settings            
# Ashley McGlone, Microsoft PFE            
# January 2011            
# Parameters:            
#   dom       FQDN of the domain where the GPOs reside            
#   src       string name of the GPO to copy settings from            
#   dest      string name of the GPO to copy settings to            
#   newDest   switch to create dest GPO if it does not exist            
#   copymode  part of GPO to copy: all, user, computer            
Param (            
# We must continue on errors due to the way we enumerate GPO registry            
# paths and values in the function CopyValues.            
$ErrorActionPreference = "SilentlyContinue"            
Import-Module ActiveDirectory            
Import-Module GroupPolicy            
# Help            
if ($dom -eq $null -and `
    $src -eq $null -and `
    $dest -eq $null -and `
    $copymode -eq $null) {            
    "Copy-GPORegistryValue by Ashley McGlone, Microsoft PFE"            
    "For more info:"            
    "This script copies registry-based GPO settings from one GPO into another."            
    "Use this script to copy and/or merge policy settings."            
    "NOTE: This version does not copy GPO preferences."            
    ".\Copy-GPRegistryValue.ps1 [-dom DomainFQDN] -src `"Source GPO`""            
    "   -dest `"Destination GPO`" [-newDest]"            
    "   [-copymode all/user/computer]"            
    "The -dom switch will default to the current domain if blank."            
    "The -copymode will default to all if blank."            
    "The -newDest switch will create a new destination GPO of the specified"            
    "name. If the GPO already exists, then the copy will proceed."            
# Validate parameters            
if ($dom -eq $null) {            
    $dom = (Get-ADDomain).DNSRoot            
} else {            
    $dom = (Get-ADDomain -Identity $dom).DNSRoot            
    If ($error.Count -ne 0) {            
        "Domain name does not exist.  Please specify a valid domain FQDN."            
if ($src -eq $null) {            
    "Source GPO name cannot be blank."            
} else {            
    $src = Get-GPO -Name $src            
    If ($error.Count -ne 0) {            
        "Source GPO does not exist.  Be sure to use quotes around the name."            
if ($dest -eq $null) {            
    "Destination GPO name cannot be blank."            
} else {            
    if ($newDest -eq $true) {            
        $desttemp = $dest            
        $dest = New-GPO -Name $desttemp            
        If ($error.Count -ne 0) {            
            "The new destination GPO already exists."            
            "Do you want to merge into this GPO (y/n)?"            
            $choice = Read-Host            
            if ($choice -eq "y") {            
                $dest = Get-GPO -Name $desttemp            
            } else {            
    } else {            
        $dest = Get-GPO -Name $dest            
        If ($error.Count -ne 0) {            
            "Destination GPO does not exist.  Be sure to use quotes around the name."            
if ($copymode -eq $null) {            
    $copymode = "all"            
} else {            
    if ($copymode -ne "all" -and `
        $copymode -ne "user" -and `
        $copymode -ne "computer") {            
        "copymode must be one of the following values:"            
        "all, user, computer"            
# Echo parameters for this run            
"Domain: $dom"            
"Source GPO: $($src.DisplayName)"            
"Destination GPO: $($dest.DisplayName)"            
"New Destination: $newDest"            
"CopyMode: $copymode"            
# Copy GPO registry values recursively beginning at a specified root.            
# Essentially this routine does a get from the source and a set on            
# the destination.  Of course nothing is ever that simple, so we have            
# to account for the policystate "delete" which disables a setting;            
# this is like a "negative set".            
# We recurse down each registry path until we find a value to            
# get/set.            
# If we try to get a value from a path (non-leaf level), then we get            
# an error and continue to dig down the path.  If we get a value and            
# no error, then we do the set.            
# User values have a single root: HKCU\Software.            
# Computer values have two roots: HKLM\System & HKLM\Software.            
# You can find these roots yourself by analyzing ADM and ADMX files.            
# It is normal to see an error in the output, because all of these            
# roots are not used in all policies.            
Function CopyValues ($Key) {            
    $path = Get-GPRegistryValue -GUID $src.ID -Key $Key            
    If ($error.Count -eq 0) {            
        ForEach ($keypath in $path) {            
            $keypath | ForEach-Object {Write-Host $_}            
            If ($keypath.HasValue) {            
                If ($keypath.PolicyState -eq "Delete") {   # PolicyState = "Delete"            
                    Set-GPRegistryValue -Disable -Domain $dom -GUID $dest.ID `
                      -Key $keypath.FullKeyPath -ValueName $keypath.Valuename            
                } Else {   # PolicyState = "Set"            
                    $keypath | Set-GPRegistryValue -Domain $dom -GUID $dest.ID            
            } Else {            
                CopyValues $keypath.FullKeyPath            
    } Else {            
# Call the main copy routine for the specified scope of $copymode            
Function Copy-GPRegistryValue {            
    # Copy user settings            
    If (($copymode -eq "user") -or ($copymode -eq "all")) {            
        CopyValues "HKCU\Software"            
    # Copy computer settings            
    If (($copymode -eq "computer") -or ($copymode -eq "all")) {            
        CopyValues "HKLM\System"            
        CopyValues "HKLM\Software"            
# Start the copy            
#   ><>




Comments (34)

  1. Anonymous says:

    200 proof powershell goodness! Too many GPOs that need to effect all computers but don't have the time to merge them into one? Check out the below link!…/finally-copy-and-merge-gpos-powershell

  2. The registry settings in the source GPO will be appended to the target GPO, and anywhere there is a conflict the source GPO settings will overwrite the target.  This does not work for user rights assignment, because that falls outside of the registry settings in the policy.

  3. Anonymous says:

    Do I need my DC's to be on Windows Server 2008 R2 for these commands to work? as e have Windows Server 2003 DC's.

    I am getting an error when running the following:

    .copy-gpregistryvalue.ps1 -src "Testing" -dest "Starter Computer"

    I get an error which states:

    Source GPO does not exist.  Be sure to use quotes around the name.

    Which as you can see I have and the GPO does exist on all DC's

    Any thoughts?

    It will be brilliant for our consolodation project we are doing and I will find it really useful if I can use it 🙂

  4. Hi Bobby,

    Congratulations on exploring the depths of PowerShell.  The recursion depth limit is hard-coded in PowerShell v1 and v2.  Although that may change in v3, for now there is no way to change the allowable recursion depth.  I am not aware of any work arounds.

    Exactly how large is the GPO you are trying to merge?


  5. Hi Danny,

    Please post the exact command line you are trying to execute from the PowerShell console.  Please paste it here exactly as you typed it.  Thanks.


  6. Anonymous says:

    Great job….awesome scripting !!!!!

    You saved my day….BIG THX !!!

  7. Anonymous says:

    Well its working great for me! 🙂 Saved me countless hours and is speeding up our consolodation project!

    Great Script!

    Many Thanks


  8. On further investigation I realized that the script uses the AD cmdlets to validate the $dom parameter.  I was not trapping for a case without the AD web service.  We can eliminate most of this so that it will run in a pure 2003 environment.

    Remove the line that says “Import-Module ActiveDirectory”.  Then find these lines:

    if ($dom -eq $null) {            

       $dom = (Get-ADDomain).DNSRoot            

    } else {            

       $dom = (Get-ADDomain -Identity $dom).DNSRoot            

       If ($error.Count -ne 0) {            

           "Domain name does not exist.  Please specify a validdomainFQDN."            





    And replace with:

    if ($dom -eq $null) {            

       $dom = $env:UserDNSDomain


    That should remove any dependencies on the ADWS and get rid of the error you were seeing.  Just make sure that you correctly specify the FQDN in the $dom parameter on the command line.


  9. Anonymous says:

    @Anders Libach Johansen

    Thanks for that do you guys run Windows 2003 DC's as well? Could this be the issue?

  10. Hi everyone,

    I've not replied to comments on this post in a while.  Point taken.  My apologies.  Thanks for the feedback.

    I went back to the lab today and tried some assorted scenarios.  I install the RSAT SP1 on a Windows 7 client, and then joined it to three different domains to test the Group Policy module using "Get-GPO -All".

    1.  In the 2008 R2 domain of course it worked.

    2.  In the 2003 R2 with AD Web Service it worked.

    3.  In the 2003 R2 without AD Web Service it also worked.

    Based on my testing the Group Policy module should work against a pure 2003 domain.

    That means there is something hinky with either my script or your environment.  The script uses simple error handling which may not cover every scenario and could give misleading messages.

    Please change the line:

      $ErrorActionPreference = "SilentlyContinue"


      $ErrorActionPreference = "Stop"

    Then reproduce the error again, and type this at the console:

      $error[0] | fl * -force

    Send me the output via the Email Blog Author link (top right).  This should point us in the right direction.




  11. Anonymous says:


    Sounds like the same error that both myself and Mark Flynn is having, but it seems that the blogger doesn't visit his post anymore since we haven't gotten any replies yet.

  12. Marc says:

    Thx… this is great

  13. Adeel says:

    Great but only limited to registry based settings.

  14. Bobby says:

    tired to merge a large GPO.  Got a call depth error as it reached 1000.  any ideas for workaround?

  15. Ashley says:

    The USGCB Windows 7 GPO are the ones that I’m trying to merge into one GPO using this script.  These are in xml format.  Is there a way to merge these using any other process?

    I saw this post where the function could be put in but I’m not a coder and don’t know where this would be inserted in the script.



  16. danny says:

    Any ides why this error..I am new to sorry guys

    You must provide a value expression on the right-hand side of the '-' operator.

    At line:1 char:32

    + "c:copy-gpregistryvalue.ps1" – <<<< src "123" -dest "Merged GPO" -newDest

       + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException

       + FullyQualifiedErrorId : ExpectedValueExpression

  17. Altria says:

    So when you specify merging the group policy do you mean he target GPO settings are not overwritten but rather the source GPO settings are appended? Will this work for User Rights Assignments?

  18. Preston Gallwas says:

    I dream a day when I don't have to roll my own module to do GPPref editing in powershell…scripts like this give me hope.  Keep up the work

  19. Stu says:

    First of all, thanks for the script – it has given me a reason to start looking at PowerShell after putting it off for a long time!

    I decided to test it out in a test environment using an exported gpo from production, and came across an issue whereby the script gets stuck in an infinite loop if the setting's HasValue property is False, but the PolicyState is Delete. Because the HasValue is false, your script assumes that there must a further subfolder (where there will be a setting) and so calls CopyValues again (and again…). But in fact the setting is at this level, but it is a delete.

    An example of this is the gpo setting:

    [User ConfigurationPoliciesAdministrative TemplatesWindows ComponentsInternet ExplorerCompatibility View] "Use Policy List of Internet Explorer 7 sites=Disabled"

    I got round this issue by referring to the PolicyState property, rather than HasValue, and performing the actions based on its existence (but do not know if this would have any nasty ramifications?)

    But this then threw up another issue, in that the 'Set-GPRegistryValue -Disable' cmdlet does not seem to amend the destination gpo correctly. This seems to be in all cases where the gpo configuration in registry.pol is "**delvals." as viewable by eg polviewer.

    What happens is that the registry.pol file is updated (seemingly) correctly, and the setting works on applicable clients, and is viewable if editing the gpo directly and opening up the setting, BUT it is NOT visible if looking at the Settings page in GPMC; instead it shows nothing. The only difference that I can see is that using PowerShell, the entry into registry.pol is "**delVals.", whereas editing it manually it comes up as "**delvals." ie with a small v. Is GPMC's reading of registry.pol case-sensistive? Is there anyway round this issue?

    Also, on a side note, the script does not look at gp preferences, although it looks like there are cmdlets to get and set these; will you be updating the script to use these?

    Thanks again

  20. Mark Flynn says:

    Hi I have tried using your script to merge GPO's from my Domain into one. But I recieve the following error each time i try.

    *** Real GPO's replaced with GPO A & Merged GPO ***

    .MergeGPO_testScript.ps1 -src "GPO A" -dest "Merged GPO" -newDest

    Source GPO does not exist.  Be sure to use quotes around the name.

    I double checked and my GPO's exist in Group Policy.

    Any Ideas?


  21. Anders Johansen says:

    @Mark Flynn,

    Have that one as well…

    @Ashley McGlone

    Have copied the names directly from the GPMC. Listing the group policy using the Get-GPO works fine.

    The syntax I'm using is this:

    .copy-gpsettings.ps1 -src "NET-GPO-IEsettings" -dest "NET-GPO-IE" -dom ADM.NET.LOCAL

  22. Jason Poyner says:

    This is an awesome script, but it does have some bugs and limitation as others have noted in the comments. I have fixed some of the bugs and added logging to a text file. My updated script is here:

    Once I get the permissions fixed the script should also be available here:…/copy-gpregistryvalue.ps1

  23. ThomMck says:

    Can you explain the benefit of doing this?

    I know it may "tidy things up" but will it actually improve performance?

  24. Hi Thom,

    That's a great question.  You can make a case for larger or smaller policies, but it all depends on the administrative needs.  This article lays out the pros and cons:…/2008.01.gpperf.aspx

    Personally I prefer fewer, larger policies.  Regardless, this script is handy, because this is a popular request from GPO admins.  It is helpful when testing settings in separate policies and then rolling them up into a larger production policy.

    Thanks for the question,

    Ashley (GoateePFE)

  25. Tony Mels says:

    One of the problems I had running this script was not bein able to reach the primary domain controller of my test environment.
    The solution was to add the "-Server " switch to all the GP powershell commandlets.

  26. how do you run this? says:

    I took this script and ran it but it did not do anything. How do you run it? If I have GPONum1 and GPONum2 how do I merge them with this script?

  27. totalnet32 says:

    unable to copy registry setting to new GPO:
    Domain: labtest.Local
    Source GPO: ctxsession
    Destination GPO: newmergedgpo
    New Destination: True
    CopyMode: all

    Get-GPRegistryValue : The following Group Policy registry setting was not found : "HKEY_CURRENT_USERSoftware".
    Parameter name: keyPath
    At C:UsersAdministratordesktopcopy-gpregistryvalue.ps1:159 char:32
    + $path = Get-GPRegistryValue <<<< -GUID $src.ID -Key $Key
    + CategoryInfo : InvalidArgument: (Microsoft.Group…tryValueComm
    and:GetGPRegistryValueCommand) [Get-GPRegistryValue], ArgumentException
    + FullyQualifiedErrorId : UnableToRetrievePolicyRegistryItem,Microsoft.GroupPolicy.Commands.GetGPRegistryValueCommand

    Get-GPRegistryValue : The following Group Policy registry setting was not found : "HKEY_LOCAL_MACHINESystem".
    Parameter name: keyPath
    At C:UsersAdministratordesktopcopy-gpregistryvalue.ps1:159 char:32
    + $path = Get-GPRegistryValue <<<< -GUID $src.ID -Key $Key
    + CategoryInfo : InvalidArgument: (Microsoft.Group…tryValueComm and:GetGPRegistryValueCommand) [Get-GPRegistryValue], ArgumentException
    + FullyQualifiedErrorId : UnableToRetrievePolicyRegistryItem,Microsoft.GroupPolicy.Commands.GetGPRegistryValueCommand


    KeyPath : SoftwarePolicies
    FullKeyPath : HKEY_LOCAL_MACHINESoftwarePolicies
    Hive : LocalMachine

    KeyPath : SoftwarePolicies
    FullKeyPath : HKEY_LOCAL_MACHINESoftwarePolicies
    Hive : LocalMachine

  28. CiRiB says:

    i have the same Problem "Get-GPRegistryValue : The following Group Policy registry setting was not found : "HKEY_CURRENT_USERSoftware"." How can i fix this?

  29. Mark says:

    i just used the xp SSF starter gpo for user and computer
    it works fine until i reach a endless loop

    HKEY_LOCAL_MACHINESoftwarePoliciesMicrosoftWindows NTTerminal ServicesRAUnsolicit
    KeyPath : SoftwarePoliciesMicrosoftWindows NTTerminal ServicesRAUnsolicit
    FullKeyPath : HKEY_LOCAL_MACHINESoftwarePoliciesMicrosoftWindows NTTerminal ServicesRAUnsolicit
    Hive : LocalMachine

    KeyPath : SoftwarePoliciesMicrosoftWindows NTTerminal ServicesRAUnsolicit
    FullKeyPath : HKEY_LOCAL_MACHINESoftwarePoliciesMicrosoftWindows NTTerminal ServicesRAUnsolicit
    Hive : LocalMachine

  30. Batman says:

    Hi Guys

    How do you address the error which states Source GPO does not exist. Be sure to use quotes around the name."? Please advise

  31. Anonymous says:

    Do you have Group Policies gone wild? Did you realize too late that it might not be such a good idea to delegate GPO creation to half the IT department? Have you wanted to combine multiple policies into one for simplicity? This blog post is for you.

  32. Hello Batman,
    Please try the updated code here and see if you get the same error:
    Or use the Bat-a-rang. Your pick.

  33. Anonymous says:

    A host of reference material for AD and Group Policy

  34. denice anne corney says:

    Invaluable article – Incidentally , if your company are interested in merging of some PDF files , my secretary encountered a service here" >AltoMerge.