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.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." } }