Add 12 AI-generated PowerShell scripts with documentation

⚠️ IMPORTANT: These scripts are AI-GENERATED and UNTESTED

Exchange Scripts (5):
- Get-MailboxPermissions.ps1: Audit delegate access permissions
- Get-InactiveMailboxes.ps1: Identify stale mailboxes
- Compare-MailboxDatabases.ps1: Database health comparison
- Export-DistributionGroups.ps1: Distribution group inventory
- Get-MailflowStats.ps1: Transport log analysis

Active Directory Scripts (3):
- Get-ADUserLastLogon.ps1: True LastLogon across all DCs
- Export-OUStructure.ps1: OU hierarchy with GPO links
- Compare-ADGroupMemberships.ps1: Compare user group memberships

System Maintenance Scripts (4):
- Get-ServerInventory.ps1: Hardware/software inventory report
- Monitor-DiskSpace.ps1: Disk space monitoring with alerts
- Backup-ExchangeCertificates.ps1: Certificate backup to PFX
- Test-ExchangeHealth.ps1: Aggregated Exchange health checks

Documentation:
- Updated CLAUDE.md with AI-generated scripts section
- Added AI-GENERATED-SCRIPTS.md with warnings and testing guide

All scripts include prominent warnings and follow established patterns
from existing scripts. Require thorough testing before production use.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Martien de Kleijn
2025-10-15 10:52:44 +02:00
parent 5e9d160d48
commit 62134801aa
14 changed files with 2565 additions and 0 deletions

View File

