Using Powershell to get Exchange config for scoping a project to migrate to Office 365

Working for a Manage Service Provider (MSP) you get some certain advantages like getting experience with a wide range of technology and manufacturers or the ability to repeat projects and perfect them. Most IT departments out there will one day migrate from their in-house Exchange email to a hosted SaaS platform like Office 365 but once they do that, the experience is over and most may not every get to experience it again. Working with an MSP means that you are the IT department for many companies and you get to repeat some of these projects and improve upon the next time.  Being that I come from a process and Six Sigma background, this aspect really appeals to me.

Recently we were tasked with scoping a project to move a company from an on-prem Exchange server to Office 365 and the engineer that was working on this was manually going to the server and using the Exchange console to get a list of users, distribution groups, and mailbox permissions. We then were told that another 4 companies would like us to let them know what it would take to move their email as well. I was then asked for some help to make this process a little easier.  I wrote a quick script that still helps us today in reducing the time and effort in scoping Office 365 migrations.

Let's dive in to the code.

We will first start off by setting the verbose preference to continue so that Write-Verbose will work properly. If you have read my other posts, you will learn that this is a common theme that I do because I like to use the Start-Transcript and Stop-Transcript cmdlets. I am also setting C:\Temp as the location that I want to output the three CSV files that will have the data. I could have put them into one Excel file but CSVs are easier to work with in Powershell and we may use these later to setup the Office 365 tenant.

$verbosepreference = "continue"
$output = 'C:\temp'

In this section, we are creating a hash table to store an array of Microsoft version numbers and the official name that corresponds to them. We need get the version number of Exchange that is installed on the server so that we can match it to the official name. We start by getting the version number and using it to find the item in the hash table. When we find the item in the has table we will then get the full name. We then use some if\elseif statements to match the full name to the year and subsequently installing the PSSnapin that relates to that version.  Yes I could have just put the year in the hash table but I wrote this so that anyone can read it and understand. 

This is not necessary if you ran this in the Exchange Powershell console but it would be needed to add the Exchange cmdlets to the Powershell ISE should you use that or should you want to add functionality to execute this remotely.

We then output the list of Exchange servers to the console. This is not really needed but I liked that it gave me a list that I could see to ensure that I am working on the only one.

# creating a hash table to store the version numbers
# of Exchange so we can determine what PSSnapin to load
Write-Verbose "Creating a hash table to store the version numbers of Exchange so we can determine what PSSnapin to load"
$versions = @{
"Microsoft Exchange Server 2003" = "6.5.6944";
"Microsoft Exchange Server 2003 SP1" = "6.5.7226";
"Microsoft Exchange Server 2003 SP2" = "6.5.7638";
"Microsoft Exchange Server 2003 SP2 March 2008 update" = "6.5.7653.33";
"Microsoft Exchange Server 2003 SP2 August 2008 update" = "6.5.7654.4";
"Microsoft Exchange Server 2007" = "8.0.685.24";
"Microsoft Exchange Server 2007 " = "8.0.685.25";
"Microsoft Exchange Server 2007 SP1" = "8.1.240.006";
"Microsoft Exchange Server 2007 SP2" = "8.2.176.002";
"Microsoft Exchange Server 2007 SP3" = "8.3.83.006";
"Microsoft Exchange Server 2010" = "14.0.639.21";
"Microsoft Exchange Server 2010 SP1" = "14.1.218.15";
"Microsoft Exchange Server 2010 SP2" = "14.2.247.5";
"Microsoft Exchange Server 2010 SP3" = "14.3.123.4";
"Microsoft Exchange Server 2013" = "15.0.516.032";
"Microsoft Exchange Server 2016" = "15.1.669.32"
}

# retrieve the version of Exchange that is installed
Write-Verbose "Retrieving the version of Exchange that is installed"
$installver = (get-wmiobject win32_product | where {$_.name -match "exchange server" -and $_.name -notmatch "Language Pack"}).version
$installname = ($versions.GetEnumerator() | Where {$_.value -eq $installver}).name

# load the PSSnapin for the particular version of Exchange
Write-Verbose "Loading the PSSnapin for the particular version of Exchange"
if ($installname -match "2007"){
    Write-Verbose "Loading the PSSnapin for Exchange 2007"
    Add-PSSnapin Microsoft.Exchange.Management.PowerShell.Admin
}elseif ($installname -match "2010") {
    Write-Verbose "Loading the PSSnapin for Exchange 2010"
    Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010
}elseif ($installname -match "2013") {
    Write-Verbose "Loading the PSSnapin for Exchange 2013"
    Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn
}elseif ($installname -match "2016") {
    Write-Verbose "Loading the PSSnapin for Exchange 2016"
    Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn
}else {
    Write-Verbose "Exchange did not match 2007, 2010, 2013, or 2016"
}

