Overview
Effective monitoring and logging are critical for maintaining Active Directory health, security, and performance. This guide covers comprehensive monitoring strategies that include performance metrics, security events, service health, and compliance auditing with automated alerting and response capabilities.
This comprehensive guide provides enterprise-level monitoring, logging, and auditing strategies for Active Directory environments with modern security practices, automation techniques, and integration with enterprise monitoring solutions.
Prerequisites
Technical Requirements
- Windows Server 2019 or later (Windows Server 2022 recommended)
- PowerShell 5.1 or later with ActiveDirectory module
- Enterprise monitoring solution (SCOM, Nagios, SolarWinds, or similar)
- SIEM solution for security event correlation
- Sufficient storage for log retention (minimum 90 days recommended)
Planning Requirements
- Monitoring requirements and baseline performance metrics defined
- Security event monitoring and alerting thresholds established
- Compliance requirements identified (SOX, HIPAA, PCI-DSS, etc.)
- Log retention and archival policies defined
- Incident response procedures documented
Security Requirements
- Least privilege access for monitoring accounts
- Secure log transmission and storage
- Event correlation and threat detection capabilities
- Compliance audit trail requirements
- Data protection and privacy considerations
Monitoring Architecture
Core Components
- Performance Monitoring: System metrics, service health, and resource utilization
- Security Event Monitoring: Authentication events, security policy changes, and threat detection
- Service Health Monitoring: Critical AD services availability and functionality
- Replication Monitoring: Inter-site and intra-site replication health
- Capacity Planning: Growth trends and resource forecasting
Integration Points
- SIEM Integration: Security event correlation and threat detection
- Enterprise Monitoring: Performance metrics and service availability
- Cloud Services: Azure AD Connect, hybrid scenarios
- Backup Systems: Backup success/failure monitoring
- Change Management: Configuration change tracking
Performance Monitoring
Critical Performance Counters
# Comprehensive AD performance monitoring script
function Start-ADPerformanceMonitoring {
param(
[string[]]$DomainControllers = (Get-ADDomainController -Filter *).Name,
[int]$SampleInterval = 60,
[int]$MonitoringDuration = 3600,
[string]$LogPath = "C:\Monitoring\ADPerformance",
[hashtable]$Thresholds = @{
'CPU' = 80
'Memory' = 85
'DiskQueue' = 2
'LDAPBinds' = 1000
'KerberosAuth' = 500
}
)
# Ensure log directory exists
if (-not (Test-Path $LogPath)) {
New-Item -Path $LogPath -ItemType Directory -Force
}
$PerformanceCounters = @(
# System Performance
'\Processor(_Total)\% Processor Time',
'\Memory\% Committed Bytes In Use',
'\PhysicalDisk(_Total)\Avg. Disk Queue Length',
'\System\Processor Queue Length',
# Active Directory Specific
'\NTDS\LDAP Successful Binds/sec',
'\NTDS\LDAP Searches/sec',
'\NTDS\DS Directory Reads/sec',
'\NTDS\DS Directory Writes/sec',
'\NTDS\DRA Pending Replication Synchronizations',
'\NTDS\DS Threads in Use',
# Kerberos Authentication
'\Security System-Wide Statistics\Kerberos Authentications',
'\Security System-Wide Statistics\KDC AS Requests',
'\Security System-Wide Statistics\KDC TGS Requests',
# Database Performance
'\Database ==> Instances(lsass/NTDSA)\Database Cache % Hit',
'\Database ==> Instances(lsass/NTDSA)\Database Cache Size (MB)',
'\Database ==> Instances(lsass/NTDSA)\Database Page Fault Stalls/sec',
# Network Performance
'\Network Interface(*)\Bytes Total/sec',
'\Network Interface(*)\Packets/sec'
)
foreach ($DC in $DomainControllers) {
Start-Job -Name "Monitor-$DC" -ScriptBlock {
param($DC, $Counters, $Interval, $Duration, $LogPath, $Thresholds)
$EndTime = (Get-Date).AddSeconds($Duration)
$LogFile = Join-Path $LogPath "ADPerf_$DC_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
while ((Get-Date) -lt $EndTime) {
try {
$PerfData = Get-Counter -ComputerName $DC -Counter $Counters -SampleInterval $Interval -MaxSamples 1
foreach ($Sample in $PerfData.CounterSamples) {
$Result = [PSCustomObject]@{
Timestamp = $Sample.Timestamp
Computer = $DC
Counter = $Sample.Path
Value = $Sample.CookedValue
Instance = $Sample.InstanceName
}
# Check thresholds and generate alerts
$CounterName = ($Sample.Path -split '\\')[-1]
switch -Wildcard ($CounterName) {
'*Processor Time*' {
if ($Sample.CookedValue -gt $Thresholds.CPU) {
Write-Warning "HIGH CPU on $DC`: $($Sample.CookedValue)%"
}
}
'*Committed Bytes*' {
if ($Sample.CookedValue -gt $Thresholds.Memory) {
Write-Warning "HIGH MEMORY on $DC`: $($Sample.CookedValue)%"
}
}
'*Queue Length*' {
if ($Sample.CookedValue -gt $Thresholds.DiskQueue) {
Write-Warning "HIGH DISK QUEUE on $DC`: $($Sample.CookedValue)"
}
}
}
$Result | Export-Csv -Path $LogFile -Append -NoTypeInformation
}
}
catch {
Write-Error "Failed to collect performance data from $DC`: $($_.Exception.Message)"
}
Start-Sleep -Seconds $Interval
}
} -ArgumentList $DC, $PerformanceCounters, $SampleInterval, $MonitoringDuration, $LogPath, $Thresholds
}
Write-Host "Performance monitoring started for $($DomainControllers.Count) domain controllers" -ForegroundColor Green
Write-Host "Monitoring duration: $($MonitoringDuration/60) minutes" -ForegroundColor Green
Write-Host "Log path: $LogPath" -ForegroundColor Green
}
# Advanced AD health monitoring with automated alerting
function Test-ADHealthMetrics {
param(
[string[]]$DomainControllers = (Get-ADDomainController -Filter *).Name,
[string]$AlertEmail = $null,
[string]$SMTPServer = $null
)
$HealthResults = @()
foreach ($DC in $DomainControllers) {
Write-Host "Checking health metrics for $DC..." -ForegroundColor Blue
try {
# Service Status Check
$Services = @(
'NTDS', 'DNS', 'Netlogon', 'W32Time', 'DFSR',
'KDC', 'EventLog', 'RpcSs', 'RPCSS'
)
$ServiceStatus = foreach ($Service in $Services) {
$Svc = Get-Service -ComputerName $DC -Name $Service -ErrorAction SilentlyContinue
[PSCustomObject]@{
Service = $Service
Status = if ($Svc) { $Svc.Status } else { 'NotFound' }
StartType = if ($Svc) { $Svc.StartType } else { 'Unknown' }
}
}
# Replication Status
$ReplStatus = Get-ADReplicationPartnerMetadata -Target $DC -Scope Domain
$ReplIssues = $ReplStatus | Where-Object {
$_.LastReplicationSuccess -lt (Get-Date).AddHours(-2) -or
$_.ConsecutiveReplicationFailures -gt 0
}
# Event Log Errors (last 24 hours)
$EventErrors = Get-WinEvent -ComputerName $DC -FilterHashtable @{
LogName = 'Directory Service', 'System', 'Application'
Level = 1, 2 # Critical and Error
StartTime = (Get-Date).AddDays(-1)
} -MaxEvents 100 -ErrorAction SilentlyContinue
# Disk Space Check
$DiskSpace = Get-WmiObject -ComputerName $DC -Class Win32_LogicalDisk | Where-Object {
$_.DriveType -eq 3 # Fixed disk
} | Select-Object DeviceID,
@{Name='SizeGB';Expression={[math]::Round($_.Size/1GB,2)}},
@{Name='FreeGB';Expression={[math]::Round($_.FreeSpace/1GB,2)}},
@{Name='PercentFree';Expression={[math]::Round(($_.FreeSpace/$_.Size)*100,2)}}
$LowDiskSpace = $DiskSpace | Where-Object { $_.PercentFree -lt 20 }
# Compile health summary
$HealthSummary = [PSCustomObject]@{
DomainController = $DC
Timestamp = Get-Date
ServicesDown = ($ServiceStatus | Where-Object { $_.Status -ne 'Running' }).Count
ReplicationIssues = $ReplIssues.Count
CriticalEvents = $EventErrors.Count
LowDiskSpaceCount = $LowDiskSpace.Count
OverallHealth = 'Healthy'
Issues = @()
}
# Determine overall health
if ($HealthSummary.ServicesDown -gt 0) {
$HealthSummary.OverallHealth = 'Critical'
$HealthSummary.Issues += "Services down: $($HealthSummary.ServicesDown)"
}
if ($HealthSummary.ReplicationIssues -gt 0) {
$HealthSummary.OverallHealth = 'Warning'
$HealthSummary.Issues += "Replication issues: $($HealthSummary.ReplicationIssues)"
}
if ($HealthSummary.CriticalEvents -gt 10) {
$HealthSummary.OverallHealth = 'Warning'
$HealthSummary.Issues += "High error count: $($HealthSummary.CriticalEvents)"
}
if ($HealthSummary.LowDiskSpaceCount -gt 0) {
$HealthSummary.OverallHealth = 'Warning'
$HealthSummary.Issues += "Low disk space on $($HealthSummary.LowDiskSpaceCount) drives"
}
$HealthResults += $HealthSummary
# Send alert if issues found
if ($HealthSummary.OverallHealth -ne 'Healthy' -and $AlertEmail -and $SMTPServer) {
$Subject = "AD Health Alert - $DC - $($HealthSummary.OverallHealth)"
$Body = "Domain Controller: $DC`n"
$Body += "Status: $($HealthSummary.OverallHealth)`n"
$Body += "Issues: $($HealthSummary.Issues -join ', ')`n"
$Body += "Timestamp: $(Get-Date)`n"
Send-MailMessage -To $AlertEmail -Subject $Subject -Body $Body -SmtpServer $SMTPServer
}
}
catch {
Write-Error "Failed to check health for $DC`: $($_.Exception.Message)"
}
}
return $HealthResults
}
Performance Baseline Establishment
# Establish performance baselines for capacity planning
function New-ADPerformanceBaseline {
param(
[string[]]$DomainControllers = (Get-ADDomainController -Filter *).Name,
[int]$BaselineDays = 30,
[string]$ReportPath = "C:\Reports\AD_Baseline_$(Get-Date -Format 'yyyyMMdd').html"
)
$BaselineData = @()
foreach ($DC in $DomainControllers) {
Write-Host "Collecting baseline data for $DC..." -ForegroundColor Blue
# Collect key performance metrics
$Metrics = @{
'CPUAverage' = (Get-Counter -ComputerName $DC -Counter '\Processor(_Total)\% Processor Time' -SampleInterval 300 -MaxSamples 288 |
ForEach-Object { $_.CounterSamples.CookedValue } | Measure-Object -Average).Average
'MemoryAverage' = (Get-Counter -ComputerName $DC -Counter '\Memory\% Committed Bytes In Use' -SampleInterval 300 -MaxSamples 288 |
ForEach-Object { $_.CounterSamples.CookedValue } | Measure-Object -Average).Average
'LDAPBindsPerSec' = (Get-Counter -ComputerName $DC -Counter '\NTDS\LDAP Successful Binds/sec' -SampleInterval 300 -MaxSamples 288 |
ForEach-Object { $_.CounterSamples.CookedValue } | Measure-Object -Average).Average
'KerberosAuthPerSec' = (Get-Counter -ComputerName $DC -Counter '\Security System-Wide Statistics\Kerberos Authentications' -SampleInterval 300 -MaxSamples 288 |
ForEach-Object { $_.CounterSamples.CookedValue } | Measure-Object -Average).Average
}
$BaselineData += [PSCustomObject]@{
DomainController = $DC
BaselineDate = Get-Date
CPUBaseline = [math]::Round($Metrics.CPUAverage, 2)
MemoryBaseline = [math]::Round($Metrics.MemoryAverage, 2)
LDAPBindsBaseline = [math]::Round($Metrics.LDAPBindsPerSec, 2)
KerberosAuthBaseline = [math]::Round($Metrics.KerberosAuthPerSec, 2)
RecommendedCPUThreshold = [math]::Round($Metrics.CPUAverage * 1.5, 2)
RecommendedMemoryThreshold = [math]::Round($Metrics.MemoryAverage * 1.2, 2)
}
}
# Generate baseline report
$Html = @"
<!DOCTYPE html>
<html>
<head>
<title>Active Directory Performance Baseline Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
.header { background-color: #4CAF50; color: white; padding: 10px; }
</style>
</head>
<body>
<div class="header">
<h1>Active Directory Performance Baseline Report</h1>
<p>Generated: $(Get-Date)</p>
<p>Baseline Period: $BaselineDays days</p>
</div>
<h2>Performance Baselines</h2>
<table>
<tr>
<th>Domain Controller</th>
<th>CPU Baseline (%)</th>
<th>Memory Baseline (%)</th>
<th>LDAP Binds/sec</th>
<th>Kerberos Auth/sec</th>
<th>Recommended CPU Threshold</th>
<th>Recommended Memory Threshold</th>
</tr>
"@
foreach ($Data in $BaselineData) {
$Html += @"
<tr>
<td>$($Data.DomainController)</td>
<td>$($Data.CPUBaseline)</td>
<td>$($Data.MemoryBaseline)</td>
<td>$($Data.LDAPBindsBaseline)</td>
<td>$($Data.KerberosAuthBaseline)</td>
<td>$($Data.RecommendedCPUThreshold)</td>
<td>$($Data.RecommendedMemoryThreshold)</td>
</tr>
"@
}
$Html += @"
</table>
<h2>Monitoring Recommendations</h2>
<ul>
<li>Set CPU alerts at recommended threshold levels</li>
<li>Monitor memory usage trends for capacity planning</li>
<li>Track authentication patterns for performance optimization</li>
<li>Review baselines monthly and adjust thresholds accordingly</li>
</ul>
</body>
</html>
"@
$Html | Out-File -FilePath $ReportPath -Encoding UTF8
Write-Host "Baseline report generated: $ReportPath" -ForegroundColor Green
return $BaselineData
}
Security Event Monitoring
Critical Security Events
# Comprehensive security event monitoring for Active Directory
function Start-ADSecurityMonitoring {
param(
[string[]]$DomainControllers = (Get-ADDomainController -Filter *).Name,
[string]$SIEMServer = $null,
[int]$MonitoringInterval = 300, # 5 minutes
[string]$AlertEmail = $null,
[string]$SMTPServer = $null
)
# Critical security events to monitor
$SecurityEvents = @{
# Authentication Events
4625 = 'Failed Logon'
4648 = 'Logon with Explicit Credentials'
4768 = 'Kerberos TGT Request'
4769 = 'Kerberos Service Ticket Request'
4771 = 'Kerberos Pre-authentication Failed'
4776 = 'Domain Controller Authentication'
# Account Management
4720 = 'User Account Created'
4722 = 'User Account Enabled'
4724 = 'Password Reset'
4725 = 'User Account Disabled'
4726 = 'User Account Deleted'
4738 = 'User Account Changed'
4740 = 'User Account Locked Out'
4767 = 'User Account Unlocked'
# Group Management
4727 = 'Security-enabled Global Group Created'
4728 = 'Member Added to Security-enabled Global Group'
4729 = 'Member Removed from Security-enabled Global Group'
4731 = 'Security-enabled Local Group Created'
4732 = 'Member Added to Security-enabled Local Group'
4733 = 'Member Removed from Security-enabled Local Group'
4756 = 'Member Added to Security-enabled Universal Group'
4757 = 'Member Removed from Security-enabled Universal Group'
# Privilege Escalation
4672 = 'Special Privileges Assigned'
4673 = 'Privileged Service Called'
4674 = 'Operation Attempted on Privileged Object'
# Policy Changes
4713 = 'Kerberos Policy Changed'
4719 = 'System Audit Policy Changed'
4739 = 'Domain Policy Changed'
4864 = 'Namespace Collision'
# System Events
1102 = 'Audit Log Cleared'
4608 = 'Windows Starting'
4609 = 'Windows Shutting Down'
4616 = 'System Time Changed'
# Directory Service Changes
5136 = 'Directory Service Object Modified'
5137 = 'Directory Service Object Created'
5138 = 'Directory Service Object Undeleted'
5139 = 'Directory Service Object Moved'
5141 = 'Directory Service Object Deleted'
}
foreach ($DC in $DomainControllers) {
Start-Job -Name "SecurityMonitor-$DC" -ScriptBlock {
param($DC, $Events, $Interval, $SIEMServer, $AlertEmail, $SMTPServer)
$LastCheck = (Get-Date).AddMinutes(-5)
while ($true) {
try {
$CurrentCheck = Get-Date
# Query security events
$SecurityLogs = Get-WinEvent -ComputerName $DC -FilterHashtable @{
LogName = 'Security'
ID = $Events.Keys
StartTime = $LastCheck
EndTime = $CurrentCheck
} -ErrorAction SilentlyContinue
foreach ($Event in $SecurityLogs) {
$EventData = [PSCustomObject]@{
Timestamp = $Event.TimeCreated
Computer = $DC
EventID = $Event.Id
EventName = $Events[$Event.Id]
Level = $Event.LevelDisplayName
User = $Event.Properties[5].Value
Source = $Event.Properties[18].Value
Message = $Event.Message
ProcessName = $Event.Properties[9].Value
}
# Send to SIEM if configured
if ($SIEMServer) {
# Send via syslog or API (implementation depends on SIEM)
Send-SyslogMessage -Server $SIEMServer -Message ($EventData | ConvertTo-Json)
}
# Generate alerts for critical events
$CriticalEvents = @(4625, 4740, 1102, 4713, 4719)
if ($Event.Id -in $CriticalEvents) {
$AlertSubject = "CRITICAL AD Security Event - $($Events[$Event.Id]) on $DC"
$AlertBody = "Event ID: $($Event.Id)`n"
$AlertBody += "Event: $($Events[$Event.Id])`n"
$AlertBody += "Time: $($Event.TimeCreated)`n"
$AlertBody += "Computer: $DC`n"
$AlertBody += "Details: $($Event.Message)`n"
if ($AlertEmail -and $SMTPServer) {
Send-MailMessage -To $AlertEmail -Subject $AlertSubject -Body $AlertBody -SmtpServer $SMTPServer
}
}
# Log to file
$LogFile = "C:\Logs\ADSecurity_$DC_$(Get-Date -Format 'yyyyMMdd').log"
"$($EventData.Timestamp) - $($EventData.EventName) - $($EventData.User)" | Add-Content -Path $LogFile
}
$LastCheck = $CurrentCheck
Start-Sleep -Seconds $Interval
}
catch {
Write-Error "Security monitoring error on $DC`: $($_.Exception.Message)"
Start-Sleep -Seconds 60
}
}
} -ArgumentList $DC, $SecurityEvents, $MonitoringInterval, $SIEMServer, $AlertEmail, $SMTPServer
}
Write-Host "Security monitoring started for $($DomainControllers.Count) domain controllers" -ForegroundColor Green
}
# Threat detection and analysis
function Search-ADSecurityThreats {
param(
[string[]]$DomainControllers = (Get-ADDomainController -Filter *).Name,
[datetime]$StartTime = (Get-Date).AddHours(-24),
[datetime]$EndTime = (Get-Date),
[string]$ReportPath = "C:\Reports\AD_ThreatAnalysis_$(Get-Date -Format 'yyyyMMdd_HHmmss').html"
)
$ThreatIndicators = @()
foreach ($DC in $DomainControllers) {
Write-Host "Analyzing security events on $DC..." -ForegroundColor Blue
try {
# Brute Force Detection (multiple failed logons)
$FailedLogons = Get-WinEvent -ComputerName $DC -FilterHashtable @{
LogName = 'Security'
ID = 4625
StartTime = $StartTime
EndTime = $EndTime
} -ErrorAction SilentlyContinue
$BruteForceAttempts = $FailedLogons | Group-Object {$_.Properties[5].Value} |
Where-Object {$_.Count -gt 10} | ForEach-Object {
[PSCustomObject]@{
ThreatType = 'Brute Force Attack'
Severity = 'High'
TargetUser = $_.Name
AttemptCount = $_.Count
DomainController = $DC
FirstAttempt = ($_.Group | Sort-Object TimeCreated)[0].TimeCreated
LastAttempt = ($_.Group | Sort-Object TimeCreated)[-1].TimeCreated
}
}
# Privilege Escalation Detection
$PrivilegeEvents = Get-WinEvent -ComputerName $DC -FilterHashtable @{
LogName = 'Security'
ID = 4672, 4673, 4674
StartTime = $StartTime
EndTime = $EndTime
} -ErrorAction SilentlyContinue
$SuspiciousPrivileges = $PrivilegeEvents | Group-Object {$_.Properties[1].Value} |
Where-Object {$_.Count -gt 50} | ForEach-Object {
[PSCustomObject]@{
ThreatType = 'Suspicious Privilege Usage'
Severity = 'Medium'
User = $_.Name
EventCount = $_.Count
DomainController = $DC
FirstEvent = ($_.Group | Sort-Object TimeCreated)[0].TimeCreated
LastEvent = ($_.Group | Sort-Object TimeCreated)[-1].TimeCreated
}
}
# Account Manipulation Detection
$AccountChanges = Get-WinEvent -ComputerName $DC -FilterHashtable @{
LogName = 'Security'
ID = 4720, 4722, 4724, 4738
StartTime = $StartTime
EndTime = $EndTime
} -ErrorAction SilentlyContinue
$RapidAccountChanges = $AccountChanges | Group-Object {$_.Properties[0].Value} |
Where-Object {$_.Count -gt 5} | ForEach-Object {
[PSCustomObject]@{
ThreatType = 'Rapid Account Manipulation'
Severity = 'Medium'
TargetAccount = $_.Name
ChangeCount = $_.Count
DomainController = $DC
FirstChange = ($_.Group | Sort-Object TimeCreated)[0].TimeCreated
LastChange = ($_.Group | Sort-Object TimeCreated)[-1].TimeCreated
}
}
$ThreatIndicators += $BruteForceAttempts
$ThreatIndicators += $SuspiciousPrivileges
$ThreatIndicators += $RapidAccountChanges
}
catch {
Write-Error "Failed to analyze threats on $DC`: $($_.Exception.Message)"
}
}
# Generate threat analysis report
if ($ThreatIndicators.Count -gt 0) {
$Html = @"
<!DOCTYPE html>
<html>
<head>
<title>Active Directory Threat Analysis 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: #f2f2f2; }
.high { background-color: #ffcccc; }
.medium { background-color: #fff3cd; }
.low { background-color: #d4edda; }
.header { background-color: #dc3545; color: white; padding: 10px; }
</style>
</head>
<body>
<div class="header">
<h1>Active Directory Threat Analysis Report</h1>
<p>Analysis Period: $StartTime to $EndTime</p>
<p>Threats Detected: $($ThreatIndicators.Count)</p>
</div>
<h2>Detected Threats</h2>
<table>
<tr>
<th>Threat Type</th>
<th>Severity</th>
<th>Target/User</th>
<th>Count</th>
<th>Domain Controller</th>
<th>First Occurrence</th>
<th>Last Occurrence</th>
</tr>
"@
foreach ($Threat in $ThreatIndicators | Sort-Object Severity, ThreatType) {
$SeverityClass = $Threat.Severity.ToLower()
$Html += @"
<tr class="$SeverityClass">
<td>$($Threat.ThreatType)</td>
<td>$($Threat.Severity)</td>
<td>$($Threat.TargetUser ?? $Threat.User ?? $Threat.TargetAccount)</td>
<td>$($Threat.AttemptCount ?? $Threat.EventCount ?? $Threat.ChangeCount)</td>
<td>$($Threat.DomainController)</td>
<td>$($Threat.FirstAttempt ?? $Threat.FirstEvent ?? $Threat.FirstChange)</td>
<td>$($Threat.LastAttempt ?? $Threat.LastEvent ?? $Threat.LastChange)</td>
</tr>
"@
}
$Html += @"
</table>
<h2>Recommended Actions</h2>
<ul>
<li><strong>High Severity Threats:</strong> Immediate investigation and response required</li>
<li><strong>Medium Severity Threats:</strong> Review and validate activity within 24 hours</li>
<li><strong>Brute Force Attacks:</strong> Consider account lockout policies and MFA implementation</li>
<li><strong>Privilege Escalation:</strong> Review privileged account activities and access controls</li>
<li><strong>Account Manipulation:</strong> Validate all account changes through change management</li>
</ul>
</body>
</html>
"@
$Html | Out-File -FilePath $ReportPath -Encoding UTF8
Write-Host "Threat analysis report generated: $ReportPath" -ForegroundColor Yellow
Write-Host "Threats detected: $($ThreatIndicators.Count)" -ForegroundColor $(if ($ThreatIndicators.Count -gt 0) { 'Red' } else { 'Green' })
}
else {
Write-Host "No threats detected in the specified time period" -ForegroundColor Green
}
return $ThreatIndicators
}
Service Health Monitoring
Critical Active Directory Services
# Comprehensive service monitoring with automated remediation
function Monitor-ADServices {
param(
[string[]]$DomainControllers = (Get-ADDomainController -Filter *).Name,
[switch]$AutoRestart,
[string]$AlertEmail = $null,
[string]$SMTPServer = $null,
[int]$CheckInterval = 300 # 5 minutes
)
# Critical AD services with dependencies
$CriticalServices = @{
'NTDS' = @{
DisplayName = 'Active Directory Domain Services'
Dependencies = @('RpcSs', 'EventLog')
RestartAllowed = $true
Priority = 'Critical'
}
'DNS' = @{
DisplayName = 'DNS Server'
Dependencies = @('RpcSs', 'EventLog')
RestartAllowed = $true
Priority = 'Critical'
}
'Netlogon' = @{
DisplayName = 'Netlogon'
Dependencies = @('RpcSs', 'NTDS')
RestartAllowed = $true
Priority = 'Critical'
}
'W32Time' = @{
DisplayName = 'Windows Time'
Dependencies = @()
RestartAllowed = $true
Priority = 'High'
}
'DFSR' = @{
DisplayName = 'DFS Replication'
Dependencies = @('RpcSs')
RestartAllowed = $true
Priority = 'High'
}
'KDC' = @{
DisplayName = 'Kerberos Key Distribution Center'
Dependencies = @('NTDS')
RestartAllowed = $false # Requires careful handling
Priority = 'Critical'
}
'EventLog' = @{
DisplayName = 'Windows Event Log'
Dependencies = @()
RestartAllowed = $true
Priority = 'Critical'
}
'RpcSs' = @{
DisplayName = 'Remote Procedure Call (RPC)'
Dependencies = @()
RestartAllowed = $false # System critical
Priority = 'Critical'
}
}
foreach ($DC in $DomainControllers) {
Start-Job -Name "ServiceMonitor-$DC" -ScriptBlock {
param($DC, $Services, $AutoRestart, $AlertEmail, $SMTPServer, $CheckInterval)
while ($true) {
try {
$ServiceIssues = @()
foreach ($ServiceName in $Services.Keys) {
$ServiceInfo = $Services[$ServiceName]
$Service = Get-Service -ComputerName $DC -Name $ServiceName -ErrorAction SilentlyContinue
if (-not $Service) {
$Issue = [PSCustomObject]@{
Service = $ServiceName
DisplayName = $ServiceInfo.DisplayName
Status = 'NotInstalled'
Action = 'Manual intervention required'
Priority = $ServiceInfo.Priority
Timestamp = Get-Date
}
$ServiceIssues += $Issue
continue
}
if ($Service.Status -ne 'Running') {
$Issue = [PSCustomObject]@{
Service = $ServiceName
DisplayName = $ServiceInfo.DisplayName
Status = $Service.Status
Action = 'None'
Priority = $ServiceInfo.Priority
Timestamp = Get-Date
}
# Attempt automatic restart if enabled and allowed
if ($AutoRestart -and $ServiceInfo.RestartAllowed) {
try {
Write-Host "Attempting to restart $ServiceName on $DC..." -ForegroundColor Yellow
Start-Service -InputObject $Service
Start-Sleep -Seconds 30
$Service.Refresh()
if ($Service.Status -eq 'Running') {
$Issue.Action = 'Successfully restarted'
Write-Host "$ServiceName restarted successfully on $DC" -ForegroundColor Green
} else {
$Issue.Action = 'Restart failed'
Write-Host "Failed to restart $ServiceName on $DC" -ForegroundColor Red
}
}
catch {
$Issue.Action = "Restart error: $($_.Exception.Message)"
Write-Error "Error restarting $ServiceName on $DC`: $($_.Exception.Message)"
}
} else {
$Issue.Action = 'Manual restart required'
}
$ServiceIssues += $Issue
}
}
# Send alerts for service issues
if ($ServiceIssues.Count -gt 0 -and $AlertEmail -and $SMTPServer) {
$CriticalIssues = $ServiceIssues | Where-Object { $_.Priority -eq 'Critical' }
if ($CriticalIssues.Count -gt 0) {
$Subject = "CRITICAL: AD Service Issues on $DC"
$Body = "Critical Active Directory service issues detected on $DC`:`n`n"
foreach ($Issue in $CriticalIssues) {
$Body += "Service: $($Issue.DisplayName) ($($Issue.Service))`n"
$Body += "Status: $($Issue.Status)`n"
$Body += "Action: $($Issue.Action)`n"
$Body += "Time: $($Issue.Timestamp)`n`n"
}
$Body += "Please investigate immediately to prevent service degradation."
Send-MailMessage -To $AlertEmail -Subject $Subject -Body $Body -SmtpServer $SMTPServer -Priority High
}
}
# Log service status
$LogFile = "C:\Logs\ADServices_$DC_$(Get-Date -Format 'yyyyMMdd').log"
if ($ServiceIssues.Count -gt 0) {
foreach ($Issue in $ServiceIssues) {
"$(Get-Date) - $($Issue.Service) - $($Issue.Status) - $($Issue.Action)" | Add-Content -Path $LogFile
}
}
Start-Sleep -Seconds $CheckInterval
}
catch {
Write-Error "Service monitoring error on $DC`: $($_.Exception.Message)"
Start-Sleep -Seconds 60
}
}
} -ArgumentList $DC, $CriticalServices, $AutoRestart, $AlertEmail, $SMTPServer, $CheckInterval
}
Write-Host "Service monitoring started for $($DomainControllers.Count) domain controllers" -ForegroundColor Green
if ($AutoRestart) {
Write-Host "Automatic service restart enabled (where permitted)" -ForegroundColor Yellow
}
}
# Service dependency analysis and health report
function Get-ADServiceHealth {
param(
[string[]]$DomainControllers = (Get-ADDomainController -Filter *).Name,
[string]$ReportPath = "C:\Reports\AD_ServiceHealth_$(Get-Date -Format 'yyyyMMdd_HHmmss').html"
)
$ServiceHealth = @()
foreach ($DC in $DomainControllers) {
Write-Host "Checking service health on $DC..." -ForegroundColor Blue
try {
$Services = Get-Service -ComputerName $DC | Where-Object {
$_.Name -in @('NTDS', 'DNS', 'Netlogon', 'W32Time', 'DFSR', 'KDC', 'EventLog', 'RpcSs', 'LanmanServer', 'LanmanWorkstation', 'COMSysApp')
}
foreach ($Service in $Services) {
$StartupType = (Get-WmiObject -ComputerName $DC -Class Win32_Service -Filter "Name='$($Service.Name)'").StartMode
$ProcessId = (Get-WmiObject -ComputerName $DC -Class Win32_Service -Filter "Name='$($Service.Name)'").ProcessId
$ServiceHealth += [PSCustomObject]@{
DomainController = $DC
ServiceName = $Service.Name
DisplayName = $Service.DisplayName
Status = $Service.Status
StartupType = $StartupType
ProcessId = $ProcessId
CanPauseAndContinue = $Service.CanPauseAndContinue
CanShutdown = $Service.CanShutdown
CanStop = $Service.CanStop
CheckTime = Get-Date
}
}
}
catch {
Write-Error "Failed to check service health on $DC`: $($_.Exception.Message)"
}
}
# Generate service health report
$HealthySugar = $ServiceHealth | Where-Object { $_.Status -eq 'Running' }
$UnhealthyServices = $ServiceHealth | Where-Object { $_.Status -ne 'Running' }
$Html = @"
<!DOCTYPE html>
<html>
<head>
<title>Active Directory Service 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: #f2f2f2; }
.running { background-color: #d4edda; }
.stopped { background-color: #f8d7da; }
.starting { background-color: #fff3cd; }
.header { background-color: #007bff; color: white; padding: 10px; }
.summary { background-color: #f8f9fa; padding: 15px; margin-bottom: 20px; }
</style>
</head>
<body>
<div class="header">
<h1>Active Directory Service Health Report</h1>
<p>Generated: $(Get-Date)</p>
<p>Domain Controllers Checked: $($DomainControllers.Count)</p>
</div>
<div class="summary">
<h2>Health Summary</h2>
<p><strong>Total Services Checked:</strong> $($ServiceHealth.Count)</p>
<p><strong>Healthy Services:</strong> $($HealthySugar.Count)</p>
<p><strong>Unhealthy Services:</strong> $($UnhealthyServices.Count)</p>
<p><strong>Overall Health:</strong> $(if ($UnhealthyServices.Count -eq 0) { "HEALTHY" } else { "ISSUES DETECTED" })</p>
</div>
"@
if ($UnhealthyServices.Count -gt 0) {
$Html += @"
<h2>Service Issues</h2>
<table>
<tr>
<th>Domain Controller</th>
<th>Service</th>
<th>Display Name</th>
<th>Status</th>
<th>Startup Type</th>
<th>Check Time</th>
</tr>
"@
foreach ($Service in $UnhealthyServices) {
$StatusClass = $Service.Status.ToLower()
$Html += @"
<tr class="$StatusClass">
<td>$($Service.DomainController)</td>
<td>$($Service.ServiceName)</td>
<td>$($Service.DisplayName)</td>
<td>$($Service.Status)</td>
<td>$($Service.StartupType)</td>
<td>$($Service.CheckTime)</td>
</tr>
"@
}
$Html += "</table>"
}
$Html += @"
<h2>All Services Status</h2>
<table>
<tr>
<th>Domain Controller</th>
<th>Service</th>
<th>Display Name</th>
<th>Status</th>
<th>Startup Type</th>
<th>Process ID</th>
</tr>
"@
foreach ($Service in $ServiceHealth | Sort-Object DomainController, ServiceName) {
$StatusClass = $Service.Status.ToLower()
$Html += @"
<tr class="$StatusClass">
<td>$($Service.DomainController)</td>
<td>$($Service.ServiceName)</td>
<td>$($Service.DisplayName)</td>
<td>$($Service.Status)</td>
<td>$($Service.StartupType)</td>
<td>$($Service.ProcessId)</td>
</tr>
"@
}
$Html += @"
</table>
</body>
</html>
"@
$Html | Out-File -FilePath $ReportPath -Encoding UTF8
Write-Host "Service health report generated: $ReportPath" -ForegroundColor Green
return $ServiceHealth
}
Replication Monitoring
Active Directory Replication Health
# Comprehensive AD replication monitoring
function Monitor-ADReplication {
param(
[string[]]$DomainControllers = (Get-ADDomainController -Filter *).Name,
[int]$MaxReplicationLag = 60, # minutes
[string]$AlertEmail = $null,
[string]$SMTPServer = $null,
[string]$ReportPath = "C:\Reports\AD_ReplicationHealth_$(Get-Date -Format 'yyyyMMdd_HHmmss').html"
)
$ReplicationStatus = @()
$ReplicationIssues = @()
foreach ($DC in $DomainControllers) {
Write-Host "Checking replication status for $DC..." -ForegroundColor Blue
try {
# Get replication partner metadata
$Partners = Get-ADReplicationPartnerMetadata -Target $DC -Scope Domain
foreach ($Partner in $Partners) {
$ReplicationLag = (Get-Date) - $Partner.LastReplicationSuccess
$IsHealthy = $ReplicationLag.TotalMinutes -le $MaxReplicationLag -and $Partner.ConsecutiveReplicationFailures -eq 0
$Status = [PSCustomObject]@{
SourceDC = $DC
PartnerDC = $Partner.Partner
Partition = $Partner.Partition
LastReplicationSuccess = $Partner.LastReplicationSuccess
LastReplicationAttempt = $Partner.LastReplicationAttempt
ReplicationLagMinutes = [math]::Round($ReplicationLag.TotalMinutes, 2)
ConsecutiveFailures = $Partner.ConsecutiveReplicationFailures
LastReplicationResult = $Partner.LastReplicationResult
IsHealthy = $IsHealthy
CheckTime = Get-Date
}
$ReplicationStatus += $Status
if (-not $IsHealthy) {
$ReplicationIssues += $Status
}
}
# Check replication queue
$ReplQueue = Get-ADReplicationQueueOperation -Server $DC
if ($ReplQueue.Count -gt 0) {
Write-Warning "$DC has $($ReplQueue.Count) pending replication operations"
}
}
catch {
Write-Error "Failed to check replication on $DC`: $($_.Exception.Message)"
$ReplicationIssues += [PSCustomObject]@{
SourceDC = $DC
PartnerDC = 'Unknown'
Partition = 'Unknown'
LastReplicationSuccess = 'Error'
LastReplicationAttempt = 'Error'
ReplicationLagMinutes = 9999
ConsecutiveFailures = 9999
LastReplicationResult = "Error: $($_.Exception.Message)"
IsHealthy = $false
CheckTime = Get-Date
}
}
}
# Generate alerts for replication issues
if ($ReplicationIssues.Count -gt 0 -and $AlertEmail -and $SMTPServer) {
$Subject = "Active Directory Replication Issues Detected"
$Body = "Replication issues detected in Active Directory:`n`n"
foreach ($Issue in $ReplicationIssues) {
$Body += "Source DC: $($Issue.SourceDC)`n"
$Body += "Partner DC: $($Issue.PartnerDC)`n"
$Body += "Partition: $($Issue.Partition)`n"
$Body += "Last Success: $($Issue.LastReplicationSuccess)`n"
$Body += "Lag (minutes): $($Issue.ReplicationLagMinutes)`n"
$Body += "Consecutive Failures: $($Issue.ConsecutiveFailures)`n"
$Body += "Last Result: $($Issue.LastReplicationResult)`n`n"
}
Send-MailMessage -To $AlertEmail -Subject $Subject -Body $Body -SmtpServer $SMTPServer
}
# Generate replication health report
$HealthyReplications = $ReplicationStatus | Where-Object { $_.IsHealthy }
$UnhealthyReplications = $ReplicationStatus | Where-Object { -not $_.IsHealthy }
$Html = @"
<!DOCTYPE html>
<html>
<head>
<title>Active Directory Replication 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: #f2f2f2; }
.healthy { background-color: #d4edda; }
.unhealthy { background-color: #f8d7da; }
.warning { background-color: #fff3cd; }
.header { background-color: #28a745; color: white; padding: 10px; }
.summary { background-color: #f8f9fa; padding: 15px; margin-bottom: 20px; }
</style>
</head>
<body>
<div class="header">
<h1>Active Directory Replication Health Report</h1>
<p>Generated: $(Get-Date)</p>
<p>Maximum Acceptable Lag: $MaxReplicationLag minutes</p>
</div>
<div class="summary">
<h2>Replication Summary</h2>
<p><strong>Total Replication Links:</strong> $($ReplicationStatus.Count)</p>
<p><strong>Healthy Replications:</strong> $($HealthyReplications.Count)</p>
<p><strong>Unhealthy Replications:</strong> $($UnhealthyReplications.Count)</p>
<p><strong>Overall Health:</strong> $(if ($UnhealthyReplications.Count -eq 0) { "HEALTHY" } else { "ISSUES DETECTED" })</p>
</div>
"@
if ($UnhealthyReplications.Count -gt 0) {
$Html += @"
<h2>Replication Issues</h2>
<table>
<tr>
<th>Source DC</th>
<th>Partner DC</th>
<th>Partition</th>
<th>Last Success</th>
<th>Lag (min)</th>
<th>Failures</th>
<th>Last Result</th>
</tr>
"@
foreach ($Issue in $UnhealthyReplications) {
$Html += @"
<tr class="unhealthy">
<td>$($Issue.SourceDC)</td>
<td>$($Issue.PartnerDC)</td>
<td>$($Issue.Partition)</td>
<td>$($Issue.LastReplicationSuccess)</td>
<td>$($Issue.ReplicationLagMinutes)</td>
<td>$($Issue.ConsecutiveFailures)</td>
<td>$($Issue.LastReplicationResult)</td>
</tr>
"@
}
$Html += "</table>"
}
$Html += @"
<h2>All Replication Status</h2>
<table>
<tr>
<th>Source DC</th>
<th>Partner DC</th>
<th>Partition</th>
<th>Last Success</th>
<th>Lag (min)</th>
<th>Failures</th>
<th>Status</th>
</tr>
"@
foreach ($Status in $ReplicationStatus | Sort-Object SourceDC, PartnerDC) {
$HealthClass = if ($Status.IsHealthy) { "healthy" } else { "unhealthy" }
$Html += @"
<tr class="$HealthClass">
<td>$($Status.SourceDC)</td>
<td>$($Status.PartnerDC)</td>
<td>$($Status.Partition)</td>
<td>$($Status.LastReplicationSuccess)</td>
<td>$($Status.ReplicationLagMinutes)</td>
<td>$($Status.ConsecutiveFailures)</td>
<td>$(if ($Status.IsHealthy) { "Healthy" } else { "Issue" })</td>
</tr>
"@
}
$Html += @"
</table>
</body>
</html>
"@
$Html | Out-File -FilePath $ReportPath -Encoding UTF8
Write-Host "Replication health report generated: $ReportPath" -ForegroundColor Green
return $ReplicationStatus
}
# Force replication and monitor convergence
function Invoke-ADReplicationSync {
param(
[string[]]$DomainControllers = (Get-ADDomainController -Filter *).Name,
[switch]$AllPartitions,
[int]$TimeoutMinutes = 30
)
foreach ($DC in $DomainControllers) {
Write-Host "Initiating replication sync on $DC..." -ForegroundColor Blue
try {
if ($AllPartitions) {
# Sync all partitions
$Partitions = Get-ADReplicationPartnerMetadata -Target $DC |
Select-Object -ExpandProperty Partition -Unique
foreach ($Partition in $Partitions) {
Write-Host " Syncing partition: $Partition" -ForegroundColor Gray
Sync-ADObject -Object $Partition -Source $DC -Destination "*"
}
} else {
# Sync domain partition only
$DomainDN = (Get-ADDomain).DistinguishedName
Sync-ADObject -Object $DomainDN -Source $DC -Destination "*"
}
Write-Host "Replication sync initiated on $DC" -ForegroundColor Green
}
catch {
Write-Error "Failed to initiate replication sync on $DC`: $($_.Exception.Message)"
}
}
# Monitor convergence
Write-Host "Monitoring replication convergence..." -ForegroundColor Yellow
$StartTime = Get-Date
$ConvergenceAchieved = $false
while (-not $ConvergenceAchieved -and ((Get-Date) - $StartTime).TotalMinutes -lt $TimeoutMinutes) {
Start-Sleep -Seconds 30
$PendingOperations = 0
foreach ($DC in $DomainControllers) {
try {
$Queue = Get-ADReplicationQueueOperation -Server $DC
$PendingOperations += $Queue.Count
}
catch {
Write-Warning "Could not check replication queue on $DC"
}
}
if ($PendingOperations -eq 0) {
$ConvergenceAchieved = $true
Write-Host "Replication convergence achieved!" -ForegroundColor Green
} else {
Write-Host "Pending operations: $PendingOperations" -ForegroundColor Yellow
}
}
if (-not $ConvergenceAchieved) {
Write-Warning "Replication convergence not achieved within $TimeoutMinutes minutes"
}
return $ConvergenceAchieved
}
Logging and Auditing
Enterprise Logging Strategy
# Configure comprehensive AD logging and forwarding
function Set-ADLoggingConfiguration {
param(
[string[]]$DomainControllers = (Get-ADDomainController -Filter *).Name,
[string]$SIEMServer = $null,
[int]$SyslogPort = 514,
[hashtable]$LogLevels = @{
'Knowledge Consistency Checker' = 3
'Security Events' = 4
'Replication Events' = 3
'Global Catalog' = 2
'Inter-site Messaging' = 2
}
)
foreach ($DC in $DomainControllers) {
Write-Host "Configuring logging on $DC..." -ForegroundColor Blue
try {
# Configure diagnostic logging levels
foreach ($Component in $LogLevels.Keys) {
$Level = $LogLevels[$Component]
$RegPath = "HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics"
Invoke-Command -ComputerName $DC -ScriptBlock {
param($Component, $Level)
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics" -Name $Component -Value $Level
} -ArgumentList $Component, $Level
Write-Host " Set $Component logging to level $Level" -ForegroundColor Gray
}
# Configure security auditing
$AuditPolicies = @(
'Audit Account Logon Events',
'Audit Account Management',
'Audit Directory Service Access',
'Audit Logon Events',
'Audit Policy Change',
'Audit Privilege Use',
'Audit System Events'
)
foreach ($Policy in $AuditPolicies) {
Invoke-Command -ComputerName $DC -ScriptBlock {
param($Policy)
auditpol /set /subcategory:$Policy /success:enable /failure:enable
} -ArgumentList $Policy
}
# Configure event log sizes
$EventLogs = @{
'Security' = 1048576000 # 1GB
'System' = 104857600 # 100MB
'Application' = 104857600 # 100MB
'Directory Service' = 524288000 # 500MB
}
foreach ($LogName in $EventLogs.Keys) {
$MaxSize = $EventLogs[$LogName]
Invoke-Command -ComputerName $DC -ScriptBlock {
param($LogName, $MaxSize)
$Log = Get-WinEvent -ListLog $LogName
$Log.MaximumSizeInBytes = $MaxSize
$Log.SaveChanges()
} -ArgumentList $LogName, $MaxSize
}
# Configure log forwarding to SIEM if specified
if ($SIEMServer) {
$WinRMConfig = @"
winrm quickconfig -q
winrm set winrm/config/client '@{TrustedHosts="$SIEMServer"}'
wecutil qc /q
"@
Invoke-Command -ComputerName $DC -ScriptBlock {
param($Config)
Invoke-Expression $Config
} -ArgumentList $WinRMConfig
}
Write-Host "Logging configuration completed on $DC" -ForegroundColor Green
}
catch {
Write-Error "Failed to configure logging on $DC`: $($_.Exception.Message)"
}
}
}
# Real-time log monitoring and alerting
function Start-ADLogMonitoring {
param(
[string[]]$DomainControllers = (Get-ADDomainController -Filter *).Name,
[string]$SIEMServer = $null,
[hashtable]$CriticalEventIDs = @{
1102 = 'Audit log cleared'
4740 = 'Account locked out'
4625 = 'Failed logon'
4648 = 'Explicit credential logon'
4719 = 'Audit policy changed'
4713 = 'Kerberos policy changed'
},
[string]$AlertEmail = $null,
[string]$SMTPServer = $null
)
foreach ($DC in $DomainControllers) {
Start-Job -Name "LogMonitor-$DC" -ScriptBlock {
param($DC, $CriticalEvents, $SIEMServer, $AlertEmail, $SMTPServer)
# Register for real-time event monitoring
$EventArgs = @{
FilterHashtable = @{
LogName = 'Security', 'System', 'Directory Service'
ID = $CriticalEvents.Keys
}
}
Register-WinEvent -FilterHashtable $EventArgs -Action {
$Event = $Event.SourceEventArgs.NewEvent
$EventData = [PSCustomObject]@{
Computer = $env:COMPUTERNAME
TimeCreated = $Event.TimeCreated
Id = $Event.Id
LevelDisplayName = $Event.LevelDisplayName
Message = $Event.Message
UserId = $Event.UserId
ProcessId = $Event.ProcessId
}
# Send to SIEM
if ($SIEMServer) {
$SyslogMessage = "<14>$(Get-Date -Format 'MMM dd HH:mm:ss') $($EventData.Computer) AD-Monitor: $($EventData | ConvertTo-Json -Compress)"
$UdpClient = New-Object System.Net.Sockets.UdpClient
$Bytes = [System.Text.Encoding]::UTF8.GetBytes($SyslogMessage)
$UdpClient.Send($Bytes, $Bytes.Length, $SIEMServer, 514)
$UdpClient.Close()
}
# Send email alert for critical events
if ($AlertEmail -and $SMTPServer -and $Event.Id -in @(1102, 4740, 4719, 4713)) {
$Subject = "CRITICAL AD Event - $($CriticalEvents[$Event.Id]) on $($EventData.Computer)"
$Body = "Critical Active Directory event detected:`n`n"
$Body += "Event ID: $($Event.Id)`n"
$Body += "Event: $($CriticalEvents[$Event.Id])`n"
$Body += "Time: $($Event.TimeCreated)`n"
$Body += "Computer: $($EventData.Computer)`n"
$Body += "Details: $($Event.Message)`n"
Send-MailMessage -To $AlertEmail -Subject $Subject -Body $Body -SmtpServer $SMTPServer -Priority High
}
# Log to file
$LogFile = "C:\Logs\ADCriticalEvents_$($EventData.Computer)_$(Get-Date -Format 'yyyyMMdd').log"
"$($EventData.TimeCreated) - ID:$($EventData.Id) - $($CriticalEvents[$EventData.Id]) - $($EventData.UserId)" | Add-Content -Path $LogFile
}
# Keep the job running
while ($true) {
Start-Sleep -Seconds 60
}
} -ArgumentList $DC, $CriticalEventIDs, $SIEMServer, $AlertEmail, $SMTPServer
}
Write-Host "Real-time log monitoring started for $($DomainControllers.Count) domain controllers" -ForegroundColor Green
}
Compliance and Reporting
Compliance Auditing Framework
# Generate comprehensive compliance audit report
function New-ADComplianceReport {
param(
[string[]]$DomainControllers = (Get-ADDomainController -Filter *).Name,
[ValidateSet('SOX', 'HIPAA', 'PCI-DSS', 'NIST', 'Custom')]
[string[]]$ComplianceFrameworks = @('SOX', 'NIST'),
[datetime]$StartDate = (Get-Date).AddDays(-30),
[datetime]$EndDate = (Get-Date),
[string]$ReportPath = "C:\Reports\AD_ComplianceReport_$(Get-Date -Format 'yyyyMMdd_HHmmss').html"
)
$ComplianceData = @{
'UserAccountManagement' = @()
'PrivilegedAccess' = @()
'AuthenticationEvents' = @()
'PolicyChanges' = @()
'SystemAccess' = @()
}
foreach ($DC in $DomainControllers) {
Write-Host "Collecting compliance data from $DC..." -ForegroundColor Blue
try {
# User Account Management Events
$UserMgmtEvents = Get-WinEvent -ComputerName $DC -FilterHashtable @{
LogName = 'Security'
ID = 4720, 4722, 4723, 4724, 4725, 4726, 4738, 4740, 4767
StartTime = $StartDate
EndTime = $EndDate
} -ErrorAction SilentlyContinue
foreach ($Event in $UserMgmtEvents) {
$ComplianceData.UserAccountManagement += [PSCustomObject]@{
Timestamp = $Event.TimeCreated
Computer = $DC
EventID = $Event.Id
User = $Event.Properties[0].Value
Actor = $Event.Properties[4].Value
Action = switch ($Event.Id) {
4720 { 'Account Created' }
4722 { 'Account Enabled' }
4723 { 'Password Change Attempted' }
4724 { 'Password Reset' }
4725 { 'Account Disabled' }
4726 { 'Account Deleted' }
4738 { 'Account Changed' }
4740 { 'Account Locked' }
4767 { 'Account Unlocked' }
}
}
}
# Privileged Access Events
$PrivEvents = Get-WinEvent -ComputerName $DC -FilterHashtable @{
LogName = 'Security'
ID = 4672, 4673, 4674, 4648
StartTime = $StartDate
EndTime = $EndDate
} -ErrorAction SilentlyContinue
foreach ($Event in $PrivEvents) {
$ComplianceData.PrivilegedAccess += [PSCustomObject]@{
Timestamp = $Event.TimeCreated
Computer = $DC
EventID = $Event.Id
User = $Event.Properties[1].Value
Privileges = $Event.Properties[2].Value
Process = $Event.Properties[9].Value
}
}
# Authentication Events
$AuthEvents = Get-WinEvent -ComputerName $DC -FilterHashtable @{
LogName = 'Security'
ID = 4624, 4625, 4634, 4647, 4768, 4769, 4771, 4776
StartTime = $StartDate
EndTime = $EndDate
} -ErrorAction SilentlyContinue | Select-Object -First 1000
foreach ($Event in $AuthEvents) {
$ComplianceData.AuthenticationEvents += [PSCustomObject]@{
Timestamp = $Event.TimeCreated
Computer = $DC
EventID = $Event.Id
User = $Event.Properties[5].Value
LogonType = if ($Event.Properties.Count -gt 8) { $Event.Properties[8].Value } else { 'Unknown' }
SourceIP = if ($Event.Properties.Count -gt 18) { $Event.Properties[18].Value } else { 'Unknown' }
Result = if ($Event.Id -in @(4624, 4634, 4647, 4768, 4769, 4776)) { 'Success' } else { 'Failure' }
}
}
# Policy Change Events
$PolicyEvents = Get-WinEvent -ComputerName $DC -FilterHashtable @{
LogName = 'Security'
ID = 4713, 4719, 4739, 4817
StartTime = $StartDate
EndTime = $EndDate
} -ErrorAction SilentlyContinue
foreach ($Event in $PolicyEvents) {
$ComplianceData.PolicyChanges += [PSCustomObject]@{
Timestamp = $Event.TimeCreated
Computer = $DC
EventID = $Event.Id
Actor = $Event.Properties[1].Value
PolicyType = switch ($Event.Id) {
4713 { 'Kerberos Policy' }
4719 { 'Audit Policy' }
4739 { 'Domain Policy' }
4817 { 'Audit Settings' }
}
Changes = $Event.Message
}
}
}
catch {
Write-Error "Failed to collect compliance data from $DC`: $($_.Exception.Message)"
}
}
# Generate compliance report
$TotalUserActions = $ComplianceData.UserAccountManagement.Count
$TotalPrivilegedActions = $ComplianceData.PrivilegedAccess.Count
$TotalAuthAttempts = $ComplianceData.AuthenticationEvents.Count
$FailedAuthAttempts = ($ComplianceData.AuthenticationEvents | Where-Object { $_.Result -eq 'Failure' }).Count
$PolicyChanges = $ComplianceData.PolicyChanges.Count
$Html = @"
<!DOCTYPE html>
<html>
<head>
<title>Active Directory Compliance Audit 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: #f2f2f2; }
.success { background-color: #d4edda; }
.failure { background-color: #f8d7da; }
.warning { background-color: #fff3cd; }
.header { background-color: #6f42c1; color: white; padding: 10px; }
.summary { background-color: #f8f9fa; padding: 15px; margin-bottom: 20px; }
.framework { background-color: #e3f2fd; padding: 10px; margin: 10px 0; }
</style>
</head>
<body>
<div class="header">
<h1>Active Directory Compliance Audit Report</h1>
<p>Audit Period: $StartDate to $EndDate</p>
<p>Compliance Frameworks: $($ComplianceFrameworks -join ', ')</p>
</div>
<div class="summary">
<h2>Executive Summary</h2>
<p><strong>User Account Management Actions:</strong> $TotalUserActions</p>
<p><strong>Privileged Access Events:</strong> $TotalPrivilegedActions</p>
<p><strong>Authentication Attempts:</strong> $TotalAuthAttempts</p>
<p><strong>Failed Authentication Attempts:</strong> $FailedAuthAttempts</p>
<p><strong>Policy Changes:</strong> $PolicyChanges</p>
<p><strong>Domain Controllers Audited:</strong> $($DomainControllers.Count)</p>
</div>
"@
# Add framework-specific sections
foreach ($Framework in $ComplianceFrameworks) {
$Html += @"
<div class="framework">
<h3>$Framework Compliance Requirements</h3>
<ul>
"@
switch ($Framework) {
'SOX' {
$Html += @"
<li>User access management and review (Covered: User Account Management)</li>
<li>Privileged access controls (Covered: Privileged Access Events)</li>
<li>Authentication and authorization logging (Covered: Authentication Events)</li>
<li>System changes and configuration management (Covered: Policy Changes)</li>
"@
}
'HIPAA' {
$Html += @"
<li>Access control and user authentication (Covered: Authentication Events)</li>
<li>Audit controls and logging (Covered: All Event Categories)</li>
<li>Information access management (Covered: Privileged Access Events)</li>
<li>Transmission security (Covered: System Access Events)</li>
"@
}
'NIST' {
$Html += @"
<li>Access Control (AC) - User management and privileged access</li>
<li>Audit and Accountability (AU) - Comprehensive event logging</li>
<li>Identification and Authentication (IA) - Authentication events</li>
<li>Configuration Management (CM) - Policy change tracking</li>
"@
}
}
$Html += @"
</ul>
</div>
"@
}
# Add detailed event tables
if ($ComplianceData.UserAccountManagement.Count -gt 0) {
$Html += @"
<h2>User Account Management Events</h2>
<table>
<tr>
<th>Timestamp</th>
<th>Domain Controller</th>
<th>Action</th>
<th>Target User</th>
<th>Actor</th>
</tr>
"@
foreach ($Event in ($ComplianceData.UserAccountManagement | Sort-Object Timestamp -Descending | Select-Object -First 100)) {
$Html += @"
<tr>
<td>$($Event.Timestamp)</td>
<td>$($Event.Computer)</td>
<td>$($Event.Action)</td>
<td>$($Event.User)</td>
<td>$($Event.Actor)</td>
</tr>
"@
}
$Html += "</table>"
}
if ($ComplianceData.PolicyChanges.Count -gt 0) {
$Html += @"
<h2>Policy Changes</h2>
<table>
<tr>
<th>Timestamp</th>
<th>Domain Controller</th>
<th>Policy Type</th>
<th>Actor</th>
</tr>
"@
foreach ($Event in $ComplianceData.PolicyChanges) {
$Html += @"
<tr class="warning">
<td>$($Event.Timestamp)</td>
<td>$($Event.Computer)</td>
<td>$($Event.PolicyType)</td>
<td>$($Event.Actor)</td>
</tr>
"@
}
$Html += "</table>"
}
$Html += @"
</body>
</html>
"@
$Html | Out-File -FilePath $ReportPath -Encoding UTF8
Write-Host "Compliance audit report generated: $ReportPath" -ForegroundColor Green
return $ComplianceData
}
Cloud and Hybrid Monitoring
Azure AD Connect Health Monitoring
# Monitor hybrid environment health
function Monitor-HybridADHealth {
param(
[string]$AADConnectServer,
[string]$TenantId,
[string]$SubscriptionId,
[string]$ReportPath = "C:\Reports\Hybrid_AD_Health_$(Get-Date -Format 'yyyyMMdd_HHmmss').html"
)
$HybridHealth = @()
# Check Azure AD Connect status
if ($AADConnectServer) {
try {
$ConnectStatus = Invoke-Command -ComputerName $AADConnectServer -ScriptBlock {
Import-Module ADSync
$SyncCycles = Get-ADSyncScheduler
$LastSync = Get-ADSyncRunHistoryCount | Select-Object -First 1
$ConnectorSpaces = Get-ADSyncConnectorStatistics
[PSCustomObject]@{
SchedulerEnabled = $SyncCycles.SyncCycleEnabled
NextSyncCycle = $SyncCycles.NextSyncCyclePolicyType
LastSyncTime = $LastSync.StartDate
LastSyncResult = $LastSync.Result
ConnectorCount = $ConnectorSpaces.Count
}
}
$HybridHealth += [PSCustomObject]@{
Component = 'Azure AD Connect'
Status = if ($ConnectStatus.SchedulerEnabled) { 'Healthy' } else { 'Issue' }
LastSync = $ConnectStatus.LastSyncTime
Details = "Scheduler: $($ConnectStatus.SchedulerEnabled), Result: $($ConnectStatus.LastSyncResult)"
}
}
catch {
$HybridHealth += [PSCustomObject]@{
Component = 'Azure AD Connect'
Status = 'Error'
LastSync = 'Unknown'
Details = "Error: $($_.Exception.Message)"
}
}
}
# Check password writeback (if configured)
try {
$PWWriteback = Get-ADSyncAADPasswordResetConfiguration -ErrorAction SilentlyContinue
if ($PWWriteback) {
$HybridHealth += [PSCustomObject]@{
Component = 'Password Writeback'
Status = if ($PWWriteback.PasswordWritebackEnabled) { 'Enabled' } else { 'Disabled' }
LastSync = 'N/A'
Details = "Enabled: $($PWWriteback.PasswordWritebackEnabled)"
}
}
}
catch {
Write-Warning "Could not check password writeback status"
}
# Generate hybrid health report
$Html = @"
<!DOCTYPE html>
<html>
<head>
<title>Hybrid Active Directory Health Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
.healthy { background-color: #d4edda; }
.issue { background-color: #f8d7da; }
.enabled { background-color: #d4edda; }
.disabled { background-color: #fff3cd; }
.header { background-color: #17a2b8; color: white; padding: 10px; }
</style>
</head>
<body>
<div class="header">
<h1>Hybrid Active Directory Health Report</h1>
<p>Generated: $(Get-Date)</p>
<p>Tenant ID: $TenantId</p>
</div>
<h2>Hybrid Components Status</h2>
<table>
<tr>
<th>Component</th>
<th>Status</th>
<th>Last Sync</th>
<th>Details</th>
</tr>
"@
foreach ($Component in $HybridHealth) {
$StatusClass = $Component.Status.ToLower()
$Html += @"
<tr class="$StatusClass">
<td>$($Component.Component)</td>
<td>$($Component.Status)</td>
<td>$($Component.LastSync)</td>
<td>$($Component.Details)</td>
</tr>
"@
}
$Html += @"
</table>
</body>
</html>
"@
$Html | Out-File -FilePath $ReportPath -Encoding UTF8
Write-Host "Hybrid health report generated: $ReportPath" -ForegroundColor Green
return $HybridHealth
}
Best Practices and Recommendations
Monitoring Best Practices
- Baseline Establishment: Establish performance baselines during normal operations
- Threshold Setting: Set appropriate alerting thresholds based on baseline data
- Alert Tuning: Regularly review and tune alerts to minimize false positives
- Escalation Procedures: Implement clear escalation paths for different severity levels
- Documentation: Maintain up-to-date monitoring documentation and procedures
Security Monitoring
- Real-time Alerting: Implement real-time alerting for critical security events
- Threat Correlation: Use SIEM solutions for advanced threat detection and correlation
- Behavioral Analysis: Monitor for unusual patterns and behaviors
- Compliance Auditing: Regular compliance audits and reporting
- Incident Response: Integrate monitoring with incident response procedures
Performance Optimization
- Minimize Monitoring Overhead: Balance monitoring completeness with performance impact
- Efficient Data Collection: Use sampling and aggregation to reduce data volume
- Storage Management: Implement appropriate log retention and archival policies
- Network Impact: Consider bandwidth usage for centralized monitoring
- Regular Review: Periodically review monitoring strategy and adjust as needed
Additional Resources
Microsoft Documentation
- Monitor and troubleshoot AD DS
- Active Directory Replication and Topology Management
- Security Monitoring for Active Directory
Security Frameworks
Monitoring Tools
This guide provides comprehensive monitoring and logging strategies for Active Directory environments. Regular review and updates ensure continued effectiveness and security.