get-GPOBackup.ps1


<#PSScriptInfo
 
.VERSION 1.56
 
.GUID 63e6c25c-7a63-4aec-a009-eec1c4791608
 
.AUTHOR Fabian Niesen (Infrastrukturhelden.de)
 
.COMPANYNAME
 
.COPYRIGHT Fabian Niesen 2018
 
.TAGS GPO Grouppolicy Backup
 
.LICENSEURI
 
.PROJECTURI https://www.infrastrukturhelden.de/microsoft-infrastruktur/active-directory/gruppenrichtlinien-richtig-sichern-und-dokumentieren.html
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES ActiveDirectory
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
 
 
#>
 



<#
.SYNOPSIS
Creates backup of the GPO with according html Reports. The script creates a subfolder based upon an actual timestamp.
     
.DESCRIPTION
PowerShell script to periodically back up the Group Policy and documentation of version using HTML reports. Ideal as a scheduled task to perform regular backups of the GPO. Each backup creates a new subfolder with a time stamp.
The script does not require Administrative privileges. If the script runs with Administrative privileges, it will write status information to the event log.
Based opon the german blog article contained in the link. Get the actual Script version from Microsoft TechNet: http://bit.ly/gpobackup
If you like this script, please rate it in the TechNet Gallery.
 
.EXAMPLE
C:\PS> get-GPOBackup.ps1
 
.EXAMPLE
C:\PS> get-GPOBackup.ps1 -BackupPath C:\GPOBACKUP -KeepDate 90
 
.EXAMPLE
C:\PS> get-GPOBackup.ps1 -BackupPath C:\GPOBACKUP -KeepDate 90 -Verbose
 
.PARAMETER BackupPath
Path to Backup.
 
.PARAMETER KeepDate
Amount of days to keep the old versions.
 
.PARAMETER characters
Characters to be replaced in GPO names with an "_".
 
.PARAMETER testerror
Switch to force an Error for testing
 
.PARAMETER testwarning
Switch to force an Warrning for testing
 
.PARAMETER testerrorwarning
Switch to force an Error with Warrning for testing
 
.NOTES
Author : Fabian Niesen (www.fabian-niesen.de)
Filename : get-GPOBackup.ps1
Requires : PowerShell Version 3.0
Version : 1.56
History : 1.0.0 FN 27/07/14 initial version
             1.1.0 FN 25/08/14 Change script to handle new GUID on GPO backup
             1.1.1 FN 03/09/14 Fix Targetpath for secured enviorment
             1.2 FN 20/01/15 move download to TechNet, translation to english,
                                   implement Eventlog if Run as Admin
             1.3 FN 09/02/15 Change Name to get-GPOBackup, Change Path to parameter
                                   instead Config, Implementing KeepDate, Implementing Error-Logging
             1.4 FN 25/02/15 Tracking runtime, fixes in documentation
             1.55 FN 11/08/17 Add some Debug Options. Filtering GPO Names for unwanted Characters
             1.56 FN 04/03/18 Fix Eventlog handling. Add additional Eventlog posibilities,
                                   for this I change and add EventIDs. Some fixes with the get-Help output
 
.LINK
https://www.infrastrukturhelden.de/microsoft-infrastruktur/active-directory/gruppenrichtlinien-richtig-sichern-und-dokumentieren.html
#>


Param(
    [Parameter(Mandatory=$false, Position=0, ValueFromPipeline=$False)]
    [String]$BackupPath="C:\Temp\GPOBackup",
    [Parameter(Mandatory=$false, Position=1, ValueFromPipeline=$False)]
    [Int]$KeepDate="93",
    [Parameter(Mandatory=$false, Position=2, ValueFromPipeline=$false)]
    [string]$characters = ". $%&!?#*:;\><|",
    [switch]$testerror,
    [switch]$testwarning,
    [switch]$testerrorwarning
)
$ErrorActionPreference = "Stop"
$GPList = @()
$regex = "[$([regex]::Escape($characters))]"
#[Array]$GPList = $null

$before = Get-Date

