monitor-gpo.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
<#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
        }
    }
}