-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathAD-HealthMonitor.ps1
477 lines (405 loc) · 14.7 KB
/
AD-HealthMonitor.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
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
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
<#
.SYNOPSIS
Active Directory Health Monitor and Maintenance Script.
.DESCRIPTION
This script:
- Performs comprehensive AD health checks
- Tests DC connectivity and critical services
- Checks AD replication status
- Monitors FSMO roles
- Validates DNS health
- Checks AD database and log files
- Identifies account issues (expired, locked, inactive)
- Monitors Group Policy status
- Generates detailed HTML report
- Optionally fixes common issues
.NOTES
Author: 13city
Compatible with: Windows Server 2012 R2, 2016, 2019, 2022
Requirements:
- ActiveDirectory PowerShell module
- Domain Controller role
- Enterprise Admin or Domain Admin rights
- Write access to report directory
- DNS Server role (for DNS health checks)
.PARAMETER DomainController
Target Domain Controller for health checks
Example: "DC01.contoso.local"
Default: Local computer name
.PARAMETER ReportPath
Full path where HTML report will be saved
Example: "C:\Reports\ADHealth.html"
Default: Desktop\AD-HealthReport.html
.PARAMETER MaxPasswordAge
Maximum allowed password age in days for account compliance checks
Used to identify accounts approaching password expiration
Default: 90 days
.PARAMETER InactiveDays
Number of days without logon to consider an account inactive
Used for dormant account detection and reporting
Default: 30 days
.PARAMETER FixIssues
Switch to automatically fix common issues found during health check
Includes: Unlocking accounts, enabling disabled accounts
Default: False (report only, no automatic fixes)
.EXAMPLE
.\AD-HealthMonitor.ps1
Runs health check on local Domain Controller with default settings:
- Uses local computer as Domain Controller
- Saves report to Desktop
- Reports issues without fixing them
- Uses default thresholds for password age and inactive accounts
.EXAMPLE
.\AD-HealthMonitor.ps1 -DomainController "DC01.contoso.local" -ReportPath "D:\Reports\ADHealth.html" -FixIssues
Runs comprehensive health check with custom settings:
- Targets specific Domain Controller
- Saves report to custom location
- Automatically fixes common issues
- Uses default thresholds
.EXAMPLE
.\AD-HealthMonitor.ps1 -MaxPasswordAge 60 -InactiveDays 45
Runs health check with custom compliance thresholds:
- Flags passwords older than 60 days
- Identifies accounts inactive for 45+ days
- Uses local computer as Domain Controller
- Reports issues without fixing them
#>
param (
[Parameter(Mandatory=$false)]
[string]$DomainController = $env:COMPUTERNAME,
[Parameter(Mandatory=$false)]
[string]$ReportPath = "$env:USERPROFILE\Desktop\AD-HealthReport.html",
[Parameter(Mandatory=$false)]
[int]$MaxPasswordAge = 90,
[Parameter(Mandatory=$false)]
[int]$InactiveDays = 30,
[Parameter(Mandatory=$false)]
[switch]$FixIssues
)
# Import required modules
Import-Module ActiveDirectory
# Function to test DC connectivity and services
function Test-DomainController {
param (
[string]$DCName
)
try {
$services = @(
"NTDS", # AD DS
"DFSR", # DFS Replication
"DNS", # DNS Server
"KDC", # Kerberos Key Distribution Center
"NetLogon" # Net Logon
)
$results = foreach ($service in $services) {
$svc = Get-Service -ComputerName $DCName -Name $service -ErrorAction SilentlyContinue
[PSCustomObject]@{
ServiceName = $service
Status = $svc.Status
StartType = $svc.StartType
}
}
# Test LDAP connectivity
$null = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DCName")
return $results
}
catch {
Write-Error "Failed to connect to Domain Controller $DCName : $_"
return $null
}
}
# Function to check AD replication status
function Get-ADReplicationStatus {
try {
$results = Get-ADReplicationPartnerMetadata -Target * -Scope Server |
Select-Object Server, Partner, LastReplicationSuccess, LastReplicationResult, ConsecutiveReplicationFailures
return $results
}
catch {
Write-Error "Failed to get replication status: $_"
return $null
}
}
# Function to check FSMO roles
function Get-FSMORoles {
try {
$roles = Get-ADDomain | Select-Object InfrastructureMaster, RIDMaster, PDCEmulator
$forestRoles = Get-ADForest | Select-Object DomainNamingMaster, SchemaMaster
return [PSCustomObject]@{
InfrastructureMaster = $roles.InfrastructureMaster
RIDMaster = $roles.RIDMaster
PDCEmulator = $roles.PDCEmulator
DomainNamingMaster = $forestRoles.DomainNamingMaster
SchemaMaster = $forestRoles.SchemaMaster
}
}
catch {
Write-Error "Failed to get FSMO roles: $_"
return $null
}
}
# Function to check DNS health
function Test-DNSHealth {
param (
[string]$DCName
)
try {
$results = @()
# Check DNS service
$dnsService = Get-Service -ComputerName $DCName -Name DNS
$results += [PSCustomObject]@{
Check = "DNS Service"
Status = $dnsService.Status
Details = "DNS Server service status"
}
# Check DNS zones
$zones = Get-DnsServerZone -ComputerName $DCName
foreach ($zone in $zones) {
$results += [PSCustomObject]@{
Check = "Zone: $($zone.ZoneName)"
Status = $zone.ZoneType
Details = "Records: $($zone.RecordCount)"
}
}
# Check SRV records
$dcSrvRecords = Get-DnsServerResourceRecord -ComputerName $DCName -ZoneName $env:USERDNSDOMAIN -RRType SRV
$results += [PSCustomObject]@{
Check = "DC SRV Records"
Status = if ($dcSrvRecords) { "Present" } else { "Missing" }
Details = "Count: $($dcSrvRecords.Count)"
}
return $results
}
catch {
Write-Error "Failed to check DNS health: $_"
return $null
}
}
# Function to check AD database and log files
function Test-ADDatabase {
param (
[string]$DCName
)
try {
$ntdsPath = Invoke-Command -ComputerName $DCName -ScriptBlock {
$ntdsParams = Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Parameters"
return $ntdsParams."DSA Database file"
}
$dbPath = Split-Path $ntdsPath -Parent
$results = Invoke-Command -ComputerName $DCName -ScriptBlock {
param($path)
$files = Get-ChildItem $path -Filter "*.dit*"
$logs = Get-ChildItem $path -Filter "*.log"
return [PSCustomObject]@{
DatabaseSize = (Get-Item "$path\ntds.dit").Length
LogFiles = $logs.Count
LogSize = ($logs | Measure-Object Length -Sum).Sum
FreeSpace = (Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Root -eq (Split-Path $path -Qualifier) }).Free
}
} -ArgumentList $dbPath
return $results
}
catch {
Write-Error "Failed to check AD database: $_"
return $null
}
}
# Function to check account issues
function Get-AccountIssues {
try {
$results = @()
# Check expired accounts
$expiredAccounts = Search-ADAccount -AccountExpired
$results += [PSCustomObject]@{
Check = "Expired Accounts"
Count = $expiredAccounts.Count
Details = "Accounts that have expired"
}
# Check locked accounts
$lockedAccounts = Search-ADAccount -LockedOut
$results += [PSCustomObject]@{
Check = "Locked Accounts"
Count = $lockedAccounts.Count
Details = "Accounts that are locked out"
}
# Check password expired
$pwdExpired = Search-ADAccount -PasswordExpired
$results += [PSCustomObject]@{
Check = "Password Expired"
Count = $pwdExpired.Count
Details = "Accounts with expired passwords"
}
# Check inactive accounts
$inactiveDate = (Get-Date).AddDays(-$InactiveDays)
$inactiveAccounts = Get-ADUser -Filter {LastLogonTimeStamp -lt $inactiveDate} -Properties LastLogonTimeStamp
$results += [PSCustomObject]@{
Check = "Inactive Accounts"
Count = $inactiveAccounts.Count
Details = "Accounts inactive for $InactiveDays days"
}
return $results
}
catch {
Write-Error "Failed to check account issues: $_"
return $null
}
}
# Function to check Group Policy status
function Test-GroupPolicy {
try {
$results = @()
# Get GPO status
$gpos = Get-GPO -All
foreach ($gpo in $gpos) {
$results += [PSCustomObject]@{
Name = $gpo.DisplayName
Status = $gpo.GpoStatus
UserVersion = $gpo.UserVersion
ComputerVersion = $gpo.ComputerVersion
CreationTime = $gpo.CreationTime
ModificationTime = $gpo.ModificationTime
}
}
return $results
}
catch {
Write-Error "Failed to check Group Policy status: $_"
return $null
}
}
# Function to fix common issues
function Repair-ADIssues {
param (
[object]$AccountIssues
)
try {
$fixed = @()
# Unlock accounts
$lockedAccounts = Search-ADAccount -LockedOut
foreach ($account in $lockedAccounts) {
Unlock-ADAccount -Identity $account.SamAccountName
$fixed += "Unlocked account: $($account.SamAccountName)"
}
# Enable inactive accounts that should be active
$inactiveDate = (Get-Date).AddDays(-$InactiveDays)
$inactiveAccounts = Get-ADUser -Filter {LastLogonTimeStamp -lt $inactiveDate -and Enabled -eq $false} -Properties LastLogonTimeStamp
foreach ($account in $inactiveAccounts) {
Enable-ADAccount -Identity $account.SamAccountName
$fixed += "Enabled account: $($account.SamAccountName)"
}
return $fixed
}
catch {
Write-Error "Failed to fix AD issues: $_"
return $null
}
}
# Function to generate HTML report
function New-HTMLReport {
param (
[object]$DCHealth,
[object]$ReplicationStatus,
[object]$FSMORoles,
[object]$DNSHealth,
[object]$DatabaseStatus,
[object]$AccountIssues,
[object]$GPOStatus,
[object]$FixedIssues
)
$html = @"
<!DOCTYPE html>
<html>
<head>
<title>Active Directory Health Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
table { border-collapse: collapse; width: 100%; margin-bottom: 20px; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #4CAF50; color: white; }
tr:nth-child(even) { background-color: #f2f2f2; }
h2 { color: #4CAF50; }
.warning { background-color: #fff3cd; }
.critical { background-color: #f8d7da; }
</style>
</head>
<body>
<h1>Active Directory Health Report - $(Get-Date -Format 'yyyy-MM-dd HH:mm')</h1>
<h2>Domain Controller Health</h2>
$($DCHealth | ConvertTo-Html -Fragment)
<h2>Replication Status</h2>
$($ReplicationStatus | ConvertTo-Html -Fragment)
<h2>FSMO Roles</h2>
$($FSMORoles | ConvertTo-Html -Fragment)
<h2>DNS Health</h2>
$($DNSHealth | ConvertTo-Html -Fragment)
<h2>Database Status</h2>
$($DatabaseStatus | ConvertTo-Html -Fragment)
<h2>Account Issues</h2>
$($AccountIssues | ConvertTo-Html -Fragment)
<h2>Group Policy Status</h2>
$($GPOStatus | ConvertTo-Html -Fragment)
$(if ($FixedIssues) {
"<h2>Fixed Issues</h2>
$($FixedIssues | ConvertTo-Html -Fragment)"
})
</body>
</html>
"@
return $html
}
# Main execution
try {
Write-Host "Starting Active Directory health check..."
# Check DC health
$dcHealth = Test-DomainController -DCName $DomainController
Write-Host "Domain Controller health check completed"
# Check replication
$replicationStatus = Get-ADReplicationStatus
Write-Host "Replication status check completed"
# Check FSMO roles
$fsmoRoles = Get-FSMORoles
Write-Host "FSMO roles check completed"
# Check DNS
$dnsHealth = Test-DNSHealth -DCName $DomainController
Write-Host "DNS health check completed"
# Check database
$databaseStatus = Test-ADDatabase -DCName $DomainController
Write-Host "Database check completed"
# Check accounts
$accountIssues = Get-AccountIssues
Write-Host "Account issues check completed"
# Check Group Policy
$gpoStatus = Test-GroupPolicy
Write-Host "Group Policy check completed"
# Fix issues if requested
$fixedIssues = $null
if ($FixIssues) {
$fixedIssues = Repair-ADIssues -AccountIssues $accountIssues
Write-Host "Issue remediation completed"
}
# Generate and save report
$report = New-HTMLReport -DCHealth $dcHealth `
-ReplicationStatus $replicationStatus `
-FSMORoles $fsmoRoles `
-DNSHealth $dnsHealth `
-DatabaseStatus $databaseStatus `
-AccountIssues $accountIssues `
-GPOStatus $gpoStatus `
-FixedIssues $fixedIssues
$report | Out-File -FilePath $ReportPath -Encoding UTF8
Write-Host "Health report generated successfully at: $ReportPath"
# Output critical warnings
if ($accountIssues.Where({$_.Check -eq "Locked Accounts"}).Count -gt 0) {
Write-Warning "There are locked accounts that need attention"
}
if ($replicationStatus.Where({$_.LastReplicationResult -ne 0}).Count -gt 0) {
Write-Warning "Replication errors detected"
}
$criticalServices = $dcHealth.Where({$_.Status -ne "Running"})
if ($criticalServices) {
Write-Warning "Critical services are not running: $($criticalServices.ServiceName -join ', ')"
}
}
catch {
Write-Error "An error occurred during health check: $_"
}