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:
190
Exchange/Compare-MailboxDatabases.ps1
Normal file
190
Exchange/Compare-MailboxDatabases.ps1
Normal file
@ -0,0 +1,190 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Compare health metrics across all Exchange mailbox databases
|
||||
|
||||
.DESCRIPTION
|
||||
Generates a comparison report of database sizes, whitespace, mount status,
|
||||
backup age, and circular logging settings. Alerts on databases exceeding
|
||||
thresholds or with configuration issues.
|
||||
|
||||
.PARAMETER WhitespaceThresholdGB
|
||||
Alert if available whitespace exceeds this value (default: 50)
|
||||
|
||||
.PARAMETER BackupAgeThresholdDays
|
||||
Alert if last backup is older than this many days (default: 2)
|
||||
|
||||
.PARAMETER OutputFolder
|
||||
Destination folder for reports. Default: .\DatabaseComparison-<date>
|
||||
|
||||
.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 -Status parameter support (Get-MailboxDatabase -Status)
|
||||
- Tested compatibility: Exchange 2013/2016/2019 (not validated)
|
||||
|
||||
.EXAMPLE
|
||||
.\Compare-MailboxDatabases.ps1
|
||||
|
||||
.EXAMPLE
|
||||
.\Compare-MailboxDatabases.ps1 -WhitespaceThresholdGB 100 -BackupAgeThresholdDays 1
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[int]$WhitespaceThresholdGB = 50,
|
||||
[int]$BackupAgeThresholdDays = 2,
|
||||
[string]$OutputFolder = (Join-Path -Path (Get-Location) -ChildPath ("DatabaseComparison-" + (Get-Date -Format "yyyyMMdd-HHmm")))
|
||||
)
|
||||
|
||||
function NowTag { (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") }
|
||||
|
||||
function Convert-BytesToGB([string]$sizeStr) {
|
||||
if (-not $sizeStr) { return 0 }
|
||||
try {
|
||||
if ($sizeStr -match "([\d\.,]+)\s*(KB|MB|GB|TB)") {
|
||||
$num = [double]($matches[1] -replace ',', '.')
|
||||
$bytes = switch ($matches[2].ToUpper()) {
|
||||
"KB" { $num * 1KB }
|
||||
"MB" { $num * 1MB }
|
||||
"GB" { $num * 1GB }
|
||||
"TB" { $num * 1TB }
|
||||
}
|
||||
return [math]::Round($bytes / 1GB, 2)
|
||||
}
|
||||
} catch {}
|
||||
return 0
|
||||
}
|
||||
|
||||
Write-Host "[$(NowTag)] ⚠️ AI-GENERATED SCRIPT - UNTESTED" -ForegroundColor Yellow
|
||||
Write-Host "[$(NowTag)] Starting database comparison analysis..." -ForegroundColor Green
|
||||
|
||||
# Create output folder
|
||||
New-Item -ItemType Directory -Path $OutputFolder -Force | Out-Null
|
||||
|
||||
# Get all databases with status
|
||||
Write-Host "[$(NowTag)] Retrieving mailbox databases..."
|
||||
$databases = Get-MailboxDatabase -Status -ErrorAction SilentlyContinue | Sort-Object Name
|
||||
|
||||
$dbCount = ($databases | Measure-Object).Count
|
||||
Write-Host "[$(NowTag)] Found $dbCount databases"
|
||||
|
||||
# Analyze each database
|
||||
$dbAnalysis = @()
|
||||
$alerts = @()
|
||||
|
||||
foreach ($db in $databases) {
|
||||
Write-Host "[$(NowTag)] Analyzing $($db.Name)..."
|
||||
|
||||
$dbSizeGB = Convert-BytesToGB ([string]$db.DatabaseSize)
|
||||
$whitespaceGB = Convert-BytesToGB ([string]$db.AvailableNewMailboxSpace)
|
||||
|
||||
$backupAge = $null
|
||||
$backupStatus = "Unknown"
|
||||
if ($db.LastFullBackup) {
|
||||
$backupAge = [int]((Get-Date) - $db.LastFullBackup).TotalDays
|
||||
$backupStatus = if ($backupAge -le $BackupAgeThresholdDays) { "OK" } else { "OLD" }
|
||||
} else {
|
||||
$backupStatus = "NEVER"
|
||||
}
|
||||
|
||||
# Generate alerts
|
||||
$dbAlerts = @()
|
||||
|
||||
if (-not $db.Mounted) {
|
||||
$dbAlerts += "Database is DISMOUNTED"
|
||||
$alerts += [PSCustomObject]@{
|
||||
Database = $db.Name
|
||||
Severity = "CRITICAL"
|
||||
Alert = "Database is dismounted"
|
||||
}
|
||||
}
|
||||
|
||||
if ($whitespaceGB -gt $WhitespaceThresholdGB) {
|
||||
$dbAlerts += "Excessive whitespace: $whitespaceGB GB"
|
||||
$alerts += [PSCustomObject]@{
|
||||
Database = $db.Name
|
||||
Severity = "WARNING"
|
||||
Alert = "Excessive whitespace: $whitespaceGB GB (threshold: $WhitespaceThresholdGB GB)"
|
||||
}
|
||||
}
|
||||
|
||||
if ($backupStatus -eq "NEVER") {
|
||||
$dbAlerts += "Never backed up"
|
||||
$alerts += [PSCustomObject]@{
|
||||
Database = $db.Name
|
||||
Severity = "CRITICAL"
|
||||
Alert = "Database has never been backed up"
|
||||
}
|
||||
} elseif ($backupStatus -eq "OLD") {
|
||||
$dbAlerts += "Backup is $backupAge days old"
|
||||
$alerts += [PSCustomObject]@{
|
||||
Database = $db.Name
|
||||
Severity = "WARNING"
|
||||
Alert = "Last backup is $backupAge days old (threshold: $BackupAgeThresholdDays days)"
|
||||
}
|
||||
}
|
||||
|
||||
if ($db.CircularLoggingEnabled) {
|
||||
$dbAlerts += "Circular logging enabled"
|
||||
$alerts += [PSCustomObject]@{
|
||||
Database = $db.Name
|
||||
Severity = "INFO"
|
||||
Alert = "Circular logging is enabled"
|
||||
}
|
||||
}
|
||||
|
||||
$dbAnalysis += [PSCustomObject]@{
|
||||
Database = $db.Name
|
||||
Server = $db.Server
|
||||
Mounted = $db.Mounted
|
||||
DatabaseSizeGB = $dbSizeGB
|
||||
WhitespaceGB = $whitespaceGB
|
||||
WhitespacePercent = if ($dbSizeGB -gt 0) { [math]::Round(($whitespaceGB / $dbSizeGB) * 100, 2) } else { 0 }
|
||||
CircularLogging = $db.CircularLoggingEnabled
|
||||
LastFullBackup = $db.LastFullBackup
|
||||
BackupAgeDays = $backupAge
|
||||
BackupStatus = $backupStatus
|
||||
EdbFilePath = $db.EdbFilePath
|
||||
LogFolderPath = $db.LogFolderPath
|
||||
Alerts = ($dbAlerts -join "; ")
|
||||
}
|
||||
}
|
||||
|
||||
# Export full analysis
|
||||
$csvFile = Join-Path $OutputFolder "Database-Comparison.csv"
|
||||
$dbAnalysis | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $csvFile
|
||||
Write-Host "[$(NowTag)] Database analysis exported: $csvFile" -ForegroundColor Green
|
||||
|
||||
# Export alerts
|
||||
if ($alerts.Count -gt 0) {
|
||||
$alertsFile = Join-Path $OutputFolder "Database-Alerts.csv"
|
||||
$alerts | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $alertsFile
|
||||
Write-Host "[$(NowTag)] Alerts exported: $alertsFile" -ForegroundColor Yellow
|
||||
|
||||
Write-Host "`nALERTS FOUND:" -ForegroundColor Yellow
|
||||
$alerts | Group-Object Severity | ForEach-Object {
|
||||
Write-Host " $($_.Name): $($_.Count) alert(s)" -ForegroundColor $(
|
||||
switch ($_.Name) {
|
||||
"CRITICAL" { "Red" }
|
||||
"WARNING" { "Yellow" }
|
||||
default { "Cyan" }
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Write-Host "`n[$(NowTag)] No alerts found - all databases healthy!" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Summary statistics
|
||||
Write-Host "`nDATABASE SUMMARY:" -ForegroundColor Cyan
|
||||
Write-Host " Total Databases: $dbCount"
|
||||
Write-Host " Mounted: $(($dbAnalysis | Where-Object Mounted).Count)"
|
||||
Write-Host " Dismounted: $(($dbAnalysis | Where-Object { -not $_.Mounted }).Count)"
|
||||
Write-Host " Total Size: $([math]::Round(($dbAnalysis | Measure-Object DatabaseSizeGB -Sum).Sum, 2)) GB"
|
||||
Write-Host " Total Whitespace: $([math]::Round(($dbAnalysis | Measure-Object WhitespaceGB -Sum).Sum, 2)) GB"
|
||||
Write-Host " Circular Logging Enabled: $(($dbAnalysis | Where-Object CircularLogging).Count)"
|
||||
|
||||
Write-Host "`n[$(NowTag)] Analysis complete! Output folder: $OutputFolder"
|
||||
163
Exchange/Export-DistributionGroups.ps1
Normal file
163
Exchange/Export-DistributionGroups.ps1
Normal file
@ -0,0 +1,163 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Export complete inventory of distribution groups with members and settings
|
||||
|
||||
.DESCRIPTION
|
||||
Documents all distribution groups including members, owners, email addresses,
|
||||
and key settings. Useful for migration planning, documentation, or backup.
|
||||
|
||||
.PARAMETER OutputFolder
|
||||
Destination folder for reports. Default: .\DistributionGroups-<date>
|
||||
|
||||
.PARAMETER IncludeMembers
|
||||
Include detailed member lists (default: $true, can be slow)
|
||||
|
||||
.PARAMETER GroupFilter
|
||||
Optional filter for specific groups (default: all)
|
||||
|
||||
.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
|
||||
- Large environments may take significant time
|
||||
- Includes both Distribution Groups and Mail-Enabled Security Groups
|
||||
|
||||
.EXAMPLE
|
||||
.\Export-DistributionGroups.ps1
|
||||
|
||||
.EXAMPLE
|
||||
.\Export-DistributionGroups.ps1 -GroupFilter "Sales*" -OutputFolder "D:\Reports\Groups"
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$OutputFolder = (Join-Path -Path (Get-Location) -ChildPath ("DistributionGroups-" + (Get-Date -Format "yyyyMMdd-HHmm"))),
|
||||
[bool]$IncludeMembers = $true,
|
||||
[string]$GroupFilter = "*"
|
||||
)
|
||||
|
||||
function NowTag { (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") }
|
||||
|
||||
Write-Host "[$(NowTag)] ⚠️ AI-GENERATED SCRIPT - UNTESTED" -ForegroundColor Yellow
|
||||
Write-Host "[$(NowTag)] Starting distribution group export..." -ForegroundColor Green
|
||||
|
||||
# Create output folder
|
||||
New-Item -ItemType Directory -Path $OutputFolder -Force | Out-Null
|
||||
|
||||
# Get distribution groups
|
||||
Write-Host "[$(NowTag)] Retrieving distribution groups..."
|
||||
$groups = Get-DistributionGroup -Filter "Name -like '$GroupFilter'" -ResultSize Unlimited -ErrorAction SilentlyContinue
|
||||
|
||||
$groupCount = ($groups | Measure-Object).Count
|
||||
Write-Host "[$(NowTag)] Found $groupCount distribution groups"
|
||||
|
||||
if ($groupCount -eq 0) {
|
||||
Write-Host "[$(NowTag)] No groups found matching filter: $GroupFilter" -ForegroundColor Yellow
|
||||
exit
|
||||
}
|
||||
|
||||
# Collect group details
|
||||
$groupDetails = @()
|
||||
$groupMembers = @()
|
||||
|
||||
$current = 0
|
||||
foreach ($group in $groups) {
|
||||
$current++
|
||||
$pct = [int](($current / $groupCount) * 100)
|
||||
Write-Progress -Activity "Processing Distribution Groups" -Status "Processing $($group.Name) ($current/$groupCount)" -PercentComplete $pct
|
||||
|
||||
# Get managed by
|
||||
$managedByList = @()
|
||||
if ($group.ManagedBy) {
|
||||
foreach ($mgr in $group.ManagedBy) {
|
||||
try {
|
||||
$mgrObj = Get-Recipient $mgr -ErrorAction SilentlyContinue
|
||||
if ($mgrObj) {
|
||||
$managedByList += $mgrObj.PrimarySmtpAddress
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
# Get email addresses
|
||||
$emailAddresses = ($group.EmailAddresses | Where-Object { $_ -like "smtp:*" }) -join "; "
|
||||
|
||||
# Get member count
|
||||
$memberCount = 0
|
||||
try {
|
||||
$members = Get-DistributionGroupMember -Identity $group.Identity -ResultSize Unlimited -ErrorAction SilentlyContinue
|
||||
$memberCount = ($members | Measure-Object).Count
|
||||
|
||||
# Collect member details if requested
|
||||
if ($IncludeMembers -and $members) {
|
||||
foreach ($member in $members) {
|
||||
$groupMembers += [PSCustomObject]@{
|
||||
GroupName = $group.Name
|
||||
GroupPrimarySmtp = $group.PrimarySmtpAddress
|
||||
MemberName = $member.Name
|
||||
MemberPrimarySmtp = $member.PrimarySmtpAddress
|
||||
MemberType = $member.RecipientType
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Write-Host "[$(NowTag)] WARNING: Could not get members for $($group.Name)" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
$groupDetails += [PSCustomObject]@{
|
||||
Name = $group.Name
|
||||
DisplayName = $group.DisplayName
|
||||
Alias = $group.Alias
|
||||
PrimarySmtpAddress = $group.PrimarySmtpAddress
|
||||
EmailAddresses = $emailAddresses
|
||||
RecipientTypeDetails = $group.RecipientTypeDetails
|
||||
MemberCount = $memberCount
|
||||
ManagedBy = ($managedByList -join "; ")
|
||||
RequireSenderAuthentication = $group.RequireSenderAuthenticationEnabled
|
||||
HiddenFromAddressLists = $group.HiddenFromAddressListsEnabled
|
||||
ModerationEnabled = $group.ModerationEnabled
|
||||
SendModerationNotifications = $group.SendModerationNotifications
|
||||
AcceptMessagesOnlyFrom = (($group.AcceptMessagesOnlyFrom | ForEach-Object { $_.ToString() }) -join "; ")
|
||||
RejectMessagesFrom = (($group.RejectMessagesFrom | ForEach-Object { $_.ToString() }) -join "; ")
|
||||
BypassModerationFromSenders = (($group.BypassModerationFromSendersOrMembers | ForEach-Object { $_.ToString() }) -join "; ")
|
||||
WhenCreated = $group.WhenCreated
|
||||
WhenChanged = $group.WhenChanged
|
||||
OrganizationalUnit = $group.OrganizationalUnit
|
||||
}
|
||||
}
|
||||
|
||||
Write-Progress -Activity "Processing Distribution Groups" -Completed
|
||||
|
||||
# Export results
|
||||
Write-Host "[$(NowTag)] Exporting results..."
|
||||
|
||||
$groupsFile = Join-Path $OutputFolder "DistributionGroups.csv"
|
||||
$groupDetails | Sort-Object Name | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $groupsFile
|
||||
Write-Host "[$(NowTag)] Groups exported: $groupsFile" -ForegroundColor Green
|
||||
|
||||
if ($IncludeMembers -and $groupMembers.Count -gt 0) {
|
||||
$membersFile = Join-Path $OutputFolder "DistributionGroup-Members.csv"
|
||||
$groupMembers | Sort-Object GroupName, MemberName | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $membersFile
|
||||
Write-Host "[$(NowTag)] Members exported: $membersFile" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Summary
|
||||
Write-Host "`nSUMMARY:" -ForegroundColor Cyan
|
||||
Write-Host " Total Groups: $groupCount"
|
||||
Write-Host " Total Members: $($groupMembers.Count)"
|
||||
Write-Host " Hidden from Address Lists: $(($groupDetails | Where-Object HiddenFromAddressLists).Count)"
|
||||
Write-Host " Moderation Enabled: $(($groupDetails | Where-Object ModerationEnabled).Count)"
|
||||
Write-Host " Security Groups: $(($groupDetails | Where-Object { $_.RecipientTypeDetails -like '*Security*' }).Count)"
|
||||
|
||||
# Top 10 largest groups
|
||||
$top10 = $groupDetails | Sort-Object MemberCount -Descending | Select-Object -First 10
|
||||
if ($top10) {
|
||||
Write-Host "`nTop 10 Largest Groups:" -ForegroundColor Cyan
|
||||
$top10 | ForEach-Object {
|
||||
Write-Host " $($_.Name): $($_.MemberCount) members"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`n[$(NowTag)] Export complete! Output folder: $OutputFolder"
|
||||
191
Exchange/Get-InactiveMailboxes.ps1
Normal file
191
Exchange/Get-InactiveMailboxes.ps1
Normal file
@ -0,0 +1,191 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Identify inactive mailboxes based on LastLogonTime
|
||||
|
||||
.DESCRIPTION
|
||||
Finds mailboxes with no logon activity within specified days.
|
||||
Useful for identifying stale accounts, cost optimization, and cleanup planning.
|
||||
|
||||
.PARAMETER InactiveDays
|
||||
Number of days without logon to consider mailbox inactive (default: 90)
|
||||
|
||||
.PARAMETER OutputFolder
|
||||
Destination folder for reports. Default: .\InactiveMailboxes-<date>
|
||||
|
||||
.PARAMETER IncludeShared
|
||||
Include shared mailboxes in the report (default: $false)
|
||||
|
||||
.PARAMETER IncludeSize
|
||||
Include mailbox size information (slower, 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
|
||||
- LastLogonTime may be null for never-accessed mailboxes
|
||||
- Size calculation can be slow with large mailbox counts
|
||||
|
||||
.EXAMPLE
|
||||
.\Get-InactiveMailboxes.ps1
|
||||
|
||||
.EXAMPLE
|
||||
.\Get-InactiveMailboxes.ps1 -InactiveDays 180 -IncludeShared $true
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[int]$InactiveDays = 90,
|
||||
[string]$OutputFolder = (Join-Path -Path (Get-Location) -ChildPath ("InactiveMailboxes-" + (Get-Date -Format "yyyyMMdd-HHmm"))),
|
||||
[bool]$IncludeShared = $false,
|
||||
[bool]$IncludeSize = $true
|
||||
)
|
||||
|
||||
function NowTag { (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") }
|
||||
|
||||
Write-Host "[$(NowTag)] ⚠️ AI-GENERATED SCRIPT - UNTESTED" -ForegroundColor Yellow
|
||||
Write-Host "[$(NowTag)] Searching for mailboxes inactive for $InactiveDays+ days..." -ForegroundColor Green
|
||||
|
||||
# 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 mailboxes
|
||||
Write-Host "[$(NowTag)] Retrieving mailboxes..."
|
||||
$filter = if ($IncludeShared) {
|
||||
{ RecipientTypeDetails -notmatch "^Remote" -and Database }
|
||||
} else {
|
||||
{ RecipientTypeDetails -eq "UserMailbox" -and Database }
|
||||
}
|
||||
|
||||
$mailboxes = Get-Mailbox -ResultSize Unlimited -ErrorAction SilentlyContinue |
|
||||
Where-Object $filter
|
||||
|
||||
$mbCount = ($mailboxes | Measure-Object).Count
|
||||
Write-Host "[$(NowTag)] Found $mbCount mailboxes to check"
|
||||
|
||||
# Get statistics per database to avoid prompts
|
||||
Write-Host "[$(NowTag)] Retrieving mailbox statistics..."
|
||||
$statsByDb = @{}
|
||||
$databases = ($mailboxes.Database | Sort-Object -Unique)
|
||||
|
||||
$dbIdx = 0
|
||||
foreach ($db in $databases) {
|
||||
$dbIdx++
|
||||
Write-Progress -Activity "Collecting Mailbox Statistics" -Status "Database $db ($dbIdx/$($databases.Count))" -PercentComplete ([int](($dbIdx / $databases.Count) * 100))
|
||||
|
||||
try {
|
||||
$stats = Get-MailboxStatistics -Database $db -ErrorAction SilentlyContinue
|
||||
foreach ($stat in $stats) {
|
||||
$statsByDb[$stat.DisplayName] = $stat
|
||||
}
|
||||
} catch {
|
||||
Write-Host "[$(NowTag)] WARNING: Could not get statistics for database $db" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
Write-Progress -Activity "Collecting Mailbox Statistics" -Completed
|
||||
|
||||
# Analyze for inactive mailboxes
|
||||
Write-Host "[$(NowTag)] Analyzing mailbox activity..."
|
||||
$inactiveMailboxes = @()
|
||||
|
||||
$current = 0
|
||||
foreach ($mb in $mailboxes) {
|
||||
$current++
|
||||
$pct = [int](($current / $mbCount) * 100)
|
||||
Write-Progress -Activity "Analyzing Mailboxes" -Status "Processing $($mb.DisplayName) ($current/$mbCount)" -PercentComplete $pct
|
||||
|
||||
$stat = $statsByDb[$mb.DisplayName]
|
||||
|
||||
$lastLogon = if ($stat) { $stat.LastLogonTime } else { $null }
|
||||
$isInactive = $false
|
||||
$reason = ""
|
||||
|
||||
if ($null -eq $lastLogon) {
|
||||
$isInactive = $true
|
||||
$reason = "Never logged on"
|
||||
} elseif ($lastLogon -lt $cutoffDate) {
|
||||
$isInactive = $true
|
||||
$reason = "Last logon: $($lastLogon.ToString('yyyy-MM-dd'))"
|
||||
}
|
||||
|
||||
if ($isInactive) {
|
||||
$daysSinceLogon = if ($lastLogon) {
|
||||
[int]((Get-Date) - $lastLogon).TotalDays
|
||||
} else {
|
||||
"N/A"
|
||||
}
|
||||
|
||||
$size = $null
|
||||
$itemCount = $null
|
||||
|
||||
if ($IncludeSize -and $stat) {
|
||||
$size = $stat.TotalItemSize
|
||||
$itemCount = $stat.ItemCount
|
||||
}
|
||||
|
||||
$inactiveMailboxes += [PSCustomObject]@{
|
||||
DisplayName = $mb.DisplayName
|
||||
PrimarySmtpAddress = $mb.PrimarySmtpAddress
|
||||
RecipientTypeDetails = $mb.RecipientTypeDetails
|
||||
Database = $mb.Database
|
||||
LastLogonTime = $lastLogon
|
||||
DaysSinceLogon = $daysSinceLogon
|
||||
WhenCreated = $mb.WhenCreated
|
||||
TotalItemSize = $size
|
||||
ItemCount = $itemCount
|
||||
Reason = $reason
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Progress -Activity "Analyzing Mailboxes" -Completed
|
||||
|
||||
# Export results
|
||||
Write-Host "[$(NowTag)] Found $($inactiveMailboxes.Count) inactive mailboxes"
|
||||
|
||||
if ($inactiveMailboxes.Count -gt 0) {
|
||||
$csvFile = Join-Path $OutputFolder "Inactive-Mailboxes.csv"
|
||||
$inactiveMailboxes | Sort-Object DaysSinceLogon -Descending |
|
||||
Export-Csv -NoTypeInformation -Encoding UTF8 -Path $csvFile
|
||||
Write-Host "[$(NowTag)] Report exported: $csvFile" -ForegroundColor Green
|
||||
|
||||
# Summary by type
|
||||
$byType = $inactiveMailboxes | Group-Object RecipientTypeDetails
|
||||
Write-Host "`nInactive Mailboxes by Type:"
|
||||
foreach ($type in $byType) {
|
||||
Write-Host " $($type.Name): $($type.Count)"
|
||||
}
|
||||
|
||||
# Calculate potential storage savings
|
||||
if ($IncludeSize) {
|
||||
$totalBytes = 0
|
||||
foreach ($mb in $inactiveMailboxes) {
|
||||
if ($mb.TotalItemSize) {
|
||||
try {
|
||||
$sizeStr = [string]$mb.TotalItemSize
|
||||
if ($sizeStr -match "([\d\.,]+)\s*(KB|MB|GB|TB)") {
|
||||
$num = [double]($matches[1] -replace ',', '.')
|
||||
$bytes = switch ($matches[2].ToUpper()) {
|
||||
"KB" { $num * 1KB }
|
||||
"MB" { $num * 1MB }
|
||||
"GB" { $num * 1GB }
|
||||
"TB" { $num * 1TB }
|
||||
}
|
||||
$totalBytes += $bytes
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
$totalGB = [math]::Round($totalBytes / 1GB, 2)
|
||||
Write-Host "`nPotential storage savings: $totalGB GB" -ForegroundColor Cyan
|
||||
}
|
||||
} else {
|
||||
Write-Host "[$(NowTag)] No inactive mailboxes found!" -ForegroundColor Green
|
||||
}
|
||||
|
||||
Write-Host "`n[$(NowTag)] Analysis complete! Output folder: $OutputFolder"
|
||||
173
Exchange/Get-MailboxPermissions.ps1
Normal file
173
Exchange/Get-MailboxPermissions.ps1
Normal file
@ -0,0 +1,173 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Audit mailbox delegate access permissions across Exchange environment
|
||||
|
||||
.DESCRIPTION
|
||||
Reports on SendAs, SendOnBehalf, and FullAccess permissions for all mailboxes.
|
||||
Useful for security audits, compliance reviews, and migration planning.
|
||||
|
||||
.PARAMETER OutputFolder
|
||||
Destination folder for CSV reports. Default: .\MailboxPermissions-<date>
|
||||
|
||||
.PARAMETER MailboxFilter
|
||||
Optional filter for specific mailboxes. Default: all on-premises mailboxes
|
||||
|
||||
.PARAMETER IncludeInherited
|
||||
Include inherited permissions 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.
|
||||
|
||||
- Run in Exchange Management Shell with appropriate RBAC permissions
|
||||
- Can take significant time with large mailbox counts
|
||||
- Tested compatibility: Exchange 2013/2016/2019 (not validated)
|
||||
|
||||
.EXAMPLE
|
||||
.\Get-MailboxPermissions.ps1
|
||||
|
||||
.EXAMPLE
|
||||
.\Get-MailboxPermissions.ps1 -OutputFolder "D:\Reports\Permissions" -IncludeInherited $true
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$OutputFolder = (Join-Path -Path (Get-Location) -ChildPath ("MailboxPermissions-" + (Get-Date -Format "yyyyMMdd-HHmm"))),
|
||||
[string]$MailboxFilter = "*",
|
||||
[bool]$IncludeInherited = $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 mailbox permissions audit..." -ForegroundColor Green
|
||||
|
||||
# Create output folder
|
||||
New-Item -ItemType Directory -Path $OutputFolder -Force | Out-Null
|
||||
|
||||
# Get all on-premises mailboxes
|
||||
Write-Host "[$(NowTag)] Retrieving mailboxes..."
|
||||
$mailboxes = Get-Mailbox -Filter $MailboxFilter -ResultSize Unlimited -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.RecipientTypeDetails -notmatch "^Remote" -and $_.Database }
|
||||
|
||||
$mbCount = ($mailboxes | Measure-Object).Count
|
||||
Write-Host "[$(NowTag)] Found $mbCount mailboxes to audit"
|
||||
|
||||
# Collections
|
||||
$fullAccessPerms = @()
|
||||
$sendAsPerms = @()
|
||||
$sendOnBehalfPerms = @()
|
||||
|
||||
$current = 0
|
||||
foreach ($mb in $mailboxes) {
|
||||
$current++
|
||||
$pct = [int](($current / $mbCount) * 100)
|
||||
Write-Progress -Activity "Auditing Mailbox Permissions" -Status "Processing $($mb.DisplayName) ($current/$mbCount)" -PercentComplete $pct
|
||||
|
||||
# FullAccess permissions
|
||||
try {
|
||||
$fullAccess = Get-MailboxPermission -Identity $mb.Identity -ErrorAction SilentlyContinue |
|
||||
Where-Object {
|
||||
$_.User -notlike "NT AUTHORITY\*" -and
|
||||
$_.User -notlike "S-1-5-*" -and
|
||||
$_.AccessRights -like "*FullAccess*" -and
|
||||
($IncludeInherited -or -not $_.IsInherited)
|
||||
}
|
||||
|
||||
foreach ($perm in $fullAccess) {
|
||||
$fullAccessPerms += [PSCustomObject]@{
|
||||
Mailbox = $mb.DisplayName
|
||||
PrimarySmtpAddress = $mb.PrimarySmtpAddress
|
||||
User = $perm.User
|
||||
AccessRights = ($perm.AccessRights -join ", ")
|
||||
IsInherited = $perm.IsInherited
|
||||
Deny = $perm.Deny
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Write-Host "[$(NowTag)] ERROR getting FullAccess for $($mb.DisplayName): $_" -ForegroundColor Red
|
||||
}
|
||||
|
||||
# SendAs permissions
|
||||
try {
|
||||
$sendAs = Get-ADPermission -Identity $mb.DistinguishedName -ErrorAction SilentlyContinue |
|
||||
Where-Object {
|
||||
$_.ExtendedRights -like "*Send-As*" -and
|
||||
$_.User -notlike "NT AUTHORITY\*" -and
|
||||
$_.User -notlike "S-1-5-*" -and
|
||||
($IncludeInherited -or -not $_.IsInherited)
|
||||
}
|
||||
|
||||
foreach ($perm in $sendAs) {
|
||||
$sendAsPerms += [PSCustomObject]@{
|
||||
Mailbox = $mb.DisplayName
|
||||
PrimarySmtpAddress = $mb.PrimarySmtpAddress
|
||||
User = $perm.User
|
||||
IsInherited = $perm.IsInherited
|
||||
Deny = $perm.Deny
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Write-Host "[$(NowTag)] ERROR getting SendAs for $($mb.DisplayName): $_" -ForegroundColor Red
|
||||
}
|
||||
|
||||
# SendOnBehalf permissions
|
||||
if ($mb.GrantSendOnBehalfTo -and $mb.GrantSendOnBehalfTo.Count -gt 0) {
|
||||
foreach ($user in $mb.GrantSendOnBehalfTo) {
|
||||
$sendOnBehalfPerms += [PSCustomObject]@{
|
||||
Mailbox = $mb.DisplayName
|
||||
PrimarySmtpAddress = $mb.PrimarySmtpAddress
|
||||
User = $user
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Progress -Activity "Auditing Mailbox Permissions" -Completed
|
||||
|
||||
# Export results
|
||||
Write-Host "[$(NowTag)] Exporting results..."
|
||||
|
||||
$fullAccessFile = Join-Path $OutputFolder "FullAccess-Permissions.csv"
|
||||
$sendAsFile = Join-Path $OutputFolder "SendAs-Permissions.csv"
|
||||
$sendOnBehalfFile = Join-Path $OutputFolder "SendOnBehalf-Permissions.csv"
|
||||
$summaryFile = Join-Path $OutputFolder "Permissions-Summary.txt"
|
||||
|
||||
if ($fullAccessPerms.Count -gt 0) {
|
||||
$fullAccessPerms | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $fullAccessFile
|
||||
Write-Host "[$(NowTag)] FullAccess permissions: $fullAccessFile"
|
||||
} else {
|
||||
Write-Host "[$(NowTag)] No FullAccess permissions found"
|
||||
}
|
||||
|
||||
if ($sendAsPerms.Count -gt 0) {
|
||||
$sendAsPerms | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $sendAsFile
|
||||
Write-Host "[$(NowTag)] SendAs permissions: $sendAsFile"
|
||||
} else {
|
||||
Write-Host "[$(NowTag)] No SendAs permissions found"
|
||||
}
|
||||
|
||||
if ($sendOnBehalfPerms.Count -gt 0) {
|
||||
$sendOnBehalfPerms | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $sendOnBehalfFile
|
||||
Write-Host "[$(NowTag)] SendOnBehalf permissions: $sendOnBehalfFile"
|
||||
} else {
|
||||
Write-Host "[$(NowTag)] No SendOnBehalf permissions found"
|
||||
}
|
||||
|
||||
# Summary
|
||||
$summary = @"
|
||||
Mailbox Permissions Audit Summary
|
||||
Generated: $(Get-Date)
|
||||
|
||||
Mailboxes Audited: $mbCount
|
||||
FullAccess Permissions: $($fullAccessPerms.Count)
|
||||
SendAs Permissions: $($sendAsPerms.Count)
|
||||
SendOnBehalf Permissions: $($sendOnBehalfPerms.Count)
|
||||
|
||||
Output Folder: $OutputFolder
|
||||
"@
|
||||
|
||||
$summary | Out-File -FilePath $summaryFile -Encoding UTF8
|
||||
Write-Host "`n$summary"
|
||||
Write-Host "[$(NowTag)] Audit complete!" -ForegroundColor Green
|
||||
226
Exchange/Get-MailflowStats.ps1
Normal file
226
Exchange/Get-MailflowStats.ps1
Normal file
@ -0,0 +1,226 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Analyze Exchange message flow statistics from transport logs
|
||||
|
||||
.DESCRIPTION
|
||||
Aggregates statistics from Exchange transport logs including top senders/receivers,
|
||||
message volume by time period, and potential anomalies. Builds on Get-SMTPTraffic.ps1
|
||||
pattern but provides comprehensive analysis.
|
||||
|
||||
.PARAMETER LogPath
|
||||
Path to transport logs (default: auto-detect from Exchange install)
|
||||
|
||||
.PARAMETER DaysBack
|
||||
Number of days of logs to analyze (default: 7)
|
||||
|
||||
.PARAMETER OutputFolder
|
||||
Destination folder for reports. Default: .\MailflowStats-<date>
|
||||
|
||||
.PARAMETER TopCount
|
||||
Number of top senders/receivers to include in report (default: 25)
|
||||
|
||||
.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
|
||||
- Can be slow with large log volumes
|
||||
- Analyzes SMTP Receive logs by default
|
||||
|
||||
.EXAMPLE
|
||||
.\Get-MailflowStats.ps1
|
||||
|
||||
.EXAMPLE
|
||||
.\Get-MailflowStats.ps1 -DaysBack 30 -TopCount 50
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$LogPath = "",
|
||||
[int]$DaysBack = 7,
|
||||
[string]$OutputFolder = (Join-Path -Path (Get-Location) -ChildPath ("MailflowStats-" + (Get-Date -Format "yyyyMMdd-HHmm"))),
|
||||
[int]$TopCount = 25
|
||||
)
|
||||
|
||||
function NowTag { (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") }
|
||||
|
||||
Write-Host "[$(NowTag)] ⚠️ AI-GENERATED SCRIPT - UNTESTED" -ForegroundColor Yellow
|
||||
Write-Host "[$(NowTag)] Starting mail flow analysis..." -ForegroundColor Green
|
||||
|
||||
# Auto-detect log path if not specified
|
||||
if (-not $LogPath) {
|
||||
$exchangePath = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\ExchangeServer\v15\Setup" -ErrorAction SilentlyContinue).MsiInstallPath
|
||||
if ($exchangePath) {
|
||||
$LogPath = Join-Path $exchangePath "TransportRoles\Logs\FrontEnd\ProtocolLog\SmtpReceive"
|
||||
} else {
|
||||
Write-Host "[$(NowTag)] ERROR: Could not auto-detect Exchange log path. Please specify -LogPath parameter" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Test-Path $LogPath)) {
|
||||
Write-Host "[$(NowTag)] ERROR: Log path not found: $LogPath" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "[$(NowTag)] Log path: $LogPath"
|
||||
|
||||
# Create output folder
|
||||
New-Item -ItemType Directory -Path $OutputFolder -Force | Out-Null
|
||||
|
||||
# Get log files within date range
|
||||
$cutoffDate = (Get-Date).AddDays(-$DaysBack)
|
||||
Write-Host "[$(NowTag)] Analyzing logs from $($cutoffDate.ToString('yyyy-MM-dd')) to $(Get-Date -Format 'yyyy-MM-dd')"
|
||||
|
||||
$logFiles = Get-ChildItem -Path $LogPath -Filter "*.log" -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.LastWriteTime -ge $cutoffDate } |
|
||||
Sort-Object LastWriteTime -Descending
|
||||
|
||||
$fileCount = ($logFiles | Measure-Object).Count
|
||||
|
||||
if ($fileCount -eq 0) {
|
||||
Write-Host "[$(NowTag)] No log files found in specified date range" -ForegroundColor Yellow
|
||||
exit 0
|
||||
}
|
||||
|
||||
Write-Host "[$(NowTag)] Found $fileCount log file(s) to analyze"
|
||||
|
||||
# Parse logs
|
||||
$allMessages = @()
|
||||
$fileIdx = 0
|
||||
|
||||
foreach ($logFile in $logFiles) {
|
||||
$fileIdx++
|
||||
Write-Progress -Activity "Parsing Transport Logs" -Status "Processing $($logFile.Name) ($fileIdx/$fileCount)" -PercentComplete ([int](($fileIdx / $fileCount) * 100))
|
||||
|
||||
# Get header from file
|
||||
$header = Get-Content $logFile.FullName -TotalCount 50 | Where-Object { $_ -like '#Fields:*' } | Select-Object -First 1
|
||||
if (-not $header) { continue }
|
||||
|
||||
$fields = $header -replace '^#Fields: ', ''
|
||||
$columns = $fields -split ','
|
||||
|
||||
# Parse log entries
|
||||
Get-Content $logFile.FullName | Where-Object { -not ($_ -like '#*') -and $_ -match ',' } | ForEach-Object {
|
||||
try {
|
||||
$row = $_ -split ',(?=(?:[^"]*"[^"]*")*[^"]*$)' # Handle quoted fields
|
||||
$entry = [PSCustomObject]@{}
|
||||
|
||||
for ($i = 0; $i -lt $columns.Count -and $i -lt $row.Count; $i++) {
|
||||
$entry | Add-Member -NotePropertyName $columns[$i].Trim() -NotePropertyValue ($row[$i] -replace '^"|"$', '') -Force
|
||||
}
|
||||
|
||||
# Filter for relevant events (RECEIVE events)
|
||||
if ($entry.event -eq 'RECEIVE' -or $entry.event -eq '+') {
|
||||
$allMessages += $entry
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Progress -Activity "Parsing Transport Logs" -Completed
|
||||
|
||||
$totalMessages = $allMessages.Count
|
||||
Write-Host "[$(NowTag)] Parsed $totalMessages message(s)"
|
||||
|
||||
if ($totalMessages -eq 0) {
|
||||
Write-Host "[$(NowTag)] No messages found to analyze" -ForegroundColor Yellow
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Analyze data
|
||||
Write-Host "[$(NowTag)] Analyzing message flow patterns..."
|
||||
|
||||
# Top senders (by remote-endpoint or client-ip)
|
||||
$topSenders = $allMessages | Where-Object { $_.'remote-endpoint' } |
|
||||
Group-Object -Property 'remote-endpoint' |
|
||||
Select-Object @{N = "IPAddress"; E = { $_.Name } }, Count |
|
||||
Sort-Object Count -Descending |
|
||||
Select-Object -First $TopCount
|
||||
|
||||
# Messages by hour
|
||||
$messagesByHour = $allMessages | Where-Object { $_.'date-time' } |
|
||||
ForEach-Object {
|
||||
try {
|
||||
$dt = [DateTime]::Parse($_.'date-time')
|
||||
[PSCustomObject]@{ Hour = $dt.Hour }
|
||||
} catch {}
|
||||
} |
|
||||
Group-Object Hour |
|
||||
Select-Object @{N = "Hour"; E = { $_.Name } }, Count |
|
||||
Sort-Object Hour
|
||||
|
||||
# Messages by day
|
||||
$messagesByDay = $allMessages | Where-Object { $_.'date-time' } |
|
||||
ForEach-Object {
|
||||
try {
|
||||
$dt = [DateTime]::Parse($_.'date-time')
|
||||
[PSCustomObject]@{ Date = $dt.ToString('yyyy-MM-dd') }
|
||||
} catch {}
|
||||
} |
|
||||
Group-Object Date |
|
||||
Select-Object @{N = "Date"; E = { $_.Name } }, Count |
|
||||
Sort-Object Date
|
||||
|
||||
# Connector usage
|
||||
$connectorStats = $allMessages | Where-Object { $_.'connector-id' } |
|
||||
Group-Object -Property 'connector-id' |
|
||||
Select-Object @{N = "Connector"; E = { $_.Name } }, Count |
|
||||
Sort-Object Count -Descending
|
||||
|
||||
# Export results
|
||||
Write-Host "[$(NowTag)] Exporting results..."
|
||||
|
||||
$summaryFile = Join-Path $OutputFolder "Mailflow-Summary.txt"
|
||||
$topSendersFile = Join-Path $OutputFolder "Top-Senders-By-IP.csv"
|
||||
$byHourFile = Join-Path $OutputFolder "Messages-By-Hour.csv"
|
||||
$byDayFile = Join-Path $OutputFolder "Messages-By-Day.csv"
|
||||
$connectorFile = Join-Path $OutputFolder "Messages-By-Connector.csv"
|
||||
|
||||
$topSenders | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $topSendersFile
|
||||
$messagesByHour | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $byHourFile
|
||||
$messagesByDay | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $byDayFile
|
||||
$connectorStats | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $connectorFile
|
||||
|
||||
Write-Host "[$(NowTag)] Top senders: $topSendersFile" -ForegroundColor Green
|
||||
Write-Host "[$(NowTag)] Hourly distribution: $byHourFile" -ForegroundColor Green
|
||||
Write-Host "[$(NowTag)] Daily distribution: $byDayFile" -ForegroundColor Green
|
||||
Write-Host "[$(NowTag)] Connector stats: $connectorFile" -ForegroundColor Green
|
||||
|
||||
# Summary report
|
||||
$summary = @"
|
||||
Mail Flow Statistics Report
|
||||
Generated: $(Get-Date)
|
||||
Analysis Period: $($cutoffDate.ToString('yyyy-MM-dd')) to $(Get-Date -Format 'yyyy-MM-dd')
|
||||
Log Path: $LogPath
|
||||
|
||||
SUMMARY:
|
||||
Total Messages Analyzed: $totalMessages
|
||||
Log Files Processed: $fileCount
|
||||
Average Messages Per Day: $([math]::Round($totalMessages / $DaysBack, 0))
|
||||
|
||||
TOP $TopCount SENDERS (by IP):
|
||||
$($topSenders | ForEach-Object { " $($_.IPAddress): $($_.Count) messages" } | Out-String)
|
||||
|
||||
BUSIEST HOURS (24-hour format):
|
||||
$($messagesByHour | Sort-Object Count -Descending | Select-Object -First 5 | ForEach-Object { " Hour $($_.Hour): $($_.Count) messages" } | Out-String)
|
||||
|
||||
DAILY VOLUME:
|
||||
$($messagesByDay | ForEach-Object { " $($_.Date): $($_.Count) messages" } | Out-String)
|
||||
|
||||
CONNECTOR USAGE:
|
||||
$($connectorStats | ForEach-Object { " $($_.Connector): $($_.Count) messages" } | Out-String)
|
||||
"@
|
||||
|
||||
$summary | Out-File -FilePath $summaryFile -Encoding UTF8
|
||||
Write-Host "[$(NowTag)] Summary report: $summaryFile" -ForegroundColor Green
|
||||
|
||||
# Console output
|
||||
Write-Host "`nMAIL FLOW SUMMARY:" -ForegroundColor Cyan
|
||||
Write-Host " Total Messages: $totalMessages"
|
||||
Write-Host " Average Per Day: $([math]::Round($totalMessages / $DaysBack, 0))"
|
||||
Write-Host " Peak Hour: $(($messagesByHour | Sort-Object Count -Descending | Select-Object -First 1).Hour):00"
|
||||
Write-Host " Busiest Day: $(($messagesByDay | Sort-Object Count -Descending | Select-Object -First 1).Date)"
|
||||
|
||||
Write-Host "`n[$(NowTag)] Analysis complete! Output folder: $OutputFolder"
|
||||
Reference in New Issue
Block a user