Shore Up
A filing clerk photographing every folder in a large cabinet and placing the dated copies into a sealed archive box.
Windows

Automate a GPO Backup and Export with PowerShell

Ketan Aagja9 min read
No ratings yet

Before you run this

This guide sets up a PowerShell script that backs up every Group Policy Object in your domain to a dated folder and, optionally, exports a human-readable HTML report of each one. Backing up GPOs is a read-only operation — Backup-GPO does not change, delete, or unlink anything in Active Directory. It copies the policy settings, security filtering, and WMI filter links into files on disk. The risky part is not the backup; it's the restore, which I cover at the end and which you should treat with real care.

You need an elevated PowerShell session ("Run as administrator") running as an account that can read every GPO in the domain — in practice a Group Policy Admin or Domain Admin, or an account delegated backup rights on the Group Policy container. You also need the GroupPolicy PowerShell module, which is present on a domain controller and on any member server where you've added the Group Policy Management feature (part of RSAT). I assume:

  • Windows Server 2019 or 2022, domain-joined, run on a DC or a management server with GPMC installed.
  • Windows PowerShell 5.1 (the default). The commands work in PowerShell 7 too, but 5.1 is what ships with the OS.
  • A single domain. If you manage several, you run this per domain with the -Domain parameter.

Test it first. Read the script before you run it, and run it once by hand against a scratch folder before you schedule it. Confirm the backup folder actually fills with data and the manifest lists your GPOs. The backup itself is safe to run on production; the restore commands at the bottom overwrite live policy and are effectively irreversible once Group Policy replicates — so never test a restore against a production GPO. Restore into a lab domain or a disposable test GPO first.

There is no console lock-out risk here as there is with a firewall change, but treat the backup target like any backup: put it somewhere with restricted access, because a GPO backup can contain security-sensitive settings.

What the backup actually captures

Backup-GPO saves the settings inside each GPO and its metadata — the GPO's security filtering (the ACL on the GPO), and any WMI filter link. What it does not save is the links between GPOs and OUs (where the policy is applied in the tree), the OU structure itself, or WMI filter definitions. Those live elsewhere in AD and need a separate AD backup or export. Keep that in mind: a GPO backup is not a full Group Policy disaster-recovery plan on its own, but it is the core of one and the piece you should automate first.

The script

Save this as Backup-AllGPOs.ps1. Replace the two obvious placeholders at the top: the root path where backups should live, and your domain FQDN.

#Requires -Modules GroupPolicy
#Requires -RunAsAdministrator

# --- Settings you edit ---
$BackupRoot = 'D:\GPOBackups'          # replace with your backup location
$Domain     = 'example.com'            # replace with your domain FQDN
$MakeReports = $true                   # set $false to skip HTML reports
$RetentionDays = 90                    # delete backup folders older than this; 0 = keep all
# -------------------------

# One dated, unique folder per run so backups never overwrite each other
$stamp    = Get-Date -Format 'yyyy-MM-dd_HHmmss'
$thisRun  = Join-Path $BackupRoot $stamp
New-Item -Path $thisRun -ItemType Directory -Force | Out-Null

# Back up every GPO in the domain into this run's folder
Backup-GPO -All -Path $thisRun -Domain $Domain `
    -Comment "Scheduled backup $stamp"

# Optional: write an HTML report per GPO alongside the backup
if ($MakeReports) {
    $reportDir = Join-Path $thisRun 'Reports'
    New-Item -Path $reportDir -ItemType Directory -Force | Out-Null
    Get-GPO -All -Domain $Domain | ForEach-Object {
        $safeName = $_.DisplayName -replace '[\\/:*?"<>|]', '_'  # strip illegal filename chars
        Get-GPOReport -Guid $_.Id -ReportType Html -Domain $Domain `
            -Path (Join-Path $reportDir "$safeName.html")
    }
}

