param(
[Parameter(Mandatory=$true)] # Make parameter mandatory
[int]$ThresholdDays
)
# Define path for HTML report (dynamic filename)
$downloadsFolder = [System.IO.Path]::Combine($env:USERPROFILE, "Downloads")
$outputFile = Join-Path -Path $downloadsFolder -ChildPath ("InactiveMailboxesReport-$($ThresholdDays)Days(Navitas).html")
# Define path for server address and credentials
$serverAddressFile = ".\DefaultServer.txt"
$credentialFile = ".\AdminCredential.xml"
# --- HTML Styling (CSS) ---
$cssStyle = @"
"@
# --- Script Logic ---
$session = $null # Initialize session variable
$inactiveUserMailboxes = @()
$inactiveSharedMailboxes = @()
# --- Initialize Report Body with Title and Descriptions ---
$thresholdDate = (Get-Date).AddDays(-$ThresholdDays)
$reportTitle = "
Inactive Mailbox Report ($($ThresholdDays) Days)
"
$reportDescription = @"
This report lists user and shared mailboxes that have not been logged into since $($thresholdDate.ToString('yyyy-MM-dd')) (older than $ThresholdDays days).
Mailboxes listed with a 'Last Logon' date of 'Never' have potentially never been accessed or logon auditing was not enabled when they might have been accessed.
Note: The accuracy of 'LastLogonTime' depends on Exchange auditing configurations. This report excludes Discovery and System mailboxes.
"@
$reportBody = $reportTitle + $reportDescription
try {
# Validate the ThresholdDays parameter (already handled by Mandatory attribute, but double-check > 0)
if ($ThresholdDays -le 0) {
throw "ThresholdDays parameter must be a positive number."
}
# --- Input File Validation ---
if (-not (Test-Path $serverAddressFile)) {
throw "Server address file not found: $serverAddressFile"
}
if (-not (Test-Path $credentialFile)) {
throw "Credential file not found: $credentialFile"
}
# Import admin credentials
$serverAddress = Get-Content -Path $serverAddressFile
$credential = Import-Clixml -Path $credentialFile
# Establish session with Exchange Server
Write-Host "Connecting to Exchange Server: $serverAddress..."
$session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "http://$serverAddress/PowerShell/" -Authentication Kerberos -Credential $credential -ErrorAction Stop
Import-PSSession $session -DisableNameChecking -ErrorAction Stop
Write-Host "Successfully connected to Exchange." -ForegroundColor Green
# --- Data Retrieval and Processing ---
Write-Host "Retrieving mailboxes and checking last logon time (this may take a while)..."
# *** CORRECTED Filter syntax using -eq and -or ***
$mailboxesToCheck = Get-Mailbox -ResultSize Unlimited -Filter { (RecipientTypeDetails -eq 'UserMailbox') -or (RecipientTypeDetails -eq 'SharedMailbox') }
$totalMailboxes = $mailboxesToCheck.Count
$processedCount = 0
$progressPercent = 0
foreach ($mb in $mailboxesToCheck) {
$processedCount++
$newProgressPercent = [math]::Floor(($processedCount / $totalMailboxes) * 100)
if ($newProgressPercent -gt $progressPercent) {
$progressPercent = $newProgressPercent
Write-Progress -Activity "Processing Mailboxes" -Status "Checking $($mb.PrimarySmtpAddress) ($processedCount of $totalMailboxes)" -PercentComplete $progressPercent
}
$lastLogonTime = $null # Reset for each mailbox
try {
# Get statistics - use SilentlyContinue as some mailboxes might error here but we want to continue
$mailboxStats = Get-MailboxStatistics $mb.Identity -ErrorAction SilentlyContinue
if ($mailboxStats) {
$lastLogonTime = $mailboxStats.LastLogonTime
}
} catch {
Write-Warning "Could not retrieve statistics for $($mb.PrimarySmtpAddress): $($_.Exception.Message)"
}
# Check if inactive (Never logged on OR logon time is before threshold)
if (($null -eq $lastLogonTime) -or ($lastLogonTime -lt $thresholdDate)) {
$mailboxData = [PSCustomObject]@{
DisplayName = $mb.DisplayName
EmailAddress = $mb.PrimarySmtpAddress
LastLogonTime = if ($lastLogonTime) { $lastLogonTime } else { "Never" } # Store actual value or "Never"
RecipientType = $mb.RecipientTypeDetails
}
if ($mailboxData.RecipientType -eq "SharedMailbox") {
$inactiveSharedMailboxes += $mailboxData
} else { # Assumes UserMailbox if not Shared
$inactiveUserMailboxes += $mailboxData
}
}
}
Write-Progress -Activity "Processing Mailboxes" -Completed
Write-Host "Finished processing mailboxes. Found $($inactiveUserMailboxes.Count) inactive user mailboxes and $($inactiveSharedMailboxes.Count) inactive shared mailboxes."
# --- Generate User Mailboxes Table ---
$reportBody += "Inactive User Mailboxes (Last Logon Older than $ThresholdDays days)
"
if ($inactiveUserMailboxes.Count -gt 0) {
# Format the data for the table, handling the 'Never' case
$formattedUsers = $inactiveUserMailboxes | Select-Object DisplayName, EmailAddress, @{Name='Last Logon'; Expression={
if ($_.LastLogonTime -is [datetime]) {
# Format as date string
$_.LastLogonTime.ToString('yyyy-MM-dd HH:mm:ss')
} else {
# Apply class for 'Never'
"Never"
}
}}
# ConvertTo-Html and Decode the 'Never' span
$userTableHtml = $formattedUsers | Sort-Object DisplayName | ConvertTo-Html -Fragment -Property DisplayName, EmailAddress, 'Last Logon' # Added Sort-Object
$reportBody += [System.Net.WebUtility]::HtmlDecode($userTableHtml)
} else {
$reportBody += "No inactive user mailboxes found meeting the criteria.
"
}
# --- Generate Shared Mailboxes Table ---
$reportBody += "Inactive Shared Mailboxes (Last Logon Older than $ThresholdDays days)
"
if ($inactiveSharedMailboxes.Count -gt 0) {
# Format the data for the table
$formattedShared = $inactiveSharedMailboxes | Select-Object DisplayName, EmailAddress, @{Name='Last Logon'; Expression={
if ($_.LastLogonTime -is [datetime]) {
# Format as date string
$_.LastLogonTime.ToString('yyyy-MM-dd HH:mm:ss')
} else {
# Apply class for 'Never'
"Never"
}
}}
# ConvertTo-Html and Decode the 'Never' span
$sharedTableHtml = $formattedShared | Sort-Object DisplayName | ConvertTo-Html -Fragment -Property DisplayName, EmailAddress, 'Last Logon' # Added Sort-Object
$reportBody += [System.Net.WebUtility]::HtmlDecode($sharedTableHtml)
} else {
$reportBody += "No inactive shared mailboxes found meeting the criteria.
"
}
$reportBody += "Report generation complete.
"
} catch {
Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red
# Append error details to the report body (which already has title/description)
$reportBody += "Script Execution Error
"
$reportBody += "An error occurred while generating the inactive mailbox report.
"
$reportBody += "Error Details:
$([System.Net.WebUtility]::HtmlEncode($_.Exception.Message))
"
if ($_.Exception.StackTrace) {
$reportBody += "Stack Trace:
$([System.Net.WebUtility]::HtmlEncode($_.Exception.StackTrace) -replace "`n", "
")
"
}
if ($_.InvocationInfo) {
$reportBody += "Script Line: $($_.InvocationInfo.ScriptLineNumber)
"
}
} finally {
# --- Generate Final HTML Report ---
$reportFooter = "Report generated on: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') by $($env:USERNAME)
"
# Assemble the full HTML page
ConvertTo-Html -Head $cssStyle `
-Title "Inactive Mailbox Report ($ThresholdDays Days)" `
-Body $reportBody `
-PostContent $reportFooter | Out-File $outputFile -Encoding UTF8 -ErrorAction SilentlyContinue
# Check if the file was actually created before trying to open it
if (Test-Path $outputFile) {
Write-Host "HTML Report saved to: $outputFile" -ForegroundColor Green
Invoke-Item -Path $outputFile
} else {
Write-Host "Failed to create the report file at: $outputFile" -ForegroundColor Red
}
# Clean up Exchange session
if ($session -ne $null) {
Write-Host "Removing Exchange PSSession..."
Remove-PSSession $session
Write-Host "Session removed."
}
}