Field notes: Make the actual source client IP visible for a load-balanced SMTP service

Reading Time: 5 minutes
Secure Mail Flow

A typical headache for Microsoft Exchange Server administrators is to setup load-balancing with port 25. They face the issue that, typically in these setups, they no longer see the source client IPs of the actual clients; All traffic is translated and is presented to the SMTP service with the load balancer IP as source. This makes working with receive connectors quite difficult. Many organizations have opted to use multiple virtual IPs and/or different port numbers for the SMTP service…

I think there is a better solution. I think the Direct Server Return option on load balancers provides a better solution; This option forces the load balancer to present the original client IP to the back-end server, in our case: the SMTP service of Microsoft Exchange Server. The back-end server sends response packets directly to the client instead of the load balancer (no Server Network Address Translation (NAT)).

This works fine if you have a single subnet or no firewall(s) between the client, the load balancer and the back-end server. However, if a firewall plays a role in the communications flow, then the firewall will drop the connections because of asymmetric traffic flows; The firewall notices that the TCP information does not match with what it knows.

The (not so) secret sauce

The basic trick is to have the Windows Server respond with the Virtual IP of the load balancer. This way, the TCP packet looks like it’s coming from the load balancer, but actually it’s coming from the back-end server. Most firewalls are happy and allow the traffic.

Note: The communication flow must go back and forth through the same devices. Otherwise, you still experience asymmetric traffic flow and traffic will be blocked.

Another hurdle you may encounter is the need to disable ARP responses to the actual Virtual IP addresses used by the systems. When firewalls come into play, you need to look at the communications path of the traffic. Firewalls can manipulate traffic or do additional translation, that may interfere with what you want to achieve.

How to make the actual source IP address visible to a load-balanced SMTP service

Below, I'll share the steps I follow to setup my solution.

Create a lookback adapter on Windows Server

First, we need to create a lookback adapter on the Windows Server that hosts the SMTP service. Perform the following steps with an account that has administrator privileges:

  1. Click Start, then type cmd in the search box.
  2. When cmd.exe appears, right-click it and choose Run as administrator.
    The Administrator: Command Prompt window opens.
  3. On the command prompt, type hdwwiz.exe and press Enter.
    image
  4. Click Next >.
  5. Select Install the hardware that I manually select from a list (Advanced):
    image
  6. Click Next >:
  7. Select Network adapters, then click Next >:image
  8. Select Microsoft as the manufacturer. Then, select Microsoft KM-TEST Loopback Adapter:
    image
  9. Click Next >.
  10. Select Next > to confirm the installation:
    image
  11. Click Finish to complete the installation.

Configure the Loopback adapter on Windows Server

Now, we need to configure the network adapters on the Windows Server that hosts the SMTP service. Perform the following steps with an account that has administrator privileges:

  1. Click Start > Control Panel > Network and Sharing Center.
  2. Follow the Change adapter settings link in the left navigation pane.
  3. Select the newly created connection  and rename it to Loopback.
    image
  4. Right-click the Loopback connection and choose Properties from the menu.
  5. Confirm that Microsoft Loopback Adapter or Microsoft Loopback Adapter # is displayed in the Connect Using: field. If it is not, return to step 2 and retry the Properties for another adapter.
  6. Deselect every item in the list below This connection uses the following items:, except Internet Protocol (TCP/IP):
    image
  7. Select Internet Protocol (TCP/IP), and click Properties to open the Internet Protocol Version 4 (TCP/IPv4) Properties window.
  8. Select the Use the following IP address option.
  9. Fill in the IP address and Subnet mask fields of the Virtual Service that you configured on your load balancer:
    image
  10. Click the Advanced… button to open the Advanced TCP/IP Settings window.
  11. On the IP Settings tab of the Advanced TCP/IP Settings window, deselect Automatic Metric and provide the value 254:
    image
  12. Switch to the WINS tab.
  13. Select the Disable NetBIOS over TCP/IP option:
    image
  14. Click OK to close the Advanced TCP/IP Settings window.
  15. Click OK to close the Internet Protocol Version 4 (TCP/IPv4) Properties window.
  16. Click OK to close the connection properties.
  17. Switch back to the Administrator: Command Prompt window.
  18. On the command prompt, issue the following command with the LAN interface in scope (the interface that the load balancer points to):

    netsh interface ipv4 set interface "LAN" weakhostreceive=enabled

  19. On the command prompt, issue the following commands, too. This time, with the Loopback adapter as the target: (Repeat if you have multiple loopback interfaces)

    netsh interface ipv4 set interface "loopback" weakhostsend=enabled
    netsh interface ipv4 set interface "loopback" weakhostreceive=enabled

  20. Close the Administrator: Command Prompt window.

The configuration on the Windows Server is now complete. 

Configure the load balancer and firewalls

When you have the above configuration in place on the Windows Server that hosts the SMTP service, you can enable the Direct Server Return option in your load balancer.

Concluding

I personally try to implement the above solution in every project, that require me to build a new Microsoft Exchange Server environment with high-availability and load balancing. I've got the Direct Server Return option working with Citrix Netscaler and  KEMP Loadmaster implementations.

Reclaim control of your SMTP configuration and regain insight to the source client IP for SMTP traffic and build secure receive connectors again, with the information above.

Test before going into production.

Field Notes: Meeting the requirements for Interoperability between Microsoft Teams and Microsoft Exchange Server

Reading Time: 3 minutes

In this blog post, I want to walk you through my experiences with setting up and enable the interoperability between Microsoft Teams and on-premises Microsoft Exchange Server environments.

Since the beginning of this year, Microsoft Teams adoption has seen a tremendous uptick in usage. Organizations needed to adopt Microsoft Teams as their Unified Communications (UC) platform, but the other cloud migrations went on the back burner or were not even in scope anymore for this year. All is fine, until somebody asks: "I don’t see my calendar in Microsoft Teams… Can you fix this?"

Sure we can. In this blog post, I'll walk you through the process of enabling the interoperability between the Microsoft Teams service and the on-premises Microsoft Exchange Server environment.

Requirements

Let's start with listing the Microsoft requirements and what they actually mean in real life:

Known requirements

We start with the known requirements as listed here. Summarized these are:

  • The employee identities need to be synchronized to Azure AD;
  • The organization needs to have the Exchange Hybrid option in Azure AD Connect;
  • OAuth authentication between your on-premises Exchange and Exchange Online organizations needs to be configured;
  • Microsoft Teams scheduling of meetings by delegates require an additional partner application;
  • The employees need to have to be licensed: the Microsoft Teams license to be specific;
  • The on-premises Microsoft Exchange Server environment needs to run Microsoft Exchange Server 2016 CU3 or a newer version of Microsoft Exchange Server.
  • Good to know: The Microsoft hybrid Agent (Modern Exchange Hybrid) cannot be used for the Microsoft Teams calendar integration. It is listed here in the constraints of the Microsoft Hybrid Agent.

My approach

When a customer asks “I want to have my calendar visible is Microsoft Teams, is this possible for us and what do we need to do?”, then this is my approach:

To see if it’s even possible to realize the interoperability, we need to validate the current on-premises Microsoft Exchange Server environment in terms of the version: It needs to run at least Microsoft Exchange Server 2016 with CU3. The importance of this question is in the availability of the new AutodiscoverV2 and the new REST-based application programming interface (API) in the Exchange Web Services (EWS) capabilities. But that is not all: In order to benefit from these new capabilities, the mailbox needs to be homed on a Exchange Server 2016 CU3 or higher mailbox server. If this is all true, then I validate if Autodiscover service and Exchange Web Services of Exchange Server 2016 is accessible from the internet. If not, the organizations must be willing to allow this.

Initial checks

This results in the following checks:

  • Autodiscover and Exchange Web Services (EWS) need to be accessible from the internet and pointing to a Microsoft Exchange Server that runs at least Microsoft Exchange Server 2016 CU3;
  • The mailboxes for the employees in scope need to be homed on a Microsoft Exchange Server hat runs at least Microsoft Exchange Server 2016 CU3;
  • The employees must have Microsoft Teams licenses assigned;
  • Good to Know: Microsoft Teams will not use the configured hybrid configuration within Exchange Online, but trusts the native Autodiscover functionality. Via autodiscover.domain.tld it is pointed to the on-premises environment or via SRV records it needs to point to the on-premises environment.
  • Good to Know: Autodiscover (http) redirect will not work for the Microsoft Teams integration. This might impact the current public certificates that an organization might currently use.

If any of requirements are not met or not possible to achieve, then the integration is a NO GO.

Further implementation information

When the initial requirements are all met, we can ask the following questions:

  1. Are there any Microsoft Exchange Server left in the organization that run Microsoft Exchange Server 2010?
    If a Microsoft Exchange Server 2010 is present in the environment, then the Exchange Hybrid configuration wizard will not create the OAuth configuration with Azure AD during the setup and we need to do it manually.
  2. Is a Exchange Hybrid configuration desired and on the roadmap for the organization?
    Exchange Hybrid is a requirement from Microsoft for the interoperability between Microsoft Exchange Server and Microsoft Teams. If we cannot use the Exchange Hybrid configuration wizard (which is the preferred option),  we need to configure the Oauth configuration manually
  3. Is it required that delegates need to be able to make appointments on behalf of the delegator?
    If so, we need to perform steps 2 and 3 of the instructions listed here. The steps will provision the trust for the Skype for Business online integration.
  4. Can we configured the Exchange Hybrid Writeback permission on the AD Connector account in Active Directory for Azure AD Connect?
    Within Azure AD Connect we need to enable the synchronization option for the Exchange Hybrid feature. This requires that the configured service account has the correct writeback permissions for Exchange attributes. The list of required attributes to which we need write permissions is listed here.

Concluding

Compared to what is documented in the Microsoft docs and the real world, some parts are missing in the explanation or the requirements list of Microsoft. In this blog post I want to give you my past experience and help you with the little details to enable organizations to implement the calendar integration.

Field notes: What is the current default SMTP certificate for your Exchange Server environment?

Reading Time: 3 minutes

The last couple of weeks I have been working with several Microsoft Exchange Server environments. I encountered lots of expired certificates. Organizations wanted help with that.

One of the questions that kept coming back was:

Do I press Yes to change the default certificate, when I enabled the certificate for SMTP?

exchange-2016-assign-ssl-certificate-04

The official answer is to press No. The recommend practice is to leave it like it is. However, it begs another question:

How can I see the current default SMTP certificate?

I'll answer this latter question in this blog post.

What is the default SMTP certificate used for?

When you install Microsoft Exchange Server on a Windows Server installation, it creates a self-signed certificate with a validity period of 5 years. This certificate is assigned as the initial default SMTP  certificate. This certificate is used for the mutual TLS connections between the Microsoft Exchange Servers within an Exchange Organization. This certificate is also presented to external mail systems when mutual TLS is required.

Normally, Microsoft Exchange Server admins:

  • Configure a dedicated certificate for this connector, or;
  • Configure the fully-qualified domain name (FQDN) on the connector to match the certificate.

How do I find the current default SMTP certificate?

One would assume that you would be able to see the current certificate with native tooling provided by Microsoft.

Let's test this assumption: Open the Microsoft Exchange Management shell. Connect to the Microsoft Exchange Server environment. Execute the Get-ExchangeServer Windows PowerShell cmdlet. Examine the output. See, the information is not there.

But we're close…

Where can I find it then?

The Get-ExchangeServer Windows PowerShell cmdlet retrieves the information that is configured in the configuration container of Active Directory. In this configuration container, the Exchange Server environment configuration is stored for the entire Active Directory forest.

Specifically, Get-ExchangeServer retrieves all Active Directory objects from the follow location:

CN=Servers,CN=Exchange Administrative Group (FYDIBOHF23SPDLT),CN=Administrative Groups,CN=Exchange Organization Name,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=domain,DC=tld

Note:
The Exchange Organization Name portion of the above location is the name used with the initial installation of a Microsoft Exchange Server in the Active Directory environment.

Each object that is retrieved contains multiple attributes. One of these attributes is msExchServerInternalTLSCert. This attribute contains the actual certificate used by the environment. If you look it up trough ADSI Edit (adsiedit.msc), then you'll find a string of number (hex, octal, decimal) values.

Not very human readable… And definitely not useful to determine the actual certificate.

PowerShell to the rescue!

We now know the Active Directory object and attribute to look for. Let's bring it all together and solve the riddle using Windows PowerShell.

Getting ready

In order to run this script you need to have:

  • Active Directory PowerShell module on the machine
  • The account used, must have at least the View-Only Configuration Management role assigned to it in the Exchange Server environment
  • This script can be run from the PowerShell ISE console
  • Before running, a target Exchange Server must be specified

Running PowerShell

Run the following PowerShell script:

#Specify a name of one of the Exchange Servers

$TargetExchangeServer = "Your Exchange Server"

$ExistingSessions = Get-PSSession

if($ExistingSessions.ConfigurationName -notcontains "Microsoft.Exchange"){

$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "http://$TargetExchangeServer/PowerShell/" -Authentication Kerberos

Import-PSSession $Session

}

else{

Write-Host "Use existing session" -ForegroundColor Green

}

#Get all Exchange Servers in the environment

$ExchangeServers = (Get-ExchangeServer |Where-Object {$_.ServerRole -like "mailbox"} )| Select-Object Name,DistinguishedName

$Results = @()

#Process Information

ForEach($Server in $ExchangeServers){

$TransportCert = (Get-ADObject -Identity $Server.DistinguishedName -Properties *).msExchServerInternalTLSCert

$Cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2

$CertBlob = [System.Convert]::ToBase64String($TransportCert)

$Cert.Import([Convert]::FromBase64String($CertBlob))

$server | Add-Member -MemberType NoteProperty -Name DefaultTLSCertSubject -Value $Cert.Subject

$server | Add-Member -MemberType NoteProperty -Name DefaultTLSCertFriendlyName -Value $Cert.FriendlyName

$server | Add-Member -MemberType NoteProperty -Name DefaultTLSCertThumbprint -Value $Cert.Thumbprint

$server | Add-Member -MemberType NoteProperty -Name DefaultTLSCertExpireDate -Value $Cert.NotAfter

                             

$Results += $Server

}

#Show result
$Results | Out-GridView

Interpreting the result

The script outputs a Windows PowerShell Grid View window. An example of the result is shown here:image

Concluding

I hope this article gives you more insight where the information of the default SMTP certificate is stored and how to retrieve it. This information can be valuable, when you try to gain insights into the certificates used by the Microsoft Exchange Servers.

Security Officer: Please block the iOS native mail app (for) now!

Reading Time: 5 minutesLast week an announcement was made: The native mail app in Apple's iOS has zero-day vulnerabilities, deemed critical. No patch is available at this time.

More information about the vulnerability can be found here.

For you as IT admin this means that you probably have work to do. The main questions you may be facing from management or your security officer are:

  1. How many people use the native mail app in our organization?
  2. Can you block access to people using the native mail app?

I'll answer these two questions in this blog post, for the on-premises environment and for Office 365.

How many people use the native iOS mail app?

To gain insights into the current usage of the native mail app, we need to use Windows PowerShell.

Note:
Although the official support for Exchange Server 2010 ends October 2020, I 'm also adding the commands for Exchange Server 2010 to this blogpost. Organizations are (unfortunately) still using it.

To execute the commands you will need to be connected through the Exchange Management Shell. This applies to all versions of Exchange. The minimum permissions needed are Recipient Administrator.

Query Microsoft Exchange Server 2010

For Micrososft Exchange Server 2010, we need the Get-ActiveSyncDevice and Get-ActiveSyncDeviceStatistics Windows PowerShell cmdlets.

Use the below lines of Windows PowerShell to query Microsoft Exchange Server 2010 for usage of the native iOS mail app:

$AlliOSDevices = Get-ActiveSyncDevice -Filter {(DeviceOs -like "iOS*") -and (ClientType -eq "EAS")}

$QueryResults = $AlliOSDevices | %{Get-ActiveSyncDeviceStatistics -Identity $_.Identity} | Sort-Object -Descending -Property LastSuccessSync

$QueryResults | Export-Csv C:\temp\iOSDevicesResult.csv -Delimiter ","

$QueryResults | Out-GridView

image

Note: the warning shown in the example, is caused by the fact that for Microsoft Exchange Server 2013 and higher a different command is recommended. See Query Microsoft Exchange Server 2013 or Later for more information.

The results are now available on the C:\temp folder on the server. Load the results in Microsoft Excel and the last successful synchronization is shown on top.

Query Microsoft Exchange Server 2013, or later, and Microsoft Exchange Online

For Microsoft Exchange Server 2013, newer versions of Microsoft Exchange Server and for Microsoft Exchange Online (part of Microsoft Office 365 and Microsoft 365), we need the Get-MobileDevice and Get-MobileDeviceStatistics Windows PowerShell Cmdlets.

Use the below lines of Windows PowerShell to query Microsoft Exchange Server 2013, and later, for usage of the native iOS mail app:

$AlliOSDevices = Get-MobileDevice -Filter {(DeviceOs -like "iOS*") -and (ClientType -eq "EAS")}

$QueryResults = $AlliOSDevices | %{Get-MobileDeviceStatistics -Identity $_.Identity} | Sort-Object -Descending -Property LastSuccessSync

$QueryResults | Export-Csv C:\temp\iOSDevicesResult.csv -Delimiter ","

$QueryResults | Out-GridView

image

The results are now available on the C:\temp folder on the server. Load the results in Microsoft Excel and the last successful synchronization is shown on top.

How to block the iOS native mail app

Using the previous scripts, we known who is using the native iOS mail app. The service desk and/or security officer can use direct communications to these employees. Their message should be that use of the native mail app is (about to be) disabled. The employees should configure the Microsoft Outlook app for iOS to regain access to e-mail on their mobile devices.

Block Access

To create the block rules for iPad and iOS, we are using Windows PowerShell with an active management connection to the Microsoft Exchange Server Environment. We recommend to be be logged in as Organization Administrator of the Exchange Server environment.

In Exchange Online you'll need to have the Exchange Administrator role. The Windows PowerShell cmdlet that we are going to use is New-ActiveSyncDeviceAccessRule.

To block access for iPad and iPhones, please use the following lines of Windows PowerShell:

New-ActiveSyncDeviceAccessRule -Characteristic DeviceType -QueryString "iPhone" -AccessLevel Block

New-ActiveSyncDeviceAccessRule -Characteristic DeviceType -QueryString "iPad" -AccessLevel Block

image

Remove block rule

To rollback or remove the rules, you can use the following lines of Windows PowerShell from the Exchange Management connection. The Windows PowerShell cmdlet we are going to use is: Remove-ActiveSyncDeviceAccessRule.

Remove-ActiveSyncDeviceAccessRule "iPhone (DeviceType)" -confirm:$false

Remove-ActiveSyncDeviceAccessRule "iPad (DeviceType)" -confirm:$false

image

Is it working?

After applying the policy, it is a recommended practice to review mobile devices. In the past, a device may be manually allowed and will continue to function after activating the block rules.

To gain insight in the block status and the excluded devices, we are going to use the same Windows PowerShell cmdlets as before. This time, we're adding an additional where filter: We are going to filter out the ‘DeviceAccessStateReason’ with the global value. These devices will be automatically blocked. Below, I'll show the example for Microsoft Exchange Server 2010 and for Microsoft Exchange Server 2013 and later.

Is it working on Microsoft Exchange Server 2010?

Use the following lines of Windows PowerShell to figure out if the block rule is working on Microsoft Exchange Server 2010:

$AlliOSDevices = Get-Activesyncdevice -Filter {(DeviceOs -like "iOS*") -and (ClientType -eq "EAS")}

$QueryResults = $AlliOSDevices | %{Get-ActivesyncDeviceStatistics -Identity $_.Identity} | where {$_.DeviceAccessStateReason -ne "Global" }| Sort-Object -Descending -Property LastSuccessSync

$QueryResults | Export-Csv C:\temp\iOSDevicesBlockedAllowed.csv -Delimiter ","

$QueryResults | Out-GridView

image

Is it working on Microsoft Exchange Server 2013, or later, and Microsoft Exchange Online?

Use the following lines of Windows PowerShell to figure out if the block rule is working on Microsoft Exchange Server 2013, newer versions of Microsoft Exchange Server and for Microsoft Exchange Online (part of Microsoft Office 365 and Microsoft 365), :

$AlliOSDevices = Get-MobileDevice -Filter {(DeviceOs -like "iOS*") -and (ClientType -eq "EAS")}

$QueryResults = $AlliOSDevices | %{Get-MobileDeviceStatistics -Identity $_.Identity} |where {$_.DeviceAccessStateReason -ne "Global" }| Sort-Object -Descending -Property LastSuccessSync

$QueryResults | Export-Csv C:\temp\iOSDevicesBlockedAllowed.csv -Delimiter ","

$QueryResults | Out-GridView

image

Concluding

Don't panic when you get difficult questions from management and\or the security officer. The answer is Yes, thanks to Windows PowerShell.

We can see who is using the native app and we can block access. With a few lines of Windows PowerShell we gain insight and take control.

I hope that the above information is helpful to you in regaining security, until the zero day in the iOS native mail app is fixed.

Exchange migration “Couldn’t switch the mailbox into Sync Source mode”

Reading Time: 3 minutesExchange

The issue

During one of my Exchange Online migration projects I encountered the following error on several mailboxes:

"Message : Error: Couldn’t switch the mailbox into Sync Source mode.
This could be because of one of the following reasons:
Another administrator is currently moving the mailbox.
The mailbox is locked.
The Microsoft Exchange Mailbox Replication service (MRS) doesn’t have the correct permissions.
Network errors are preventing MRS from cleanly closing its session with the Mailbox server.
If this is the case, MRS may continue to encounter this error for up to 2 hours – this duration is controlled by the TCP KeepAlive settings on the Mailbox server.
Wait for the mailbox to be released before attempting to move this mailbox again."

