⚠️ 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>
239 lines
11 KiB
PowerShell
239 lines
11 KiB
PowerShell
<#
|
|
.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"
|