ExO-MailboxAndInboxRuleForwardingReport.ps1
<#PSScriptInfo
.VERSION 1.0.1 .GUID 4e794dab-0e07-43ed-bbd9-ec685be3421a .AUTHOR Soren Lindevang .COMPANYNAME .COPYRIGHT .TAGS PowerShell Exchange Online Office 365 Reporting Report Forwarding ForwardingRules InboxRules SMTPForwarding Mailbox MailboxForwarding Azure Automation .LICENSEURI .PROJECTURI .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES #> <# .SYNOPSIS Generate Report of Mailbox and Inbox Rules Forwarding Mails to External Recipients. .DESCRIPTION Searches for mailbox forwarding and inbox rules that forward to external recipients. Option to send a CSV report over email. Designed for execution in Azure Automation. Check out the GitHub Repo for more information: https://github.com/soren-cloud/ExO-MailboxAndInboxRuleForwardingReport .PARAMETER AutomationPSCredentialName Name of the Automation Credential used when connecting to Exchange Online. The Account should at least have "Audit Log" rights in the Exchange Online tenant. Example: Exchange Online Service Account .PARAMETER ExcludeExternalDomain External recipient domains to be excluded when searching for external forwarding. Example 1: ['domain.com'] Example 2: ['domainA.com','domainB.com'] .PARAMETER ExcludeExternalEmailAddress External recipient email adresses to be excluded when searching for external forwarding. Example 1: ['abc@domain.com'] Example 2: ['abc@domain.com','xyz@domain.com'] .PARAMETER SendMailboxForwardingReport If this switch is present, the script sends an email with a CSV file attached, if mailbox forwarding is detected. If used, please do modify the 'SendMailReport' variables in the 'Declarations' area. Example 1: true Example 2: false .PARAMETER SendInboxRuleForwardingReport If this switch is present, the script sends an email with a CSV file attached, if any inbox rules are detected. If used, please do modify the 'SendMailReport' variables in the 'Declarations' area. Example 1: true Example 2: false .PARAMETER EnableVerbose If this switch is present, 'VerbosePreference' will be set to "Continue" in the script. Build-in Verbose switch is not supported by Azure Automation (yet). Example 1: true Example 2: false Default value = false .INPUTS N/A .OUTPUTS N/A .NOTES Version: 1.0.1 Author: Soren Greenfort Lindevang Creation Date: 15.05.2018 Purpose/Change: - Added 'EnableVerbose' switch - Improved error handling - Improved output formatting - Other fixes Version: 1.0 Author: Soren Greenfort Lindevang Creation Date: 16.04.2018 Purpose/Change: Initial script development .EXAMPLE N/A #> [cmdletbinding()] param ( [Parameter( Mandatory=$true)] [string]$AutomationPSCredentialName, [Parameter( Mandatory=$false)] [string[]]$ExcludeExternalDomain, [Parameter( Mandatory=$false)] [string[]]$ExcludeExternalEmailAddress, [Parameter( Mandatory=$false)] [switch]$SendMailboxForwardingReport, [Parameter( Mandatory=$false)] [switch]$SendInboxRuleForwardingReport, [Parameter( Mandatory=$false)] [switch]$EnableVerbose ) #-----------------------------------------------------------[Functions]------------------------------------------------------------ # Test if script is running in Azure Automation function Test-AzureAutomationEnvironment { if ($env:AUTOMATION_ASSET_ACCOUNTID) { Write-Verbose "This script is executed in Azure Automation" } else { $ErrorMessage = "This script is NOT executed in Azure Automation." throw $ErrorMessage } } # Connect to Exchange Online function Connect-ExchangeOnline { param ($Credential,$Commands) try { Write-Output "Connecting to Exchange Online" Get-PSSession | Remove-PSSession $Session = New-PSSession –ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $Credential ` -Authentication Basic -AllowRedirection Import-PSSession -Session $Session -DisableNameChecking:$true -AllowClobber:$true -CommandName $Commands | Out-Null } catch { Write-Error -Message $_.Exception Stop-AutomationScript -Status Failed } Write-Verbose "Successfully connected to Exchange Online" } # Disconnect to Exchange Online function Disconnect-ExchangeOnline { try { Write-Output "Disconnecting from Exchange Online" Get-PSSession | Remove-PSSession } catch { Write-Error -Message $_.Exception Stop-AutomationScript -Status Failed } Write-Verbose "Successfully disconnected from Exchange Online" } # Stop Automation Script function Stop-AutomationScript { param( [ValidateSet("Failed","Success")] [string] $Status = "Success" ) Write-Output "" Disconnect-ExchangeOnline if ($Status -eq "Success") { Write-Output "Script successfully completed" } elseif ($Status -eq "Failed") { Write-Output "Script stopped with an Error" } Break } Function Get-MailboxForwardingToExternal { [cmdletbinding()] param ( [Parameter( Mandatory=$true)] [Alias('Mailboxes')] [psobject]$MailboxPSObject, [Parameter( Mandatory=$true)] [Alias('Recipients')] [psobject]$RecipientPSObject, [Parameter( Mandatory=$true)] [Alias('InternalDomains')] [psobject]$AcceptedDomainPSobject, [Parameter( Mandatory=$false)] [string[]]$ExcludeExternalDomain, [Parameter( Mandatory=$false)] [string[]]$ExcludeExternalEmailAddress ) Begin { Write-Verbose "Beginning Get-Get-MailboxForwardingToExternal function" Write-Verbose "$(($MailboxPSObject | Measure-Object).Count) objects in 'MailboxPSObject'" } Process { Write-Verbose "Processing Objects in 'MailboxPSObject'" $count = $null foreach ($Mailbox in $MailboxPSObject) { $count++ $WriteOutput = $false Write-Verbose "Object $count of $(($MailboxPSObject | Measure-Object).Count)" Write-Verbose "PrimarySmtpAddress: '$($Mailbox.PrimarySmtpAddress)'" # ForwardingAddress if ($Mailbox.ForwardingAddress) { $ForwardingRecipient = $RecipientPSObject | Where-Object {$_.Identity -eq $Mailbox.ForwardingAddress} $ForwardingRecipientType = $ForwardingRecipient.RecipientType Write-Verbose "ForwardingAddress: $($ForwardingRecipient.Identity)" Write-Verbose "ForwardingAddress Type: $ForwardingRecipientType" if ($ForwardingRecipientType -eq "MailContact") { $EmailAddress = ($ForwardingRecipient.ExternalEmailAddress -split "SMTP:")[1].Trim("]") $EmailAddressDomain = ($EmailAddress -split "@")[1] if (($AcceptedDomains.DomainName -notcontains $EmailAddressDomain) -and ($ExcludeExternalEmailAddress -notcontains $EmailAddress) -and ($ExcludeExternalDomain -notcontains $EmailAddressDomain)) { $ForwardingHash = $null $ForwardingHash = [ordered]@{ PrimarySmtpAddress = $Mailbox.PrimarySmtpAddress DisplayName = $Mailbox.DisplayName ExternalForwardingAddress = $EmailAddress MailboxForwardingType = "ForwardingAddress" } $Object = New-Object PSObject -Property $ForwardingHash Write-Verbose "Writing Output" Write-Output $Object } else { Write-Verbose "$EmailAddress - Internal Domain, Excluded Domain or Excluded Email Address" } } else { Write-Verbose "Skipping this type of recipient" } } else { Write-Verbose "ForwardingAddress: null" } # ForwardingSmtpAddress if ($Mailbox.ForwardingSmtpAddress) { $MailboxForwardingSmtpAddress = $Mailbox.ForwardingSmtpAddress Write-Verbose "ForwardingSmtpAddress: $MailboxForwardingSmtpAddress" $EmailAddress = ($MailboxForwardingSmtpAddress -split "SMTP:")[1].Trim("]") $EmailAddressDomain = ($EmailAddress -split "@")[1] if (($AcceptedDomains.DomainName -notcontains $EmailAddressDomain) -and ($ExcludeExternalEmailAddress -notcontains $EmailAddress) -and ($ExcludeExternalDomain -notcontains $EmailAddressDomain)) { $ForwardingHash = $null $ForwardingHash = [ordered]@{ PrimarySmtpAddress = $Mailbox.PrimarySmtpAddress DisplayName = $Mailbox.DisplayName ExternalForwardingAddress = $EmailAddress MailboxForwardingType = "ForwardingSmtpAddress" } $Object = New-Object PSObject -Property $ForwardingHash Write-Verbose "Writing Output" Write-Output $Object } else { Write-Verbose "$EmailAddress - Internal Domain, Excluded Domain or Excluded Email Address" } } else { Write-Verbose "ForwardingSMTPAddress: null" } } } End { Write-Verbose "End of Get-InboxRuleForwardingToExternal function" } } Function Get-InboxRuleForwardingToExternal { [cmdletbinding()] param ( [Parameter( Mandatory=$true)] [Alias('Mailboxes')] [psobject]$MailboxPSObject, [Parameter( Mandatory=$true)] [Alias('InternalDomain')] [psobject]$AcceptedDomainPSobject, [Parameter( Mandatory=$false)] [string[]]$ExcludeExternalDomain, [Parameter( Mandatory=$false)] [string[]]$ExcludeExternalEmailAddress ) Begin { Write-Verbose "Beginning Get-InboxRuleForwardingToExternal function" Write-Verbose "$(($MailboxPSObject | Measure-Object).Count) objects in 'MailboxPSObject'" } Process { Write-Verbose "Processing Objects in 'MailboxPSObject'" $count = $null foreach ($Mailbox in $MailboxPSObject) { $count++ Write-Verbose "Object $count of $(($MailboxPSObject | Measure-Object).Count)" Write-Verbose "PrimarySmtpAddress: '$($Mailbox.PrimarySmtpAddress)'" $ForwardingRules = $null try { $Rules = Get-InboxRule -Mailbox $Mailbox.PrimarySmtpAddress } catch { Write-Error -Message $_.Exception } $ForwardingRules = $Rules | Where-Object {$_.ForwardTo -or $_.ForwardAsAttachmentTo} foreach ($Rule in $ForwardingRules) { $Recipients = @() $Recipients = $Rule.ForwardTo | Where-Object {$_ -match "SMTP"} $Recipients += $Rule.ForwardAsAttachmentTo | Where-Object {$_ -match "SMTP"} $ExternalRecipients = @() foreach ($Recipient in $Recipients) { $EmailAddress = ($Recipient -split "SMTP:")[1].Trim("]") $EmailAddressDomain = ($EmailAddress -split "@")[1] if (($ExcludeExternalEmailAddress -notcontains $EmailAddress) -and ($AcceptedDomains.DomainName -notcontains $EmailAddressDomain) -and $ExcludeExternalDomain -notcontains $EmailAddressDomain) { $ExternalRecipients += $EmailAddress } else { Write-Verbose "$EmailAddress - Internal Domain, Excluded Domain or Excluded Email Address" } } if ($ExternalRecipients) { $ExternalRecipientsString = $ExternalRecipients -join ", " Write-Verbose "Rule '$($Rule.Name)' forwards to '$ExternalRecipientsString'" $RuleHash = $null $RuleHash = [ordered]@{ PrimarySmtpAddress = $Mailbox.PrimarySmtpAddress DisplayName = $Mailbox.DisplayName RuleId = $Rule.Identity RuleName = $Rule.Name RuleDescription = $Rule.Description ExternalRecipients = $ExternalRecipientsString } $Object = New-Object PSObject -Property $RuleHash Write-Verbose "Writing Output" Write-Output $Object } else { Write-Verbose "Rule '$($Rule.Name)' does not contain external forwarding" } } } } End { Write-Verbose "End of Get-InboxRuleForwardingToExternal function" } } #----------------------------------------------------------[Declarations]---------------------------------------------------------- # General Send Report Variables $ReportSmtpServer = "smtp.office365.com" $ReportSmtpPort = 587 $ReportSmtpPSCredentialName = $AutomationPSCredentialName $ReportSmtpFrom = "serviceaccount@domain.com" $ReportSmtpTo = "someone@domain.com" # SendInboxRuleForwardingReport Variables $SendInboxRuleForwardingReportSubject = "Report: Inbox Rules with Forwarding to External Recipients" $SendInboxRuleForwardingReportBody = "CSV file attached, containing Inbox Rule Information" # SendMailboxForwardingReport Variables $SendMailboxForwardingReportSubject = "Report: Mailbox Forwarding with External Recipients" $SendMailboxForwardingReportBody = "CSV file attached, containing Mailbox Forwarding Information" #-----------------------------------------------------------[Execution]----------------------------------------------------------- # Check if script is executed in Azure Automation Test-AzureAutomationEnvironment Write-Output "::: Parameters :::" Write-Output "AutomationPSCredentialName: $AutomationPSCredentialName" Write-Output "ExcludeExternalDomain: $ExcludeExternalDomain" Write-Output "ExcludeExternalEmailAddress: $ExcludeExternalEmailAddress" Write-Output "SendMailboxForwardingReport: $SendMailboxForwardingReport" Write-Output "SendInboxRuleForwardingReport: $SendInboxRuleForwardingReport" Write-Output "EnableVerbose: $EnableVerbose" Write-Output "" # Handle Verbose Preference if ($EnableVerbose -eq $true) { $VerbosePreference = "Continue" } # Get AutomationPSCredential Write-Output "::: Connection :::" try { Write-Output "Importing Automation Credential" $Credential = Get-AutomationPSCredential -Name $AutomationPSCredentialName -ErrorAction Stop } catch { Write-Error -Message $_.Exception Stop-AutomationScript -Status Failed } Write-Verbose "Successfully imported credentials" # Connect to Exchange Online Connect-ExchangeOnline -Credential $Credential -Commands "Get-AcceptedDomain","Get-Mailbox","Get-InboxRule","Get-Recipient" Write-Output "" # Import Accepted Domains Write-Verbose ":::Import Accepted Domains:::" try { Write-Verbose "Importing List of Accepted Domains" $AcceptedDomains = Get-AcceptedDomain -ErrorAction Stop } catch { Write-Error -Message $_.Exception Stop-AutomationScript -Status Failed } Write-Verbose "Successfully Imported List of Accepted Domains" # Import All Mailboxes try { Write-Verbose "Importing List of Mailboxes" $Mailboxes = Get-Mailbox -ResultSize Unlimited -ErrorAction Stop ` | Select-Object Identity,DisplayName,PrimarySmtpAddress,ForwardingAddress,ForwardingSmtpAddress } catch { Write-Error -Message $_.Exception Stop-AutomationScript -Status Failed } if (!$Mailboxes) { $ErrorMessage = "No Mailboxes Found!" Stop-AutomationScript -Status Failed } Write-Verbose "Successfully Imported List of Mailboxes" Write-Verbose "" # Import All Recipients Write-Verbose "::: Import Exchange Recipient List :::" try { Write-Verbose "Importing List of Recipients" $Recipients = Get-Recipient -ResultSize Unlimited -ErrorAction Stop ` | Select-Object Identity,RecipientType,ExternalEmailAddress } catch { Write-Error -Message $_.Exception Stop-AutomationScript -Status Failed } Write-Verbose "Successfully Imported List of Recipients" Write-Verbose "" # Process Mailboxes Write-Output "::: Analyzing Mailbox Forwarding :::" Write-Output "Mailboxes in Scope for Search: $(($Mailboxes | Measure-Object).Count)" $MailboxForwarding = Get-MailboxForwardingToExternal -MailboxPSObject $Mailboxes -RecipientPSObject $Recipients -AcceptedDomainPSobject $AcceptedDomains ` -ExcludeExternalEmailAddress $ExcludeExternalEmailAddress -ExcludeExternalDomain $ExcludeExternalDomain Write-Output "Mailbox Forwarding to External Recipients: $(($MailboxForwarding | Measure-Object).Count)" if ($MailboxForwarding) { Write-Output $($MailboxForwarding | fl) } Write-Output "" # Inbox Rules Write-Output "::: Analyzing Inbox Rules :::" Write-Output "Mailboxes in Scope for Search: $(($Mailboxes | Measure-Object).Count)" $RulesWithForwarding = Get-InboxRuleForwardingToExternal -MailboxPSObject $Mailboxes -AcceptedDomainPSobject $AcceptedDomains ` -ExcludeExternalEmailAddress $ExcludeExternalEmailAddress -ExcludeExternalDomain $ExcludeExternalDomain Write-Output "Inbox Rules Forwarding to External Recipients: $(($RulesWithForwarding | Measure-Object).Count)" Write-Output $RulesWithForwarding Write-Output "" # Send Mail Report if (($SendMailboxForwardingReport -and $MailboxForwarding) -or ($SendInboxRuleForwardingReport -and $RulesWithForwarding)) { Write-Output "::: Send Mail Report :::" Write-Output "Importing Automation Credential" try { $ReportSmtpPSCredential = Get-AutomationPSCredential -Name $ReportSmtpPSCredentialName -ErrorAction Stop } catch { Write-Error -Message $_.Exception Stop-AutomationScript -Status Failed } $ReportTime = Get-Date -Format "MM-dd-yyyy_HH-mm-ss" if ($SendMailboxForwardingReport -and $MailboxForwarding) { Write-Output "Generate Mailbox Forwarding CSV file" try { $CSVFileName = "MailboxForwarding_" + $ReportTime + ".csv" $CSVFilePath = $env:TEMP + "\" + $CSVFileName $MailboxForwarding | Export-CSV -LiteralPath $CSVFilePath -Encoding Unicode -NoTypeInformation -Delimiter "`t" -ErrorAction Stop } catch { Write-Error -Message $_.Exception } Write-Output "Send e-mail to '$ReportSmtpTo'" try { Send-MailMessage -To $ReportSmtpTo -From $ReportSmtpFrom -Subject $SendMailboxForwardingReportSubject ` -Body $SendMailboxForwardingReportBody -BodyAsHtml -Attachments $CSVFilePath -SmtpServer $ReportSmtpServer ` -Port $ReportSmtpPort -UseSsl -Credential $ReportSmtpPSCredential -ErrorAction Stop } catch { Write-Error -Message $_.Exception } } if ($SendInboxRuleForwardingReport -and $RulesWithForwarding) { Write-Output "Generate Inbox Rule Forwarding CSV file" try { $CSVFileName = "InboxForwardingRules_" + $ReportTime + ".csv" $CSVFilePath = $env:TEMP + "\" + $CSVFileName $RulesWithForwarding | Export-CSV -LiteralPath $CSVFilePath -Encoding Unicode -NoTypeInformation -Delimiter "`t" -ErrorAction Stop } catch { Write-Error -Message $_.Exception } Write-Output "Send e-mail to '$ReportSmtpTo'" try { Send-MailMessage -To $ReportSmtpTo -From $ReportSmtpFrom -Subject $SendInboxRuleForwardingReportSubject ` -Body $SendInboxRuleForwardingReportBody -BodyAsHtml -Attachments $CSVFilePath -SmtpServer $ReportSmtpServer ` -Port $ReportSmtpPort -UseSsl -Credential $ReportSmtpPSCredential -ErrorAction Stop } catch { Write-Error -Message $_.Exception } } } # Script Completed Stop-AutomationScript -Status Success |