Skip to content
Menu
The Lonely Administrator
  • PowerShell Tips & Tricks
  • Books & Training
  • Essential PowerShell Learning Resources
  • Privacy Policy
  • About Me
The Lonely Administrator

Get GPO Backup

Posted on May 24, 2011March 11, 2015

The GroupPolicy module from Microsoft offers a great deal of functionality from a command line. In terms of regular maintenance or administration it is pretty hard to beat, especially if you have 100s or 1000s of GPOs. When you have such a large number, backing them up is critical and easy to accomplish with the Backup-GPO cmdlet. However, if you want to restore or import from a backup, you have to resort to the graphical Group Policy Managment Console. I couldn't find any way to manage GPO backups from PowerShell so I wrote my own.

Manage and Report Active Directory, Exchange and Microsoft 365 with
ManageEngine ADManager Plus - Download Free Trial

Exclusive offer on ADManager Plus for US and UK regions. Claim now!

My function, Get-GPOBackup, will search a GPO backup folder and retrieve backup objects. All of this is done using the file system. Technically you don't need the Group Policy module to retrieve the backup information. However, if you want to restore anything then you of course need the Group Policy module loaded. The only parameter you need to specify is the path. Although the function is designed to use the value of a global variable, $GPBackupPath. So if you use this alot, set that variable. But perhaps before we get too far you'd like to see the function.

Function Get-GPOBackup {

[cmdletbinding()]

Param(
[Parameter(Position=0,Mandatory=$False,HelpMessage="What is the path to the GPO backup folder?")]
[ValidateNotNullOrEmpty()]
[string]$Path=$global:GPBackupPath,
[Parameter(Position=1)]
[string]$Name,
[switch]$Latest
)

#validate $Path
if (-Not $Path) {
$Path=Read-Host "What is the path to the GPO backup folder?"
}

Try
{
Write-Verbose "Validating $Path"
if (-Not (Test-Path $Path)) { Throw }
}
Catch
{
Write-Warning "Failed to find $Path"
Break
}

#get each folder that looks like a GUID
[regex]$regex="^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$"

Write-Verbose "Enumerating folders under $Path"

#define an array to hold each backup object
$Backups=@()

#find all folders named with a GUID
Get-ChildItem -Path $path | Where {$_.name -Match $regex -AND $_.PSIsContainer} |
foreach {

#import the Bkupinfo.xml file
$file=Join-Path $_.FullName -ChildPath "bkUpinfo.xml"
Write-Verbose "Importing $file"
[xml]$data=Get-Content -Path $file

#parse the xml file for data
$GPO=$data.BackupInst.GPODisplayName."#cdata-section"
$GPOGuid=$data.BackupInst.GPOGuid."#cdata-section"
$ID=$data.BackupInst.ID."#cdata-section"
$Comment=$data.BackupInst.comment."#cdata-section"
#convert backup time to a date time object
[datetime]$Backup=$data.BackupInst.BackupTime."#cdata-section"
$Domain=$data.BackupInst.GPODomain."#cdata-section"

#write a custom object to the pipeline
$Backups+=New-Object -TypeName PSObject -Property @{
Name=$GPO
Comment=$Comment
#strip off the {} from the Backup ID GUID
BackupID=$ID.Replace("{","").Replace("}","")
#strip off the {} from the GPO GUID
Guid=$GPOGuid.Replace("{","").Replace("}","")
Backup=$Backup
Domain=$Domain
Path=$Path
}

} #foreach

#if searching by GPO name, then filter and get just those GPOs
if ($Name)
{
Write-Verbose "Filtering for GPO: $Name"
$Backups=$Backups | where {$_.Name -like $Name}

}

Write-Verbose "Found $($Backups.Count) GPO Backups"

#if -Latest then only write out the most current version of each GPO
if ($Latest)
{
Write-Verbose "Getting Latest Backups"
$grouped=$Backups | Sort-Object -Property GUID | Group-Object -Property GUID
$grouped | Foreach {
$_.Group | Sort-Object -Property Backup | Select-Object -Last 1
}
}
else
{
$Backups
}

Write-Verbose "Ending function"

} #end function

The complete function has comment based help.

get-gpobackp help
get-gpobackp help

As I said, the -Path parameter is key. It is not mandatory in the attribute-sense, but I have code that prompts for a missing value and the path is tested before anything else is done.

#validate $Path
if (-Not $Path) {
$Path=Read-Host "What is the path to the GPO backup folder?"
}

Try
{
Write-Verbose "Validating $Path"
if (-Not (Test-Path $Path)) { Throw }
}
Catch
{
Write-Warning "Failed to find $Path"
Break
}

GPO backups are stored under a GUID for each GPO, so I use a regular expression pattern to identify these. I'm making an assumption that the path you specify won't have any other folders with GUID-based names.

#get each folder that looks like a GUID
[regex]$regex="^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$"

