Active Directory, Exchange, Microsoft, Office 365, PowerShell

How to Provision Exchange Online Mailboxes

After completing a migration to Exchange Online, it is common to have questions like:

1) What is the best practice for provisioning mailboxes?

2) How do I provision mailboxes?

3) Why should I keep this Exchange server around?

Well, these are good questions.  Let’s start with the last question.  When an organization migrates to Exchange Online, it is quite commonly thought that Exchange is no longer needed on-premise.  From a deeply technical perspective, this is true… but it is a bad idea.  As a matter of fact, it is such a bad idea that I wrote a very blunt response to another blog post suggesting that keeping hybrid in place is not really necessary.  Well, if you want to live in a world where you are maintaining your own tools, have only skilled individuals provisioning mailboxes, or you have lesser skilled folks poking around directly with Active Directory attributes, knock yourself out.  Also, this doesn’t apply to organizations that don’t synchronize their on-premise Active Directory to Azure AD.  For those that want to limit their heartburn and remain in a supported configuration, however, you must not only keep an on-premise Exchange server, but also keep it in a hybrid configuration.

Microsoft goes a long way to explain it in their article: How and when to decommission your on-premises Exchange server in a hybrid deployment.

The problem is that this article is buried deeply out there in the web and very hard to find.  Microsoft doesn’t make this very well known because this information is hard to find.  Here is the basic summary: You need to keep an Exchange server around on-premises in hybrid not because of Exchange, but because of directory synchronization.  AAD Connect (and its predecessors AAD Sync and DirSync) is built on what is now know as Microsoft Identity Manager 2016.  This is a metadirectory product that helps to synchronize and provision identities.  While there can be some bi-directional synchronization of attributes, there has to be an ultimate source of truth.  This means that when you are synchronizing to Azure AD, your on-premises Active Directory is that source of truth.  Users must be provisioned there and many attributes must also be maintained there.  Since these objects, like mailboxes, distribution groups, and mail contacts, must exist in on-premises Active Directory, it makes sense to have an administrative interface.  One could manually edit the attributes like ProxyAddresses and know that the primary SMTP address should be meet the following requirements:

1) Be prefixed with “SMTP:”

2) Be unique throughout your organization

3) Have its value saved to the Mail attribute

Other values in ProxyAddresses should be prefixed with “smtp:” or their address type prefix.  Because of these challenges, Microsoft has made it a support requirement that Exchange remain on-premises.  In fact, they have made it easier by giving all enterprise Office 365 customers a free license key for use with hybrid co-existence servers.

Aside from the support requirement, there are plenty of reasons to keep an Exchange server on-premise.  First, it makes support magnitudes more simple.  You can use the on-premises RBAC model and grant support staff the rights to provision recipients from your Exchange system without even granting rights to Office 365 or Exchange Online administratively.  Another big reason is that Exchange Online does NOT have Email Address Policies.  By maintaining a hybrid co-existence server, Email Address Policies can be established and executed there and the subsequent AD writes will be synchronized to Azure AD, this giving you Email Address Policies for Exchange Online recipients.  Finally, many time you need to relay mail from on-premises devices and applications; this is far easier if it is done centrally from a server on-premises that is granted relay rights through Exchange Online.  This simplifies firewall rules and keeps the configuration within Exchange Online to a minimum.  This relay point may as well be the hybrid co-existence server that you should already have.  Servers with hybrid co-existence perform Exchange Authentication and have a privileged existence from the standpoint of spam filtering.  Finally, if you take the server out of the topology by not keeping it in hybrid configuration, you lose access to many of the cmdlets and web interface means to properly provision recipients in Exchange Online.

Now we know why we should maintain this server, but how do we effectively utilize it?  Well, it is not a walk in the park unless you are well-acquainted with Exchange Management Shell.  Microsoft has been very inconsistent about its tools.  In the web interface, you can create a User Mailbox in Exchange Online from your on-premises server if you create a new Active Directory user account at the same time, but you cannot use an existing user account.  Also, Microsoft makes it easy to create Equipment and Room mailboxes, which are types of Shared mailboxes, but not Shared mailboxes themselves.  Microsoft recommends that you create a Shared mailbox on-premises and migrate it to Exchange Online.  This is just bad, Microsoft… you should fix this and make it work well and consistently.

Here is my cheat sheet for provisioning mailboxes and it requires Exchange Management Shell connected to the on-premises environment.  These commands count on existing AD user objects.  Let’s assume that we have the following AD user objects for which we must create Exchange Online mailboxes with their intuined types:

User Mailbox (AD user object name: “User Mailbox”):

1. From Exchange Management Shell: Get-User “User Mailbox” | Enable-RemoteMailbox -RemoteRoutingDomain

