Skip to content

Instantly share code, notes, and snippets.

@Expertcoderz
Last active March 18, 2025 13:27
Combine many Windows GPO backups into one. For working with live GPOs, consider this instead: https://learn.microsoft.com/en-us/archive/blogs/ashleymcglone/updated-copy-and-merge-group-policies-gpos-with-powershell
<#
.SYNOPSIS
Combines Group Policy Object (GPO) backups from multiple source directories
into a single unified directory containing the GPO backups.
.DESCRIPTION
Combines Group Policy Object (GPO) backups from multiple source directories
into a single unified directory containing the GPO backups.
Duplicate GPOs will be ignored based on display name.
.PARAMETER Sources
Specifies the paths to the directories containing all the GPO backups to be
merged.
.PARAMETER Destination
Specifies the path to the destination directory to contain all the GPO
backups.
#>
#Requires -Version 7
[CmdletBinding()]
param (
[Parameter(Mandatory)][string[]]$Sources,
[Parameter(Mandatory)][string]$Destination
)
$manifestNamespace = @{mfst = 'http://www.microsoft.com/GroupPolicy/GPOOperations/Manifest' }
$finalManifest = [xml]'<Backups xmlns="http://www.microsoft.com/GroupPolicy/GPOOperations/Manifest"
xmlns:mfst="http://www.microsoft.com/GroupPolicy/GPOOperations/Manifest" mfst:version="1.0"></Backups>'
if (![System.IO.Directory]::Exists($Destination)) {
Write-Verbose "Creating directory for merged GPO backups: $Destination"
New-Item $Destination -ItemType Directory | Out-Null
}
foreach ($backupDir in Get-Item $Sources) {
$manifestFile = [System.IO.Path]::Combine($backupDir, 'manifest.xml')
if (![System.IO.File]::Exists($manifestFile)) {
Write-Warning "Manifest file missing for GPO backup: $($backupDir.Name)"
continue
}
Write-Verbose "Enumerating GPOs in backup: $($backupDir.Name)"
Select-Xml -Path $manifestFile `
-XPath '/mfst:Backups/mfst:BackupInst' `
-Namespace $manifestNamespace `
| ForEach-Object {
$gpoDisplayName = $_.Node.GPODisplayName.InnerText
$gpoId = $_.Node.ID.InnerText
Write-Verbose "Found GPO: $gpoDisplayName"
Write-Debug "ID is $gpoId"
if (
($finalManifest | Select-Xml -XPath '/mfst:Backups/mfst:BackupInst/mfst:GPODisplayName' `
-Namespace $manifestNamespace).Node.InnerText -contains $gpoDisplayName
) {
Write-Verbose 'GPO is a duplicate; ignoring'
continue
}
$gpoDir = [System.IO.Path]::Combine($backupDir, $gpoId)
if (![System.IO.Directory]::Exists($gpoDir)) {
Write-Warning "GPO backup directory missing for $gpoDisplayName; skipping"
continue
}
Write-Debug 'Copying GPO backup directory'
Copy-Item -Path $gpoDir -Destination $Destination -Recurse
Write-Debug 'Appending GPO backup metadata to consolidated manifest'
$finalManifest.DocumentElement.AppendChild(
$finalManifest.ImportNode($_.Node.Clone(), $true)
) | Out-Null
}
}
Write-Verbose 'Writing to consolidated manifest.xml'
$finalManifestFile = [System.IO.Path]::Combine($Destination, 'manifest.xml')
New-Item $finalManifestFile -ItemType File | Out-Null
$finalManifest.Save($finalManifestFile)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment