Ambiguous.psm1
#region Functions #region Get-AmbiguousMessage Function Get-AmbiguousMessage { <# .SYNOPSIS Get ambiguous messages. .DESCRIPTION The Get-AmbiguousMessage function will return the messages with last error value of ambiguous recipient. .PARAMETER Server The server to check. .PARAMETER Queue Get-AmbiquousMessage -Server EXCH01 .EXAMPLE Get-AmbiquousMessage -Queue EXCH01\Submission .Notes The Get-AmbigousMessage function will only check the Submission queue #> [cmdletBinding()] Param ( [Parameter(Mandatory=$false, Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] [Alias("Name")] [string[]]$Server = $env:COMPUTERNAME ) Begin{} Process { foreach($s in $Server) { Get-Message -Queue ($s + "\Submission") | Where-Object {$_.lasterror -like "*Ambiguous*"} } } End{} } #endregion #region Export-AmbiguousMessage Function Export-AmbiguousMessage { <# .SYNOPSIS Export ambiguous messages. .DESCRIPTION The Export-AmbiguousMessage function will export the ambiguous messages to a folder. .PARAMETER Server The server to check. .PARAMETER Path The folder to save the messages to. .PARAMETER Identity The identity of the message. .EXAMPLE Export-AmbiquousMessage -Server EXCH01 -Path C:\Messages .EXAMPLE Get-AmbiguousMessage - Server EXCH01 | Export-AmbiquousMessage -Path C:\Messages #> [cmdletBinding( SupportsShouldProcess = $true, ConfirmImpact = "High" )] Param ( [Parameter(Mandatory=$false, Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, ParameterSetName="Message")] [string[]]$Identity, [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, ParameterSetName="Server")] [Alias("Name")] [string]$Server, [Parameter(Mandatory=$true, Position=1)] [string]$Path ) Begin { # Test if path exists if( -Not (Test-Path $Path)) { Throw "Could not find the export folder." } else { if( -Not (Get-Item -Path $Path).PSIsContainer) { Throw "The export directory is not a directory." } } } Process { # the full path to the folder that will hold the exported messages $fullpath = Resolve-Path $Path # the messages $messages = @() if($Server) { $messages = Get-AmbiguousMessage -Server $Server } if($Identity) { $messages = Get-Message -Identity $Identity.Identity } # Process messages foreach($m in $messages) { if($PSCmdlet.ShouldProcess($m.Identity)) { if( $m.LastError -like "*Ambig*" ) { # Suspend the message Write-Verbose ("Suspending message: " + $m.identity) Suspend-Message -Identity $m.identity -Confirm:$false -Verbose:($PSBoundParameters['Verbose'] -eq $true) # Export the message $Temp = $fullpath.Path + '\' + $m.InternetMessageID.ToString() + ".eml" $Temp = $Temp.Replace("<","_") $Temp = $Temp.Replace(">","_") Write-Verbose ("Exporting message: " + $m.Identity) Export-Message $m.Identity -Verbose:($PSBoundParameters['Verbose'] -eq $true) | AssembleMessage -Path $Temp # remove the message from the queue Write-Verbose ("Removing message: " + $m.Identity) Remove-Message -Identity $m.Identity -Confirm:$false -Verbose:($PSBoundParameters['Verbose'] -eq $true) } } } } End{} } #endregion #region Remove-AmbiguousMessage Function Remove-AmbiguousMessage { <# .SYNOPSIS Remove ambiguous messages. .DESCRIPTION The Remove-AmbiguousMessage function will remove the messages with last error value of ambiguous recipient. .PARAMETER Server The server to check. .EXAMPLE Remove-AmbiquousMessage -Server EXCH01 #> [cmdletBinding( SupportsShouldProcess = $true, ConfirmImpact = "High" )] Param ( [Parameter(Mandatory=$false, Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, ParameterSetName="Message")] [Microsoft.Exchange.Data.QueueViewer.PropertyBagBasedMessageInfo]$Identity, [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, ParameterSetName="Server")] [string]$Server ) Begin{} Process { # the messages $messages = @() if($Server) { $messages = Get-AmbiguousMessage -Server $Server } if($Identity) { $messages = Get-Message -Identity $Identity.Identity } # Process messages foreach($m in $messages) { if( $m.LastError -like "*Ambig*" ) { if($PSCmdlet.ShouldProcess($m.Identity)) { # Suspend the message Write-Verbose ("Removing message: " + $m.Identity) Remove-Message -Identity $m.Identity -Confirm:$false } } } } End{} } #endregion #region Get-AmbiguousObject function Get-AmbiguousObject { <# .Synopsis Returns all ambiguous objects in Exchange. .DESCRIPTION The Get-AmbiguousObject function returns all the objects that are ambiguous recipients on Exchange. If two or more objects share the same SMTP address in proxy addresses, messages cannot be delivered to that address. This function queries Active Directory in order to get all the currently configured SMTP addresses and then searches for duplicates. .PARAMETER DomainController The domain controller to use. .PARAMETER Properties The properties to load for the ambiguous objects. .EXAMPLE PS C:\Windows\system32> Get-AmbiguousObject DuplicateAddress AmbiguousObjects ---------------- ---------------- duplicate@lab.local {@{SamAccountName=cpolydorou; UserPrincipalName=cpolydorou@LAB.local; DistinguishedName=CN=Christos Polydorou,OU=Users,OU=LAB... Here we see that the address "duplicate@lab.local" is causing ambiguous recipients. The recipients affected are included in the AmbiguousObjects property .EXAMPLE PS C:\Windows\system32> Get-AmbiguousObject -Properties "UserPrincipalName" DuplicateAddress AmbiguousObjects ---------------- ---------------- duplicate@lab.local {@{UserPrincipalName=cpolydorou@LAB.local}, @{UserPrincipalName=}, @{UserPrincipalName=recipient2@ote.gr}} Here we are searching for ambiguous objects and we only load the "UserPrincipalName" attribute of each object. .EXAMPLE PS C:\Windows\system32> Get-AmbiguousObject -DomainController dc1.lab.local DuplicateAddress AmbiguousObjects ---------------- ---------------- duplicate@lab.local {@{DistinguishedName=CN=Christos Polydorou,OU=Users,OU=LAB,DC=LAB,DC=local; ProxyAddresses=System.Object[]}, @{DistinguishedN... The above command will return all the ambiguous objects by quering server dc1.lab.local .NOTES Please be very carefull when using this command in large environments since it may stress your domain controller. #> [CmdletBinding()] Param ( # The Active Directory Domain Controller to use [Parameter()] [string]$DomainController, [string[]]$Properties = @("DistinguishedName","ProxyAddresses") ) Begin { } Process { #region GetAllEmailAddresses Write-Verbose "Getting all email addresses." # Save all the email addresses $AllEmailAddresses = New-Object System.Collections.ArrayList # Query Active Directory # Get the domain $Domain = New-Object System.DirectoryServices.DirectoryEntry # Connect to a specific domain controller, if specified if($DomainController) { # connect to the domain controller $Entry = [adsi]("LDAP://" + $DomainController + "/" + $Domain.distinguishedName) $Searcher = [adsisearcher]$Entry } else { $Searcher = New-Object System.DirectoryServices.DirectorySearcher $Searcher.SearchRoot = $Domain } $Searcher.PageSize = 1000 $Searcher.Filter = "(proxyaddresses=*)" $Searcher.SearchScope = "Subtree" $Props = "ProxyAddresses" foreach ($p in $Props) { $Searcher.PropertiesToLoad.Add($p) | Out-Null } $Results = $Searcher.FindAll() foreach ($r in $Results) { $r.Properties["proxyaddresses"] | %{ if($_.ToLower().StartsWith("smtp:")) { $AllEmailAddresses.Add($_.Substring(5)) | Out-Null } } } #endregion #region SearchForDuplicateEmailAddresses Write-Verbose "Searching for duplicate addresses." $AllEmailAddresses.Sort() # Save all the duplicate email addresses $DuplicateEmailAddresses = New-Object System.Collections.ArrayList for($i = 0; $i -lt $AllEmailAddresses.Count; $i++) { # Check if the address is equal with the next address if($AllEmailAddresses[$i] -eq $AllEmailAddresses[$i+1]) { # The address hasn't already been added in the previous loop if($AllEmailAddresses[$i-1] -ne $AllEmailAddresses[$i]) { # Add the address $DuplicateEmailAddresses.Add($AllEmailAddresses[$i]) | Out-Null } } } #endregion #region Create custom objects Write-Verbose "Getting ambiguous objects." foreach($d in $DuplicateEmailAddresses) { # Create a custom object $ambiguousObject = New-Object psobject $ambiguousObject | Add-Member -MemberType NoteProperty -Name "DuplicateAddress" -Value $d # Get the ambiguousobjects $Searcher.Filter = "(proxyaddresses=*$d*)" foreach ($p in $Properties) { $Searcher.PropertiesToLoad.Add($p) | Out-Null } # Get the results from the query $Results = $Searcher.FindAll() # Save all duplicate object for the particular address $duplicateObjects = New-Object System.Collections.ArrayList # Create a custom object for each result and add it to the collection foreach ($r in $Results) { $customObject = New-Object psobject foreach($p in $Properties) { $customObject | Add-Member -MemberType NoteProperty -Name $p -Value ($r.properties[$p] | ForEach-Object {$_}) } $duplicateObjects.Add($customObject) | Out-Null } $ambiguousObject | Add-Member -MemberType NoteProperty -Name "AmbiguousObjects" -Value $duplicateObjects Write-Output $ambiguousObject } #endregion } End { } } #endregion #endregion #region Exports Export-ModuleMember -Function Get-AmbiguousMessage Export-ModuleMember -Function Export-AmbiguousMessage Export-ModuleMember -Function Remove-AmbiguousMessage Export-ModuleMember -Function Get-AmbiguousObject #endregion |