2. Wait for a sync cycle or manually synchronize from AAD Connect: Start-ADSyncSyncCycle -PolicyType Delta

Room Mailbox (AD user object name: “Room Mailbox”):

1. From Exchange Management Shell: Get-User “Room Mailbox” | Enable-RemoteMailbox -RemoteRoutingDomain

2. From Exchange Management Shell: Get-RemoteMailbox “Room Mailbox: | Set-RemoteMailbox -Type Room

3. Wait for a sync cycle or manually synchronize from AAD Connect: Start-ADSyncSyncCycle -PolicyType Delta

Equipment Mailbox (AD user object name: “Equipment Mailbox”):

1. From Exchange Management Shell: Get-User “Equipment Mailbox” | Enable-RemoteMailbox -RemoteRoutingDomain

2. From Exchange Management Shell: Get-RemoteMailbox “Equipment Mailbox” | Set-RemoteMailbox -Type Equipment

3. Wait for a sync cycle or manually synchronize from AAD Connect: Start-ADSyncSyncCycle -PolicyType Delta

Shared Mailbox (AD user object name: “Shared Mailbox”):

1. From Exchange Management Shell: Get-User “Shared Mailbox” | Enable-RemoteMailbox -RemoteRoutingDomain

2. Wait for a sync cycle or manually synchronize from AAD Connect: Start-ADSyncSyncCycle -PolicyType Delta

3. From Exchange Online PowerShell: Get-Mailbox “Shared Mailbox” | Set-Mailbox -Type Shared

That’s it.  Distribution groups are created on-premises just like normal and members added from on-premises.  Mail contacts are also created just like normally on-premises.  Each of these synchronize properly to Azure AD.

Ensuring that you properly provision your mailboxes on-premises means that messages relayed from on-premises devices and applications do not get NDRs and that email addresses get assigned via policies, reducing the opportunity for human error.  One thing to keep note of is that Shared mailboxes are still inconsistent from the rest; they are created as regular mailboxes and then converted to Shared mailboxes in Exchange Online directly.  I wish Microsoft would change this as it is possible… if you manually set the msExchRecipientDisplayType and msExchRecipientTypeDetails attributed to the proper values, they will be provisioned in Exchange Online properly, but this should be a requirement.

Exchange, Office 365, PowerShell

Identifying Mailboxes without a Cloud Alias

