Using variables with Invoke-Command in Remote PowerShell sessions in Exchange Online

As many of you probably know, Remote PowerShell sessions connected to Exchange Online run in the “no language” mode. This can easily be verified via:

(Get-PSSession | ? {($_.configurationname -eq "microsoft.exchange") -and ($_.Runspace.ConnectionInfo.ConnectionUri.Host -eq "outlook.office365.com")}).Runspace.SessionStateProxy.LanguageMode

Same applies to Skype for Business Online sessions, and in general is a pain in the royal behinds, as it prevents you from using variables, operators and everything else that can make your life easier. There are certain workarounds available, but in general they are still subject to limitations and can make the code very complex.

Another important limitation with Remote PowerShell sessions is the sessions stability. Everyone that has used some long running script in Exchange Online has run into this, and it’s not pretty. Some code optimizations are mandatory for such cases, and Microsoft has posted some guidance around them on the Exchange blog. Even those recommendations suffer from the aforementioned “no language mode” problem!

To better illustrate the issue, let’s take a look at the following example: we want to get a list of details for all the mailboxes in our company created after specific date. We can certainly use server-side filters to achieve this, which greatly increases efficiency:

Get-Mailbox -Filter {WhenMailboxCreated -gt "01 Jan 2016"}

With the recommendations from the blog article in mind however, we might want to use the Invoke-Command approach and return only the data that we need instead of the full objects. Using the $session variable to designate a currently opened ExO session, a working solution will look something like this:

$session = Get-PSSession -InstanceId (Get-OrganizationConfig).RunspaceId.Guid

Invoke-Command -Session $session -ScriptBlock { Get-Mailbox -Filter 'WhenMailboxCreated -gt "01 Jan 2016"' | Select-Object -Property Displayname, Identity, PrimarySMTPAddress, RecipientTypeDetails }

So far so good, but once we start looking into ways to make this a function, script or simply use some variable to designate the date, things start to get messy. The good news is it’s still doable though! Here’s an example that will work with PowerShell v3 and later, by means of exploiting the Using scope modifier:

$date = (Get-Date).AddDays(-250)

Invoke-Command -Session $session -ScriptBlock {Get-Mailbox -Filter ([scriptblock]::create("WhenMailboxCreated -gt '$using:date'")) | Select-Object -Property Displayname, Identity, PrimarySMTPAddress, RecipientTypeDetails }

The important bits: we need to use a Script block in order to prepare the filter syntax and we need to invoke the Using scope modifier to make sure the variable is evaluated locally. Remember, remote sessions running in “no language” mode do not allow you to set/reference remote variables!

For people still using older PowerShell versions (really?!), here’s a working example for V2:

Invoke-Command -Session $session -ScriptBlock {param($date) Get-Mailbox -Filter ([scriptblock]::create("WhenMailboxCreated -gt '$date'")) | Select-Object -Property Displayname, Identity, PrimarySMTPAddress, RecipientTypeDetails } -ArgumentList (Get-Date).AddDays(-250)

 

We can also use an example that does not require the need for using the Filter parameter and thus the scriptblock syntax inside the ScriptBlock parameter. For instance, if we want to get all mailboxes of the User and Shared type:

Invoke-Command -Session $session -ScriptBlock { Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails UserMailbox,SharedMailbox | Select-Object -Property Displayname,Identity,PrimarySMTPAddress,RecipientTypeDetails }

Works great, but if we want to make it dynamic, by passing the mailbox type as parameter or a variable, we need to use something like this instead:

$included = @("UserMailbox","SharedMailbox")

Invoke-Command -Session $session -ScriptBlock { Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails $Using:included | Select-Object -Property Displayname,Identity,PrimarySMTPAddress,RecipientTypeDetails }

Or in the old V2 format:

Invoke-Command -Session $session -ScriptBlock {param($log) Get-Mailbox -RecipientTypeDetails $log} -ArgumentList UserMailbox,TeamMailbox

So there you have it, a way to work around some of the restrictions of the “no language mode” for remote PowerShell sessions. It surely helped me simplify some of my scripts, by increasing their efficiency at the same time, so hopefully it will be useful to others as well!

This entry was posted in Exchange Online, Office 365, PowerShell. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *