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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.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