# retrieve a list of Exchange Servers and output to screen
Write-Verbose "Retrieve a list of Exchange Servers and output to screen"
$exc = Get-ExchangeServer
foreach ($item in $exc){
    $excname = $item.name
    Write-Verbose "There is an Exchange Server named $excname"
}

In the next section, we are getting all of the mailboxes that are on the Exchange server and only selecting the properties that we wanted for the potential script to setup the Office 365 tenant. We are then outputting those mailboxes to a CSV file and outputting the count of those to the console.

###############################################################
# Get Mailboxes and Output to CSV

# retrieve a list of mailboxes from the server
Write-Verbose "Retrieving a list of mailboxes from the server"
$mailboxes = Get-Mailbox | select name,SamAccountName,PrimarySmtpAddress,UserPrincipalName,DistinguishedName,IsValid,RecipientType,OrganizationalUnit,AccountDisabled
$mailboxes | Export-Csv "$output\UserAccounts.csv" -force -notypeinformation
# retrieve a total count of mailboxes
Write-Verbose "Retrieving a total count of mailboxes"
$count = $mailboxes.count
# output to the screen the count of mailboxes
Write-Verbose "There are $count mailboxes"

In the next section, we are getting all of the permissions on the mailboxes to see if someone has been granted rights to someone else's mailbox. We will need to reset these up later in the Office 365 tenant so we are retrieving it now. This information, on a few occasions, has also effected the scope of the project should there be a large number of items. Again we are exporting the results to a CSV file.

###############################################################
# Get Mailbox Permissions and Output to CSV

# retrieve mailbox permissions and export to csv
Write-Verbose "Retrieving mailbox permissions and export to csv"
Get-Mailbox -ResultSize Unlimited | Get-MailboxPermission | Where {$_.user -notlike "NT AUTHORITY\SELF" -and $_.IsInherited -eq $false} | 
    Select Identity,User,@{Name='Access Rights';Expression={[String]::join(‘, ‘, $_.AccessRights)}} | 
        Export-Csv $output\MailboxPermissions.csv -NoTypeInformation

In the next section, we are getting all of the Distribution Groups and all of the members of them and outputting it to a CSV file.

###############################################################
# Get Distribution Groups and Output to CSV

# retrieve a list of distribution groups from the server
Write-Verbose "Retrieving a list of distribution groups from the server"
$distgroups = Get-DistributionGroup
# retrieve a total count of distribution groups
Write-Verbose "Retrieving a total count of distribution groups"
$dgcount = $distgroups.count
# output to the screen the count of distribution groups
Write-Verbose "There are $dgcount distribution groups"

foreach ($dg in $distgroups) {
    $dgmembers = Get-DistributionGroupMember $dg
    foreach ($dgmember in $dgmembers) {
        $object = New-Object psobject
        $object | Add-Member –MemberType NoteProperty –Name Name –Value $dg.name
        $object | Add-Member –MemberType NoteProperty –Name PrimarySMTPAddress –Value $dg.PrimarySMTPAddress
        $object | Add-Member –MemberType NoteProperty –Name GroupType –Value $dg.grouptype
        $object | Add-Member –MemberType NoteProperty –Name Owner –Value $dg.ManagedBy.name
        $object | Add-Member –MemberType NoteProperty –Name UserDisplayName –Value $dgmember.DisplayName
        $object | Add-Member –MemberType NoteProperty –Name UserEmail –Value $dgmember.PrimarySMTPAddress
        $object | Add-Member –MemberType NoteProperty –Name UserType –Value $dgmember.RecipientType
        $object | Add-Member –MemberType NoteProperty –Name UserUPN –Value $dgmember.UserPrincipleName 
        $object | convertto-Csv -NoTypeInformation | out-file $output\DistributionGroups.csv -append
    }
}

Here is the script in its entirety should you like to use it! We utilize this information to setup a scope for a project in which we usually charge a per user fee to migrate the users and their permissions to Office 365 and a per distribution group fee for setting them up!

$verbosepreference = "continue"
$output = 'C:\temp'

# creating a hash table to store the version numbers
# of Exchange so we can determine what PSSnapin to load

Write-Verbose "Creating a hash table to store the version numbers of Exchange so we can determine what PSSnapin to load"
$versions = @{
"Microsoft Exchange Server 2003" = "6.5.6944";
"Microsoft Exchange Server 2003 SP1" = "6.5.7226";
"Microsoft Exchange Server 2003 SP2" = "6.5.7638";
"Microsoft Exchange Server 2003 SP2 March 2008 update" = "6.5.7653.33";
"Microsoft Exchange Server 2003 SP2 August 2008 update" = "6.5.7654.4";
"Microsoft Exchange Server 2007" = "8.0.685.24";
"Microsoft Exchange Server 2007 " = "8.0.685.25";
"Microsoft Exchange Server 2007 SP1" = "8.1.240.006";
"Microsoft Exchange Server 2007 SP2" = "8.2.176.002";
"Microsoft Exchange Server 2007 SP3" = "8.3.83.006";
"Microsoft Exchange Server 2010" = "14.0.639.21";
"Microsoft Exchange Server 2010 SP1" = "14.1.218.15";
"Microsoft Exchange Server 2010 SP2" = "14.2.247.5";
"Microsoft Exchange Server 2010 SP3" = "14.3.123.4";
"Microsoft Exchange Server 2013" = "15.0.516.032";
"Microsoft Exchange Server 2016" = "15.1.669.32"
}

