I’m going to preface this article by saying “This is not a guide to resolve a specific problem, but rather a discussion on how to work through a problem/issue which requires a script to resolve”.
Often when you first look at some of these problems they seem incredibly difficult and you can rapidly end up down a rabbit hole you didn’t intend to. (I was going to quote Yoda during the Dark Side cave scene in Empire Strikes Back, but decided against proving myself to be of a certain techie stereotype. Probably even knowing that the scene exists puts me in that category but what can you do? ๐ )
There’s a certain company in Sydney who I’ve been working with quite closely on and off for the past year or so who have had some significant wins with Office 365. The biggest to date is the implementation of a new Intranet, built on SharePoint Online, which provided targeted information to different parts of the business and consolidated multiple legacy Intranets. It was a great bit of development work by our Kloud Application Development team.
As I’m an infrastructure guy, however (and quietly jealous that I don’t know how to use Azure Stream Analytics to do funky demonstrations like the Event Hubs MeetUp in October) I’m not going to go into that particular solution.
What I will cover through is the slight issue we came across when starting the Office 365 adventure with the Intranet. There’s a quirky little behaviour which needs to be addressed if you don’t want your service desk answering calls about why “Outlook” doesn’t work.
If you assign an Enterprise (E) or Kiosk (K) license to a person, with all Office 365 services enabled (the default behaviour when you assign a license) and said person then opens up their brand new sparkly Intranet, they will see an Office 365 ribbon across the top of the page, you know the one? If you have an E license assigned, with all services enabled, it will looks a bit like this:
So what happens when I click on the Outlook link and my mailbox is on-premises? Well, you will see a sad message telling you that something went wrong while accessing your mailbox.
The “More details…” URL doesn’t really help the end user much either and so the service desk call is made. Two interesting things I’ve learnt about service desk calls:
- The IT department doesn’t like receiving them. Service desk call == Effort required to fix an incident/problem == IT revenue)
- The business doesn’t like making them. Think of IT as the equivalent of domestic plumbing: When it all works you don’t notice and get on with your day. When it stops working, it can get a little smelly, which tends to put you in a bad mood to start with. Then you have to wait for the guy to come out and figure out what’s wrong, and fix it. All the while using jargon that makes no sense at all and was undoubtedly created to hoodwink you.
But I digress.
Outlook in the Office 365 ribbon – How do we get rid of it? The answer is pretty simple, and you probably already see where I’m coming from. You need to disable the Exchange Online service. Joel Neff wrote a blog about this a while back on licensing with PowerShell and it’s pretty much what we did to ensure that Exchange Online was disabled when assigning licenses to staff.
This whole Outlook thing still presented me with a problem though. While the company had been assigning licenses with Exchange Online disabled, it needed to be re-enabled when the mailbox was moved from the on-premises Exchange environment to Exchange Online. And we had the following issues/constraints to deal with:
- We needed to move a minimum of 300 mailboxes per week and the IT engineer wasn’t (understandably) keen on having to click in multiple locations to change licenses and then initiate a move (or vice versa)
- Not all accounts had Exchange disabled, some were already enabled due to what I will call an “organic pilot”
- There were a few other license options which may, or may not, have been enabled, depending on the above mentioned pilot
- We were dealing with K1, E1 and E3 licenses
- SharePoint Online was live and hosting the Intranet, and I didn’t particularly want to be responsible for a disruption to a highly visible service.
So I bashed out a PowerShell script! The thing with PowerShell scripts is they can become very complicated, very quickly. Further, if you end up writing a single block of script, it’s not particularly re-usable later on. This is why, when I write scripts, I take a letter from my Developer friend’s book, and take the approach of breaking the problem down into discrete (re-usable) processes. Think of it as a High Level Design for the script.
I started with this:
Luckily I’ve already got a function for connecting to Exchange Online and O365 from my PowerShell profile, so they’re covered. The move mailbox bit is pretty straight forward too, the New-MoveRequest Cmdlet is all I need, something like:
[code language=”PowerShell”]
New-MoveRequest `
-Identity $upn
-RemoteHostName $onPremEWS `
-SuspendWhenReadyToComplete `
-BatchName $batchName `
-Remote `
-RemoteCredential $ExchCred `
-TargetDeliveryDomain $targetDeliveryDomain `
-BadItemLimit $badItemCount `
[/code]
There’s no particular reason or benefit to make this Cmdlet into a function, so it’ll get thrown in the main part of the script somewhere or other. The one comment I would make though is: make sure you define $batchName at the top of your script, before running through each user. Otherwise you’ll potentially have a new batch name for each mailbox move request which, take it from me, is utterly useless.
I’m now left with two processes to figure out: 1) Getting the mailbox list; 2) Assign the Exchange Online (EOL) service
Getting the mailbox list
To get the mailbox list to move I decided that the data being imported needed to be in CSV format. Largely because it’s easier for BAs and PMs to collect and massage business data in Excel, which is easily exportable as a CSV file, and if there’s a someone else willing to do some of the leg work I generally feel inclined to acquiesce!
As per our (and Microsoft’s) recommendation, the company had updated their on-premises AD UPNs to match their email addresses so all that was really needed in the CSV file was the email address of the mailboxes to be moved. The mail address is unique, so no chance of getting the wrong mailbox, and it matches the UPN, also unique and used to find Azure AD accounts.
There are a few approaches to consuming this information, you could:
- Use the Import-CSV Cmdlet
- Create a parameter for the script (good for one or two SMTP addresses, but not great for a bulk import
- Build the script to, once loaded, read the information through the pipeline
- Use a combination of the above
- Some other way I haven’t thought of.
The easiest by far appears to be to import the addresses in through use of Import-CSV. So if we just use this Cmdlet at the top of the script pointing it to a predefined location, we’re covered. Some of the other ways may be more appropriate for your situation, but for me, I like easy.
Moving on then.
Assign the EOL Service
Assigning the EOL license is difficult, but not impossible, with Joel’s Blog as a guide, you can put together a function which outputs the particular license options you want and then apply it to the user account.
In my particular case though some accounts had more than just the Exchange Online service disabled, and were a mix of E1, E3 and K1 licenses. I needed to build a license options object which was unique to the individual mailbox, enabling Exchange and leaving everything else untouched. Ahh, the real world, sooo much more problematic than what I was told when I left school…
To sort this conundrum out required some thinking and I came up with the following. Breaking out the original process “Assign EOL License” I ended up with:
If after interrogating the Azure AD user object licence, we find that Exchange is already enabled. How do we figure out if it’s disabled? We interrogate the $MSOLUser.Licenses.ServiceStatus. This took me forever to figure out how to do. The problem is that ServiceStatus is an array of objects, it formats beautifully on screen but getting at what I wanted was a different matter.
If you ever need to do this, I’ll save you the time. I ended up with the following:
[code language=”PowerShell” gutter=”false”]
$disabledPlans = $msolUser.Licenses.ServiceStatus | where {(($_. provisioningStatus -eq "disabled") -and ($_.ServicePlan.ServiceName -like "*EXCHANGE*"))}
[/code]
What I’m doing here is passing each object in the Licenses.ServiceStatus attribute to a Where-Object cmdlet which does a logic test on the object. If the logic test is true then $disabled will equal that object. If no object matches the logic test, then $disabled will be $null.
To put the logic test together, we’re looking for a disabled service ($_.provisioningStatus -eq “disabled”) but specifically the Exchange service. Remembering that each different license SKU will have a different ServiceName (e.g. EXCHANGE_S_DESKLESS for K1 licenses and EXCHANGE_S_STANDARD for E1 licenses) the approach I took to catch all different Exchange ServiceNames (and allow for future ones) was to use the logic test ($_.ServicePlan.ServiceName -like “*EXCHANGE*”).
If $disabled is equal to $null then we’ll make the Assign EOL License function return the $null value. At the next process, building the license object, if $null is returned, we don’t need a new license object, because Exchange is already enabled.
Otherwise we have more work to do to build a new license, and move onto the “Find the existing disabled services” process. As the output will be used to form the new license object it needs to not include the Exchange service. We just need to tweak the logic test above to get what we want. Something like ($_.ServicePlan.ServiceName -notlike “*EXCHANGE*”).
And we end up with:
[code language=”PowerShell” gutter=”false”]
$disabledPlans = ($msolUser.Licenses.ServiceStatus | where {(($_.provisioningStatus -eq "disabled") -and ($_.ServicePlan.ServiceName <strong>-notlike</strong> "*EXCHANGE*"))})
[/code]
Going back to Joel’s blog there’s now enough information to create a specific license object and assign it to the mailbox during the mailbox move process!
Now the basics are in place, I’d think about adding a few extra useful functions like error handling and logging ๐
Can you please share your script if it is not against your ethics?