The environment where I encountered this was an Exchange Server 2010 environment in a Database Availability Group (DAG) configuration. There where three DAG servers and two Client Access servers.
We placed two Exchange Server 2016 servers in front for the Hybrid connection with Exchange Online.

The cause

This was a uncommon error for me, so I did some research first before proceeding with the suggested action to alter the TCP settings and request that the entire Exchange Server environment is restarted.

I found several blogs with different solutions, rather then changing the TCP value. One of the suggestions was to run the mailbox repair option. I did this without success. I also tried an internal move request of the mailbox and it failed on the same error.

After reading a blog by Brad Hughes on the topic, I found a interesting remark about the cause of the error.

"When moving a mailbox, the Mailbox Replication Service (MRS) sets an "InTransitStatus" flag in the source mailbox to make sure other moves don't try to act on this source mailbox at the same time.  This flag is really just held in memory in the source Information Store (Store) process (Store.exe for 2010 and Microsoft.Exchange.Store.Worker.exe for 2013 and 2016). "

The solution for me

So my conclusion on this was:

If it's held in memory, what options do I have to 'reset' this, without restarting the servers?

Because the source mailbox databases were placed in a DAG, I asked the customer to failover the mailbox database, which contained the error user. After the failover (which caused downtime for less then a minute), I recreated the move request again and this time the mailbox was synced to Office 365.

I find this solution less disrupting and quicker than a reboot of an entire Exchange Server.

The initial error was for a primary mailbox, but this error can also occur when a user has an in-place archive. In this case, for me, it was also enough to dismount and mount the mailbox database.
Yes, you read it correctly; dismount and mount the mailbox database. The archive databases weren't part of the DAG configuration. This action also triggers a memory reset.

This is not an error that happens when you start a migration; I also got it five times during synchronization of mailboxes. The solution here was the same, failover or dismount/mount the mailbox database.

Conclusion

I would suggest to not always go blindly with the Microsoft suggestion for the fix. Investigate the root cause is of the problem.
Yes, the Microsoft suggestion would have fixed it, but for the suggested fix to work, you need to restart all Exchange servers. This will cause a failover or dismount/mount action of the mailbox database.

For me it was just enough to just failover or dismount/mount the mailbox database(s) to get the synchronization starting or continuing. Without disrupting the rest of the organization, and aiding me in meeting my deadlines as a consultant.

I hope this helps for you, too.

TOOL: My own Exchange Move Request Report script

Reading Time: 5 minutesAs a consultant I do a lot of migrations or assist in them. In this case I wrote my own script for processing the information generated by the Mailbox Replication Service. This service is used in Microsoft Exchange on-premises and in the cloud for migrating the mailboxes between databases or environments.

After multiple times doing the administration and processing of information manually. I decided to create my own processing script to retrieve the desired information and make it re-usable in Excel or other tooling, to export gathered information to CSV file format. I named the script Get Move Request Report script.

The script is provide as-is and maybe be used at your own risk.

Versions

Version 1: This is the first version and it’s basic. No advanced switch options or logging.

About the Exchange Move Request Report Script

The script consist of three functions:

1. Creates an overview of the Bad Items it found in the move request report.
2. Creates an overview of the basic move information of the move request statistics.
3. Creates an overview of the extended information of the move request statistics.
4. Creates an overview of information that quickly can be used for reports to stakeholders.

Parameters

When you execute the script and no parameters are given. It generates a bad items overview, basic move information and extended move information output of all existing move request in scope.

.\Get-MoveRequestReport.ps1

To generate only the outputs for all InProgress move requests, give the following command:

.\Get-MoveRequestReport.ps1 -Inprogress $true -AllMoves $false

To generate only the outputs for all Synced move requests, give the following command:

.\Get-MoveRequestReport.ps1 -Synced $true -AllMoves $false

To generate only the outputs for all Completed move requests, give the following command:

.\Get-MoveRequestReport.ps1 -Completed $true -AllMoves $false

Requirements

To use the script, you already need to have an Exchange PowerShell session open to the target environment (on-premises or cloud), where the move request are created.

Exchange Move Request Report Script

The script is displayed below for your review:

[CmdletBinding()]
param (
    [bool]$CompletedMoves = $false, 
    [bool]$Synced = $false,    
    [bool]$Inprogress = $false,
    [bool]$AllMoves = $true,
    [bool]$IncludeAllBadItems = $true,
    [bool]$MoveDataBasic = $true,
    [bool]$MoveDataFull = $true,
    [bool]$GenereExportInfo = $true
)

