Thursday, October 6, 2011

Merging Group Policies with PowerShell

As most environments grow they end up having many group polices enforcing a variety of different settings. At a certain point they become unmanageable and are in need of a cleanup. Merging GPOs is a pain as there isn’t really a good way of doing it – without PowerShell anyway. We faced this exactly scenario recently and lucky for us we came across this blog post by Ashley McGlone on TechNet. His script takes advantage of the Get-GPRegistryValue function to capture all of the settings and then copies them to a destination policy.

#--------------------------------------------------------------------
# Copy GPO Registry Settings
# Ashley McGlone, Microsoft PFE
# http://blogs.technet.com/b/ashleymcglone
# 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 (
$dom,
$src,
$dest,
[switch]$newDest,
$copymode
)

# We must continue on errors due to the way we enumerate GPO registry
# paths and values in the function CopyValues.
$ErrorActionPreference = "SilentlyContinue"
$error.PSBase.Clear()

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: http://blogs.technet.com/b/ashleymcglone"
""
"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."
""
"Syntax:"
".\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."
""
Return
}

#--------------------------------------------------------------------
# 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."
$error
Return
}
}

if ($src -eq $null) {
"Source GPO name cannot be blank."
Return
} else {
$src = Get-GPO -Name $src
If ($error.Count -ne 0) {
"Source GPO does not exist. Be sure to use quotes around the name."
Return
}
}

if ($dest -eq $null) {
"Destination GPO name cannot be blank."
Return
} 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 {
Return
}
}
} else {
$dest = Get-GPO -Name $dest
If ($error.Count -ne 0) {
"Destination GPO does not exist. Be sure to use quotes around the name."
Return
}
}
}

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"
Return
}
}
#--------------------------------------------------------------------


#--------------------------------------------------------------------
# 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.
#--------------------------------------------------------------------
# THIS IS THE HEART OF THE SCRIPT.
# 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) {
$Key
$error.PSBase.Clear()
$path = Get-GPRegistryValue -GUID $src.ID -Key $Key
$path
If ($error.Count -eq 0) {
ForEach ($keypath in $path) {
$keypath
$keypath | ForEach-Object {Write-Host $_}
If ($keypath.HasValue) {
$keypath.PolicyState
$keypath.Valuename
$keypath.Type
$keypath.Value
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 {
$error
}
}
#--------------------------------------------------------------------


#--------------------------------------------------------------------
# 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
Copy-GPRegistryValue

# ><>