IF ($BackupPath.EndsWith("\") -like "False") { $BackupPath =$BackupPath+"\" }
IF (!(Test-Path $BackupPath)) { new-item -Path $BackupPath -ItemType directory }

$date = get-date -format yyyyMMdd-HHmm
$ErrorLog =$BackupPath+$date+"-error.log"
$InfoLog =$BackupPath+$date+"-info.log"
$WarningLog =$BackupPath+$date+"-warning.log"
$Report = $BackupPath+$date+"-Report.csv"
Write-Verbose "Import Grouppolicy module"
try
{
  Import-Module grouppolicy 
}
catch
{
  Write-Warning "GroupPolicy Module ist missing. Please install first"
  "GroupPolicy Module ist missing. Please install first" | Out-file $ErrorLog -Append
  break
}

Write-Verbose "Check if backup path is default"
IF ($BackupPath -eq "c:\temp\GPOBackup\")
{
  Write-Warning "No BackupPath provided, use C:\TEMP\GPOBackup. To provide a Backuppath use >Get-GPOBackup -BackupPath<" 
  "BackupPath not set, use default"| Out-file $WarningLog -Append
  $Wait = $True
}


IF ($KeepDate -eq "93")
{
  Write-Warning "No KeepDate provided, delete Backups older than 93 days. To provide a an other timeperiod use >Get-GPOBackup -KeepDate<" 
  "KeepDate not set, use default"| Out-file $WarningLog -Append
  $Wait = $True
}

IF ($Wait -eq $True) { Start-Sleep -s 10 }
Write-Verbose "Start housekeeping, deleting old backups"

try
{
  Write-Host "Start deleting Backups older than $KeepDate days" 
  Get-ChildItem $BackupPath |? {$_.PSIsContainer -and $_.LastWriteTime -le (Get-Date).AddDays(-$KeepDate)} |% {Remove-Item $_.Fullname -Recurse -Force }
}
catch
{
  $Error.Item($Error.Count - 1) | Format-List * -Force | Out-file $ErrorLog -Append
  "Deletion of old backups failed" | Out-file $ErrorLog -Append
  Write-Warning "Deletion of old backups failed"
}


$BackupPath =$BackupPath+$date
IF (!(Test-Path $BackupPath)) { New-Item -Path $BackupPath -ItemType directory | out-null }
ELSE 
{ 
  "Backup already exist. Script started twice or you use a time machine!" | Out-file $ErrorLog -Append  
  Write-Warning "Backup already exist. Script started twice or you use a time machine!"
  break
}

### Processing GPO
Write-Verbose "Query GPOs"
$GPOS = get-GPO -all
Write-Verbose "Start processing GPO"
Write-Progress -activity "Processing GPO" -Status "starting" -PercentComplete "0" -Id 1
[int]$i = "0"
FOREACH ( $GPO in $GPOS)
{
  $i++
  Write-Progress -activity "Processing GPO" -Status "$($GPO.DisplayName)" -PercentComplete (($i / $GPOS.count)*100) -Id 1
  $GPOname = $($GPO.DisplayName).Trim()
  $GPOname = $GPOname -replace $regex,"_"
  IF (!($($GPO.DisplayName) -eq $GPOname)) 
  { 
    Write-Verbose "Filtered GPO Name >$($GPO.DisplayName)< to: >$GPOname<" 
    "Filtered GPO Name >$($GPO.DisplayName)< to: >$GPOname<" | Out-File $InfoLog -Append
  }
  $bpath = $BackupPath+"\"+$GPOname
  New-Item -Path $bpath -ItemType directory | Out-Null
  Write-Verbose "Starting backup $($GPO.DisplayName)"
  try
  {
    $gptemp = Backup-Gpo -Name $($GPO.DisplayName) -Path $bpath 
    $GPitem = New-Object -TypeName psobject
    $GPitem | Add-Member -MemberType NoteProperty -Name DisplayName -Value $gptemp.DisplayName
    $GPitem | Add-Member -MemberType NoteProperty -Name GpoId -Value $gptemp.GpoId
    $GPitem | Add-Member -MemberType NoteProperty -Name Id -Value $gptemp.Id
    $GPitem | Add-Member -MemberType NoteProperty -Name BackupDirectory -Value $gptemp.BackupDirectory
    $GPitem | Add-Member -MemberType NoteProperty -Name CreationTime -Value $gptemp.CreationTime
    $GPitem | Add-Member -MemberType NoteProperty -Name DomainName -Value $gptemp.DomainName
    $GPitem | Add-Member -MemberType NoteProperty -Name Comment -Value $gptemp.Comment
    $GPList += $GPitem
  }
  catch
  {
    $Error.Item($Error.Count - 1) | Format-List * -Force | Out-file $ErrorLog -Append
    Write-Warning "$($GPO.DisplayName) backup failed"
    "$($GPO.DisplayName) Backup failed"| Out-file $ErrorLog -Append
  }
  Write-Verbose "Starting HTML report $($GPO.DisplayName)"
  try
  {
    Get-GPOReport $($GPO.DisplayName) -ReportType HTML -Path "$bpath\$GPOname.html" 
  }
  catch
  {
    $Error.Item($Error.Count - 1) | Format-List * -Force | Out-file $ErrorLog -Append
    Write-Warning "$($GPO.DisplayName) HTML report failed"
    "$($GPO.DisplayName) HTML report failed"| Out-file $ErrorLog -Append
  }
}

$GPList | Export-Csv $Report -NoTypeInformation -Delimiter ";"
Write-Output "Creating a report about all handled GPO. Please check $Report"

###Prepare Errorhandling Output
$EHMessage = @()
IF ((Test-Path $ErrorLog) -and (Test-Path $WarningLog))
{
  IF ($EHID -eq $null) { $EHID = "301" }
  $EHCategory = "Error"
  $EHMessage += "Backup completed with Errors and Warnings. Targetpath: $BackupPath"
  $EHMessage += "--- ERRORLOG $ErrorLog ---"
  $EHMessage += Get-Content $ErrorLog
  $EHMessage += "--- WARNINGLOG $WarningLog ---"
  $EHMessage += Get-Content $WarningLog

}
ELSEIF (Test-Path $ErrorLog)
{
  IF ($EHID -eq $null) { $EHID = "300" }
  $EHCategory = "Error"
  $EHMessage += "Backup completed with Errors. Targetpath: $BackupPath" 
  $EHMessage += "--- ERRORLOG $ErrorLog ---"
  $EHMessage += Get-Content $ErrorLog
}
ELSeIF (Test-Path $WarningLog)
{
  IF ($EHID -eq $null) { $EHID = "200" }
  $EHCategory = "Warning"
  $EHMessage += "Backup completed with Warnings. Targetpath: $BackupPath"
  $EHMessage += "--- WARNINGLOG $WarningLog ---"
  $EHMessage += Get-Content $WarningLog
}
ELSEIF (Test-Path $InfoLog)
{
  IF ($EHID -eq $null) { $EHID = "101" }
  $EHCategory = "Information"
  $EHMessage += "Backup completed only with Informations. Targetpath: $BackupPath"
  $EHMessage += "--- INFOLOG $InfoLog ---"
  $EHMessage += Get-Content $InfoLog
}
ELSE
  {
  IF ($EHID -eq $null) { $EHID = "100" }
  $EHCategory = "Information"
  $EHMessage += "Backup completed. Targetpath: $BackupPath"
  }

  IF ($testerrorwarning -eq $true) { $EHID = "301" ; $EHCategory = "Error" ; $EHMessage += "Test Error with Warnings" }
  ELSEIF ($testerror -eq $true) { $EHID = "300" ; $EHCategory = "Error" ; $EHMessage += "Test Error" }
  ELSEIF ($testwarning -eq $true) { $EHID = "200" ; $EHCategory = "Warning" ; $EHMessage += "Test Warning" }
  $Message = $EHMessage | Out-String
  IF ($EHCategory -like "Error" -or  $EHCategory -like "Warning") { Write-Warning $Message }
  ELSE { Write-output $Message }


Write-Verbose "Check Admin privilages for creation of the EventLog entries"
### Proof for administrative permissions (UAC) and start EventLog Handling
If (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole( [Security.Principal.WindowsBuiltInRole] "Administrator"))
{
��� Write-Warning "Not run as administrator, eventlog handling will be disabled."��� 
} ELSE {
  $skipeventlog = $false
  Try 
  { 
    Write-verbose "Check for Eventlog source"
    ( Get-EventLog -LogName Application -Source "GPObackup").count -ge 0 | Out-Null
  }
  Catch 
  {  
    Write-Verbose "Eventlog Source >GPObackup< not found. Try to create"
    "Eventlog Source >GPObackup< not found. Try to create" | Out-File $InfoLog -Append
    Try 
    {
      New-EventLog �LogName Application �Source �GPObackup�
    }
    catch 
    { 
      write-warning "Creating EventLog category failed, cancel eventlog handling"
      "Creating EventLog category failed, cancel eventlog handling"| Out-file $ErrorLog -Append 
      $skipeventlog = $true
    }
  }
  IF ( $skipeventlog -eq $false)
  {
    write-eventlog -logname Application -source "GPObackup" -EventId $EHID -EntryType $EHCategory -Message $Message 
    Write-verbose "Eventlog entry written."
    ### Add for verbose
    IF ($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent) { Get-EventLog -LogName Application -Source "GPObackup" -Newest 1 | FL }
  }
}

$after = Get-Date

$time = $after - $before
$buildTime = "`nBuild finished in ";
if ($time.Minutes -gt 0)
{
    $buildTime += "{0} minute(s) " -f $time.Minutes;
}

$buildTime += "{0} second(s)" -f $time.Seconds;
Write-verbose $buildTime
Write-verbose "Done. Have a nice day!"