#region Functions
function generateBadItemInformation ($BadItem,$User)
{
    $ExportEntry = New-Object PSObject
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name Identity -Value $User.Alias
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name BatchName -Value $User.Batchname
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name DisplayName -Value $User.DisplayName
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name Date -Value $BadItem.Date
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name Subject -Value $BadItem.Subject
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name Kind -Value $BadItem.Kind
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name FolderName -Value $BadItem.FolderName
    
    
    $ExportEntry   
}

function generateBasicMoveInformation ($User)
{
    $ExportEntry = New-Object PSObject
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name Identity -Value $User.Alias
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name BatchName -Value $User.Batchname
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name DisplayName -Value $User.DisplayName
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name Status -Value $User.Status
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name SyncStage -Value $User.SyncStage
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name RemoteDatabase -Value $User.RemoteDatabase
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name RemoteHostName -Value $User.RemoteHostName
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name Message -Value $User.Message
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name RecipientTypeDetails -Value $User.RecipientTypeDetails
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalMailboxSize -Value $User.TotalMailboxSize
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalMailboxItemCount -Value $User.TotalMailboxItemCount
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalArchiveSize -Value $User.TotalArchiveSize
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalArchiveItemCount -Value $User.TotalArchiveItemCount
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalPrimarySize -Value $User.TotalPrimarySize
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalPrimaryItemCount -Value $User.TotalPrimaryItemCount
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name BytesTransferred -Value $User.BytesTransferred

    $ExportEntry   
}

function generateFullMoveInformation ($User)
{
    $ExportEntry = New-Object PSObject
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name Identity -Value $User.Alias
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name BatchName -Value $User.Batchname
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name DisplayName -Value $User.DisplayName
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name Status -Value $User.Status
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name SyncStage -Value $User.SyncStage
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name RemoteDatabase -Value $User.RemoteDatabase
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name RemoteHostName -Value $User.RemoteHostName
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name Message -Value $User.Message
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name RecipientTypeDetails -Value $User.RecipientTypeDetails
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalMailboxSize -Value $User.TotalMailboxSize
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalMailboxItemCount -Value $User.TotalMailboxItemCount
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalArchiveSize -Value $User.TotalArchiveSize
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalArchiveItemCount -Value $User.TotalArchiveItemCount
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalPrimarySize -Value $User.TotalPrimarySize
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalPrimaryItemCount -Value $User.TotalPrimaryItemCount
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name BytesTransferred -Value $User.BytesTransferred
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name OverallDuration -Value $User.OverallDuration
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalSuspendedDuration -Value $User.TotalSuspendedDuration
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalFailedDuration -Value $User.TotalFailedDuration
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalQueuedDuration -Value $User.TotalQueuedDuration
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalInProgressDuration -Value $User.TotalInProgressDuration
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name StatusDetail -Value $User.StatusDetail
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name SourceVersion -Value $User.SourceVersion
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name FailureCode -Value $User.FailureCode
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name FailureType -Value $User.FailureType
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name FailureSide -Value $User.FailureSide
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name QueuedTimestamp -Value $User.QueuedTimestamp
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name StartTimestamp -Value $User.StartTimestamp
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name LastUpdateTimestamp -Value $User.LastUpdateTimestamp
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name LastSuccessfulSyncTimestamp -Value $User.LastSuccessfulSyncTimestamp
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name InitialSeedingCompletedTimestamp -Value $User.InitialSeedingCompletedTimestamp
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalStalledDueToContentIndexingDuration -Value $User.TotalStalledDueToContentIndexingDuration
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalStalledDueToMdbReplicationDuration -Value $User.TotalStalledDueToMdbReplicationDuration
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalStalledDueToMailboxLockedDuration -Value $User.TotalStalledDueToMailboxLockedDuration
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalStalledDueToReadThrottle -Value $User.TotalStalledDueToReadThrottle
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalStalledDueToWriteThrottle -Value $User.TotalStalledDueToWriteThrottle
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalStalledDueToReadCpu -Value $User.TotalStalledDueToReadCpu
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalStalledDueToWriteCpu -Value $User.TotalStalledDueToWriteCpu
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalStalledDueToReadUnknown -Value $User.TotalStalledDueToReadUnknown
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalStalledDueToWriteUnknown -Value $User.TotalStalledDueToWriteUnknown
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalTransientFailureDuration -Value $User.TotalTransientFailureDuration

    
    $ExportEntry   
}

