New versions of the Azure AD PowerShell modules available

A week ago, Microsoft released new versions of the Azure AD ( and Azure AD Preview modules ( As usual, no release notes were provided, so I did a quick comparison of the cmdlets available in the new releases, as well as any changed parameters. As the last time I did this exercise was 8 months ago, some of the changes listed below aren’t necessarily that new.

Most importantly, we now have cmdlets that allow us to work with Conditional Access policies and named locations:

  • Get-AzureADMSConditionalAccessPolicy – list all CA policies in the tenant
  • Get-AzureADMSNamedLocationPolicy – list all named locations
  • New-AzureADMSConditionalAccessPolicy – create a new CA policy
  • New-AzureADMSNamedLocationPolicy – create a new named location
  • Remove-AzureADMSConditionalAccessPolicy – remove CA policies
  • Remove-AzureADMSNamedLocationPolicy – remove named locations
  • Set-AzureADMSConditionalAccessPolicy – modify existing CA policies
  • Set-AzureADMSNamedLocationPolicy – modify named locations

Of course, given how complex a CA policy can be, you’d probably want to continue managing those via the UI. On the other hand, reporting on CA policies in the tenant is easier now, as we don’t have to call the Graph API like we did in our recent article on the subject. If you are feeling adventurous, you can certainly use the Graph API or the new PowerShell cmdlets to create CA policies, although my attempts always resulted in validation error for the Conditions variable, even when copy/pasting the examples from the cmdlet help. There’s probably a reason why the cmdlets are in the Preview module, after all ūüôā

Luckily, the named location cmdlets seem to work a bit better, so you can use them to report on or manage the corresponding objects. Those come in two flavors, a “country” named location, where you list specific set of countries, or “IP” named location, where you specify a set of IPs and CIDRs. Interestingly, the cmdlets seem to allow you to designate a “country” named range as trusted, but that looks like a parameter set issue and not something that has an actual effect. In any case, here’s how to play with them:

C:\> Get-AzureADMSNamedLocationPolicy | ? {$_.OdataType -eq "#microsoft.graph.countryNamedLocation"}

C:\> New-AzureADMSNamedLocationPolicy -OdataType "#microsoft.graph.countryNamedLocation" -DisplayName "Test2" -CountriesAndRegions @("IN","AL")

OdataType                         : #microsoft.graph.countryNamedLocation
Id                                : 1b4420a7-1df9-43ef-9a8a-462b205822ac
DisplayName                       : Test2
IpRanges                          :
IsTrusted                         :
CountriesAndRegions               : {IN, AL}
IncludeUnknownCountriesAndRegions : False

Other cmdlets worth mentioning are the Get/Set-AzureADMSAuthorizationPolicy, which allow us to exert more granular control over end-user consent settings, as detailed in this document. As part of the process, you can also modify the set of permissions, resources or application ids/publishers users can consent to, via the so-called Azure AD permission grant policies and the corresponding *-AzureADMSPermissionGrantPolicy set of cmdlets. Although not all relevant controls seem to be currently exposed in the Azure AD blade, I’d still recommend using the GUI-based approach to handle those. There’s also a¬†bunch of application management cmdlets, which aren’t that new so I’ll skip them.

When it comes to changed parameters, the CompanyName attribute has been exposed for the *-AzureADUser cmdlets, thus they now have the corresponding –CompanyName parameter for setting/removing its value. In addition, the Get-AzureADDirectoryRole now has a –Filter parameter. The Get-AzureADMSGroup cmdlet features a –Select parameter as part of the Azure AD Preview module updates, while *-AzureADMSGroup cmdlets bring support for sensitive labels via the –LabelId parameter. Lastly, the New-AzureADMSInvitation cmdlet has a –ResetRedempton switch, meaning we no longer need to delete/recreate invitations and guest users as troubleshooting steps, at least in some scenarios.

Posted in Azure AD, Office 365, PowerShell | Leave a comment

Multi-tenant scenarios for ExO PowerShell certificate-based authentication

Now that we finally have support for automating Exchange Online PowerShell tasks in a manner compatible with modern authentication, organizations can start reworking their script to take advantage of the new capabilities. This also brings good news for ISVs that develop solutions which rely on ExO Remote PowerShell, as with the ongoing effort to disable basic authentication across the board, it was hard to explain to customers why they had to add exceptions to their conditional access policies or whitelist certain IPs in order to run say a third-party reporting solution.

In this short article, we will cover how to enable multi-tenant support for Exchange Online Remote PowerShell while using the new certificate-based authentication method. The same steps will apply to using the less secure client secret method. You should already be familiar with the generic steps to enable the process, as detailed in the official documentation or this article.

Assuming you’ve already created the required Azure AD application, the first important step is to make sure the application is configured as multi-tenant one, which you can do by selecting the corresponding option under Supported account types in the Azure AD blade. Do make sure that the application also has at least one redirect URI of the “web” type (under Platform configuration, hit Add platform, Web).

The next step is to add the application to another tenant, for example by having the customer click on an URL like the below one:

where the highlighted part corresponds to the client id of the application. At this point, an administrator from the other tenant will have to provide consent, by (carefully reviewing the information first and then) hitting the Accept button:

With this, the application will be added to the other tenant, and you will now be able to obtain a token for it. However, as explained in the article over at the Quadrotech blog, in order to make PowerShell actually connect to the Exchange Online endpoint, you need some additional configuration. Thus, while you can actually obtain a token at this point, it will not contain the required wids claim, as shown below:

Because you can obtain the token, you can actually try to connect to ExO PowerShell, but you will be greeted with an error like the below:

Connect-ExchangeOnline -CertificateThumbprint "1168BA5FC1CB62220E00487E9C07276EF33FF7DC" -Organization -AppId "ae11ad05-9b02-4bb6-a906-78b7s3757f5f" -ShowBanner:$false

New-ExoPSSession : Connecting to remote server failed with the following error message : ÓÜįǪ̂«ę For more information, see the about_Remote_Troubleshooting Help topic.
At C:\Program Files\WindowsPowerShell\Modules\ExchangeOnlineManagement\0.4578.1\ExchangeOnlineManagementBeta.psm1:484 char:30
+ ... PSSession = New-ExoPSSession -ExchangeEnvironmentName $ExchangeEnviro ...
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ResourceUnavailable: (:) [New-ExoPSSession], PSRemotingTransportException
    + FullyQualifiedErrorId : System.Management.Automation.Remoting.PSRemotingDataStructureException,Microsoft.Exchange.Management.ExoPowershellSnapin.NewExoPSSession

The above might seem like a useless detail, but it serves to illustrate all the needed configuration steps as well as some basic troubleshooting.

Anyway, ignoring the above details and remembering that one of the requirements was to add an admin role to the service principal associated with our Azure AD app, we can navigate to the Roles and administrators page and add the required role. For example, we can add the Global admin role, although in a real-life situation you’d probably want to use something more restrictive:

Note that this needs to be performed in the customer tenant, and for each tenant your application will need access to!

Now, after a suitable role has been added, we can request an access token and validate that the wids claim is present:

At this point, we should be able to execute the Connect-ExchangeOnline cmdlet and connect just fine (make sure to provide the customer’s tenant default domain for the –Organization parameter value!):

And that’s pretty much all there is to it! Of course, you’d probably want to add some additional checks and error handling to the code. On customer’s side of this, it’s also a good idea to confirm that any changes made by the application are diligently audited, which you can do via the Search-AdminAuditLog cmdlet or the EAC:

Have fun!

Posted in Uncategorized | Leave a comment

Connecting to Exchange Online PowerShell via client secret

Microsoft just released a new version of the Exchange Online (V2) PowerShell module, which brings support for much awaited feature – seamless connectivity that satisfies MFA requirements thanks to using the certificate-based authentication flow. Now, one can argue that this isn’t “true” MFA and point to the inherit auditing issues when using this flow, but that’s true for all other apps leveraging it, and not something the Exchange team can be blamed for.

We did a thorough review/guide of the new method in our recent post over at the Quadrotech¬†blog. Here I’ll add few more details that were left out for brevity or simply border the “unsupported” frontier and should not be used unless you really want to get your hands dirty. We start by getting the obvious thing out of the way – yes, you can actually use a client secret as well, although it is considered less secure and Microsoft chose not to support it via the module. But nothing is stopping you from generating a client secret for your app, then using it to connect via the “manual” method outlined below. But more on that later.

First, what is this “manual” method I talk about? Well, if you are familiar with the way Exchange Online Remote PowerShell implements support for modern authentication (or have seen my previous articles on the subject), you are aware that the bulk of the client-side changes are introduced to handle the token request/renewal process. Once the token is obtained, it is “proxied” via the good old basic authentication WinRM endpoint, to a slightly different URI – one that has the “BasicAuthToOAuthConversion=true” value added. The backend of course has also received some updates in order to handle this, but those are out of our hands, and thus not as interesting.

The important thing is that once you understand the process, you can bypass the built-in cmdlets and create your own “connect to Exchange Online via modern authentication” function, which might suit your needs better. Same applies to the features released in the new version of the module. Once you have configured the Azure AD application, it’s permissions and the certificate to be used for authentication, you can use any number of methods to obtain a token, for example:

Add-Type -Path 'C:\Program Files\WindowsPowerShell\Modules\AzureAD\\Microsoft.IdentityModel.Clients.ActiveDirectory.dll'

$authContext45 = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList ""
$secret = Get-ChildItem cert://currentuser/my/"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
$CAC = [Microsoft.IdentityModel.Clients.ActiveDirectory.ClientAssertionCertificate]::new("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",$secret)
$authenticationResult = $authContext45.AcquireTokenAsync("",$CAC)

where I have pointed to a certificate stored in the Personal store for the current user and also provided the client_id of the application registered in Azure AD. If everything checks out and a valid token is obtained, you are ready to establish the remote PowerShell session. One small detail you need to know first – when passing the token, you need to specify the username in the following format: “OAuthUser@tenantGUID”. Here’s how it works:

$token = $authenticationResult.Result.AccessToken
$Authorization = "Bearer {0}" -f $Token
$Password = ConvertTo-SecureString -AsPlainText $Authorization -Force
$Ctoken = New-Object System.Management.Automation.PSCredential -ArgumentList "OAuthUser@923712ba-352a-4eda-bece-09d0684d0cfb",$Password

$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri -Credential $Ctoken -Authentication Basic -AllowRedirection -Verbose
Import-PSSession $Session

And with that, you should have an Exchange Online Remote PowerShell session established and all the cmdlets corresponding to the admin role granted to the application’s service principal available.

A variation of the above can be used to point to a certificate file, instead of using the certificate store provider. Assuming we have a cert.pfx file, protected with a password, the following example can be used:

$authContext45 = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList ""

$certFile = "C:\cert.pfx"
$certFilePassword = "P4$$w0rd"
$secret = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate -ArgumentList $certFile,$certFilePassword
$CAC = [Microsoft.IdentityModel.Clients.ActiveDirectory.ClientAssertionCertificate]::new("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx",$secret)

$authenticationResult = $authContext45.AcquireTokenAsync("", $CAC)

Or if you don’t want to get bothered by certificates and don’t care about the increased security they provide, you can simply add a client secret to the application you registered in Azure AD and use the client credentials flow instead:

$authContext4 = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList ""
$ccred4 = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential -ArgumentList "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "very_long_string"
$authenticationResult4 = $authContext4.AcquireTokenAsync("", $ccred4)

And that’s pretty much it. Once you have a valid token, use it to establish the remote PowerShell session via the sample above. Don’t forget to add some proper error handling and most importantly, logic to detect token expiry and renew/obtain new tokens as needed!

One additional detail you might need when troubleshooting connectivity issues – the token must contain the wid claim and within it, the admin role(s) to which the service principal object corresponding to your Azure AD application has been assigned. You already know how to get the token, as for decoding it use a site such as or just do it directly via PowerShell as detailed in this article. Here’s how a sample valid token would look like:

Posted in Exchange Online, Office 365, PowerShell | 2 Comments

Microsoft updates the *-RecoverableItems cmdlets to include results from the Purges folder

Two years back, Microsoft introduced a set of cmdlets that aim to relieve some of the administrative burden related to recovering deleted content. While Exchange has some robust capabilities that help reduce the chance of data being inadvertently purged out of the system, it’s not uncommon for end users to get confused on how the process of recovering deleted items work, and in turn make them knock on¬†help desk’s door. And of course there are situations where messages have been “purged” by the user or retention policy, so only admins can attempt the recovery.

The two cmdlets, namely Get-RecoverableItems and Restore-RecoverableItems, allowed admins to query the content of the Deleted Items and Deletions folders and restore any items as necessary. Without having to go to the user desk or involve him in a remote session, which is of course a big win for the support team. Unfortunately, the cmdlets didn’t cover the Purges folder, where items “purged” by users are held for the duration of the Single Item Recovery period or the duration of any hold applicable to said items. In turn, for such items we had to rely on the good old Search-Mailbox cmdlet or perform an eDiscovery/Content search, which made the process a bit more complicated.

Now, either because of the impending doom faced by the Search-Mailbox cmdlet and the EAC eDiscovery tools, or simply as the next step in the release cycle, the two *-RecoverableItems cmdlets have received support for the Purges folder. This brings both good and bad news. The good news being that the single item recovery process is now a lot faster/easier than before, all you need to do is run the Get-RecoverableItems cmdlet against the Purges folder and find the item(s) in question, then use the Restore-RecoverableItems cmdlet to bring them back to life. Here’s an example:

Get-RecoverableItems vasil -SourceFolder PurgedItems | select -First 1

Get-RecoverableItems vasil -SourceFolder PurgedItems | select -First 1 | Restore-RecoverableItems

Nice and easy, right? Well, now for the bad news. Since the Purges folder is not only used for items that are being preserved for the duration of the Single item recovery window, but also for items on hold (which you can think of are on “unlimited” single item recovery retention window), the amount of items you might find in the folder can be very high. Luckily, this will mostly affect mailboxes that do not have an Online archive enabled, as once such is configured the Recoverable Items 14 days move to archive retention tag will move such items to the corresponding folder in the Online archive’s RecoverableItems subtree, as the name suggest. As a side note, the *-RecoverableItems cmdlets do not support the archive mailbox.

But, if the mailbox is put on hold and doesn’t happen to have an archive enabled, you might end up with situations like the one shown on the screenshot below, where the Purges folder contains a whooping amount of 200k+ items:

Working with such number of items can pose some challenges, and even though the cmdlets do feature some filtering capabilities, those might prove inadequate in some scenarios. Given the use case the cmdlets are intended for, it makes sense that only messages that were originally located in one of the default or user-created folders are returned and only for the duration of the single item recovery window. For all other messages, either those preserved by a hold or the gazillion system-generated messages that end up in the Purges folder, it might be best to leave the recovery process to the eDiscovery tools.

As another side note, the cmdlets are more than capable of actually handling such number of items it seems, and they will happily return up to 10000 (yes, that’s 10k, not 1000 as suggested by the documentation) items when using the default value for the –ResultSize parameter. The question is how efficiently can we as humans work with such a large set of data, even when exported to CSV and opened in Excel.

In any case, the new capabilities introduced for the Get-RecoverableItems and the Restore-RecoverableItems cmdlets are welcome additions and should prove useful.

Posted in Exchange Online, Office 365, PowerShell | Leave a comment

Exchange Online V2 PowerShell module goes GA

I wont go into much details here, as we’ve covered the module extensively in our webinars, so I will just refer you to those for additional details:

Working with the new Exchange cmdlets held back in Feb

Working with the new Exchange cmdlets V2 from last month

The 1.0.1 “GA” version expands support for filtering and addresses some bugs, the important thing being that it’s now “officially supported” so you can start using it in production. You can get the latest version from the PSGallery here.

And no, it does not address the “run some script automatically” scenario, but there are news coming on that front shortly. As promised, we will have another webinar dedicated to this specific scenario, with lots and lots of additional details. Stay tuned ūüôā

Posted in Exchange Online, Office 365, PowerShell | Leave a comment