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,185 @@
<#
.SYNOPSIS
Report on inactive Active Directory user accounts based on true LastLogon
.DESCRIPTION
Queries all domain controllers to find the most recent LastLogon time for users.
The LastLogon attribute is not replicated, so each DC must be queried individually
to get accurate results. Identifies accounts inactive for specified days.
.PARAMETER InactiveDays
Number of days without logon to consider account inactive (default: 90)
.PARAMETER OutputFolder
Destination folder for reports. Default: .\InactiveADUsers-<date>
.PARAMETER SearchBase
Optional OU to limit search scope (default: entire domain)
.PARAMETER IncludeDisabled
Include disabled accounts in the report (default: $false)
.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.
- Requires Active Directory PowerShell module
- Run with appropriate AD permissions
- Can be slow in large environments (queries each DC)
- LastLogon attribute is not replicated between DCs
.EXAMPLE
.\Get-ADUserLastLogon.ps1
.EXAMPLE
.\Get-ADUserLastLogon.ps1 -InactiveDays 180 -SearchBase "OU=Users,DC=domain,DC=com"
#>
[CmdletBinding()]
param(
[int]$InactiveDays = 90,
[string]$OutputFolder = (Join-Path -Path (Get-Location) -ChildPath ("InactiveADUsers-" + (Get-Date -Format "yyyyMMdd-HHmm"))),
[string]$SearchBase = "",
[bool]$IncludeDisabled = $false
)
function NowTag { (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") }
Write-Host "[$(NowTag)] ⚠️ AI-GENERATED SCRIPT - UNTESTED" -ForegroundColor Yellow
Write-Host "[$(NowTag)] Starting AD user lastlogon analysis..." -ForegroundColor Green
# Import AD module
try {
Import-Module ActiveDirectory -ErrorAction Stop
} catch {
Write-Host "[$(NowTag)] ERROR: Active Directory module not available" -ForegroundColor Red
exit 1
}
# Create output folder
New-Item -ItemType Directory -Path $OutputFolder -Force | Out-Null
$cutoffDate = (Get-Date).AddDays(-$InactiveDays)
Write-Host "[$(NowTag)] Cutoff date: $($cutoffDate.ToString('yyyy-MM-dd'))"
# Get all domain controllers
Write-Host "[$(NowTag)] Discovering domain controllers..."
$domainControllers = Get-ADDomainController -Filter * | Select-Object -ExpandProperty HostName
Write-Host "[$(NowTag)] Found $($domainControllers.Count) domain controller(s)"
# Get all users
Write-Host "[$(NowTag)] Retrieving AD users..."
$getUserParams = @{
Filter = "ObjectClass -eq 'user' -and Enabled -eq 'True'"
Properties = @('LastLogon', 'LastLogonDate', 'WhenCreated', 'Department', 'Title', 'Description', 'PasswordLastSet', 'PasswordNeverExpires', 'Enabled')
}
if ($SearchBase) {
$getUserParams['SearchBase'] = $SearchBase
}
if ($IncludeDisabled) {
$getUserParams['Filter'] = "ObjectClass -eq 'user'"
}
$users = Get-ADUser @getUserParams
$userCount = ($users | Measure-Object).Count
Write-Host "[$(NowTag)] Found $userCount user(s) to analyze"
# Query each DC for LastLogon and find the most recent
$userLogonData = @()
$current = 0
foreach ($user in $users) {
$current++
$pct = [int](($current / $userCount) * 100)
Write-Progress -Activity "Querying LastLogon from Domain Controllers" -Status "Processing $($user.SamAccountName) ($current/$userCount)" -PercentComplete $pct
$mostRecentLogon = $null
foreach ($dc in $domainControllers) {
try {
$userFromDC = Get-ADUser -Identity $user.SamAccountName -Server $dc -Properties LastLogon -ErrorAction SilentlyContinue
if ($userFromDC.LastLogon) {
$logonDate = [DateTime]::FromFileTime($userFromDC.LastLogon)
if ($null -eq $mostRecentLogon -or $logonDate -gt $mostRecentLogon) {
$mostRecentLogon = $logonDate
}
}
} catch {
Write-Verbose "Could not query $dc for $($user.SamAccountName)"
}
}
# Determine if inactive
$isInactive = $false
$reason = ""
$daysSinceLogon = "N/A"
if ($null -eq $mostRecentLogon) {
$isInactive = $true
$reason = "Never logged on"
} elseif ($mostRecentLogon -lt $cutoffDate) {
$isInactive = $true
$daysSinceLogon = [int]((Get-Date) - $mostRecentLogon).TotalDays
$reason = "Last logon: $($mostRecentLogon.ToString('yyyy-MM-dd'))"
} else {
$daysSinceLogon = [int]((Get-Date) - $mostRecentLogon).TotalDays
}
if ($isInactive) {
$userLogonData += [PSCustomObject]@{
SamAccountName = $user.SamAccountName
DisplayName = $user.Name
UserPrincipalName = $user.UserPrincipalName
Enabled = $user.Enabled
LastLogon = $mostRecentLogon
DaysSinceLogon = $daysSinceLogon
LastLogonDate = $user.LastLogonDate # Replicated attribute for comparison
WhenCreated = $user.WhenCreated
PasswordLastSet = $user.PasswordLastSet
PasswordNeverExpires = $user.PasswordNeverExpires
Department = $user.Department
Title = $user.Title
Description = $user.Description
DistinguishedName = $user.DistinguishedName
Reason = $reason
}
}
}
Write-Progress -Activity "Querying LastLogon from Domain Controllers" -Completed
# Export results
Write-Host "[$(NowTag)] Found $($userLogonData.Count) inactive user(s)"
if ($userLogonData.Count -gt 0) {
$csvFile = Join-Path $OutputFolder "Inactive-ADUsers.csv"
$userLogonData | Sort-Object DaysSinceLogon -Descending |
Export-Csv -NoTypeInformation -Encoding UTF8 -Path $csvFile
Write-Host "[$(NowTag)] Report exported: $csvFile" -ForegroundColor Green
# Summary statistics
Write-Host "`nINACTIVE USER SUMMARY:" -ForegroundColor Cyan
Write-Host " Total Inactive Users: $($userLogonData.Count)"
Write-Host " Never Logged On: $(($userLogonData | Where-Object { $_.DaysSinceLogon -eq 'N/A' }).Count)"
Write-Host " Enabled Accounts: $(($userLogonData | Where-Object Enabled).Count)"
Write-Host " Disabled Accounts: $(($userLogonData | Where-Object { -not $_.Enabled }).Count)"
Write-Host " Password Never Expires: $(($userLogonData | Where-Object PasswordNeverExpires).Count)"
# By department
$byDept = $userLogonData | Where-Object Department | Group-Object Department | Sort-Object Count -Descending | Select-Object -First 5
if ($byDept) {
Write-Host "`nTop 5 Departments with Inactive Users:" -ForegroundColor Cyan
$byDept | ForEach-Object {
Write-Host " $($_.Name): $($_.Count)"
}
}
} else {
Write-Host "[$(NowTag)] No inactive users found!" -ForegroundColor Green
}
Write-Host "`n[$(NowTag)] Analysis complete! Output folder: $OutputFolder"