@ -0,0 +1,178 @@
<#
.SYNOPSIS
Backup Exchange certificates to PFX files
.DESCRIPTION
Exports all Exchange certificates to password-protected PFX files.
Alerts on certificates expiring within specified days.
.PARAMETER OutputFolder
Destination folder for PFX exports. Default: .\CertBackup-<date>
.PARAMETER Password
Password for PFX files (required)
.PARAMETER ExpiryWarningDays
Alert on certificates expiring within this many days (default: 90)
.PARAMETER IncludePrivateKey
Export with private key (required for restoration, default: $true)
.NOTES
⚠️ AI-GENERATED SCRIPT - UNTESTED
This script was generated by Claude AI and has not been tested in production.
Review and test thoroughly in a non-production environment before use.
- Run in Exchange Management Shell with appropriate RBAC permissions
- Requires password for PFX protection
- Store PFX files securely - they contain private keys
- Tested compatibility: Exchange 2013/2016/2019 (not validated)
.EXAMPLE
$pwd = Read-Host "Enter PFX password" -AsSecureString
.\Backup-ExchangeCertificates.ps1 -Password $pwd
.EXAMPLE
$pwd = ConvertTo-SecureString "P@ssw0rd!" -AsPlainText -Force
.\Backup-ExchangeCertificates.ps1 -Password $pwd -ExpiryWarningDays 60
#>
[CmdletBinding()]
param(
[string]$OutputFolder = (Join-Path -Path (Get-Location) -ChildPath ("CertBackup-" + (Get-Date -Format "yyyyMMdd-HHmm"))),
[Parameter(Mandatory = $true)]
[System.Security.SecureString]$Password,
[int]$ExpiryWarningDays = 90,
[bool]$IncludePrivateKey = $true
)
function NowTag { (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") }
Write-Host "[$(NowTag)] ⚠️ AI-GENERATED SCRIPT - UNTESTED" -ForegroundColor Yellow
Write-Host "[$(NowTag)] Starting Exchange certificate backup..." -ForegroundColor Green
# Create output folder
New-Item -ItemType Directory -Path $OutputFolder -Force | Out-Null
Write-Host "[$(NowTag)] Output folder: $OutputFolder"
# Get all Exchange certificates
Write-Host "[$(NowTag)] Retrieving Exchange certificates..."
$certs = Get-ExchangeCertificate -ErrorAction SilentlyContinue
if (-not $certs) {
Write-Host "[$(NowTag)] No Exchange certificates found" -ForegroundColor Yellow
exit 0
}
$certCount = ($certs | Measure-Object).Count
Write-Host "[$(NowTag)] Found $certCount certificate(s)"
# Process each certificate
$exported = @()
$warnings = @()
foreach ($cert in $certs) {
$thumbprint = $cert.Thumbprint
$subject = $cert.Subject
$notAfter = $cert.NotAfter
$daysUntilExpiry = [int](($notAfter - (Get-Date)).TotalDays)
Write-Host "[$(NowTag)] Processing: $subject (Thumbprint: $thumbprint)"
# Check expiry
$status = "OK"
if ($daysUntilExpiry -lt 0) {
$status = "EXPIRED"
$warnings += [PSCustomObject]@{
Thumbprint = $thumbprint
Subject = $subject
NotAfter = $notAfter
DaysRemaining = $daysUntilExpiry
Status = $status
}
Write-Host " WARNING: Certificate EXPIRED on $($notAfter.ToString('yyyy-MM-dd'))" -ForegroundColor Red
} elseif ($daysUntilExpiry -lt $ExpiryWarningDays) {
$status = "EXPIRING SOON"
$warnings += [PSCustomObject]@{
Thumbprint = $thumbprint
Subject = $subject
NotAfter = $notAfter
DaysRemaining = $daysUntilExpiry
Status = $status
}
Write-Host " WARNING: Certificate expires in $daysUntilExpiry days ($($notAfter.ToString('yyyy-MM-dd')))" -ForegroundColor Yellow
} else {
Write-Host " Status: OK - Expires in $daysUntilExpiry days"
}
# Export to PFX
$fileName = "$($thumbprint).pfx"
$filePath = Join-Path $OutputFolder $fileName
try {
if ($IncludePrivateKey) {
# Export with private key
Export-ExchangeCertificate -Thumbprint $thumbprint -FileName $filePath -Password $Password -ErrorAction Stop | Out-Null
Write-Host " Exported: $fileName (with private key)" -ForegroundColor Green
} else {
# Export public key only (using .NET method)
$certObj = Get-Item -Path "Cert:\LocalMachine\My\$thumbprint" -ErrorAction SilentlyContinue
if ($certObj) {
$certBytes = $certObj.Export('Cert')
[System.IO.File]::WriteAllBytes($filePath, $certBytes)
Write-Host " Exported: $fileName (public key only)" -ForegroundColor Green
} else {
throw "Certificate not found in LocalMachine\My"
}
}
$exported += [PSCustomObject]@{
Thumbprint = $thumbprint
Subject = $subject
Services = ($cert.Services -join ", ")
NotBefore = $cert.NotBefore
NotAfter = $notAfter
DaysRemaining = $daysUntilExpiry
Status = $status
FileName = $fileName
FilePath = $filePath
FileSize = (Get-Item $filePath).Length
}
} catch {
Write-Host " ERROR exporting certificate: $($_.Exception.Message)" -ForegroundColor Red
}
}
# Export inventory
$inventoryFile = Join-Path $OutputFolder "Certificate-Inventory.csv"
$exported | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $inventoryFile
Write-Host "`n[$(NowTag)] Inventory exported: $inventoryFile" -ForegroundColor Green
# Export warnings if any
if ($warnings.Count -gt 0) {
$warningsFile = Join-Path $OutputFolder "Certificate-Warnings.csv"
$warnings | Sort-Object DaysRemaining | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $warningsFile
Write-Host "[$(NowTag)] Warnings exported: $warningsFile" -ForegroundColor Yellow
}
# Summary
Write-Host "`nBACKUP SUMMARY:" -ForegroundColor Cyan
Write-Host " Total Certificates: $certCount"
Write-Host " Successfully Exported: $($exported.Count)" -ForegroundColor Green
Write-Host " Failed: $($certCount - $exported.Count)" -ForegroundColor $(if ($certCount - $exported.Count -gt 0) { "Red" } else { "Green" })
Write-Host " Expired: $(($warnings | Where-Object Status -eq 'EXPIRED').Count)" -ForegroundColor Red
Write-Host " Expiring Soon: $(($warnings | Where-Object Status -eq 'EXPIRING SOON').Count)" -ForegroundColor Yellow
if ($warnings.Count -gt 0) {
Write-Host "`nCERTIFICATE WARNINGS:" -ForegroundColor Yellow
$warnings | Sort-Object DaysRemaining | ForEach-Object {
$color = if ($_.Status -eq "EXPIRED") { "Red" } else { "Yellow" }
Write-Host " $($_.Status): $($_.Subject) - $($_.DaysRemaining) days" -ForegroundColor $color
}
}
Write-Host "`n[$(NowTag)] Backup complete! Output folder: $OutputFolder"
Write-Host "`nIMPORTANT: Store PFX files securely - they contain private keys!" -ForegroundColor Cyan

View File

@ -0,0 +1,238 @@
<#
.SYNOPSIS
Comprehensive server hardware and software inventory report
.DESCRIPTION
Collects system information including CPU, RAM, disk, OS version, installed roles,
and key services. Generates Word document (with HTML fallback) similar to
Exchange-Inventory.ps1 structure.
.PARAMETER OutputFolder
Destination folder for reports. Default: .\ServerInventory-<date>
.PARAMETER IncludeInstalledSoftware
Include installed software list (can be slow, default: $false)
.PARAMETER IncludeWindowsFeatures
Include Windows Features/Roles (default: $true)
.NOTES
⚠️ AI-GENERATED SCRIPT - UNTESTED
This script was generated by Claude AI and has not been tested in production.
Review and test thoroughly in a non-production environment before use.
- Run with Administrator privileges for complete information
- Works on Windows Server 2012+
- Report generation uses Word COM or HTML fallback
.EXAMPLE
.\Get-ServerInventory.ps1
.EXAMPLE
.\Get-ServerInventory.ps1 -IncludeInstalledSoftware $true -OutputFolder "D:\Inventory"
#>
[CmdletBinding()]
param(
[string]$OutputFolder = (Join-Path -Path (Get-Location) -ChildPath ("ServerInventory-" + (Get-Date -Format "yyyyMMdd-HHmm"))),
[bool]$IncludeInstalledSoftware = $false,
[bool]$IncludeWindowsFeatures = $true
)
function NowTag { (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") }
Write-Host "[$(NowTag)] ⚠️ AI-GENERATED SCRIPT - UNTESTED" -ForegroundColor Yellow
Write-Host "[$(NowTag)] Starting server inventory..." -ForegroundColor Green
# Create output folder
New-Item -ItemType Directory -Path $OutputFolder -Force | Out-Null
$ReportDocx = Join-Path $OutputFolder "Server-Inventory.docx"
$ReportHtml = Join-Path $OutputFolder "Server-Inventory.html"
# Word COM or HTML fallback (reused pattern from Exchange-Inventory.ps1)
$script:Word = $null
$script:Doc = $null
$script:UseHtml = $false
$script:HtmlSb = New-Object System.Text.StringBuilder
function Start-Report {
try {
$script:Word = New-Object -ComObject Word.Application -ErrorAction Stop
$script:Word.Visible = $false
$script:Doc = $script:Word.Documents.Add()
Add-Heading "Server Inventory Report" 1
Add-Paragraph ("Server: $env:COMPUTERNAME")
Add-Paragraph ("Generated: " + (Get-Date).ToString("yyyy-MM-dd HH:mm"))
} catch {
$script:UseHtml = $true
[void]$script:HtmlSb.AppendLine("<html><head><meta charset='utf-8'><title>Server Inventory</title>")
[void]$script:HtmlSb.AppendLine("<style>body{font-family:Segoe UI,Arial,sans-serif;margin:20px} h1{font-size:24px;color:#0066cc} h2{font-size:20px;margin-top:30px;color:#333} table{border-collapse:collapse;width:100%;margin:15px 0} th,td{border:1px solid #ccc;padding:8px;text-align:left} th{background:#f3f3f3;font-weight:bold}</style></head><body>")
[void]$script:HtmlSb.AppendLine("<h1>Server Inventory Report</h1>")
[void]$script:HtmlSb.AppendLine("<p><strong>Server:</strong> $env:COMPUTERNAME</p>")
[void]$script:HtmlSb.AppendLine("<p><strong>Generated:</strong> " + (Get-Date).ToString("yyyy-MM-dd HH:mm") + "</p>")
}
}
function Add-Heading([string]$Text, [int]$Level = 2) {
if ($script:UseHtml) {
[void]$script:HtmlSb.AppendLine("<h$Level>$Text</h$Level>")
} else {
$range = $script:Doc.Range()
$range.Collapse(0) | Out-Null
$range.Text = "$Text`r"
$style = switch ($Level) { 1 { 'Heading 1' } 2 { 'Heading 2' } default { 'Heading 2' } }
$range.set_Style($style)
$range.InsertParagraphAfter() | Out-Null
}
}
function Add-Paragraph([string]$Text) {
if ($script:UseHtml) {
$enc = [System.Web.HttpUtility]::HtmlEncode($Text)
[void]$script:HtmlSb.AppendLine("<p>$enc</p>")
} else {
$range = $script:Doc.Range()
$range.Collapse(0) | Out-Null
$range.Text = $Text
$range.InsertParagraphAfter() | Out-Null
}
}
function Add-Table([object]$Objects, [string[]]$PropOrder, [string]$Title) {
if ($null -eq $Objects) {
Add-Paragraph ("{0}: No data." -f $Title)
return
}
$arr = @()
if ($Objects -is [System.Collections.IEnumerable] -and -not ($Objects -is [string])) {
foreach ($o in $Objects) { $arr += $o }
} else { $arr = @($Objects) }
if ($arr.Count -eq 0) {
Add-Paragraph ("{0}: No data." -f $Title)
return
}
Add-Heading $Title 3
if ($script:UseHtml) {
[void]$script:HtmlSb.AppendLine("<table><thead><tr>")
foreach ($p in $PropOrder) { [void]$script:HtmlSb.AppendLine("<th>$([System.Web.HttpUtility]::HtmlEncode($p))</th>") }
[void]$script:HtmlSb.AppendLine("</tr></thead><tbody>")
foreach ($o in $arr) {
[void]$script:HtmlSb.AppendLine("<tr>")
foreach ($p in $PropOrder) {
$val = if ($o.$p) { [string]$o.$p } else { "" }
[void]$script:HtmlSb.AppendLine("<td>$([System.Web.HttpUtility]::HtmlEncode($val))</td>")
}
[void]$script:HtmlSb.AppendLine("</tr>")
}
[void]$script:HtmlSb.AppendLine("</tbody></table>")
} else {
$rows = $arr.Count
$cols = $PropOrder.Count
$range = $script:Doc.Range()
$table = $script:Doc.Tables.Add($range, [Math]::Max(1, $rows) + 1, $cols)
for ($c = 1; $c -le $cols; $c++) { $table.Cell(1, $c).Range.Text = $PropOrder[$c - 1] }
$table.Rows.Item(1).Range.Bold = $true
for ($r = 0; $r -lt $rows; $r++) {
for ($c = 0; $c -lt $cols; $c++) {
$val = if ($arr[$r].($PropOrder[$c])) { [string]$arr[$r].($PropOrder[$c]) } else { "" }
$table.Cell($r + 2, $c + 1).Range.Text = $val
}
}
$table.AutoFitBehavior(2) | Out-Null
$range.InsertParagraphAfter() | Out-Null
}
}
function End-Report {
if ($script:UseHtml) {
[void]$script:HtmlSb.AppendLine("</body></html>")
$script:HtmlSb.ToString() | Out-File -LiteralPath $ReportHtml -Encoding UTF8
Write-Host "[$(NowTag)] HTML report: $ReportHtml" -ForegroundColor Green
} else {
$wdFormatXMLDocument = 12
$script:Doc.SaveAs([ref]$ReportDocx, [ref]$wdFormatXMLDocument)
$script:Doc.Close(); $script:Word.Quit()
Write-Host "[$(NowTag)] Word report: $ReportDocx" -ForegroundColor Green
}
}
Start-Report
# System Information
Write-Host "[$(NowTag)] Collecting system information..."
$cs = Get-CimInstance -ClassName Win32_ComputerSystem
$os = Get-CimInstance -ClassName Win32_OperatingSystem
$bios = Get-CimInstance -ClassName Win32_BIOS
$sysInfo = [PSCustomObject]@{
ComputerName = $cs.Name
Domain = $cs.Domain
Manufacturer = $cs.Manufacturer
Model = $cs.Model
TotalPhysicalMemoryGB = [math]::Round($cs.TotalPhysicalMemory / 1GB, 2)
NumberOfProcessors = $cs.NumberOfProcessors
NumberOfLogicalProcessors = $cs.NumberOfLogicalProcessors
OSName = $os.Caption
OSVersion = $os.Version
OSArchitecture = $os.OSArchitecture
InstallDate = $os.InstallDate
LastBootUpTime = $os.LastBootUpTime
BIOSVersion = $bios.SMBIOSBIOSVersion
SerialNumber = $bios.SerialNumber
}
Add-Heading "System Information" 2
Add-Table $sysInfo @("ComputerName", "Domain", "Manufacturer", "Model", "TotalPhysicalMemoryGB", "NumberOfProcessors", "NumberOfLogicalProcessors", "OSName", "OSVersion", "OSArchitecture", "InstallDate", "LastBootUpTime", "BIOSVersion", "SerialNumber") "System Details"
# CPU Information
Write-Host "[$(NowTag)] Collecting CPU information..."
$cpus = Get-CimInstance -ClassName Win32_Processor | Select-Object Name, NumberOfCores, NumberOfLogicalProcessors, MaxClockSpeed, CurrentClockSpeed
Add-Table $cpus @("Name", "NumberOfCores", "NumberOfLogicalProcessors", "MaxClockSpeed", "CurrentClockSpeed") "Processors"
# Disk Information
Write-Host "[$(NowTag)] Collecting disk information..."
$disks = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3" | Select-Object @{N = "Drive"; E = { $_.DeviceID } }, @{N = "SizeGB"; E = { [math]::Round($_.Size / 1GB, 2) } }, @{N = "FreeGB"; E = { [math]::Round($_.FreeSpace / 1GB, 2) } }, @{N = "UsedGB"; E = { [math]::Round(($_.Size - $_.FreeSpace) / 1GB, 2) } }, @{N = "PercentFree"; E = { [math]::Round(($_.FreeSpace / $_.Size) * 100, 2) } }, VolumeName, FileSystem
Add-Table $disks @("Drive", "SizeGB", "FreeGB", "UsedGB", "PercentFree", "VolumeName", "FileSystem") "Disk Drives"
# Network Adapters
Write-Host "[$(NowTag)] Collecting network adapter information..."
$nics = Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration -Filter "IPEnabled=True" | Select-Object Description, @{N = "IPAddress"; E = { $_.IPAddress -join ", " } }, @{N = "SubnetMask"; E = { $_.IPSubnet -join ", " } }, @{N = "DefaultGateway"; E = { $_.DefaultIPGateway -join ", " } }, @{N = "DNSServers"; E = { $_.DNSServerSearchOrder -join ", " } }, MACAddress, DHCPEnabled
Add-Table $nics @("Description", "IPAddress", "SubnetMask", "DefaultGateway", "DNSServers", "MACAddress", "DHCPEnabled") "Network Adapters"
# Windows Features/Roles
if ($IncludeWindowsFeatures) {
Write-Host "[$(NowTag)] Collecting Windows features..."
try {
Import-Module ServerManager -ErrorAction Stop
$features = Get-WindowsFeature | Where-Object Installed | Select-Object Name, DisplayName, FeatureType
Add-Table $features @("Name", "DisplayName", "FeatureType") "Installed Windows Features/Roles"
} catch {
Add-Paragraph "Windows Features: Could not retrieve (ServerManager module not available)"
}
}
# Services
Write-Host "[$(NowTag)] Collecting critical services..."
$services = Get-Service | Where-Object { $_.StartType -eq "Automatic" -and $_.Status -ne "Running" } | Select-Object Name, DisplayName, Status, StartType
if ($services) {
Add-Table $services @("Name", "DisplayName", "Status", "StartType") "Automatic Services Not Running (Potential Issues)"
} else {
Add-Paragraph "All automatic services are running"
}
# Installed Software (optional, can be slow)
if ($IncludeInstalledSoftware) {
Write-Host "[$(NowTag)] Collecting installed software (this may take a while)..."
$software = Get-CimInstance -ClassName Win32_Product | Select-Object Name, Version, Vendor, InstallDate | Sort-Object Name
Add-Table $software @("Name", "Version", "Vendor", "InstallDate") "Installed Software"
}
# Hotfixes
Write-Host "[$(NowTag)] Collecting installed hotfixes..."
$hotfixes = Get-HotFix | Sort-Object InstalledOn -Descending | Select-Object -First 20 | Select-Object HotFixID, Description, InstalledBy, InstalledOn
Add-Table $hotfixes @("HotFixID", "Description", "InstalledBy", "InstalledOn") "Recent Hotfixes (Last 20)"
End-Report
Write-Host "[$(NowTag)] Inventory complete! Output folder: $OutputFolder"

183
Misc/Monitor-DiskSpace.ps1 Normal file
View File

@ -0,0 +1,183 @@
<#
.SYNOPSIS
Monitor disk space and alert when below threshold
.DESCRIPTION
Checks all fixed drives for available space and alerts when below threshold.
Can be scheduled as a task for proactive monitoring.
.PARAMETER ThresholdPercent
Alert when free space falls below this percentage (default: 15)
.PARAMETER ThresholdGB
Alternative: alert when free space falls below this many GB (default: not used)
.PARAMETER LogPath
Path to log file for alerts (default: C:\Temp\DiskSpace-Monitor.log)
.PARAMETER SendEmail
Send email alerts (requires email parameters, default: $false)
.PARAMETER SmtpServer
SMTP server for email alerts
.PARAMETER EmailFrom
From address for email alerts
.PARAMETER EmailTo
To address(es) for email alerts (comma-separated)
.NOTES
⚠️ AI-GENERATED SCRIPT - UNTESTED
This script was generated by Claude AI and has not been tested in production.
Review and test thoroughly in a non-production environment before use.
- Suitable for scheduled task execution
- Exit codes: 0=OK, 1=Warning (below threshold), 2=Error
- Email functionality requires SMTP access
.EXAMPLE
.\Monitor-DiskSpace.ps1
.EXAMPLE
.\Monitor-DiskSpace.ps1 -ThresholdPercent 20 -LogPath "D:\Logs\DiskMonitor.log"
.EXAMPLE
.\Monitor-DiskSpace.ps1 -SendEmail $true -SmtpServer "smtp.domain.com" -EmailFrom "alerts@domain.com" -EmailTo "admin@domain.com"
#>
[CmdletBinding()]
param(
[int]$ThresholdPercent = 15,
[int]$ThresholdGB = 0,
[string]$LogPath = "C:\Temp\DiskSpace-Monitor.log",
[bool]$SendEmail = $false,
[string]$SmtpServer = "",
[string]$EmailFrom = "",
[string]$EmailTo = ""
)
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$stamp = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss')
$line = "[$stamp] [$Level] $Message"
Write-Output $line
# Ensure log directory exists
$logDir = Split-Path -Path $LogPath -Parent
if ($logDir -and -not (Test-Path -Path $logDir)) {
try {
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
} catch {}
}
try {
Add-Content -Path $LogPath -Value $line -Encoding UTF8 -ErrorAction SilentlyContinue
} catch {}
}
Write-Log "⚠️ AI-GENERATED SCRIPT - UNTESTED" "WARNING"
Write-Log "Starting disk space monitoring on $env:COMPUTERNAME"
# Get all fixed drives
$disks = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3" -ErrorAction SilentlyContinue
if (-not $disks) {
Write-Log "ERROR: Could not retrieve disk information" "ERROR"
exit 2
}
$alerts = @()
$allDisksOK = $true
foreach ($disk in $disks) {
$drive = $disk.DeviceID
$sizeGB = [math]::Round($disk.Size / 1GB, 2)
$freeGB = [math]::Round($disk.FreeSpace / 1GB, 2)
$usedGB = [math]::Round(($disk.Size - $disk.FreeSpace) / 1GB, 2)
$percentFree = [math]::Round(($disk.FreeSpace / $disk.Size) * 100, 2)
Write-Log "Drive $drive - Size: $sizeGB GB, Free: $freeGB GB ($percentFree%)"
# Check thresholds
$alertTriggered = $false
$alertReason = ""
if ($ThresholdGB -gt 0 -and $freeGB -lt $ThresholdGB) {
$alertTriggered = $true
$alertReason = "Free space ($freeGB GB) below threshold ($ThresholdGB GB)"
} elseif ($percentFree -lt $ThresholdPercent) {
$alertTriggered = $true
$alertReason = "Free space ($percentFree%) below threshold ($ThresholdPercent%)"
}
if ($alertTriggered) {
$allDisksOK = $false
$alertMsg = "ALERT: Drive $drive - $alertReason"
Write-Log $alertMsg "ALERT"
$alerts += [PSCustomObject]@{
Drive = $drive
SizeGB = $sizeGB
FreeGB = $freeGB
UsedGB = $usedGB
PercentFree = $percentFree
Reason = $alertReason
}
}
}
# Send email if configured and alerts exist
if ($SendEmail -and $alerts.Count -gt 0) {
if (-not $SmtpServer -or -not $EmailFrom -or -not $EmailTo) {
Write-Log "Email alerts enabled but SMTP parameters incomplete" "WARNING"
} else {
Write-Log "Sending email alert to $EmailTo via $SmtpServer"
$subject = "DISK SPACE ALERT: $env:COMPUTERNAME"
$body = @"
Disk Space Alert on $env:COMPUTERNAME
Generated: $(Get-Date)
The following drives have triggered disk space alerts:
"@
foreach ($alert in $alerts) {
$body += @"
Drive: $($alert.Drive)
Size: $($alert.SizeGB) GB
Free: $($alert.FreeGB) GB ($($alert.PercentFree)%)
Used: $($alert.UsedGB) GB
Alert: $($alert.Reason)
"@
}
$body += @"
Threshold Settings:
Percent Threshold: $ThresholdPercent%
GB Threshold: $ThresholdGB GB
Please investigate and free up disk space as needed.
"@
try {
Send-MailMessage -To $EmailTo.Split(',') -From $EmailFrom -Subject $subject -Body $body -SmtpServer $SmtpServer -ErrorAction Stop
Write-Log "Email sent successfully"
} catch {
Write-Log "ERROR sending email: $($_.Exception.Message)" "ERROR"
}
}
}
# Exit with appropriate code
if ($allDisksOK) {
Write-Log "All disks have sufficient free space"
exit 0
} else {
Write-Log "$($alerts.Count) disk(s) below threshold"
exit 1
}

View File

@ -0,0 +1,290 @@
<#
.SYNOPSIS
Quick Exchange server health check with aggregated results
.DESCRIPTION
Runs multiple Exchange health cmdlets and aggregates results into a single
report. Includes service health, replication health, MAPI connectivity,
and database mount status.
.PARAMETER OutputFolder
Destination folder for reports. Default: .\ExchangeHealth-<date>
.PARAMETER IncludeMailflow
Test mail flow using Test-Mailflow cmdlet (default: $false, can be slow)
.PARAMETER ServerName
Specific server to test (default: local server)
.NOTES
⚠️ AI-GENERATED SCRIPT - UNTESTED
This script was generated by Claude AI and has not been tested in production.
Review and test thoroughly in a non-production environment before use.
- Run in Exchange Management Shell
- Some tests require specific server roles
- Tests are non-intrusive (read-only)
.EXAMPLE
.\Test-ExchangeHealth.ps1
.EXAMPLE
.\Test-ExchangeHealth.ps1 -ServerName "EXCH01" -IncludeMailflow $true
#>
[CmdletBinding()]
param(
[string]$OutputFolder = (Join-Path -Path (Get-Location) -ChildPath ("ExchangeHealth-" + (Get-Date -Format "yyyyMMdd-HHmm"))),
[bool]$IncludeMailflow = $false,
[string]$ServerName = $env:COMPUTERNAME
)
function NowTag { (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") }
Write-Host "[$(NowTag)] ⚠️ AI-GENERATED SCRIPT - UNTESTED" -ForegroundColor Yellow
Write-Host "[$(NowTag)] Starting Exchange health check on $ServerName..." -ForegroundColor Green
# Create output folder
New-Item -ItemType Directory -Path $OutputFolder -Force | Out-Null
$allResults = @()
$criticalIssues = @()
$warnings = @()
# Test 1: Service Health
Write-Host "[$(NowTag)] Testing service health..."
try {
$serviceHealth = Test-ServiceHealth -Server $ServerName -ErrorAction SilentlyContinue
if ($serviceHealth) {
foreach ($svc in $serviceHealth) {
$result = [PSCustomObject]@{
TestCategory = "ServiceHealth"
TestName = $svc.Role
Server = $ServerName
Status = if ($svc.RequiredServicesRunning) { "PASS" } else { "FAIL" }
Details = "Required services: $($svc.RequiredServicesRunning)"
}
$allResults += $result
if (-not $svc.RequiredServicesRunning) {
$criticalIssues += "Service Health FAILED for role: $($svc.Role)"
}
}
Write-Host " Service Health: COMPLETED" -ForegroundColor Green
} else {
Write-Host " Service Health: No results (cmdlet may not be available)" -ForegroundColor Yellow
}
} catch {
Write-Host " Service Health: ERROR - $($_.Exception.Message)" -ForegroundColor Red
$allResults += [PSCustomObject]@{
TestCategory = "ServiceHealth"
TestName = "Test-ServiceHealth"
Server = $ServerName
Status = "ERROR"
Details = $_.Exception.Message
}
}
# Test 2: Replication Health (DAG servers only)
Write-Host "[$(NowTag)] Testing replication health..."
try {
$replHealth = Test-ReplicationHealth -Server $ServerName -ErrorAction SilentlyContinue
if ($replHealth) {
foreach ($test in $replHealth) {
$status = if ($test.Result -eq "Passed") { "PASS" } elseif ($test.Result -eq "Failed") { "FAIL" } else { "WARNING" }
$result = [PSCustomObject]@{
TestCategory = "ReplicationHealth"
TestName = $test.Check
Server = $ServerName
Status = $status
Details = $test.Error
}
$allResults += $result
if ($status -eq "FAIL") {
$criticalIssues += "Replication Health FAILED: $($test.Check) - $($test.Error)"
} elseif ($status -eq "WARNING") {
$warnings += "Replication Health WARNING: $($test.Check) - $($test.Error)"
}
}
Write-Host " Replication Health: COMPLETED" -ForegroundColor Green
} else {
Write-Host " Replication Health: Not applicable (not a DAG member)" -ForegroundColor Yellow
}
} catch {
Write-Host " Replication Health: ERROR or not applicable - $($_.Exception.Message)" -ForegroundColor Yellow
$allResults += [PSCustomObject]@{
TestCategory = "ReplicationHealth"
TestName = "Test-ReplicationHealth"
Server = $ServerName
Status = "N/A"
Details = "Not applicable or error: $($_.Exception.Message)"
}
}
# Test 3: MAPI Connectivity
Write-Host "[$(NowTag)] Testing MAPI connectivity..."
try {
$databases = Get-MailboxDatabase -Server $ServerName -Status -ErrorAction SilentlyContinue
foreach ($db in $databases) {
if ($db.Mounted) {
try {
$mapiTest = Test-MapiConnectivity -Database $db.Name -ErrorAction Stop
$status = if ($mapiTest.Result -eq "Success") { "PASS" } else { "FAIL" }
$result = [PSCustomObject]@{
TestCategory = "MapiConnectivity"
TestName = "Database: $($db.Name)"
Server = $ServerName
Status = $status
Details = "Latency: $($mapiTest.Latency.TotalMilliseconds) ms"
}
$allResults += $result
if ($status -eq "FAIL") {
$criticalIssues += "MAPI Connectivity FAILED for database: $($db.Name)"
}
} catch {
$result = [PSCustomObject]@{
TestCategory = "MapiConnectivity"
TestName = "Database: $($db.Name)"
Server = $ServerName
Status = "ERROR"
Details = $_.Exception.Message
}
$allResults += $result
$warnings += "MAPI test error for $($db.Name): $($_.Exception.Message)"
}
}
}
Write-Host " MAPI Connectivity: COMPLETED" -ForegroundColor Green
} catch {
Write-Host " MAPI Connectivity: ERROR - $($_.Exception.Message)" -ForegroundColor Red
}
# Test 4: Database Mount Status
Write-Host "[$(NowTag)] Checking database mount status..."
try {
$databases = Get-MailboxDatabase -Server $ServerName -Status -ErrorAction SilentlyContinue
foreach ($db in $databases) {
$status = if ($db.Mounted) { "PASS" } else { "FAIL" }
$result = [PSCustomObject]@{
TestCategory = "DatabaseMount"
TestName = "Database: $($db.Name)"
Server = $ServerName
Status = $status
Details = "Mounted: $($db.Mounted)"
}
$allResults += $result
if (-not $db.Mounted) {
$criticalIssues += "Database DISMOUNTED: $($db.Name)"
}
}
Write-Host " Database Mount Status: COMPLETED" -ForegroundColor Green
} catch {
Write-Host " Database Mount Status: ERROR - $($_.Exception.Message)" -ForegroundColor Red
}
# Test 5: Mail Flow (optional)
if ($IncludeMailflow) {
Write-Host "[$(NowTag)] Testing mail flow (this may take a minute)..."
try {
$mailflowTest = Test-Mailflow -TargetMailboxServer $ServerName -ErrorAction Stop
$status = if ($mailflowTest.TestMailflowResult -eq "Success") { "PASS" } else { "FAIL" }
$result = [PSCustomObject]@{
TestCategory = "Mailflow"
TestName = "Test-Mailflow"
Server = $ServerName
Status = $status
Details = "Result: $($mailflowTest.TestMailflowResult), Latency: $($mailflowTest.MessageLatencyTime.TotalMilliseconds) ms"
}
$allResults += $result
if ($status -eq "FAIL") {
$criticalIssues += "Mail Flow test FAILED"
}
Write-Host " Mail Flow: COMPLETED" -ForegroundColor Green
} catch {
Write-Host " Mail Flow: ERROR - $($_.Exception.Message)" -ForegroundColor Red
$allResults += [PSCustomObject]@{
TestCategory = "Mailflow"
TestName = "Test-Mailflow"
Server = $ServerName
Status = "ERROR"
Details = $_.Exception.Message
}
}
}
# Export results
Write-Host "[$(NowTag)] Exporting results..."
$csvFile = Join-Path $OutputFolder "Health-Check-Results.csv"
$allResults | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $csvFile
Write-Host "[$(NowTag)] Results exported: $csvFile" -ForegroundColor Green
# Summary
$passCount = ($allResults | Where-Object Status -eq "PASS").Count
$failCount = ($allResults | Where-Object Status -eq "FAIL").Count
$errorCount = ($allResults | Where-Object Status -eq "ERROR").Count
$totalTests = $allResults.Count
$summaryFile = Join-Path $OutputFolder "Health-Check-Summary.txt"
$summary = @"
Exchange Health Check Summary
Server: $ServerName
Generated: $(Get-Date)
TEST RESULTS:
Total Tests: $totalTests
Passed: $passCount
Failed: $failCount
Errors: $errorCount
Overall Status: $(if ($failCount -eq 0 -and $errorCount -eq 0) { "HEALTHY" } else { "ISSUES DETECTED" })
$(if ($criticalIssues.Count -gt 0) {
"CRITICAL ISSUES:
$($criticalIssues | ForEach-Object { " - $_" } | Out-String)
"} else { "" })
$(if ($warnings.Count -gt 0) {
"WARNINGS:
$($warnings | ForEach-Object { " - $_" } | Out-String)
"} else { "" })
DETAILED RESULTS:
$($allResults | ForEach-Object { " [$($_.Status)] $($_.TestCategory) - $($_.TestName): $($_.Details)" } | Out-String)
"@
$summary | Out-File -FilePath $summaryFile -Encoding UTF8
Write-Host "[$(NowTag)] Summary: $summaryFile" -ForegroundColor Green
# Console output
Write-Host "`nHEALTH CHECK SUMMARY:" -ForegroundColor Cyan
Write-Host " Total Tests: $totalTests"
Write-Host " Passed: $passCount" -ForegroundColor Green
Write-Host " Failed: $failCount" -ForegroundColor $(if ($failCount -gt 0) { "Red" } else { "Green" })
Write-Host " Errors: $errorCount" -ForegroundColor $(if ($errorCount -gt 0) { "Red" } else { "Green" })
if ($criticalIssues.Count -gt 0) {
Write-Host "`nCRITICAL ISSUES DETECTED:" -ForegroundColor Red
$criticalIssues | ForEach-Object { Write-Host " - $_" -ForegroundColor Red }
}
if ($warnings.Count -gt 0) {
Write-Host "`nWARNINGS:" -ForegroundColor Yellow
$warnings | ForEach-Object { Write-Host " - $_" -ForegroundColor Yellow }
}
Write-Host "`n[$(NowTag)] Health check complete! Output folder: $OutputFolder"
# Exit code
if ($failCount -gt 0 -or $errorCount -gt 0) {
exit 1
} else {
exit 0
}