Using PowerShell to backup Group Policy Objects
I'm sure we all do nightly backups of our domain controllers and Active Directory some restoring at is a pain if all you need is the settings for a GPO that got edited yesterday. Fortunately GPOs have their own native backup and restore system (https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc759276(v%3dws.10)). The system works well, but it's really only designed to to backup/restore 1 policy at a time. PowerShell makes it much easier to backup all of your policies with a single command Backup-GPO -All -Path "c:\Temp". It works great but I wanted a little more than just the backup.
The script below will backup your group polices, export an HTML file with all the settings for your GPO and generates a list of what policy is linked where (also part of the HTML file). It is designed so you don't need to configure anything unless you want to. The script will get domain and forest information and will use the path it is run in to store the data. I currently have this running in System Center Orchestrator nightly so we have a daily backup of our group policies.
<#
About
Name: Active Directory GPO Backup
Script: GpoBackup.ps1
Author: James Sargent
Created: 2018/09/27
Modified: 2018/09/27
Version: 1.00.0927a
Summary
Does the following for all GPOs in all domains in the forest
Native backup of all GPOs
Exports HTML reports for all GPOs
Creates a list of GPOs and where they are linked (Includes and identifies non-linked GPOs)
Instructions
There is very little to configure as all information is gathered automatically with the exception
of how long you want to keep the data, default is 7 days. To change this simply update the
$intDaysToKeep value.
When running the script in a multi-forest domain the user running the script must have sufficient access
in all domains to Backup GPOs, Export GPOs, etc. This will likely be a member of the Enterprise Admins
group.
The data is compressed in to 2 zip files, one for backups, and one for reports, the link information
CSV file is in the report zip file.
The zip files are stored in a folders for each domain under the Backups folder
Requirements
PowerShell 4.0 or higher
AD PowerShell Module (RSAT)
#>
# Variables
# ------------------------------------------------------------------------
$intDaysToKeep = 7 # Number of days to keep reports
$strPath = (Get-Location).Path + "\" # Path to script location
$strPathTemp = $strPath + "Temp\" # Path to temp folder, ensure paths have a trailing \
$strPathBackups = $strPath + "Backups\" # Path to backups folder, ensure paths have a trailing \
$dtStartTime = Get-Date
# Functions
# ------------------------------------------------------------------------
Function funBackup ($strDomain)
{
$arrGPOLinks = @()
# Set Paths
$tmpPathBackup = $strPathTemp + "Backup\"
$tmpPathReports = $strPathTemp + "Reports\"
$strPathDomainBackups = $strPathBackups + $strDomain + "\"
# If paths don't exisit create them
If (!(Test-Path $tmpPathBackup)) {New-Item -ItemType Directory -Path $tmpPathBackup}
If (!(Test-Path $tmpPathReports)) {New-Item -ItemType Directory -Path $tmpPathReports}
If (!(Test-Path $strPathDomainBackups)) {New-Item -ItemType Directory -Path $strPathDomainBackups}
# Get PDCEmulator
$strDC = Get-AdDomainController -Filter {OperationMasterRoles -Like "PDCEmulator" -And Domain -eq $strDomain} | Select -ExpandProperty HostName
# Get GPO List
$arrGPOList = Get-GPO -All -Domain $strDomain -Server $strDC
# Count GPOs for Progress bar
$numRecords = $arrGPOList.count
foreach ($arrPolicy in $arrGPOList)
{
$tmpName = $arrPolicy.DisplayName
$tmpID = $arrPolicy.ID.Guid
$tmpComment = "Backup of " + $tmpName + " on " + (Get-Date($dtStartTime) -Format "yyyy-MM-dd")
$tmpGPOLinks = $Null
<#
Backup GPO
GPO will be backed up to a folder that matches the GUID, this backup can be used
to do a Restore in the GPEdit tool
#>
Backup-GPO -Guid $tmpID -Path $tmpPathBackup -Comment $tmpComment
<#
Export HTML Report
An HTML file with the display name of the GPO will be created with all of the GPO Settings.
Example: Default Domain Policy.html
#>
Get-GPOReport -Name $tmpName -Path ($tmpPathReports + $tmpName + ".html") -ReportType HTML
<#
Get GPO link lnformation
Additional a text file with the display name of the gpo will be created with information on
all of the OU's the policy was linked.
Example: Default Domain Policy.link
#>
#$arrGPOLinks += ([xml](Get-GPOReport -Name $tmpName -ReportType XML)).GPO.LinksTo | Select @{n="Policy";e={$tmpName}},@{n="OU";e={$_.SOMName}},@{n="Path";e={$_.SOMPath}},@{n="LinkStatus";e={$_.Enabled}},@{n="Enforced";e={$_.NoOverrided}}
$tmpGPOLinks = ([xml](Get-GPOReport -Name $tmpName -ReportType XML)).GPO.LinksTo | Select @{n="Policy";e={$tmpName}},@{n="OU";e={$_.SOMName}},@{n="Path";e={$_.SOMPath}},@{n="LinkStatus";e={$_.Enabled}},@{n="Enforced";e={$_.NoOverrided}}
If ($tmpGPOLinks -eq $Null)
{
$tmpGPOLinks = New-Object PSObject -Property @{
Policy = $tmpName
OU = "NA"
Path = "NA"
LinkStatus = "NA"
Enforced = "NA"}
}
$arrGPOLinks += $tmpGPOLinks
}
# Write links file
$arrGPOLinks | Export-CSV ($tmpPathReports + "GPOLinks-" + (Get-Date($dtStartTime) -f "yyyy-MM-dd") + ".csv") -NoTypeInformation
# Compress Backups
Compress-Archive -Path $tmpPathBackup -DestinationPath ($strPathDomainBackups + "GPOBackup-" + (Get-Date($dtStartTime) -f "yyyy-MM-dd") + ".zip")
# Compress Reports
Compress-Archive -Path $tmpPathReports -DestinationPath ($strPathDomainBackups + "GPOReports-" + (Get-Date($dtStartTime) -f "yyyy-MM-dd") + ".zip")
# Cleanup Temp folder
Remove-Item ($strPathTemp + "*") -Force -Recurse -Confirm:$False
# Cleanup backup folder
Get-ChildItem -Path $strPathDomainBackups | Where {$_.CreationTime -lt $dtStartTime.AddDays(-$intDaysToKeep)} | Remove-Item -Force -Confirm:$False
}
# ----------- Main script -----------
# Get Domain List
$arrDomains = Get-ADForest | Select -ExpandProperty Domains
# Verify the Temp path exists
If (!(Test-Path $strPathTemp)) {New-Item -ItemType Directory -Path $strPathTemp}
# Verify the Backups path exists
If (!(Test-Path $strPathBackups )) {New-Item -ItemType Directory -Path $strPathBackups}
# Backup each domain
# The account this script is running under will need access to each domains AD
ForEach ($strDomain in $arrDomains)
{
funBackup $strDomain
}