function GenerateExcelInformation ($BadItems,$User)
{
    $ExportEntry = New-Object PSObject
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name Identity -Value $User.Alias
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name BatchName -Value $User.Batchname
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name DisplayName -Value $User.DisplayName
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name Status -Value $User.Status
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name SyncStage -Value $User.SyncStage
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name RemoteDatabase -Value $User.RemoteDatabase
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name RemoteHostName -Value $User.RemoteHostName
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name Message -Value $User.Message
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name RecipientTypeDetails -Value $User.RecipientTypeDetails
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalMailboxSize -Value $User.TotalMailboxSize
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name TotalMailboxItemCount -Value $User.TotalMailboxItemCount
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name BadItems -Value $BadItems.Count
    $ExportEntry | Add-Member -MemberType "NoteProperty" -Name BytesTransferred -Value $User.BytesTransferred
    
    $ExportEntry   
}


#endregion

#region Global Vales
$LogPath = "C:\Scripts\MRS\Reports\"
$FileTimeStamp = (Get-Date -Format yyyyMMdd-HHmmss).ToString()
$ExportBadItems = @()
$ExportBasicInformation = @()
$ExportFullInformation = @()
$ExportExcelInfo = @()
$Moves = @()
#endregion


#region Collect Movedata

If($AllMoves){
    $Moves = Get-MoveRequest | Get-MoveRequestStatistics -IncludeReport
}

If($CompletedMoves){
    $Moves = Get-MoveRequest -MoveStatus Completed | Get-MoveRequestStatistics
}

If($Synced){
    $Moves += Get-MoveRequest -MoveStatus Synced | Get-MoveRequestStatistics
}

If($Inprogress){
    $Moves += Get-MoveRequest -MoveStatus Inprogress | Get-MoveRequestStatistics
}

#endregion

#region Process moves Data

If($IncludeAllBadItems){

    $Moves2 = get-migrationuser | where{$_.SkippedItemCount -gt 0} | Get-MoveRequestStatistics -IncludeReport
    Foreach ($Move in $Moves2){
        $Baditems = ""
        $Baditems = ($Move.Report).Baditems
          
            If($Baditems -eq ""){
                Write-Host "No Errors found for " -BackgroundColor Green
            }
                Else{
                    Foreach($BadItem in $Baditems){
                        $ExportBadItems += generateBadItemInformation -BadItem $Baditem -User $Move
                    }
                }
    }

}

If($GenereExportInfo){

    Foreach ($Move in $Moves){
        $Baditems = ""
        $Baditems = ($Move.Report).Baditems
          
            If($Baditems -eq ""){
                Write-Host "No Errors found for " -BackgroundColor Green
            }
            Else{
                $ExportExcelInfo += GenerateExcelInformation -BadItems $Baditems -User $Move
            }
    }

}



If($MoveDataBasic){

     Foreach ($Move in $Moves){
        $ExportBasicInformation += generateBasicMoveInformation -User $Move
     }           
}


If($MoveDataFull){

    Foreach ($Move in $Moves){
        $ExportFullInformation += generateFullMoveInformation -User $Move           
    }
}

#endregion

#region Export data
If($IncludeAllBadItems){
    $ExportBadItems | ogv
    $FileName = $LogPath+"BadItems"+$FileTimeStamp+".csv"
    $ExportBadItems | Export-Csv -Path $FileName -Delimiter ";" -NoTypeInformation
}

If($MoveDataBasic){
    $ExportBasicInformation | ogv
    $FileName = $LogPath+"BasicMoveData"+$FileTimeStamp+".csv"
    $ExportBasicInformation |Export-Csv -Path $FileName -Delimiter ";" -NoTypeInformation
}

If($MoveDataFull){
    $ExportFullInformation | ogv
    $FileName = $LogPath+"FullMoveData"+$FileTimeStamp+".csv"
    $ExportFullInformation | Export-Csv -Path $FileName -Delimiter ";" -NoTypeInformation
}


if($ExportExcelInfo){
    $ExportExcelInfo | ogv
    $FileName = $LogPath+"ExcelData"+$FileTimeStamp+".csv"
    $ExportExcelInfo | Export-Csv -Path $FileName -Delimiter ";" -NoTypeInformation
}

#endregion

I hope this is also useful for your migrations and have fun with the script. As time goes and report demands change. I will update the script on this blog to keep you up-to-date.