In a hybrid Exchange scenario, mailboxes should be stamped with aliases that are mapped to the tenant unique namespaces (e.g. *  If a mailbox does not contain these aliases, it will fail to migrate.  These aliases are normally applied via Email Address Policies, but if the AD object is set to block Email Address Policies from applying, this won’t work.  A quick trick to identify these aliases is as follows (from the Exchange Management Shell, on-premise):
$Mailboxes = Get-Mailbox -ResultSize Unlimited -Filter {EmailAddressPolicyEnabled -eq $False -and EmailAddresses -NotLike "*"}
Let me know if this works for you in the comments.  Thanks!

Microsoft, PowerShell

Considerations for an Open Sourced PowerShell


With the news that PowerShell has been open sourced and brought to Linux and Mac OSX, we are witnessing the fruits of Microsoft’s journey to embrace other platforms in an effort to reach and support customers’ needs.  It is pretty exciting as my personal journey with technology began on the other side, using Linux and working with open sourced solutions and embracing Microsoft platforms.  We can see this with the release of the Office suite on Android and iOS as an equal release to anything else.

Announcement here: PowerShell is open sourced and is available on Linux

So, what does this all mean for your PowerShell scripts?  There definitely are some considerations.  First, not everything PowerShell is available.  For example, Windows specific commands obviously won’t be available on Linux or Mac OSX.  Other considerations are related to common aliases; like ‘ls’.  This command is aliased to Get-ChildItem in PowerShell.  What problems does this cause?  Well, the PowerShell team decided to not apply this alias in Linux/Mac because it might seem presumptuous.  The recommended action for scripts to be the most portable is to not use aliases.  If you are at the prompt running commands, go ahead and use whatever time savers you have at your disposal, but if you are saving it, use the full cmdlet name.  This is not only good for portability, but it also makes scripts easier read when comparing them to documentation for those not familiar with the alias.

In addition, we are accustomed to the .ps1 extension in Windows, but this doesn’t mean anything in Linux/Mac; files are files.  Those familiar with shell scripting on these platforms will know that the first line of the script denotes what executable should be used to parse the script.  So, PowerShell scripts meant to be portable should use the familiar file extension and the first line beginning with #!/usr/bin/powershell; this will simply be read as a comment on the Windows platform.  This also suggests that if you have only developed or tested for a particular platform, that it might be reasonable to only use the requirement for that platform as a means to communicate its status.

Other items that have not been open sourced are components that belong to other teams.  It will be up to those teams to determine if that is the direction they will take.  For instance, the PowerShell ISE is actually built on top of Visual Studio and the PowerShell team cannot decide to open source it.

PowerShell has been open sourced with the MIT license.

What are your thoughts and concerns?

Microsoft, PowerShell

Splitting CSV files in PowerShell with Split-Csv

In the course of my work, I am often times dealing with large CSV files that need to be broken up into smaller files, for whatever reason.  If the CSV file has a header, I can’t simply use something that breaks up the file by number of lines because I need to retain the header for use in each file.  So, I wrote up a script that I call Split-Csv.

Split-Csv takes a file path and a batch size as parameters, Path and BatchSize, respectively:

Split-Csv <InputFilePath> -BatchSize <int>

Script contents:

    [Parameter(Mandatory=$True,Position=1,HelpMessage="File path")]
    [Parameter(Mandatory=$True,HelpMessage="Batch size")]

If(!(Test-Path $Path -ErrorAction SilentlyContinue)) {
    Write-Error "$Path not found."

$inputFile = Get-Item $Path

$ParentDirectory = Split-Path $Path

$strFilename = dir $Path | Select-Object BaseName,Extension

$strBaseName = $strFilename.BaseName
$strExtension = $strFilename.Extension

$objFile = Import-Csv $Path

$Count = [math]::ceiling($objFile.Count/$BatchSize)

$Length = "D" + ([math]::floor([math]::Log10($Count) + 1)).ToString()

1 .. $Count | ForEach-Object {
    $i = $_.ToString($Length)
    $Offset = $BatchSize * ($_ - 1)
    $outputFile = $ParentDirectory + "\" + $strBaseName + "-" + $i + $strExtension
    If($_ -eq 1) {
        $objFile | Select-Object -First $BatchSize | Export-Csv $outputFile -NoTypeInformation
    } Else {
        $objFile | Select-Object -First $BatchSize -Skip $Offset | Export-Csv $outputFile -NoTypeInformation

Easy enough.

There are a couple of things of note.

1) I just drop these back into the same directory as the original; and,
2) I name the output files the same as the original appended with the number represented by their batch, zero-padded.

That’s it.

Microsoft, Office 365, PowerShell

Moving an AD User from One Group to Another via PowerShell

This week I had a request that seemed rather simple.  Given a CSV file containing the User Principal Name of the user, determine if the user is a member of a specific Active Directory group, remove it, and add it as a member to another specific Active Directory group.  I am not sure of what the purpose of this is, but it is part of an Office 365 migration and given my own experiences with using groups for things security related and not-so-security related, it could simply be a matter of identifying users or classifying them.  Anyhow, the final solution isn’t very complicated, but it is slightly more than what I would have expected.

The problem stems from what I have grown accustomed in dealing with Exchange Management Shell, I can identify a mailbox by numerous methods: Display Name, email address, alias, SAM Account Name, or User Principal Name.  However, the Get-ADUser command doesn’t offer a direct means to identify the user(s) via User Principal Name:


This fails.

The ultimate solution isn’t all that complicated:

Get-ADUser -Filter {UserPrincipalName -eq ""}


So, here is the workflow:

  1. Validate that the user exists (done)
  2. Determine if the user is a member of the “source” group, if not, exit
  3. If the user is a member of the “source” group, remove it
  4. Add the user as a member of the “target” group

So, this requires the Active Directory module within PowerShell (I have not tested this after writing it here, as I simply reproduced it from memory… definitely test and let me know if there is an issue in the comments):

Import-Module ActiveDirectory

$SourceGroup = "<SourceGrpName>"
$TargetGroup = "<TargetGrpName>"
$Users = Import-Csv "<FilePath>" # Contains a list of users as UserPrincipalName

$Users | ForEach-Object {
	$UPN = $_.UserPrincipalName
	$User = Get-ADUser -Filter {UserPrincipalName -eq "$UPN"}
	If($User.Count -eq 0) {
		Write-Host "Error: user ""$UPN"" does not exist"
	} Else {
		$Group = Get-ADGroup "$SourceGroup" -Properties Member
		If($Group.Count -eq 0) {
			Write-Host "Error: source group ""$SourceGroup"" does not exist"
		} Else {
			If($Group.Member -Contains $User.DistinguishedName) {
				Remove-ADGroupMember $Group -Member $User -Confirm:$False
				Get-ADGroup "$TargetGroup" | Add-ADGroupMember -Member "$User"
			} Else {
				Write-Host "Exiting: user ""$UPN"" is not a member of group ""$SourceGroup""