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:
185
ActiveDirectory/Get-ADUserLastLogon.ps1
Normal file
185
ActiveDirectory/Get-ADUserLastLogon.ps1
Normal 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"
|
||||
Reference in New Issue
Block a user