# retrieve the version of Exchange that is installed
Write-Verbose "Retrieving the version of Exchange that is installed"
$installver = (get-wmiobject win32_product | where {$_.name -match "exchange server" -and $_.name -notmatch "Language Pack"}).version
$installname = ($versions.GetEnumerator() | Where {$_.value -eq $installver}).name

# load the PSSnapin for the particular version of Exchange
Write-Verbose "Loading the PSSnapin for the particular version of Exchange"
if ($installname -match "2007"){
    Write-Verbose "Loading the PSSnapin for Exchange 2007"
    Add-PSSnapin Microsoft.Exchange.Management.PowerShell.Admin
}elseif ($installname -match "2010") {
    Write-Verbose "Loading the PSSnapin for Exchange 2010"
    Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010
}elseif ($installname -match "2013") {
    Write-Verbose "Loading the PSSnapin for Exchange 2013"
    Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn
}elseif ($installname -match "2016") {
    Write-Verbose "Loading the PSSnapin for Exchange 2016"
    Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn
}else {
    Write-Verbose "Exchange did not match 2007, 2010, 2013, or 2016"
}

# retrieve a list of Exchange Servers and output to screen
Write-Verbose "Retrieve a list of Exchange Servers and output to screen"
$exc = Get-ExchangeServer
foreach ($item in $exc){
    $excname = $item.name
    Write-Verbose "There is an Exchange Server named $excname"
}

###############################################################
# Get Mailboxes and Output to CSV

# retrieve a list of mailboxes from the server
Write-Verbose "Retrieving a list of mailboxes from the server"
$mailboxes = Get-Mailbox | select name,SamAccountName,PrimarySmtpAddress,UserPrincipalName,DistinguishedName,IsValid,RecipientType,OrganizationalUnit,AccountDisabled
$mailboxes | Export-Csv "$output\UserAccounts.csv" -force -notypeinformation
# retrieve a total count of mailboxes
Write-Verbose "Retrieving a total count of mailboxes"
$count = $mailboxes.count
# output to the screen the count of mailboxes
Write-Verbose "There are $count mailboxes"

###############################################################
# Get Mailbox Permissions and Output to CSV

# retrieve mailbox permissions and export to csv
Write-Verbose "Retrieving mailbox permissions and export to csv"
Get-Mailbox -ResultSize Unlimited | Get-MailboxPermission | Where {$_.user -notlike "NT AUTHORITY\SELF" -and $_.IsInherited -eq $false} | 
    Select Identity,User,@{Name='Access Rights';Expression={[String]::join(‘, ‘, $_.AccessRights)}} | 
        Export-Csv $output\MailboxPermissions.csv -NoTypeInformation


###############################################################
# Get Distribution Groups and Output to CSV

# retrieve a list of distribution groups from the server
Write-Verbose "Retrieving a list of distribution groups from the server"
$distgroups = Get-DistributionGroup
# retrieve a total count of distribution groups
Write-Verbose "Retrieving a total count of distribution groups"
$dgcount = $distgroups.count
# output to the screen the count of distribution groups
Write-Verbose "There are $dgcount distribution groups"

foreach ($dg in $distgroups) {
    $dgmembers = Get-DistributionGroupMember $dg
    foreach ($dgmember in $dgmembers) {
        $object = New-Object psobject
        $object | Add-Member –MemberType NoteProperty –Name Name –Value $dg.name
        $object | Add-Member –MemberType NoteProperty –Name PrimarySMTPAddress –Value $dg.PrimarySMTPAddress
        $object | Add-Member –MemberType NoteProperty –Name GroupType –Value $dg.grouptype
        $object | Add-Member –MemberType NoteProperty –Name Owner –Value $dg.ManagedBy.name
        $object | Add-Member –MemberType NoteProperty –Name UserDisplayName –Value $dgmember.DisplayName
        $object | Add-Member –MemberType NoteProperty –Name UserEmail –Value $dgmember.PrimarySMTPAddress
        $object | Add-Member –MemberType NoteProperty –Name UserType –Value $dgmember.RecipientType
        $object | Add-Member –MemberType NoteProperty –Name UserUPN –Value $dgmember.UserPrincipleName 
        $object | convertto-Csv -NoTypeInformation | out-file $output\DistributionGroups.csv -append
    }
}

I hope that this script will serve you well and keep an eye out later for the script that imports all of this into the Office 365 tenant.

Leave a Reply

Your email address will not be published.