Each backup consists of a few XML files. My function parses the XML and creates a custom object for each backup. These are added to a temporary array.

#define an array to hold each backup object
$Backups=@()

#find all folders named with a GUID
Get-ChildItem -Path $path | Where {$_.name -Match $regex -AND $_.PSIsContainer} |
foreach {

#import the Bkupinfo.xml file
$file=Join-Path $_.FullName -ChildPath "bkUpinfo.xml"
Write-Verbose "Importing $file"
[xml]$data=Get-Content -Path $file

#parse the xml file for data
$GPO=$data.BackupInst.GPODisplayName."#cdata-section"
$GPOGuid=$data.BackupInst.GPOGuid."#cdata-section"
$ID=$data.BackupInst.ID."#cdata-section"
$Comment=$data.BackupInst.comment."#cdata-section"
#convert backup time to a date time object
[datetime]$Backup=$data.BackupInst.BackupTime."#cdata-section"
$Domain=$data.BackupInst.GPODomain."#cdata-section"

#write a custom object to the pipeline and add to the temporary array
$Backups+=New-Object -TypeName PSObject -Property @{
Name=$GPO
Comment=$Comment
#strip off the {} from the Backup ID GUID
BackupID=$ID.Replace("{","").Replace("}","")
#strip off the {} from the GPO GUID
Guid=$GPOGuid.Replace("{","").Replace("}","")
Backup=$Backup
Domain=$Domain
Path=$Path
}
} #foreach

Now for the really fun part. The function supports a few other parameters that allow you to search for a GPO by name. So if you specify -Name, everything else is filtered out.

#if searching by GPO name, then filter and get just those GPOs
if ($Name)
{
Write-Verbose "Filtering for GPO: $Name"
$Backups=$Backups | where {$_.Name -like $Name}

}

I also include a parameter to get the latest backup. I accomplish this by grouping the backup objects by their GPO GUID, sorting by backup date and selecting the latest one.

#if -Latest then only write out the most current version of each GPO
if ($Latest)
{
Write-Verbose "Getting Latest Backups"
$grouped=$Backups | Sort-Object -Property GUID | Group-Object -Property GUID
$grouped | Foreach {
$_.Group | Sort-Object -Property Backup | Select-Object -Last 1
}
}

You should end up with an object like this for each GPO backup.

Backup : 5/2/2011 8:10:13 PM
Name : WinRM Configuration
BackupID : 5AAFD6DF-24C0-40EF-9DF1-AC9C279E010B
Path : \\server01\backup\gpo
Domain : jdhlab.local
Guid : 38228a9f-f2ec-4b48-8829-4f3131e4f77c
Comment : second backup

I tried to create an object so that if you wanted to restore a GPO you could with minimal effort. Unfortunately, the Import-GPO cmdlet doesn't have enough support for parameter binding. So if you want to restore a backup, you'll need to resort to a ForEach-Object command like this:

PS C:\> Get-GPOBackup \\coredc01\backup\gpo -name "Ex*Laptop" -latest | foreach {Import-GPO -TargetGuid $_.Guid -backupid $_.backupID -path $_.path}

DisplayName : Executive Laptop
DomainName : jdhlab.local
Owner : JDHLAB\Domain Admins
Id : af6f4a88-5e8a-49ce-84f7-f04677a7d80d
GpoStatus : AllSettingsEnabled
Description : configure C-level laptops
CreationTime : 9/8/2010 8:36:10 AM
ModificationTime : 5/23/2011 9:52:43 AM
UserVersion : AD Version: 2, SysVol Version: 2
ComputerVersion : AD Version: 9, SysVol Version: 9
WmiFilter :

My function is in a stand-alone script so to use it, you either need to copy and paste the function into your profile or dot source the script. Even though I wrote it to help with imports, you could easily use it to manage a backup folder, deleting obsolete backups. The function is writing an object to the pipeline that you can use in whatever manner you can think of. I hope you'll let me know what you think and if there is anything you think is missing.

Download Get-GPOBackup.


Behind the PowerShell Pipeline

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to print (Opens in new window) Print
  • Click to email a link to a friend (Opens in new window) Email

Like this:

Like Loading...

Related

reports

Powered by Buttondown.

Join me on Mastodon

The PowerShell Practice Primer
Learn PowerShell in a Month of Lunches Fourth edition


Get More PowerShell Books

Other Online Content

github



PluralSightAuthor

Active Directory ADSI Automation Backup Books CIM CLI conferences console Friday Fun FridayFun Function functions Get-WMIObject GitHub hashtable HTML Hyper-V Iron Scripter ISE Measure-Object module modules MrRoboto new-object objects Out-Gridview Pipeline PowerShell PowerShell ISE Profile prompt Registry Regular Expressions remoting SAPIEN ScriptBlock Scripting Techmentor Training VBScript WMI WPF Write-Host xml

©2025 The Lonely Administrator | Powered by SuperbThemes!
%d