-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathGPOMagnifier
456 lines (421 loc) · 20.6 KB
/
GPOMagnifier
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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
#========================================================================
# GPO Magnifier Tool
# Created by: Micky Balladelli
#
# This script goes through all the GPOs in a domain and verifies them according to
# best practices.
#
# It checks:
# - Unlinked GPOs
# - GPO links that are disabled
# - Disabled GPO
# - Empty GPOs
# - Enabled GPOs without settings
# - WMI filters used in GPOs, WMI filter info is retrieved such as name, author, and code
# - GPOs with tombstone owners
#
#
# Usage:
#
# 1. Enter the name of the domain you want to check in the domain text box
# (defaults to the current domain)
# 2. Enter a server if you want to query a particular domain controller [optional]
# Click Check
#
# 3. A result is displayed in the output text window.
#
# 4. Click on Export to CSV if you want to export the results found
#
# 5. Click on Find, to generate a powershell script with the fixes.
# Note that not all issues are fixed:
# a. Empty GPOs will be removed
# b. GPO without links will be removed
# c. Disabled links will be deleted
# d. Empty User configuration settings will set the GPO status accordingly
# e. Empty Computer configuration settings will set the GPO status accordingly
#
# Disclaimer: code is provided as is, without support. Use at your own risk.
#
#========================================================================
# Import the necessary modules
Add-Type -AssemblyName System.Windows.Forms
try {
import-module -Name grouppolicy -ErrorAction Stop
} catch {
# the grouppolicy module requires at least Windows 7 or Windows Server 2008.
$null = [Windows.Forms.MessageBox]::Show($_.Exception.Message, 'Error')
}
#define a newline variable, it will be handy in the multiline textbox
$nl = "`r`n"
# save the location of the current script
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path -Path $scriptpath
#region Import the Assemblies
Add-Type -AssemblyName System.Windows.Forms | Out-Null
Add-Type -AssemblyName System.Drawing | Out-Null
#endregion
$FormEvent_Load = {Add-Type -AssemblyName System.Windows.Forms | Out-Null}
Function Get-ReportGPO {
[CmdletBinding()]
Param($GPO, $GPOreport, [string]$issue, $issueID, $WMI)
$linkArray = @()
$disLinkArray = @()
ForEach ($link in $GPOReport.GPO.LinksTo){If ($link -ne $null){If ($link.Enabled -eq $true){$linkArray+=$link.SOMPath} else {$disLinkArray+=$link.SOMPath}}}
$linkString = "$linkArray"
$disLinkString = "$disLinkArray"
$retReport = New-Object -TypeName PSCustomObject -Property @{
Name = $GPO.Displayname
Links = $linkString
DisabledLinks = $disLinkString
DisabledLinkArray = $disLinkArray
GUID = $GPO.Id
IssueFound = $issue
IssueID = $issueID
CreationTime = $GPO.CreationTime
ModificationTime = $GPO.ModificationTime
GPOStatus = $GPO.GpoStatus
WMIFilter = $GPO.WMIFilter.Name
WMIdata = $WMI
Owner = $GPO.Owner
Description = $GPO.Description
Path = $GPO.Path
}
Return $retReport
}
# the following function's code is from Glenn Sizemore to convert a CN to DN
Function ConvertFrom-Canonical {
[CmdletBinding()]
Param([string]$canoincal = (throw "$Canonical is required!"))
$obj = $canoincal.Replace(',','\,').Split('/')
[string]$DN = 'CN=' + $obj[$obj.count - 1]
For ($i = $obj.count - 2;$i -ge 1;$i--){$DN += ',OU=' + $obj[$i]}
$obj[0].split('.') | ForEach-Object {$DN += ',DC=' + $_}
Return $dn
}
$form = New-Object -TypeName System.Windows.Forms.Form
#Get AD Domain
$DomainDNS = [DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().Name
$btnFind_OnClick = {
$text = $textOut.Text
If ($text.Length -eq 0){
$null = [Windows.Forms.MessageBox]::Show('Nothing to fix', 'Error')
Return
}
$dialog = New-Object -TypeName System.Windows.Forms.SaveFileDialog
$dialog.Filter = 'All Files (*.ps1)|*.ps1'
$dialog.ShowHelp = $true
$dialog.InitialDirectory = $dir
$dialog.Title = 'Save script to file'
$res = $dialog.ShowDialog()
If ($res -eq 'OK') {
$fileout = $dialog.FileName
$textOut.AppendText('Generating script...')
$textOut.AppendText($nl)
# Create the file
'# File generated by GPO Magnifier tool.' | Out-File -FilePath $fileout
'import-module grouppolicy -ErrorAction Stop' | Out-File -FilePath $fileout -Append
$nl | Out-File -FilePath $fileout -Append
# Generate the script
# Start by doing the "light stuff", i.e. anything that is not removing the GPO
ForEach ($element in $report){
Switch ($element.IssueID) {
'2' { # Remove disabled Links
ForEach ($link in $element.DisabledLinkArray) {
If ($link -ne $null) {
If ($link.Enabled -eq $false) {
"# `""+$element.Name+"`""+' disabled link '+ $DN + ' will be removed' | Out-File -FilePath $fileout -Append
('Remove-GPLink -guid '+$element.guid+' -target '+"`""+$DN+"`"") | Out-File -FilePath $fileout -Append
$nl | Out-File -FilePath $fileout -Append
$DN = ConvertFrom-Canonical -canoincal ($element.DisabledLinks)
$textOut.AppendText("`""+$element.Name+"`""+' disabled link '+ $DN + ' will be removed')
$textOut.AppendText($nl)
$textOut.AppendText('Remove-GPLink -guid '+$element.guid+' -target '+"`""+$DN+"`"")
$textOut.AppendText($nl)
}
}
}
}
'3' { # Computer settings disabled
"# `""+$element.Name+"`""+' status will change from ' + $element.GPOStatus +' to ComputerSettingsDisabled' | Out-File -FilePath $fileout -Append
'(get-gpo -guid '+$element.guid+").gpostatus = `"ComputerSettingsDisabled`"" | Out-File -FilePath $fileout -Append
$nl | Out-File -FilePath $fileout -Append
$textOut.AppendText("`""+$element.Name+"`""+' status will change from ' + $element.GPOStatus +' to ComputerSettingsDisabled')
$textOut.AppendText($nl)
$textOut.AppendText('(get-gpo -guid '+$element.guid+").gpostatus = `"ComputerSettingsDisabled`"")
$textOut.AppendText($nl)
}
'4' { # User settings disabled
"# `""+$element.Name+"`""+' status will change from ' + $element.GPOStatus +' to UserSettingsDisabled' | Out-File -FilePath $fileout -Append
'(get-gpo -guid '+$element.guid+").gpostatus = `"UserSettingsDisabled`"" | Out-File -FilePath $fileout -Append
$nl | Out-File -FilePath $fileout -Append
$textOut.AppendText("`""+$element.Name+"`""+' status will change from ' + $element.GPOStatus +' to UserSettingsDisabled')
$textOut.AppendText($nl)
$textOut.AppendText('(get-gpo -guid '+$element.guid+").gpostatus = `"UserSettingsDisabled`"")
$textOut.AppendText($nl)
}
default {
# do nothing
}
}
}
# Now generate all the remove GPO scripts
ForEach ($element in $report) {
Switch ($element.IssueID) {
# Remove the GPO
'1' {
"# `""+$element.Name+"`""+' will be deleted. ' + $element.IssueFound | Out-File -FilePath $fileout -Append
'remove-gpo -guid ' + $element.guid | Out-File -FilePath $fileout -Append
$nl | Out-File -FilePath $fileout -Append
$textOut.AppendText("`""+$element.Name+"`""+' will be deleted. ' + $element.IssueFound)
$textOut.AppendText($nl)
$textOut.AppendText('remove-gpo -guid '+$element.guid)
$textOut.AppendText($nl)
}
}
}
}
}
$btnSave_OnClick = {
$text = $textOut.Text
If ($text.Length -eq 0){
$null = [Windows.Forms.MessageBox]::Show('Nothing to save', 'Error')
Return
}
$dialog = New-Object -TypeName System.Windows.Forms.SaveFileDialog
$dialog.Filter = 'All Files (*.csv)|*.csv'
$dialog.InitialDirectory = $dir
$dialog.ShowHelp = $true
$dialog.Title = 'Save Audit to file'
$res = $dialog.ShowDialog()
If ($res -eq 'OK'){
$fileout = $dialog.FileName
$report | Select-Object -Property Name,
IssueFound,
Links,
DisabledLinks,
GPOStatus,
CreationTime,
ModificationTime,
WMIFilter,
WMIdata,
Owner,
Path |
Export-CSV -Path $fileout -NoTypeInformation
}
}
$btnCheck_OnClick = {
$domainName = $textDomain.Text
$serverName = $textServer.Text
$report = @()
$GPOPolicies = @()
$textOut.Text = ''
[int]$count = 0
# Get all GPOs from the given domain
Try {
If ($serverName.Length -eq 0){
[array]$GPOPolicies = Get-GPO -domain $domainName -All
} else {
[array]$GPOPolicies = Get-GPO -domain $domainName -Server $serverName -All
}
} Catch {
[Windows.Forms.MessageBox]::Show($_.Exception.Message, 'Error') | Out-Null
Return
}
ForEach ($GPO in $GPOPolicies) {
$status.Text = 'Domain ' + $domainName + ' contains ' + $GPOPolicies.Count + ' GPOs. Retrieving GPO detailed information...' + $count + '/' + $GPOPolicies.Count
If ($serverName.Length -eq 0){
[xml]$GPOReport = (Get-GPOReport -guid $GPO.Id -ReportType xml -Domain $domainName)
} else {
[xml]$GPOReport = (Get-GPOReport -guid $GPO.Id -ReportType xml -Domain $domainName -Server $serverName)
}
$count++
# test if the GPO's is linked
If ($GPOReport.gpo.LinksTo -eq $null){
$report += Get-ReportGPO -GPO $GPO -GPOreport $GPOReport -issue 'GPO has no links' -issueID 1
Continue ### no point in checking the other points. This GPO is not used
} else {
# test if the GPO links are disabled
If (-not (($GPOReport.GPO.LinksTo | ForEach-Object {$_.Enabled}) -eq $true)) {
$report += Get-ReportGPO -GPO $GPO -GPOreport $GPOReport -issue 'All GPO links are disabled' -issueID 1
} else {
If (($GPOReport.GPO.LinksTo | ForEach-Object {$_.Enabled}) -eq $false){
$report += Get-ReportGPO -GPO $GPO -GPOreport $GPOReport -issue 'Some GPO links are disabled' -issueID 2
}
}
}
# test if the GPO's has no settings
If (!$GPOReport.gpo.Computer.ExtensionData -and !$GPOReport.GPO.User.ExtensionData){
$report += Get-ReportGPO -GPO $GPO -GPOreport $GPOReport -issue 'GPO is empty' -issueID 1
Continue # no point in checking the other points. This GPO is empty
} else {
# test if the extension is empty and the GPO is enabled
If (!$GPOReport.gpo.Computer.ExtensionData -and $GPO.GpoStatus -eq 'ComputerSettingsEnabled'){
$report += Get-ReportGPO -GPO $GPO -GPOreport $GPOReport -issue 'Computer settings enabled without data' -issueID 3
}
If (!$GPOReport.gpo.Computer.ExtensionData -and $GPO.GpoStatus -eq 'AllSettingsEnabled'){
$report += Get-ReportGPO -GPO $GPO -GPOreport $GPOReport -issue 'Computer settings enabled without data' -issueID 3
}
If (!$GPOReport.gpo.User.ExtensionData -and $GPO.GpoStatus -eq 'UserSettingsEnabled'){
$report += Get-ReportGPO -GPO $GPO -GPOreport $GPOReport -issue 'User settings enabled without data' -issueID 4
}
If (!$GPOReport.gpo.User.ExtensionData -and $GPO.GpoStatus -eq 'AllSettingsEnabled'){
$report += Get-ReportGPO -GPO $GPO -GPOreport $GPOReport -issue 'User settings enabled without data' -issueID 4
}
}
# test if the GPO is disabled
If ($GPO.GpoStatus -eq 'AllSettingsDisabled'){
$report += Get-ReportGPO -GPO $GPO -GPOreport $GPOReport -issue 'GPO is disabled' -issueID 1
}
# test if the GPO's has a WMI filter
$result = @{}
If ($GPO.WMIFilter.Name.Length -gt 0){
# let's find what this filter does
$wmiFilterAttr = 'msWMI-Name', 'msWMI-Parm1', 'msWMI-Parm2', 'msWMI-Author', 'msWMI-ID'
$search = New-Object -TypeName System.DirectoryServices.DirectorySearcher -ArgumentList ([ADSI]('LDAP://' + $domainName))
$wminame = $GPO.WMIFilter.Name
$search.Filter = "(&(objectClass=msWMI-Som)(msWMI-Name=$wminame))"
$search.PropertiesToLoad.AddRange($wmiFilterAttr)
$result = $search.FindOne()
$WMI = New-Object -TypeName PSCustomObject -Property @{
Name = [string]$result.Properties['mswmi-name']
Parm1 = [string]$result.Properties['mswmi-parm1']
Parm2 = [string]$result.Properties['mswmi-parm2']
Author = [string]$result.Properties['mswmi-author']
ID = [string]$result.Properties['mswmi-ID']
}
$report += Get-ReportGPO -GPO $GPO -GPOreport $GPOReport -issue 'WMI filter found' -issueID 0 -WMI $WMI
}
# test if the GPO's owner is a tumbstone
$GPOowner = $GPO.Owner | Out-String
If ($GPOowner.Length -eq 0){
$report += Get-ReportGPO -GPO $GPO -GPOreport $GPOReport -issue 'Owner not found' -issueID 0
} else {
If ($GPOowner.StartsWith('S-') -eq $true){
$report += Get-ReportGPO -GPO $GPO -GPOreport $GPOReport -issue 'Owner is a tombstone' -issueID 0
}
}
}
$status.Text = 'Domain ' + $domainName + ' contains ' + $GPOPolicies.Count + ' GPOs. Analysis done'
$textOut.AppendText(($report | Select-Object -Property IssueFound, Name, Links, DisabledLinks, GUID, GPOStatus, WMIFilter, WMIdata, Description | Out-String))
$textOut.AppendText($nl)
$textOut.AppendText($report.Count)
$textOut.AppendText(' issues were found in a total of ')
$textOut.AppendText($GPOPolicies.Count)
$textOut.AppendText(' GPOs' )
$textOut.AppendText($nl)
}
# Add a label
$label = New-Object -TypeName System.Windows.Forms.Label
$label.Text = 'Domain:'
$label.Location = New-Object -TypeName System.Drawing.Point -ArgumentList (3,5)
$label.Size = New-Object -TypeName System.Drawing.Size -ArgumentList (50, 20)
$form.Controls.Add($label)
# Add the domain input textbox
$textDomain = New-Object -TypeName System.Windows.Forms.TextBox
$textDomain.TabIndex = 1
$textDomain.Size = New-Object -TypeName System.Drawing.Size -ArgumentList (130, 30)
$textDomain.Text = "$DomainDNS"
$textDomain.Location = New-Object -TypeName System.Drawing.Point -ArgumentList (55, 3)
$form.Controls.Add($textDomain)
# Add another label
$label = New-Object -TypeName System.Windows.Forms.Label
$label.Text = 'Server:'
$label.Location = New-Object -TypeName System.Drawing.Point -ArgumentList (190,5)
$label.Size = New-Object -TypeName System.Drawing.Size -ArgumentList (50, 20)
$form.Controls.Add($label)
# Add the server input textbox
$textServer = New-Object -TypeName System.Windows.Forms.TextBox
$textServer.TabIndex = 2
$textServer.Size = New-Object -TypeName System.Drawing.Size -ArgumentList (130, 30)
$textServer.Location = New-Object -TypeName System.Drawing.Point -ArgumentList (238, 3)
$form.Controls.Add($textServer)
# Add the output text box
$textOut = New-Object -TypeName System.Windows.Forms.TextBox
$textOut.TabIndex = 6
$textOut.Size = New-Object -TypeName System.Drawing.Size -ArgumentList (605, 382)
$textOut.Location = New-Object -TypeName System.Drawing.Point -ArgumentList (1, 30)
$textOut.Multiline = $true
$textOut.ScrollBars = 'Vertical'
$textOut.Font = 'Courier New, 10pt'
$form.Controls.Add($textOut)
# Add the Check button
$btnCheck = New-Object -TypeName System.Windows.Forms.Button
$btnCheck.TabIndex = 3
$btnCheck.Name = 'btnCheck'
$btnCheck.Size = New-Object -TypeName System.Drawing.Size -ArgumentList (75,23)
$btnCheck.UseVisualStyleBackColor = $True
$btnCheck.Font = $FontSans825B
$btnCheck.Text = 'Check'
$btnCheck.Location = New-Object -TypeName System.Drawing.Point -ArgumentList (370,3)
$btnCheck.DataBindings.DefaultDataSourceUpdateMode = 0
$btnCheck.add_Click($btnCheck_OnClick)
$form.Controls.Add($btnCheck)
# Add the Save button
$btnSave = New-Object -TypeName System.Windows.Forms.Button
$btnSave.TabIndex = 4
$btnSave.Name = 'btnSave'
$btnSave.Size = New-Object -TypeName System.Drawing.Size -ArgumentList (75,23)
$btnSave.UseVisualStyleBackColor = $True
$btnSave.Font = $FontSans825B
$btnSave.Text = 'Export CSV'
$btnSave.Location = New-Object -TypeName System.Drawing.Point -ArgumentList (448,3)
$btnSave.DataBindings.DefaultDataSourceUpdateMode = 0
$btnSave.add_Click($btnSave_OnClick)
$form.Controls.Add($btnSave)
# Create the Find it button
$btnFind = New-Object -TypeName System.Windows.Forms.Button
$btnFind.TabIndex = 5
$btnFind.Name = 'btnFind'
$btnFind.Size = New-Object -TypeName System.Drawing.Size -ArgumentList (75,23)
$btnFind.UseVisualStyleBackColor = $True
$btnFind.Font = $FontSans825B
$btnFind.Text = 'Find'
$btnFind.Location = New-Object -TypeName System.Drawing.Point -ArgumentList (526,3)
$btnFind.DataBindings.DefaultDataSourceUpdateMode = 0
$btnFind.add_Click($btnFind_OnClick)
$form.Controls.Add($btnFind)
# Create the statusBar
$statusStrip = New-Object -TypeName System.Windows.Forms.StatusStrip
$statusStrip.Location = New-Object -TypeName System.Drawing.Point -ArgumentList (0, 463)
$statusStrip.Name = 'statusStrip'
$statusStrip.Size = New-Object -TypeName System.Drawing.Size -ArgumentList (664, 22)
$status = New-Object -TypeName System.Windows.Forms.ToolStripStatusLabel
$status.Text = ''
$null = $statusStrip.Items.add($status)
$form.Controls.Add($statusStrip)
# Set initial dialog size and title
$form.Size = New-Object -TypeName System.Drawing.Size -ArgumentList (620,475)
$form.Text = 'GPO Magnifier Tool'
$OnResize = {
$system_drawing_size = New-Object -TypeName System.Drawing.Size
$system_drawing_size.Width = $form.Width - 15
$system_drawing_size.Height = $form.Height - 93
$textOut.Size = $system_drawing_size
}
$form.add_Resize($OnResize)
# change that ugly default icon
$code = @"
using System;
using System.Drawing;
using System.Runtime.InteropServices;
namespace System {
public class IconExtractor {
public static Icon Extract(string file, int number, bool largeIcon) {
IntPtr large;
IntPtr small;
ExtractIconEx(file, number, out large, out small, 1);
try {
return Icon.FromHandle(largeIcon ? large : small);
} catch {
return null;
}
}
[DllImport("Shell32.dll", EntryPoint = "ExtractIconExW", CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
private static extern int ExtractIconEx(string sFile, int iIndex, out IntPtr piLargeVersion, out IntPtr piSmallVersion, int amountIcons);
}
}
"@
Add-Type -TypeDefinition $code -ReferencedAssemblies System.Drawing | Out-Null
$form.Icon = [IconExtractor]::Extract('shell32.dll', 22, $true)
#Show the Form
$form.ShowDialog() | Out-Null