# Optional: prune old backup folders
if ($RetentionDays -gt 0) {
    $cutoff = (Get-Date).AddDays(-$RetentionDays)
    Get-ChildItem -Path $BackupRoot -Directory |
        Where-Object { $_.CreationTime -lt $cutoff } |
        Remove-Item -Recurse -Force -WhatIf   # remove -WhatIf once you trust the pruning
}

A few notes on the non-obvious parts:

  • Backup-GPO -All writes each GPO into its own GUID-named subfolder and maintains a manifest.xml that maps those GUIDs back to display names. That manifest is what Restore-GPO reads later, so don't rename or reorganise the folder contents.
  • I give each run its own timestamped folder. Backup-GPO can append multiple backups into one path, but separate dated folders are far easier to reason about and to prune.
  • The report loop replaces characters that are illegal in Windows filenames so a GPO called, say, Web Filtering: HTTP/HTTPS doesn't break the export.
  • The pruning step ships with -WhatIf so the first runs only log what they would delete. Remove -WhatIf once you've watched it name the right folders. This is deliberately the one destructive line in the script, and it's gated.

Get-GPOReport also accepts -ReportType Xml if you'd rather diff machine-readable output between runs. HTML is the mainstream choice for humans; XML is the one to reach for if you feed it into change-tracking.

Schedule it

Run it unattended with Task Scheduler. Register a daily task that runs the script under an account with the rights described above:

$action  = New-ScheduledTaskAction -Execute 'powershell.exe' `
    -Argument '-NoProfile -ExecutionPolicy Bypass -File "C:\Scripts\Backup-AllGPOs.ps1"'
$trigger = New-ScheduledTaskTrigger -Daily -At 2:00AM

Register-ScheduledTask -TaskName 'GPO Backup' `
    -Action $action -Trigger $trigger `
    -User 'EXAMPLE\svc-gpobackup' `
    -RunLevel Highest                    # run elevated

Replace C:\Scripts\... with your script's real path and EXAMPLE\svc-gpobackup with your service account. When you run Register-ScheduledTask interactively without -Password, it prompts you to store the account's credentials. Prefer a dedicated service account over a personal admin login so the task keeps working after a password change on your own account. For the exact parameter set on these cmdlets, see Microsoft Learn for Register-ScheduledTask and New-ScheduledTaskAction.

Verify it worked

After a run, confirm three things.

Check the folder actually contains GPO backups and a manifest:

Get-ChildItem 'D:\GPOBackups' -Directory | Sort-Object CreationTime | Select-Object -Last 1 |
    ForEach-Object { Get-ChildItem $_.FullName }

You should see one GUID-named subfolder per GPO plus a manifest.xml. Confirm the count of backed-up GPOs matches what's actually in the domain:

(Get-GPO -All).Count          # number of GPOs in the domain

Compare that against the number of GUID folders in the latest backup. If the report option is on, open a file or two from the Reports folder in a browser and confirm the settings render.

How to restore (handle with care)

Restore is the irreversible half. Restore-GPO writes the saved settings back into the existing GPO of the same name — it overwrites current settings once it replicates. Never point this at a production GPO to "try it."

To restore a single GPO from a backup path:

Restore-GPO -Name 'Your GPO Display Name' -Path 'D:\GPOBackups\2025-01-15_020000' -Domain 'example.com'

To restore everything from one backup set, Restore-GPO -All -Path ... exists, but doing a bulk restore is a genuine disaster-recovery action — do it deliberately, not casually.

If instead you want to load a backup into a new or different GPO (for example, to inspect or stage settings without touching the live one), that's Import-GPO, which takes a -BackupId, a -Path, and a -TargetName. Check the exact syntax on Microsoft Learn for Import-GPO before you use it, and stage into a throwaway GPO first.

For the authoritative parameter reference, see Microsoft Learn for Backup-GPO, Get-GPOReport, Restore-GPO, and Import-GPO in the GroupPolicy module.

Written by
Ketan Aagja

Runs enterprise networks and security for a living, and writes Shore Up to turn two decades of hands-on Linux, Windows and mail-server work into guides you can actually use.

More about the author →

Was this article helpful?

Tap a star — no sign-in needed.

Be the first to rate this article.