monitor-gpo.ps1
<#PSScriptInfo
.VERSION 1.4 .GUID a9a9268e-acf3-4972-8c29-a7480f409e63 .AUTHOR Nimal Raj .TAGS GroupPolicy,Automation,GPO,Backup .PROJECTURI https://github.com/Raj-GT/Windows-GroupPolicy-Monitor .EXTERNALMODULEDEPENDENCIES ActiveDirectory,GroupPolicy #> <# .SYNOPSIS Watch for Group Policy changes under monitored OU (and child OUs) and take automatic backups and optionally, alert via e-mail .DESCRIPTION When run (ideally on a recurring schedule via Task Scheduler) the script will check Group Policies linked under $watchedOU for changes and perform an automatic backup of new and changed policies. It will also generate individual HTML/XML reports of the policies and save it with the backups with an option to send a summary of changes via e-mail. Each set of backup is created under it's own folder and kept indefinitely. .INPUTS None .OUTPUTS None .LINK https://github.com/Raj-GT/Windows-GroupPolicy-Monitor https://www.experts-exchange.com/articles/30751/Automating-Group-Policy-Backups.html .NOTES Version: 1.4 Author: Nimal Raj Revisions: 19/07/2017 Initial draft (1.1) 20/07/2017 Published in PowerShell Gallery (1.3) 22/07/2017 Bug fix to enable backup of domain root policies (1.4) #> #Requires -Version 3.0 #---------------------------------------------------------[Modules]--------------------------------------------------------- Import-Module ActiveDirectory,GroupPolicy #--------------------------------------------------------[Variables]-------------------------------------------------------- $watchedOU = "DC=CORP,DC=CONTOSO,DC=COM" # Domain Root works as well $rootDN = "DC=CORP,DC=CONTOSO,DC=COM" # Required for our quick and dirty DN2Canonical function $domainname = "CORP.CONTOSO.COM" # Required for our quick and dirty DN2Canonical function $SMTP = "relay.contoso.com" # Assumes port TCP/25. Add -Port to Send-MailMessage if different $mailFrom = "donotreply@contoso.com" # From-address. For authenticated relays add -Credential to Send-MailMessage $alertRecipient = "windows-admins@contoso.com" # To-address. Leave empty to skip e-mail alerts $scriptPath = $PSScriptRoot # Change the default backup path if required $reportType = "HTML" # Valid options are HTML and XML $backupFolder = "$scriptPath\Backups\" + (get-date -Format "yyyy-MM-ddThhmmss") # E-mail template $mailbody = @' <style> body,p,h3 { font-family: calibri; } h3 { margin-bottom: 5px; } th { text-align: center; background: #003829; color: #FFF; padding: 5px; } td { padding: 5px 20px; } tr { background: #E7FFF9; } </style> <p>GPO Monitor has detected the following changes...</p> #mailcontent# '@ # No user variables beyond this point $ErrorActionPreference = "SilentlyContinue" $GPCurrent = @() $GPLast = $null $reportbody = $null #--------------------------------------------------------[Functions]-------------------------------------------------------- Function Backup ($GPO) { If ($GPO) { New-Item -Path $backupFolder -ItemType Directory; $GPO | Backup-GPO -Path $backupFolder; $GPO | ForEach-Object { Get-GPOReport -Name $_.PolicyName -ReportType $reportType -Path ("$backupFolder\"+$_.PolicyName+".$reportType") }; } } Function DN2Canon ($OUPath) { $Canon = $OUPath -Replace($rootDN,$domainname) -Replace("OU=","") -Split(",") [Array]::Reverse($Canon) $Canon = $Canon -Join "\" return($Canon) } #--------------------------------------------------------[Execution]-------------------------------------------------------- # Generate list of GPOs linked under $watchedOU $GPOLinks = (Get-ADOrganizationalUnit -SearchBase $watchedOU -Filter 'gpLink -gt "*"' | Get-GPInheritance).gpolinks # Grab policies linked at domain root (in case $watchedOU is domain root) If ($watchedOU.StartsWith("DC=")) { $GPOLinks += (Get-ADDomain | Get-GPInheritance).gpolinks } ForEach ($GPO in $GPOLinks) { $GPCurrent += New-Object -TypeName PSCustomObject -Property @{ PolicyName = (Get-GPO $GPO.GpoId).DisplayName; UpdateTime = (Get-GPO $GPO.GpoId).ModificationTime; Enabled = $GPO.Enabled; Guid = $GPO.GpoId; OU = DN2Canon($GPO.Target); } } # Load the list of GPOs from last run for comparison $GPLast = Import-Clixml -Path "$scriptPath\GPLast.xml" # If no list is available then assume first run, create the list, backup all GPOs under $watchedOU and generate HTML/XML reports If (!$GPLast -AND $GPCurrent) { $GPCurrent | Export-Clixml -Path "$scriptPath\GPLast.xml"; Backup($GPCurrent) } Else { # Let's compare the old list ($GPLast) to the current one ($GPCurrent) $GPList = $GPCurrent # Check for GPOs removed (guid missing from the current list) $RemovedGPO = Compare-Object $GPLast $GPList -Property Guid -PassThru | Where-Object {$_.SideIndicator -eq "<="} # Check for new GPOs (new guid in the current list) $NewGPO = Compare-Object $GPLast $GPList -Property Guid -PassThru | Where-Object {$_.SideIndicator -eq "=>"} # Remove the new GPO from the list before checking for changes (since new == change) $GPList = Compare-Object $GPList $NewGPO -Property Guid -PassThru # Check for changed GPOs $ChangedGPO = Compare-Object $GPLast $GPList -Property UpdateTime -PassThru | Where-Object {$_.SideIndicator -eq "=>"} # If anything has changed then create a backup (of new and changed GPOs), update GPLast.xml list and send -email If ($RemovedGPO -OR $NewGPO -OR $ChangedGPO) { $GPCurrent | Export-Clixml -Path "$scriptPath\GPLast.xml" -Force; Backup($NewGPO); Backup($ChangedGPO); # If $alertRecipient is not empty, then generate and send a summary of changes via e-mail If ($alertRecipient) { # Generate HTML tables for the report If ($NewGPO) { $reportbody += $NewGPO | ConvertTo-Html -Fragment -Property PolicyName,OU,UpdateTime -PreContent "<h3>Policies Added</h3>" } If ($ChangedGPO) { $reportbody += $ChangedGPO | ConvertTo-Html -Fragment -Property PolicyName,OU,UpdateTime -PreContent "<h3>Policies Updated</h3>" } If ($RemovedGPO) { $reportbody += $RemovedGPO | ConvertTo-Html -Fragment -Property PolicyName,OU,UpdateTime -PreContent "<h3>Policies Removed</h3>" } $mailbody = $mailbody.Replace("#mailcontent#",$reportbody) $mailbody = $mailbody.Replace("PolicyName","Policy Name") $mailbody = $mailbody.Replace("OU","Organizational Unit") $mailbody = $mailbody.Replace("UpdateTime","Update Time") Send-MailMessage -SmtpServer $SMTP -To $alertRecipient -From $mailFrom -Subject "Group Policy Monitor" -Body $mailbody -BodyAsHtml } } } |