This commit is contained in:
Mike McFetridge
2026-07-20 09:23:17 -04:00
parent c1315882da
commit 72272e4006
3179 changed files with 562960 additions and 14 deletions
+101
View File
@@ -0,0 +1,101 @@
# Script to validate an Active Directory Domain Controller's operational status
# Requires: ActiveDirectory PowerShell module and Domain Admin / Account Operator privileges
# Ensure AD Module is available
if (-not (Get-Module -ListAvailable -Name ActiveDirectory)) {
Write-Error "The ActiveDirectory module is required to run this script. Please install RSAT."
exit
}
# Set the target domain controller name
$DCName = Read-Host "Enter the Domain Controller Name (FQDN or Hostname)"
try {
# Check if the DC is online and retrievable
Write-Host "Verifying Domain Controller existence..." -ForegroundColor Cyan
$DC = Get-ADDomainController -Identity $DCName -ErrorAction Stop
Write-Host "Domain Controller '$DCName' found online." -ForegroundColor Green
---
# Test Critical Services (Replaces the invalid Test-ComputerSecureChannel logic)
Write-Host "`nChecking Critical AD Services..." -ForegroundColor Cyan
$CriticalServices = @("NTDS", "Netlogon", "SamSs", "W32Time", "DNS")
try {
$Services = Get-Service -ComputerName $DCName -Name $CriticalServices -ErrorAction Stop
foreach ($Service in $Services) {
if ($Service.Status -eq "Running") {
Write-Host " Service $($Service.Name) is Running." -ForegroundColor Green
} else {
Write-Warning " Service $($Service.Name) is $($Service.Status)!"
}
}
} catch {
Write-Warning "Failed to query services on '$DCName'. Check RPC/firewall settings."
}
---
# Check DNS Registration
Write-Host "`nChecking DNS Registration..." -ForegroundColor Cyan
try {
$Test = Resolve-DnsName -Name $DC.ForceLogonDC -ErrorAction Stop
if ($Test) {
Write-Host "DNS registration is OK." -ForegroundColor Green
}
} catch {
Write-Warning "DNS resolution failed for '$DCName'."
}
---
# Check Replication Status
Write-Host "`nChecking Replication Status..." -ForegroundColor Cyan
try {
$ReplicationStatuses = Get-ADReplicationStatus -DomainController $DCName -ErrorAction Stop
$ReplFailures = 0
foreach ($Status in $ReplicationStatuses) {
# 0 indicates success in AD replication cycles
if ($Status.LastReplicationSuccess -eq 0 -or $Status.LastAttemptResult -eq "Success") {
continue
} else {
Write-Warning " Replication Link Failed: Partner ($($Status.Partner)) -> Result: $($Status.LastAttemptResult)"
$ReplFailures++
}
}
if ($ReplFailures -eq 0) {
Write-Host "All replication links are healthy." -ForegroundColor Green
}
} catch {
Write-Warning "Failed to check replication status. Ensure you have proper replication viewing permissions."
}
---
# Check Directory Services Event Logs for Errors
Write-Host "`nChecking Directory Services Event Logs (Last 10 Errors)..." -ForegroundColor Cyan
try {
# Changed to 'Directory Service' log as it is far more relevant to DC health than 'Application'
$ErrorEvents = Get-WinEvent -ComputerName $DCName -LogName "Directory Service" -MaxEvents 10 -FilterHashtable @{Level=2} -ErrorAction SilentlyContinue
if ($ErrorEvents) {
Write-Warning "Found Error Events in Directory Service Log:"
foreach ($Event in $ErrorEvents) {
Write-Host " Time: $($Event.TimeCreated)"
Write-Host " ID: $($Event.Id)"
Write-Host " Description: $($Event.Message.Substring(0, [Math]::Min(100, $Event.Message.Length)))..."
}
} else {
Write-Host "No recent error events found in Directory Service log." -ForegroundColor Green
}
} catch {
Write-Warning "Could not read Event Logs from '$DCName'."
}
Write-Host "`n========================================="
Write-Host "Health check completed for '$DCName'." -ForegroundColor Cyan
} catch {
Write-Error "A critical error occurred: $($_.Exception.Message)"
}
+660
View File
@@ -0,0 +1,660 @@
<#
.NOTES
The script correctly performs these AD health checks:
DNS resolution and ping connectivity
Service status (DNS, NTDS, NetLogon)
Disk space monitoring with color-coded alerts
Time synchronization validation
Comprehensive DCDiag tests (21 different tests)
FSMO role identification
Uptime monitoring
The HTML report output is well-formatted with color coding for pass/warn/fail status. The script is production-ready for AD health monitoring.
This script is completely safe for production AD environments. It performs only read-only operations and will not cause any disruption.
Here's what the script does (all non-disruptive):
Read-Only Operations:
DNS lookups (Resolve-DnsName)
Network connectivity tests (Test-Connection)
Service status queries (Get-Service)
System information queries (Get-CimInstance)
Time synchronization checks (w32tm /stripchart)
DCDiag diagnostic tests (designed for production use)
No Changes Made:
❌ No service restarts
❌ No configuration modifications
❌ No AD object changes
❌ No registry modifications
Minimal Impact:
Light network traffic from connectivity tests
Standard WMI queries (same as monitoring tools)
DCDiag tests are Microsoft's official diagnostic tools used regularly in production
Best Practices for Production:
Run during business hours (generates useful real-time health data)
Consider running from a management workstation rather than a DC
The script includes progress indicators so you can monitor execution
This type of health check script is commonly run by AD administrators as part of routine maintenance and monitoring.
Microsoft's DCDiag tool (which this script uses) is specifically designed for production diagnostics.
.SYNOPSIS
Get-ADHealth.ps1 - Domain Controller Health Check Script.
.DESCRIPTION
This script performs a list of common health checks to a specific domain, or the entire forest. The results are then compiled into a colour coded HTML report.
.OUTPUTS
The results are currently only output to HTML for email or as an HTML report file, or sent as an SMTP message with an HTML body.
.PARAMETER DomainName
Perform a health check on a specific Active Directory domain.
.PARAMETER ReportFile
Output the report details to a file in the current directory.
.PARAMETER SendEmail
Send the report via email. You have to configure the correct SMTP settings.
.EXAMPLE
.\Get-ADHealth.ps1 -ReportFile
Checks all domains and all domain controllers in your current forest and creates a report.
.EXAMPLE
.\Get-ADHealth.ps1 -DomainName alitajran.com -ReportFile
Checks all the domain controllers in the specified domain "alitajran.com" and creates a report.
.EXAMPLE
.\Get-ADHealth.ps1 -DomainName alitajran.com -SendEmail
Checks all the domain controllers in the specified domain "alitajran.com" and sends the resulting report as an email message.
.LINK
alitajran.com/active-directory-health-check-powershell-script
#>
[CmdletBinding()]
Param(
[Parameter( Mandatory = $false)]
[string]$DomainName,
[Parameter( Mandatory = $false)]
[switch]$ReportFile,
[Parameter( Mandatory = $false)]
[switch]$SendEmail
)
#...................................
# Global Variables
#...................................
$allTestedDomainControllers = [System.Collections.Generic.List[Object]]::new()
$allDomainControllers = [System.Collections.Generic.List[Object]]::new()
$now = Get-Date
$date = $now.ToShortDateString()
$reportTime = $now
$reportFileNameTime = $now.ToString("yyyyMMdd_HHmmss")
$reportemailsubject = "Domain Controller Health Report"
$smtpsettings = @{
To = 'email@domain.com'
From = 'adhealth@yourdomain.com'
Subject = "$reportemailsubject - $date"
SmtpServer = "mail.domain.com"
Port = "25"
#Credential = (Get-Credential)
#UseSsl = $true
}
#...................................
# Functions
#...................................
# This function gets all the domains in the forest.
Function Get-AllDomains() {
Write-Verbose "Running function Get-AllDomains"
$allDomains = (Get-ADForest).Domains
return $allDomains
}
# This function gets all the domain controllers in a specified domain.
Function Get-AllDomainControllers ($ComputerName) {
Write-Verbose "Running function Get-AllDomainControllers"
$allDomainControllers = Get-ADDomainController -Filter * -Server $ComputerName | Sort-Object HostName
return $allDomainControllers
}
# This function tests the domain controller against DNS.
Function Get-DomainControllerNSLookup($ComputerName) {
Write-Verbose "Running function Get-DomainControllerNSLookup"
try {
$domainControllerNSLookupResult = Resolve-DnsName $ComputerName -Type A | Select-Object -ExpandProperty IPAddress
if ($domainControllerNSLookupResult) {
$domainControllerNSLookupResult = 'Success'
} else {
$domainControllerNSLookupResult = 'Fail'
}
}
catch {
$domainControllerNSLookupResult = 'Fail'
}
return $domainControllerNSLookupResult
}
# This function tests the connectivity to the domain controller.
Function Get-DomainControllerPingStatus($ComputerName) {
Write-Verbose "Running function Get-DomainControllerPingStatus"
if ((Test-Connection $ComputerName -Count 1 -quiet) -eq $True) {
$domainControllerPingStatus = "Success"
}
else {
$domainControllerPingStatus = 'Fail'
}
return $domainControllerPingStatus
}
# This function tests the domain controller uptime.
Function Get-DomainControllerUpTime($ComputerName) {
Write-Verbose "Running function Get-DomainControllerUpTime"
if ((Test-Connection $ComputerName -Count 1 -Quiet) -eq $True) {
try {
$W32OS = Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $ComputerName -ErrorAction SilentlyContinue
$timespan = (Get-Date) - $W32OS.LastBootUpTime
[int]$uptime = "{0:00}" -f $timespan.TotalHours
}
catch {
$uptime = 'CIM Failure'
}
}
else {
$uptime = 'Fail'
}
return $uptime
}
# This function checks the time synchronization offset.
function Get-TimeDifference($ComputerName) {
Write-Verbose "Running function Get-TimeDifference"
if ((Test-Connection $ComputerName -Count 1 -Quiet) -eq $True) {
try {
$currentTime, $timeDifference = (& w32tm /stripchart /computer:$ComputerName /samples:1 /dataonly)[-1].Trim("s") -split ',\s*'
$diff = [double]$timeDifference
$diffRounded = [Math]::Round($diff, 1, [MidPointRounding]::AwayFromZero)
}
catch {
$diffRounded = 'Fail'
}
}
else {
$diffRounded = 'Fail'
}
return $diffRounded
}
# This function checks the DNS, NTDS and Netlogon services.
Function Get-DomainControllerServices($ComputerName) {
Write-Verbose "Running function DomainControllerServices"
$thisDomainControllerServicesTestResult = [PSCustomObject]@{
DNSService = $null
NTDSService = $null
NETLOGONService = $null
}
if ((Test-Connection $ComputerName -Count 1 -quiet) -eq $True) {
if ((Get-Service -ComputerName $ComputerName -Name DNS -ErrorAction SilentlyContinue).Status -eq 'Running') {
$thisDomainControllerServicesTestResult.DNSService = 'Success'
}
else {
$thisDomainControllerServicesTestResult.DNSService = 'Fail'
}
if ((Get-Service -ComputerName $ComputerName -Name NTDS -ErrorAction SilentlyContinue).Status -eq 'Running') {
$thisDomainControllerServicesTestResult.NTDSService = 'Success'
}
else {
$thisDomainControllerServicesTestResult.NTDSService = 'Fail'
}
if ((Get-Service -ComputerName $ComputerName -Name netlogon -ErrorAction SilentlyContinue).Status -eq 'Running') {
$thisDomainControllerServicesTestResult.NETLOGONService = 'Success'
}
else {
$thisDomainControllerServicesTestResult.NETLOGONService = 'Fail'
}
}
else {
$thisDomainControllerServicesTestResult.DNSService = 'Fail'
$thisDomainControllerServicesTestResult.NTDSService = 'Fail'
$thisDomainControllerServicesTestResult.NETLOGONService = 'Fail'
}
return $thisDomainControllerServicesTestResult
}
# This function runs the DCDiag tests and saves them in a variable for later processing.
Function Get-DomainControllerDCDiagTestResults($ComputerName) {
Write-Verbose "Running function Get-DomainControllerDCDiagTestResults"
# Initialize the object with all properties set to null
$DCDiagTestResults = [PSCustomObject]@{
ServerName = $ComputerName
Connectivity = $null
Advertising = $null
FrsEvent = $null
DFSREvent = $null
SysVolCheck = $null
KccEvent = $null
KnowsOfRoleHolders = $null
MachineAccount = $null
NCSecDesc = $null
NetLogons = $null
ObjectsReplicated = $null
Replications = $null
RidManager = $null
Services = $null
SystemLog = $null
VerifyReferences = $null
CheckSDRefDom = $null
CrossRefValidation = $null
LocatorCheck = $null
Intersite = $null
FSMOCheck = $null
}
if ((Test-Connection $ComputerName -Count 1 -quiet) -eq $True) {
# Define an array of parameters for Dcdiag.exe
$params = @(
"/s:$ComputerName",
"/test:Connectivity",
"/test:Advertising",
"/test:FrsEvent",
"/test:DFSREvent",
"/test:SysVolCheck",
"/test:KccEvent",
"/test:KnowsOfRoleHolders",
"/test:MachineAccount",
"/test:NCSecDesc",
"/test:NetLogons",
"/test:ObjectsReplicated",
"/test:Replications",
"/test:RidManager",
"/test:Services",
"/test:SystemLog",
"/test:VerifyReferences",
"/test:CheckSDRefDom",
"/test:CrossRefValidation",
"/test:LocatorCheck",
"/test:Intersite",
"/test:FSMOCheck"
)
$DCDiagTest = (Dcdiag.exe @params) -split ('[\r\n]')
$TestName = $null
$TestStatus = $null
$DCDiagTest | ForEach-Object {
switch -Regex ($_) {
"Starting test:" {
$TestName = ($_ -replace ".*Starting test:").Trim()
}
"passed test|failed test" {
$TestStatus = if ($_ -match "passed test") { "Passed" } else { "Failed" }
}
}
if ($TestName -and $TestStatus) {
# Set the property value directly
$DCDiagTestResults.$TestName = $TestStatus
$TestName = $null
$TestStatus = $null
}
}
}
else {
# If the domain controller is not reachable, set all tests to 'Failed'
foreach ($property in $DCDiagTestResults.PSObject.Properties.Name) {
if ($property -ne "ServerName") {
$DCDiagTestResults.$property = "Failed"
}
}
}
return $DCDiagTestResults
}
# This function checks the free space in percentage on the OS drive
Function Get-DomainControllerOSDriveFreeSpace ($ComputerName) {
Write-Verbose "Running function Get-DomainControllerOSDriveFreeSpace"
if ((Test-Connection $ComputerName -Count 1 -Quiet) -eq $True) {
try {
$thisOSDriveLetter = (Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $ComputerName -ErrorAction Stop).SystemDrive
$thisOSDiskDrive = Get-CimInstance -ClassName Win32_LogicalDisk -ComputerName $ComputerName -Filter "DeviceID='$thisOSDriveLetter'" -ErrorAction Stop
$thisOSPercentFree = [math]::Round($thisOSDiskDrive.FreeSpace / $thisOSDiskDrive.Size * 100)
}
catch {
$thisOSPercentFree = 'CIM Failure'
}
}
else {
$thisOSPercentFree = "Fail"
}
return $thisOSPercentFree
}
# This function checks the free disk space on the OS drive in GB
Function Get-DomainControllerOSDriveFreeSpaceGB ($ComputerName) {
Write-Verbose "Running function Get-DomainControllerOSDriveFreeSpaceGB"
if ((Test-Connection $ComputerName -Count 1 -Quiet) -eq $True) {
try {
$thisOSDriveLetter = (Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $ComputerName -ErrorAction Stop).SystemDrive
$thisOSDiskDrive = Get-CimInstance -ClassName Win32_LogicalDisk -ComputerName $ComputerName -Filter "DeviceID='$thisOSDriveLetter'" -ErrorAction Stop
# Convert bytes to GB, rounding to 2 decimal places
$freeSpaceGB = [math]::Round($thisOSDiskDrive.FreeSpace / 1GB, 2)
}
catch {
$freeSpaceGB = 'CIM Failure'
}
}
else {
$freeSpaceGB = 'Fail'
}
return $freeSpaceGB
}
# This function generates HTML code from the results of the above functions.
Function New-ServerHealthHTMLTableCell() {
param( $lineitem )
$htmltablecell = $null
switch ($($reportline."$lineitem")) {
"Success" { $htmltablecell = "<td class=""pass"">$($reportline."$lineitem")</td>" }
"Passed" { $htmltablecell = "<td class=""pass"">$($reportline."$lineitem")</td>" }
"Pass" { $htmltablecell = "<td class=""pass"">$($reportline."$lineitem")</td>" }
"Warn" { $htmltablecell = "<td class=""warn"">$($reportline."$lineitem")</td>" }
"Fail" { $htmltablecell = "<td class=""fail"">$($reportline."$lineitem")</td>" }
"Failed" { $htmltablecell = "<td class=""fail"">$($reportline."$lineitem")</td>" }
"Could not test server uptime." { $htmltablecell = "<td class=""fail"">$($reportline."$lineitem")</td>" }
default { $htmltablecell = "<td>$($reportline."$lineitem")</td>" }
}
return $htmltablecell
}
if (!($DomainName)) {
Write-Host "No domain specified, using all domains in forest" -ForegroundColor Yellow
try {
$allDomains = Get-AllDomains
$reportFileName = 'forest_health_report_' + (Get-ADForest).name + '_' + $reportFileNameTime + '.html'
}
catch {
Write-Error "Failed to query AD Forest: $_"
exit 1
}
}
else {
Write-Host "Domain name specified on cmdline" -ForegroundColor Cyan
$allDomains = $DomainName
$reportFileName = 'dc_health_report_' + $DomainName + '_' + $reportFileNameTime + '.html'
}
foreach ($domain in $allDomains) {
Write-Host "Testing domain" $domain -ForegroundColor Green
$allDomainControllers = Get-AllDomainControllers $domain
# Force array first, then get count
$allDomainControllers = @($allDomainControllers)
$totalDCs = $allDomainControllers.Count
# Initialize counter for display
$currentDCNumber = 0
foreach ($domainController in $allDomainControllers) {
$currentDCNumber++
$stopWatch = [system.diagnostics.stopwatch]::StartNew()
Write-Host "Testing domain controller ($currentDCNumber of $totalDCs) $($domainController.HostName)" -ForegroundColor Cyan
$DCDiagTestResults = Get-DomainControllerDCDiagTestResults $domainController.HostName
$thisDomainController = [PSCustomObject]@{
Server = ($domainController.HostName).ToLower()
Site = $domainController.Site
"OS Version" = $domainController.OperatingSystem
"IPv4 Address" = $domainController.IPv4Address
"Operation Master Roles" = $domainController.OperationMasterRoles
"DNS" = Get-DomainControllerNSLookup $domainController.HostName
"Ping" = Get-DomainControllerPingStatus $domainController.HostName
"Uptime (hours)" = Get-DomainControllerUpTime $domainController.HostName
"OS Free Space (%)" = Get-DomainControllerOSDriveFreeSpace $domainController.HostName
"OS Free Space (GB)" = Get-DomainControllerOSDriveFreeSpaceGB $domainController.HostName
"Time offset (seconds)" = Get-TimeDifference $domainController.HostName
"DNS Service" = (Get-DomainControllerServices $domainController.HostName).DNSService
"NTDS Service" = (Get-DomainControllerServices $domainController.HostName).NTDSService
"NetLogon Service" = (Get-DomainControllerServices $domainController.HostName).NETLOGONService
"DCDIAG: Connectivity" = $DCDiagTestResults.Connectivity
"DCDIAG: Advertising" = $DCDiagTestResults.Advertising
"DCDIAG: FrsEvent" = $DCDiagTestResults.FrsEvent
"DCDIAG: DFSREvent" = $DCDiagTestResults.DFSREvent
"DCDIAG: SysVolCheck" = $DCDiagTestResults.SysVolCheck
"DCDIAG: KccEvent" = $DCDiagTestResults.KccEvent
"DCDIAG: FSMO KnowsOfRoleHolders" = $DCDiagTestResults.KnowsOfRoleHolders
"DCDIAG: MachineAccount" = $DCDiagTestResults.MachineAccount
"DCDIAG: NCSecDesc" = $DCDiagTestResults.NCSecDesc
"DCDIAG: NetLogons" = $DCDiagTestResults.NetLogons
"DCDIAG: ObjectsReplicated" = $DCDiagTestResults.ObjectsReplicated
"DCDIAG: Replications" = $DCDiagTestResults.Replications
"DCDIAG: RidManager" = $DCDiagTestResults.RidManager
"DCDIAG: Services" = $DCDiagTestResults.Services
"DCDIAG: SystemLog" = $DCDiagTestResults.SystemLog
"DCDIAG: VerifyReferences" = $DCDiagTestResults.VerifyReferences
"DCDIAG: CheckSDRefDom" = $DCDiagTestResults.CheckSDRefDom
"DCDIAG: CrossRefValidation" = $DCDiagTestResults.CrossRefValidation
"DCDIAG: LocatorCheck" = $DCDiagTestResults.LocatorCheck
"DCDIAG: Intersite" = $DCDiagTestResults.Intersite
"DCDIAG: FSMO Check" = $DCDiagTestResults.FSMOCheck
"Processing Time (seconds)" = $stopWatch.Elapsed.Seconds
}
$allTestedDomainControllers.Add($thisDomainController)
}
}
# Common HTML head and styles
$htmlhead = "<html>
<style>
BODY { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-size: 10pt; }
H1 { font-size: 20px; }
H2 { font-size: 16px; }
H3 { font-size: 14px; }
TABLE { border: 1px solid #ccc; border-collapse: collapse; font-size: 10pt;}
TH { border: 1px solid #ccc; background: #f2f2f2; padding: 10px; color: #000000;}
TD { border: 1px solid #ccc; padding: 10px; }
td.pass { background: #6BBF59;}
td.warn { background: #FFD966;}
td.fail { background: #D9534F; color: #ffffff;}
td.info { background: #5BC0DE;}
</style>
<body>
<h1 align=""left"">Domain Controller Health Check Report</h1>
<h3 align=""left"">Generated: $reportTime </h3>"
# Domain Controller Health Report Table Header
$htmltableheader = "<h3>Domain Controller Health Summary</h3>
<h3>Forest: $((Get-ADForest).Name)</h3>
<p>
<table style=""width: 100%; border-collapse: separate; "">
<tr>
<th style=""text-align: center"">Server</th>
<th style=""text-align: center; "">Site</th>
<th style=""text-align: center; "">OS Version</th>
<th style=""text-align: center; "">IPv4 Address</th>
<th style=""text-align: center; "">Operation Master Roles</th>
<th style=""text-align: center; "">DNS</th>
<th style=""text-align: center; "">Ping</th>
<th style=""text-align: center; "">Uptime (hours)</th>
<th style=""text-align: center; "">OS Free Space (%)</th>
<th style=""text-align: center; "">OS Free Space (GB)</th>
<th style=""text-align: center; "">Time offset (seconds)</th>
<th style=""text-align: center; "">DNS Service</th>
<th style=""text-align: center; "">NTDS Service</th>
<th style=""text-align: center; "">NetLogon Service</th>
<th style=""text-align: center; "">DCDIAG: Connectivity</th>
<th style=""text-align: center; "">DCDIAG: Advertising</th>
<th style=""text-align: center; "">DCDIAG: FrsEvent</th>
<th style=""text-align: center; "">DCDIAG: DFSREvent</th>
<th style=""text-align: center; "">DCDIAG: SysVolCheck</th>
<th style=""text-align: center; "">DCDIAG: KccEvent</th>
<th style=""text-align: center; "">DCDIAG: FSMO KnowsOfRoleHolders</th>
<th style=""text-align: center; "">DCDIAG: MachineAccount</th>
<th style=""text-align: center; "">DCDIAG: NCSecDesc</th>
<th style=""text-align: center; "">DCDIAG: NetLogons</th>
<th style=""text-align: center; "">DCDIAG: ObjectsReplicated</th>
<th style=""text-align: center; "">DCDIAG: Replications</th>
<th style=""text-align: center; "">DCDIAG: RidManager</th>
<th style=""text-align: center; "">DCDIAG: Services</th>
<th style=""text-align: center; "">DCDIAG: SystemLog</th>
<th style=""text-align: center; "">DCDIAG: VerifyReferences</th>
<th style=""text-align: center; "">DCDIAG: CheckSDRefDom</th>
<th style=""text-align: center; "">DCDIAG: CrossRefValidation</th>
<th style=""text-align: center; "">DCDIAG: LocatorCheck</th>
<th style=""text-align: center; "">DCDIAG: Intersite</th>
<th style=""text-align: center; "">DCDIAG: FSMO Check</th>
<th style=""text-align: center; "">Processing Time (seconds)</th>
</tr>"
# Domain Controller Health Report Table
$serverhealthhtmltable = $serverhealthhtmltable + $htmltableheader
# This section will process through the $allTestedDomainControllers array object and create and colour the HTML table based on certain conditions.
foreach ($reportline in $allTestedDomainControllers) {
if (Test-Path variable:fsmoRoleHTML) {
Remove-Variable fsmoRoleHTML
}
if (($reportline."Operation Master Roles").Count -gt 0) {
$fsmoRoleHTML = ($reportline."Operation Master Roles" | ForEach-Object { "$_`r`n" }) -join '<br>'
}
else {
$fsmoRoleHTML = 'None<br>'
}
$htmltablerow = "<tr>"
$htmltablerow += "<td>$($reportline.Server)</td>"
$htmltablerow += "<td>$($reportline.Site)</td>"
$htmltablerow += "<td>$($reportline."OS Version")</td>"
$htmltablerow += "<td>$($reportline."IPv4 Address")</td>"
$htmltablerow += "<td>$fsmoRoleHTML</td>"
$htmltablerow += (New-ServerHealthHTMLTableCell "DNS" )
$htmltablerow += (New-ServerHealthHTMLTableCell "Ping")
if ($($reportline."Uptime (hours)") -eq "CIM Failure") {
$htmltablerow += "<td class=""warn"">Could not test server uptime.</td>"
}
elseif ($($reportline."Uptime (hours)") -eq "Fail") {
$htmltablerow += "<td class=""fail"">Fail</td>"
}
else {
$hours = [int]$($reportline."Uptime (hours)")
if ($hours -le 24) {
$htmltablerow += "<td class=""warn"">$hours</td>"
}
else {
$htmltablerow += "<td class=""pass"">$hours</td>"
}
}
$osSpace = $reportline."OS Free Space (%)"
if ($osSpace -eq "CIM Failure") {
$htmltablerow += "<td class=""warn"">Could not test server free space.</td>"
}
elseif ($osSpace -eq "Fail") {
$htmltablerow += "<td class=""fail"">$osSpace</td>"
}
elseif ($osSpace -le 5) {
$htmltablerow += "<td class=""fail"">$osSpace</td>"
}
elseif ($osSpace -le 30) {
$htmltablerow += "<td class=""warn"">$osSpace</td>"
}
else {
$htmltablerow += "<td class=""pass"">$osSpace</td>"
}
$osSpaceGB = $reportline."OS Free Space (GB)"
if ($osSpaceGB -eq "CIM Failure") {
$htmltablerow += "<td class=""warn"">Could not test server free space.</td>"
}
elseif ($osSpaceGB -eq "Fail") {
$htmltablerow += "<td class=""fail"">$osSpaceGB</td>"
}
elseif ($osSpaceGB -lt 5) {
$htmltablerow += "<td class=""fail"">$osSpaceGB</td>"
}
elseif ($osSpaceGB -lt 10) {
$htmltablerow += "<td class=""warn"">$osSpaceGB</td>"
}
else {
$htmltablerow += "<td class=""pass"">$osSpaceGB</td>"
}
$time = $reportline."Time offset (seconds)"
if ($time -ge 1) {
$htmltablerow += "<td class=""fail"">$time</td>"
}
else {
$htmltablerow += "<td class=""pass"">$time</td>"
}
$htmltablerow += (New-ServerHealthHTMLTableCell "DNS Service")
$htmltablerow += (New-ServerHealthHTMLTableCell "NTDS Service")
$htmltablerow += (New-ServerHealthHTMLTableCell "NetLogon Service")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: Connectivity")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: Advertising")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: FrsEvent")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: DFSREvent")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: SysVolCheck")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: KccEvent")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: FSMO KnowsOfRoleHolders")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: MachineAccount")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: NCSecDesc")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: NetLogons")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: ObjectsReplicated")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: Replications")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: RidManager")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: Services")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: SystemLog")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: VerifyReferences")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: CheckSDRefDom")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: CrossRefValidation")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: LocatorCheck")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: Intersite")
$htmltablerow += (New-ServerHealthHTMLTableCell "DCDIAG: FSMO Check")
$processingTime = $reportline."Processing Time (seconds)"
$htmltablerow += "<td>$processingTime</td>"
[array]$serverhealthhtmltable += $htmltablerow
}
$serverhealthhtmltable += "</table></p>"
$htmltail = "* DNS test is performed using Resolve-DnsName. This cmdlet is only available from Windows 2012 onwards.
</body>
</html>"
$htmlreport = $htmlhead + $serverhealthhtmltable + $htmltail
if ($ReportFile) {
$htmlreport | Out-File $reportFileName -Encoding UTF8
}
if ($SendEmail) {
try {
# Send email with both inline HTML and attachment
$htmlreport | Out-File $reportFileName -Encoding UTF8
Send-MailMessage @smtpsettings -Body $htmlreport -BodyAsHtml -Attachments $reportFileName -Encoding ([System.Text.Encoding]::UTF8) -ErrorAction Stop
Write-Host "Email sent successfully." -ForegroundColor Green
}
catch {
Write-Host "Failed to send email. Error: $_" -ForegroundColor Red
}
}
+293
View File
@@ -0,0 +1,293 @@
<#
.SYNOPSIS
Queries common user information from either Local Accounts or Active Directory.
.DESCRIPTION
This script retrieves detailed information about user accounts.
It can query local user accounts on the machine or Active Directory user accounts
if the machine is domain-joined and the Active Directory module is available.
.PARAMETER UserName
Specifies the username (SamAccountName for AD, or Name for Local) of the user
to query. If omitted, all users based on the specified source will be listed.
.PARAMETER ActiveDirectory
Use this switch to query Active Directory.
Requires the 'ActiveDirectory' module to be installed and the machine to be
connected to a domain. If not specified, local users are queried by default.
.PARAMETER IncludeGroups
Use this switch to include group memberships for each user.
This can take longer, especially for Active Directory.
.EXAMPLE
.\GetUserInformation.ps1 -UserName "jdoe"
Queries information for the local user 'jdoe'.
.EXAMPLE
.\GetUserInformation.ps1 -ActiveDirectory -UserName "s.smith"
Queries information for the Active Directory user 's.smith'.
.EXAMPLE
.\GetUserInformation.ps1 -AllUsers -ActiveDirectory -IncludeGroups
Lists all Active Directory users with their group memberships.
.EXAMPLE
.\GetUserInformation.ps1
Lists all local users on the machine.
.NOTES
Author: ChatGPT (with improvements for clarity and robustness)
Date: 2023-10-27
Version: 1.1
For Active Directory queries:
- Ensure you have the ActiveDirectory module installed (part of RSAT tools).
- Your user account needs appropriate permissions to read AD objects.
#>
[CmdletBinding(DefaultParameterSetName='AllUsers')]
param (
[Parameter(ParameterSetName='SpecificUser', Position=0)]
[string]$UserName,
[Parameter(ParameterSetName='AllUsers')]
[switch]$AllUsers,
[Parameter()]
[switch]$ActiveDirectory,
[Parameter()]
[switch]$IncludeGroups
)
function Get-LocalUserInformation {
[CmdletBinding()]
param (
[string]$TargetUsername
)
Write-Host "Querying local user accounts..." -ForegroundColor Cyan
$users = @()
try {
if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey('TargetUsername')) {
# Try to get a specific local user
$user = Get-LocalUser -Name $TargetUsername -ErrorAction SilentlyContinue
if ($user) {
$users += $user
} else {
Write-Warning "Local user '$TargetUsername' not found."
}
} else {
# Get all local users
$users = Get-LocalUser -ErrorAction Stop
}
if ($users.Count -eq 0) {
Write-Warning "No local user accounts found matching the criteria."
return
}
foreach ($user in $users) {
$groups = @()
if ($IncludeGroups) {
try {
# Get-LocalUser has a .Groups property in PS 5.1+
# For older versions, one might have to iterate Get-LocalGroup and check members
$userGroups = $user.Groups | Select-Object -ExpandProperty Name
if ($userGroups) {
$groups = $userGroups
}
}
catch {
Write-Warning "Could not retrieve groups for local user $($user.Name): $($_.Exception.Message)"
}
}
[PSCustomObject]@{
Source = 'Local'
Username = $user.Name
FullName = $user.FullName
Description = $user.Description
Enabled = $user.Enabled
LastLogon = 'N/A (Local)'
EmailAddress = 'N/A (Local)'
Department = 'N/A (Local)'
Office = 'N/A (Local)'
PhoneNumber = 'N/A (Local)'
HomeDirectory = 'N/A (Local)'
ProfilePath = 'N/A (Local)'
Groups = if ($groups.Count -gt 0) { $groups -join ', ' } else { 'None' }
SID = $user.SID.Value
WhenCreated = $user.CreationTime
}
}
}
catch {
Write-Error "An error occurred while querying local users: $($_.Exception.Message)"
}
}
function Get-ADUserInformation {
[CmdletBinding()]
param (
[string]$TargetUsername
)
Write-Host "Querying Active Directory user accounts..." -ForegroundColor Cyan
# Check if ActiveDirectory module is available
if (-not (Get-Module -ListAvailable -Name ActiveDirectory)) {
Write-Warning "The ActiveDirectory module is not installed or available."
Write-Warning "Please install Remote Server Administration Tools (RSAT) for Active Directory Domain Services."
Write-Warning "Cannot query Active Directory."
return
}
# Import the module if not already loaded in the current session
if (-not (Get-Module -Name ActiveDirectory -ErrorAction SilentlyContinue)) {
try {
Import-Module ActiveDirectory -ErrorAction Stop
Write-Host "ActiveDirectory module loaded successfully." -ForegroundColor Green
}
catch {
Write-Error "Failed to load ActiveDirectory module: $($_.Exception.Message)"
return
}
}
$propertiesToLoad = @(
"SamAccountName", "DisplayName", "GivenName", "Surname", "Description", "Enabled",
"LastLogonTimestamp", "EmailAddress", "OfficePhone", "Department", "Office",
"HomeDirectory", "ProfilePath", "SID", "WhenCreated", "PasswordLastSet"
)
$adUsers = @()
try {
if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey('TargetUsername')) {
# Try to get a specific AD user
$user = Get-ADUser -Identity $TargetUsername -Properties $propertiesToLoad -ErrorAction SilentlyContinue
if ($user) {
$adUsers += $user
} else {
Write-Warning "Active Directory user '$TargetUsername' not found."
}
} else {
# Get all AD users (excluding disabled or specific service accounts if desired, for now, all)
$adUsers = Get-ADUser -Filter * -Properties $propertiesToLoad -ErrorAction Stop
}
if ($adUsers.Count -eq 0) {
Write-Warning "No Active Directory user accounts found matching the criteria."
return
}
foreach ($adUser in $adUsers) {
$groups = @()
if ($IncludeGroups) {
try {
$userGroups = Get-ADPrincipalGroupMembership -Identity $adUser.SamAccountName | Select-Object -ExpandProperty Name
if ($userGroups) {
$groups = $userGroups
}
}
catch {
Write-Warning "Could not retrieve groups for AD user $($adUser.SamAccountName): $($_.Exception.Message)"
}
}
# Convert LastLogonTimestamp from FileTime to DateTime
$lastLogon = if ($adUser.LastLogonTimestamp) {
[DateTime]::FromFileTime($adUser.LastLogonTimestamp)
} else {
"Not available" # Or "Never"
}
[PSCustomObject]@{
Source = 'Active Directory'
Username = $adUser.SamAccountName
FullName = $adUser.DisplayName
Description = $adUser.Description
Enabled = $adUser.Enabled
LastLogon = $lastLogon
EmailAddress = $adUser.EmailAddress
Department = $adUser.Department
Office = $adUser.Office
PhoneNumber = $adUser.OfficePhone
HomeDirectory = $adUser.HomeDirectory
ProfilePath = $adUser.ProfilePath
Groups = if ($groups.Count -gt 0) { $groups -join ', ' } else { 'None' }
SID = $adUser.SID.Value
WhenCreated = $adUser.WhenCreated
PasswordLastSet = $adUser.PasswordLastSet
}
}
}
catch {
Write-Error "An error occurred while querying Active Directory users: $($_.Exception.Message)"
}
}
# --- Main Script Logic ---
if ($ActiveDirectory) {
if ($UserName) {
Get-ADUserInformation -TargetUsername $UserName
}
else {
Get-ADUserInformation
}
} else {
# Default to local users if -ActiveDirectory not specified
if ($UserName) {
Get-LocalUserInformation -TargetUsername $UserName
}
else {
Get-LocalUserInformation
}
}
# How to Use the Script:
#
# Save: Copy the code above and save it in a file named GetUserInformation.ps1 (or any other .ps1 name) on your computer.
#
# Open PowerShell:
# Right-click on the PowerShell icon and select "Run as administrator" (recommended for querying all local users, though usually not strictly necessary for just reading common info).
# Navigate to the directory where you saved the script using cd C:\path\to\your\script.
#
# Execution Policy: If you haven't run PowerShell scripts before, you might need to adjust your execution policy. You can do this by running:
#
# Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
#
# Confirm with Y when prompted. You can revert this later with Set-ExecutionPolicy Restricted -Scope CurrentUser.
#
# Run the Script:
#
# To list all Local Users:
#
# .\GetUserInformation.ps1
#
# To query a specific Local User (e.g., "Administrator"):
#
# .\GetUserInformation.ps1 -UserName "Administrator"
#
# To list all Active Directory Users (if domain-joined and AD module installed):
#
# .\GetUserInformation.ps1 -ActiveDirectory
#
# (Be aware this might list many users in a large domain.)
#
# To query a specific Active Directory User (e.g., "johndoe"):
#
# .\GetUserInformation.ps1 -ActiveDirectory -UserName "johndoe"
#
# To include Group Memberships (can be slower for many users):
#
# .\GetUserInformation.ps1 -ActiveDirectory -IncludeGroups -UserName "johndoe"
#
# or
#
# .\GetUserInformation.ps1 -IncludeGroups # For local users