Category Archives: Azure

Switch usage model in Azure Multi-Factor Authentication Server

Azure Multi-Factor Authentication is a really great service that helps you secure both cloud apps and on premise apps with easy means. Setting it up on premise requires you to create a multi-factor authentication provider in the Azure portal.

The first thing you need to choose creating a provider is the usage model (Per user/Per authentication) and as seen in the screenshot below, you cannot change the usage model after creating the provider.

So what to do if having deployed the MFA server with a per user usage model and later the conditions are changing and the per auth usage model would be a better fit?
Since it is not possible to change the usage model of an existing provider as it is right now, you have to create a new one and reactivate your existing server with activation credentials from the new provider.

This is a pretty simple task to do still keeping all users and settings that have been done on the server, but unfortunately it comes with a pretty big caveat if you’ve enrolled a lot of users with the MFA Mobile App. That means your users will have to re-enroll the mobile app in the user portal after you have done the usage model change. As a workaround to avoid interruption doing the change, you can of course change the mobile app users to Text or Phone verification instead.

1. Identify users that have the mobile app activated and inform them about the change.
2. Make a backup copy of the data folder in the Azure MFA installation path. (in most cases, C:\Program Files\Multi-Factor Authentication Server\Data)
3. Generate activation credentials for your new auth provider with the target usage model. (You have 10 minutes before you need to generate a new set of credentials)
4. If you have the Azure MFA Server UI running, exit that and then rename the licenseKey file in the installation folder.
5. Starting the MFA Server UI again, you will now get the first run wizard where you can activate the server again. Since you won’t make any configuration changes, you can check the “Skip the Authentication Configuration Wizard” and just activate the server instead.
6. The server has now been activated against your new provider and all settings have been preserved. Do however make sure to verify all services that depend on the MFA server after the change has been done.
Also remember that the Mobile App-enabled users will get the following error when authenticating until they have re-enrolled their account with the app or changed the verification model.

As you’ve seen in the post, it is not very hard to switch the usage model, even though it can be a bit painful if you have a lot of users utilizing the mobile app. Let me know if you have questions!



Get rid of your Office 365 Scheduled tasks with Azure Automation!

Implementing Office 365 in a production environment you most often end up with quite a few scheduled scripts for licensing, shared mailbox maintenance and other similiar tasks. When moving more and more services to the cloud, moving from infrastructure services to platform services is the way of nature.

One service that can help you to get more efficient when it comes to scheduling scripts is Azure Automation. Instead of having a server where you schedule your scripts, you simply schedule them within the automation service that takes care of the rest. As of now, Azure Automation does not support use of the Azure Active Directory Powershell module, which means we cannot use the service for our licensing scripts. Make sure to vote for the suggestion to fix that here.

Exchange Online, Lync Online and SharePoint Online do however work well, so that’s what my example is going to be about. To make it easy for me I’m using a script from one of my older posts about Dynamically adding mailbox permissions through groups, with some adjustments to fit Azure Automation.

1. First of all, if you don’t already have one, you need an Azure Automation account. I’m choosing to create mine in the West Europe region. By default, it will use a free automation plan that gives you 500 minutes of job runtime per month.
2. Now create an asset/setting that will store your Exchange Online admin credentials in a secure way. We will call the asset ‘EXOCreds’ so we easliy can pick them up later in the script.2015-04-05_17-03-15
3. Create a runbook in the automation account you created earlier, in my case I called it ‘Sync-EXOSharedMailboxPermissions’.
4. Now it’s time to author the runbook, in my example I’ve just changed some minor things with parameters and credentials. If you’re a PowerShell WF Pro, there might be a lot of things you can do to improve the script.
Below you’ll find the code I’ve used:


workflow Sync-EXOSharedMailboxPermissions {
    The script will automatically assign mailbox and recipient permissions on shared mailboxes based on groups.
    Modified 2015-04-05 to support Azure automation and PowerShell workflows
    File Name: SharedMailboxViaGroups.ps1
    Author   : Johan Dahlbom, johan[at]
    Blog     :
    The script are provided “AS IS” with no guarantees, no warranties, and they confer no rights.
    Requires PowerShell Version 3.0!
    #Credentials to connect to Exchange Online
    $Credentials = Get-AutomationPSCredential -Name 'EXOCreds'
    #Prefix to search for
    $Prefix = 'SM-'
    function Connect-ExchangeOnline {
    param (
        #Clean up existing PowerShell Sessions
        Get-PSSession | Remove-PSSession
        #Connect to Exchange Online
        $Session = New-PSSession –ConfigurationName Microsoft.Exchange -ConnectionUri -Credential $Creds -Authentication Basic -AllowRedirection
        $Commands = @("Add-MailboxPermission","Add-RecipientPermission","Remove-RecipientPermission","Remove-MailboxPermission","Get-MailboxPermission","Get-User","Get-DistributionGroupMember","Get-DistributionGroup","Get-Mailbox")
        Import-PSSession -Session $Session -Prefix "Cloud" -DisableNameChecking:$true -AllowClobber:$true -CommandName $Commands | Out-Null
    Connect-ExchangeOnline -Creds $Credentials
    inlineScript {
        function Add-JDMailboxPermission {
            try {
                Add-CloudMailboxPermission -Identity $SharedMailboxName -User $Identity -AccessRights FullAccess -ErrorAction stop | Out-Null
                Add-CloudRecipientPermission -Identity $SharedMailboxName -Trustee $Identity -AccessRights SendAs -Confirm:$False -ErrorAction stop | Out-Null
                Write-Output "INFO: Successfully added $Identity to $SharedMailboxName"
            } catch {
                Write-Warning "Cannot add $Identity to $SharedMailboxName`r`n$_"
        function Remove-JDMailboxPermission {
            try {
                Remove-CloudMailboxPermission -Identity $SharedMailboxName -User $Identity -AccessRights FullAccess -Confirm:$False -ErrorAction stop -WarningAction ignore | Out-Null
                Remove-CloudRecipientPermission -Identity $SharedMailboxName -Trustee $Identity -AccessRights SendAs -Confirm:$False -ErrorAction stop -WarningAction ignore  | Out-Null
                Write-Output "INFO: Successfully removed $Identity from $SharedMailboxName"
            } catch {
                Write-Warning "Cannot remove $Identity from $SharedMailboxName`r`n$_"
        function Sync-EXOResourceGroup {
                [string]$Prefix = 'SM-'
            #Get All groups to process mailboxes for
            $MasterGroups = Get-CloudDistributionGroup -ResultSize Unlimited -Identity "$Prefix*"
            foreach ($Group in $MasterGroups) {
                #Remove prefix to get the mailbox name
                $MbxName = $Group.Name.Replace("$Prefix",'')
                $SharedMailboxName =  (Get-CloudMailbox -Identity $MbxName -ErrorAction ignore -WarningAction ignore).WindowsLiveID
                if ($SharedMailboxName) {
                    Write-Verbose -Message "Processing group $($Group.Name) and mailbox $SharedMailboxName"
                    #Get all users with explicit permissions on the mailbox
                    $SharedMailboxDelegates = Get-CloudMailboxPermission -Identity $SharedMailboxName -ErrorAction Stop -ResultSize Unlimited | Where-Object {$_.IsInherited -eq $false -and $_.User -ne "NT AUTHORITY\SELF" -and $_.User -notmatch 'S-\d-\d-\d+-\d+-\d+-\d+-\w+' -and $_.User -notlike "$Prefix*"} |  Select-Object @{Name="User";Expression={(Get-CloudUser -identity $_.User).WindowsLiveID }}
                    #Get all group members
                    $SharedMailboxMembers = Get-CloudDistributionGroupMember -Identity $Group.Identity -ResultSize Unlimited
                    #Remove users if group is empty
                    if (-not($SharedMailboxMembers) -and $SharedMailboxDelegates) {
                        Write-Warning "The group $Group is empty, will remove explicit permissions from $SharedMailboxName"
                        foreach ($user in $SharedMailboxDelegates.User) {
                            Remove-JDMailboxPermission -Identity $user -SharedMailboxName $SharedMailboxName
                        #Add users if no permissions are present
                    } elseif (-not($SharedMailboxDelegates)) {
                        foreach ($user in $SharedMailboxMembers.WindowsLiveID) {
                            Add-JDMailboxPermission -Identity $user -SharedMailboxName $SharedMailboxName
                        #Process removals and adds
                    } else {
                        #Compare the group with the users that have actual access
                        $Users = Compare-Object -ReferenceObject $SharedMailboxDelegates.User -DifferenceObject $SharedMailboxMembers.WindowsLiveID 

                        #Add users that are members of the group but do not have access to the shared mailbox
                        foreach ($user in ($users | Where-Object {$_.SideIndicator -eq "=>"})) {
                            Add-JDMailboxPermission -Identity $user.InputObject -SharedMailboxName $SharedMailboxName
                        #Remove users that have access to the shared mailbox but are not members of the group
                        foreach ($user in ($users | Where-Object {$_.SideIndicator -eq "<="})) {
                            Remove-JDMailboxPermission -Identity $user.InputObject -SharedMailboxName $SharedMailboxName
                } else {
                    Write-Warning "Could not find the mailbox $MbxName"
        #Start Processing groups and mailboxes
        Sync-EXOResourceGroup -Prefix $Using:Prefix -Verbose

5. Test the runbook by clicking the test button. You will be asked to save the runbook before you test. Hopefully your output will be as nice looking as mine. 🙂
6. If all went good, you’re now ready to publish and schedule the runbook. I’m choosing to schedule mine to run every three hours. Depending on your script runtime, you might want to change this due to cost or other factors. (Remember your free 500 minutes!)

My runbook is now published and scheduled, now it’s just to wait for the magic to happen. Hope this gives you an idea what Azure Automation can do for you!

Additional resources to get started can be found below:
Get Started with Azure Automation
Automating the Cloud with Azure Automation (MVA)

Until next time! 🙂


Real world example on Network Security Groups in Azure

I have got many follow up questions regarding my post series on building your SSO infrastructure in Azure. One of the most common questions asked, have the one regarding how to configure the internal firewalls (Network Security Groups) between the perimeter subnet and the internal subnet in the Azure Vnet.

To make it as simple as possible, I am reusing the Vnet configuration from the first post in the ADFS series, as below.
2014-11-22 13-42-09
The example below assumes that your WAP servers is not joined to the domain. Please note that after you attach an NSG to a subnet, you will have to create specific rules for each endpoint that you have created (example RDP, WinRM etc.).

See the high level sketch below with the subnets including firewall rules.
To make the configuration as easy as possible, I’m using the NSG on a subnet level.

Note: As of today, you need to create and configure your Network Security Groups using Azure PowerShell. Prior doing any of the configuration below, you do need to connect to your Azure subscription with PowerShell.

1. Create a new NSG with the command below. They are created on a location basis, which in my case will be North Europe.

New-AzureNetworkSecurityGroup -Name "North Europe Perimeter" -Location "North Europe"

After the NSG has been created, a good way to check out the rule set in detail is by running the following command and redirecting the output to GridView.

(Get-AzureNetworkSecurityGroup -Name "North Europe Perimeter" -Detailed).Rules |
    Select-Object * | Out-GridView


2. Attach the NSG to the subnet with the below command. Note that all inbound endpoints will stop working if you haven’t created a proper rule set at this point.

Set-AzureNetworkSecurityGroupToSubnet -Name "North Europe Perimeter" `
                                      -VirtualNetworkName "365lab-Azure" `
                                      -SubnetName "Azure-Perimeter"

3. Now it is time to configure the rule set. To make it a bit easier to get an overview of the rules, I am using a CSV file as input. Remember that the priority of the rules are very important. You can download an example of the csv file here.

#Get the created NSG
$NSG = Get-AzureNetworkSecurityGroup -Name "North Europe Perimeter"
#Import the csv with the rules
$Rules = Import-Csv .\NSG-Ruleset.csv 

foreach ($Rule in $Rules) {
    try {
        Set-AzureNetworkSecurityRule -Name $Rule.Name `
                                     -Type $Rule.Type `
                                     -Priority $Rule.Priority `
                                     -Action $Rule.Action `
                                     -SourceAddressPrefix $Rule.SourceAddressPrefix `
                                     -SourcePortRange $Rule.SourcePortRange `
                                     -DestinationAddressPrefix $Rule.DestinationAddressPrefix `
                                     -DestinationPortRange $Rule.DestinationPortRange `
                                     -Protocol $Rule.Protocol `
                                     -NetworkSecurityGroup $NSG -ErrorAction Stop | Out-Null
        Write-Output "Created rule $($Rule.Name) successfully"
    } catch {
        Write-Warning "Error creating rule $($Rule.Name)`r`n$_"

After a little while, you should have simliar output as below in your PowerShell console.

You have now configured a DMZ/Perimeter network subnet in Azure to support your ADFS/WAP setup. The above method can of course be used creating all kinds of rules in NSG’s. Let me know if you have any questions!


Azure AD Sync – Configure attribute based filtering using PowerShell

Most often when synchronizing your directories to AAD, you don’t want all your users to get synchronized. One of the most common methods of filtering out who should get synced and not is by using attributes.
Since AADSync arrived the process of doing this has changed a bit. In this post I will go through how to configure the filtering with PowerShell. Read here about the other methods for filtering objects in AADSync.

In this particular example I will filter out users by the following criteria:

  • UserPrincipalName DOES NOT END with

I have created a PowerShell function to make the creation a bit easier to configure the filtering. If not specifying a domain with the -DomainName parameter, it will create the rule for all your domains connected to AADSync (if you have more than one). To create a filtering configuration as in my example, just run the cmdlet as below.

New-JDAADSyncFilteringRule -Name "In from AD - User NoSync Filter" `
                           -Attribute "userPrincipalName" `
                           -Value "" `
                           -Operator NOTENDSWITH `
                           -Precedence 50

Please note that the filter is not quite as forgiving as most things usually are nowdays when it comes to case-sensitivity.


function New-JDAADSyncFilteringRule {
        The function will create AADSync filtering rules based on attributes and conditions
        New-JDAADSyncFilteringRule "Inbound from AD" -Attribute "userPrincipalName" -Value "" -Operator ENDSWITH -Precedence 50
        File Name: New-JDAADSyncFilteringRule
        Author   : Johan Dahlbom, johan[at]
        Blog     :
        The script are provided “AS IS” with no guarantees, no warranties, and they confer no rights.
        Requires PowerShell Version 3.0!
        [ValidateScript({Get-ADSyncConnector -Name $_})]

    #Import ADSync Module
    Import-Module ADSync
    #Check if connector/domain name has been provided
    if ($DomainName) {
        $ADConnectors = Get-ADSyncConnector -Name $DomainName
    } else {
        $ADConnectors = Get-ADSyncConnector | Where-Object {$_.Type -eq "AD"}

    foreach ($ADConnector in $ADConnectors) {
        try {
            #Create the Scope Filter Object
            $Scopefilter = New-Object Microsoft.IdentityManagement.PowerShell.ObjectModel.ScopeCondition
            $Scopefilter.Attribute = $Attribute
            $Scopefilter.ComparisonValue = $Value
            $Scopefilter.ComparisonOperator =  $Operator
            #Create the Attribute Flow
            $AttrFlowMappings = New-Object Microsoft.IdentityManagement.PowerShell.ObjectModel.AttributeFlowMapping
            $AttrFlowMappings.Source = "True"
            $AttrFlowMappings.Destination = "cloudFiltered"
            $AttrFlowMappings.FlowType = "constant"
            $AttrFlowMappings.ExecuteOnce = $False
            $AttrFlowMappings.ValueMergeType = "Update"
            #Add the Scope Filter to a Scope Group
            $ScopeFilterGroup = New-Object Microsoft.IdentityManagement.PowerShell.ObjectModel.ScopeConditionGroup

            $SyncRuleHt = @{
                Connector = $ADConnector.Identifier.Guid
                Name =  $Name
                SourceObjectType = "user"
                TargetObjectType = "person"
                Direction = "inbound"
                AttributeFlowMappings = $AttrFlowMappings
                LinkType = "Join"
                Precedence = $Precedence
                ScopeFilter = $ScopeFilterGroup
            Add-ADSyncRule @SyncRuleHt | Out-Null
            Write-Output "Added the Syncrule $Name ($Precedence) for the attribute $Attribute with the condition $Operator $Value"
        } catch {
            Write-Warning "$_"

Using the SyncRulesEditor.exe (or the cmdlet Get-ADSyncRule) in the folder where you have installed AADSync (most commonly C:\Program Files\Microsoft Azure AD Sync\UIShell\) and verify that your settings successfully has been saved/configured.2015-02-09_00-21-54

As always, if you have suggestions for improvements of changes in the scripts or posts, let us know! 🙂


Quick Tip: Azure AD Premium features not showing up in the portal

Thought I should share a “problem” that quite a few people have asked me about regarding Azure AD Premium.

You sign up up for a trial, or assign a SKU (for example EMS) where Active Directory Premium is included to your tenant. For some reason, no AADP features are showing up in the configure pane when you access your Azure AD as Admin.


As of now, in order to be able to manage Azure AD Premium, you need to have licenses assigned for each Admin.
After assigning Azure AD Premium Licenses to the Admin account, you will now see all the AADP-features, including Sign in branding and password reset under the configure pane, just as below.

Hope this helps you if running in to this rather simple issue! 🙂


Office 365: Deploying your SSO Identity Infrastructure in Microsoft Azure (Using Azure AD Connect) – Part 3

This is the last part of 3 in the series where we go through how to create a highly available SSO infrastructure for Office 365 in Microsoft Azure. In this part we will finish off the configuration and put all the pieces together. To make it a bit more interesting, I’ve also decided to try out the Preview of Azure AD Connect to configure the AADSync, ADFS and WAP servers.
Part 1 of the series can be found here.
Part 2 of the series can be found here.

If you haven’t already promoted your DC in Azure, the following example snippet will promote the domain controller to your existing domain using the the account you’re logged on with. Note that the Database/Log/Sysvol paths has been changed to the additional disk that was added to the DC. We do this since we need to use a separate volume that is not using host caching for the AD databases in Azure.The server will also automatically reboot after the promotion has been done.

Import-Module ADDSDeployment
$DCPromotionSettings = @{
    NoGlobalCatalog = $false
    CreateDnsDelegation = $false
    CriticalReplicationOnly = $false
    DatabasePath = "F:\Windows\NTDS"
    LogPath = "F:\Windows\NTDS"
    SysvolPath = "F:\Windows\SYSVOL"
    DomainName = "365lab.internal"
    InstallDns = $true
    NoRebootOnCompletion = $false
    SiteName = "Azure-IAAS-Dublin"
    Force = $true
    SafeModeAdministratorPassword = (ConvertTo-SecureString -String 'YourStrongDSRMPassword!' -AsPlainText -Force)
Install-ADDSDomainController @DCPromotionSettings

Instead of using pure PowerShell to configure the servers, I’ve chosen to use the new Azure AD Connect Preview, a one stop shopping-wizard for setup and configuring AADSync, ADFS, WAP against Azure AD. Sounds very promising right?

1. Download and install(AzureADConnect.msi) the tool from here. In my case I am running the wizard on the AZURE-AADSYNC1 server.
2. Go through the Prerequisite and Azure tenant wizard as below. Your Azure AD Credentials should of course be a service Account with Global administrator permissions.
3. Since we want to deploy ADFS and WAP during the installation, we click customize to be able to do that.
4. Single sign on it is! In this example we’ll use the federation service name of
5. Set up connections to your AD forest(s). Note that the credentials used here should be a proper configured service account. Check out this script for a good way to configure delegation on the service account.
6. In my case I’ll have both an Exchange Hybrid deployment and Password Write back through AADP enabled.
7. Since I have only have one forest/domain, I’m just using the default settings for the next two steps.
8. Import the .pfx file for your service. In my case I have a certificate with the CN that will be imported to all ADFS and WAP machines. If you want to set up this in a lab environment, you can use (gives you 1 year free single name certificates trusted by most browsers).
9. Now point out your ADFS and WAP servers, in my case I have two of each (as deployed in the last post). Note that you will not be able to add the servers to the wizard unless they have PS Remoting enabled. This can be enabled by running the PowerShell command ‘Enable-PSRemoting -Force’ on each machine (or put it in the deployment script :))
The server with only lower case letters will be the primary ADFS server in the farm.
It gives me a warning regarding the WAP servers since I’ve pre-deployed the WAP role.
10. Now specificy an account with Local Admin credentials on the primary ADFS server, in order to create a trust between the WAP servers and the federation servers.
11. Choose which service Account that should be used for the ADFS farm. I do recommend using Group Managed Service accounts if possible (requires minimum 2012 DC’s). In the GMSA case, the wizard will actually create a KDS Root key in your domain if you haven’t one since before. Note that this is also done with WinRM through the Domain Controller, so make sure you have that enabled there as well.
12. Choose the domain you want to use for the Federated setup. As it seems right now with this preview, you can only create federation for one domain.
13. Review your configuration. If don’t want to start off by synchronize your entire directory, uncheck “Start the synchronization..” and look in to the following site on how to filter your synchronization scope. Fire off the installation by clicking Install.
14. Installation complete! PUH! If you run in to any errors during the installation, or cancel the installation, you’ll be able to continue from where you left.
As seen above, you’ll also have the option of verifying your ADFS service DNS records.
In my case, I’ve configured the service name to point to the Azure Internal Load balancer IP ( internally, and to the WAP Cloud service name externally (, as seen below:
If you don’t want to rely on your VPN Connection for the internal STS, you could publish the internal ADFS farm through the external cloud service name, and create access rules to only allow your external public IP’s.
And my DNS records turned out to be OK according to the wizard! 🙂

In a very simple to understand wizard we’ve done a normally quite complex task as easy as it can possibly be. The end result in this case is an highly available SSO Identity infrastructure with a little help of Azure IAAS. One thing to be aware of though (might be changed later since this is a preview):

  • Since the AAD Connect Wizard (Preview) only supports one domain, it will convert the the domain to federated without the -SupportMultipleDomain. This means you’ll have to convert the first domain to standard and then back again using the -SupportMultipleDomain switch if federating with more than vanity domain. Hopefully this is something that will change when it goes RTM.
    We have now finished configuring with our highly available SSO Identity infrastructure for our Office 365/Azure Active Directory. Not to hard with the help of PowerShell and the new Azure AD Connect Wizard. I will follow this series up with some additional topics with more detailed information regarding how to create firewall rules between our subnets in Azure, and more.

    If you have any questions or suggestions, let me know!


    Office 365: Deploying your SSO Identity Infrastructure in Microsoft Azure – Part 2

    This is part 2 of 3 in a series where we go through how to create a highly available SSO infrastructure for Office 365 in Microsoft Azure. In this part we will deploy all infrastructure components such as virtual machines and load balancers, all with help from PowerShell!
    Part 1 of the series can be found here.
    Part 3 of the series can be found here.

    As mentioned, in order to automate the process of creating the SSO-infrastructure in Azure, I’ve created a PowerShell script/Hydration Kit that does that for you. Based on input from a CSV file, the Machines will not only be provisioned, they will also be configured based on their specific role as follows:

    All Machines will

    • Join the domain specified in the $DomainName parameter. If you for example would like to exclude the WAP-servers from getting AD-joined, that’s something for a later version of the script.
    • Get the Telnet-Client Installed (Important 🙂 ).
    • Get an Azure static ip assignment/subnet and InstanceSize based on the input from the CSV file.
    • Be placed in the correct cloud service, availability set (if not set to ‘None’) and subnet as specified in the CSV file.
    • Get the Operating System specified in the $ImageFamily parameter installed.

    Domain Controller

    • One 60gb extra DataDisk with HostCaching will be added to the machine (will be initialized and formatted)
    • The role AD-Domain-Services will be installed. Note that the Domain Controller will not be promoted automatically by the script.

    ADFS Server

    • An Azure Internal Load Balancer will be added with the IP Address specified in the CSV file.
    • Endpoints for HTTPS will be added to the Load Balancer pointing at the servers.
    • The role ADFS-Federation will be installed.

    2014-11-26 14-47-53

    Web Application Proxy

    • An external load balanced HTTPS endpoint will be added.
    • The role Web-Application-Proxy will be installed.

    2014-11-26 14-39-03
    2014-11-26 14-44-26
    2014-11-26 14-38-45


    In order for the script to work properly, you need to have the base infrastructure (AffinityGroups, Virtual Networks/Subnets/DNS) in place prior to running the script. Since the script is automatically joining the VM’s to the domain specified, you need to have connectivity to your local domain as well.
    The Default WinRM Endpoint is used to configure the machines, which means you might get issues if your firewall is blocking high ports, such as in the screenshot below.
    2014-11-26 14-05-59

    The Azure PowerShell module do of course need to be installed on the machine you are running the script.

    1. First, download the script and the csv sample file from here.
    2. Edit the csv file according to your needs, make sure that you put the related servers (WAP, ADFS) in the same CloudService, Subnet and Availabilityset etc. For the ADFS servers, you also need to add the Internal Load Balancer IP in the InternalLB column.
    2014-11-26 15-08-14
    3. Run the script. Remember to change the parameters to match your Azure tenant configuration.If you haven’t already added your Account to the session, you will be prompted to do so. You will find screenshots of the steps below. If you lose your Azure session (internet problems etc.) while running, the script can safely be restarted, and will pickup where it stopped after some verification steps.

    .\CreateO365AzureEnvironment.ps1 -Verbose -InputFile .\AzureMachines.csv

    a. Prompted for domain join credentials
    2014-11-26 12-59-23
    b. Prompted to set local admin credentials for the new servers
    2014-11-26 12-59-57
    c. Prompted for Azure tenant credentials
    2014-11-26 15-28-03
    4. Grab some coffee and watch PowerShell do the magic. The entire deployment has taken between 35-45 minutes when I’ve been running it. If you don’t like having the WinRM Endpoint on each exposed to the internet, you need to remove them manually after deployment.
    2014-11-26 15-00-16


        The scripts creates and configures virtual machines in Microsoft Azure based on an input CSV file.
       .\CreateO365AzureEnvironment.ps1 -InputFile '.\AzureMachines.csv' -Verbose
        File Name: CreateO365AzureEnvironment.ps1
        Author   : Johan Dahlbom, johan[at]
        Blog     :
        The script are provided “AS IS” with no guarantees, no warranties, and they confer no rights.
        Requires PowerShell Version 3.0!
    param (
      [Parameter(Mandatory=$false)][string]$AffinityGroup = '365lab-affinitygroup',
      [Parameter(Mandatory=$false)][string]$DomainName = '365lab.internal',
      [Parameter(Mandatory=$false)][string]$DomainShort = '365lab',
      [Parameter(Mandatory=$false)][string]$ImageFamily = 'Windows Server 2012 R2 Datacenter',
      [Parameter(Mandatory=$false)][string]$VnetName = '365lab-azure',
      [Parameter(Mandatory=$false)][string]$AdminUsername = '365_admin',
      [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()]$DomainJoinCreds = (Get-Credential -Message 'Please enter credentials to join the server to the domain'),
      [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()]$VMCredentials = (Get-Credential -Message 'Please enter the local admin password for the servers' -UserName $AdminUsername),
      [Parameter(Mandatory=$false)][ValidateScript({Import-Csv -Path $_})]$InputFile =  '.\AzureMachines.csv'
    #Check that script is running in an elevated command prompt
    if (-not([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole("S-1-5-32-544")) {
      Write-Warning "You need to have Administrator rights to run this script!`nPlease re-run this script as an Administrator in an elevated powershell prompt!"
    #region functions
    function Connect-Azure {
      begin {
          try {
              Import-Module Azure
          } catch {
              throw "Azure module not installed"
      } process {
        if (-not(Get-AzureSubscription -ErrorAction Ignore).IsCurrent -eq $true) {
          try {
            Add-AzureAccount -WarningAction stop -ErrorAction Stop
          } catch {
            throw "No connection to Azure"
      } end {
        $AzureSubscription = Get-AzureSubscription -Default -ErrorAction Ignore
        Set-AzureSubscription -SubscriptionId $AzureSubscription.SubscriptionId -CurrentStorageAccountName (Get-AzureStorageAccount -WarningAction Ignore).StorageAccountName
        Write-Verbose "Connected to Azure"
    function Configure-JDAzureVM {
      begin {
        Write-Verbose "Starting to configure $VM..."
      process {
        #Install mandatory features
        $AzureVM = Get-AzureVM -ServiceName $CloudService -Name $VM
        #Establish a PS Session to the AzureVM
        $SessionOptions = New-PSSessionOption -SkipCACheck -SkipCNCheck
        $VMSessionConfig = @{
          ConnectionUri = $VMUri
          SessionOption = $SessionOptions
          Credential = $VMCredentials
        $VMSession = New-PSSession @VMSessionConfig -ErrorAction Stop
        #Install mandatory features
        Write-Verbose "Installing mandatory features on $VM"
        Invoke-Command -Session $VMSession -ScriptBlock {
          #Install the MOST IMPORTANT FEATURE of them all!
          Add-WindowsFeature -Name Telnet-Client -IncludeManagementTools -WarningAction Ignore
        #Install features per role
        Write-Verbose "$VM is $Role."
        switch ($Role) {
        'Domain Controller' {
          #Add additional data disk to the domain controller, with host caching disabled
          if (-not(Get-AzureDataDisk -VM $AzureVM)) {
            Write-Verbose "Adding additional data disk..."
            Add-AzureDataDisk -CreateNew -DiskSizeInGB 60 -DiskLabel "ADDB" -HostCaching None -LUN 0 -VM $AzureVM | Update-AzureVM -ErrorAction Stop
          Write-Verbose "Installing role 'AD-Domain-Services'"
          Invoke-Command -Session $VMSession -ScriptBlock {
            #Initialize and format additional disks
            Get-Disk | Where-Object {$_.PartitionStyle -eq 'raw'} | Initialize-Disk -PartitionStyle MBR -PassThru | New-Partition -AssignDriveLetter -UseMaximumSize |
                Format-Volume -FileSystem NTFS -NewFileSystemLabel "ADDB" -Confirm:$false
            #Install Role specific features
            Add-WindowsFeature -Name AD-Domain-Services -IncludeManagementTools -WarningAction Ignore
        } 'ADFS Server' {
          #Create Internal Load Balancer for the ADFS farm if not already created
          if (-not(Get-AzureInternalLoadBalancer -ServiceName $CloudService) -and $InternalLBIP) {
            Write-Verbose "Creating/adding $vm to Internal Load Balancer $($AzureVM.AvailabilitySetName) ($InternalLBIP)"
            Add-AzureInternalLoadBalancer -ServiceName $CloudService -InternalLoadBalancerName $azurevm.AvailabilitySetName -SubnetName $Subnet -StaticVNetIPAddress $InternalLBIP
          #Add the server to the Internal Load Balanced set and add 443 as endpoint
          if (-not((Get-AzureEndpoint -VM $AzureVM).InternalLoadBalancerName -eq $AzureVM.AvailabilitySetName)) {
            Write-Verbose "Adding Internal Load Balanced endpoint on port 443 to $VM"
            Add-AzureEndpoint -Name HTTPS -Protocol tcp -LocalPort 443 -PublicPort 443 -LBSetName $AzureVM.AvailabilitySetName -InternalLoadBalancerName $AzureVM.AvailabilitySetName -VM $AzureVM -DefaultProbe | Update-AzureVM -ErrorAction Stop
          Write-Verbose "Installing role 'ADFS-Federation'"
          Invoke-Command -Session $VMSession -ScriptBlock {
            #Install Role specific features
            Add-WindowsFeature -Name ADFS-Federation -IncludeManagementTools -WarningAction Ignore
        } 'Web Application Proxy' {
          #Add the server to the Cloud Service Load Balanced set and add 443 as endpoint
          if (-not((Get-AzureEndpoint -VM $AzureVM).LBSetName -eq $AzureVM.AvailabilitySetName)) {
            Write-Verbose "Adding Load Balanced endpoint on port 443 to $"
            Add-AzureEndpoint -Name HTTPS -Protocol tcp -LocalPort 443 -PublicPort 443 -LBSetName $AzureVM.AvailabilitySetName -VM $AzureVM -DefaultProbe -LoadBalancerDistribution sourceIP | Update-AzureVM -ErrorAction Stop
          Write-Verbose "Installing role 'Web-Application-Proxy'"
          Invoke-Command -Session $VMSession -ScriptBlock {
            #Install Role specific features
            Add-WindowsFeature -Name Web-Application-Proxy -IncludeManagementTools -WarningAction Ignore
      end {
        Write-Verbose "Finished configuring $VM" 
    function New-JDAzureVM {
        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$VMName = '',
      if (-not(Get-AzureVM -ServiceName $CloudService -Name $VMName -WarningAction Ignore))  {
        #region VM Configuration
        #Get the latest VM image based on the operating system choice
        $ImageName = (Get-AzureVMImage | Where-Object {$_.ImageFamily -eq $OperatingSystem} | Sort-Object -Property PublishedDate -Descending | Select-Object -First 1).ImageName
        #Construct VM Configuration
        $VMConfigHash = @{
          Name =  $VMName
          InstanceSize = $Size
          ImageName = $ImageName
        if ($AvailabilitySet -ne 'None') {
          $VMConfigHash["AvailabilitySetName"] = $AvailabilitySet
        #VM Provisioning details
        $VMProvisionHash = @{
          AdminUsername = $VMCredentials.UserName
          Password = $VMCredentials.GetNetworkCredential().Password
          JoinDomain = $DomainName
          DomainUserName = $DomainJoinCreds.UserName.split("\")[1]
          DomainPassword = $DomainJoinCreds.GetNetworkCredential().Password
          Domain = $DomainShort
        #Create VM configuration before deploying the new VM
        $VMConfig = New-AzureVMConfig @VMConfigHash | Add-AzureProvisioningConfig @VMProvisionHash -WindowsDomain -NoRDPEndpoint |
                        Set-AzureStaticVNetIP -IPAddress $IPAddress | Set-AzureSubnet -SubnetNames $SubnetName |
                            Set-AzureVMBGInfoExtension -ReferenceName 'BGInfo'
        #endregion VM Configuration
        #region Create VM
        Write-Verbose "Creating $VMName in the Cloud Service $CloudService"
        $AzureVMHash = @{
          ServiceName = $CloudService
          VMs = $VMConfig
          VnetName = $VnetName
          AffinityGroup  = $AffinityGroup
        New-AzureVM @AzureVMHash -WaitForBoot -ErrorAction Stop -WarningAction Ignore
        #endregion Create VM
      } else {
        Write-Warning "$VMName do already exist..."
     #endregion functions
     #Connect with Azure PowerShell
      $AzureMachines = Import-Csv -Path $InputFile
      Write-Verbose "Starting deployment of machines at: $(Get-date -format u)"
    foreach ($Machine in $AzureMachines) {
      Write-Verbose "Starting to process $($Machine.Name)"
      #Create VM Configuration hashtable based on input from CSV file
      $VirtualMachine = @{
        VMName = $Machine.Name
        CloudService = $Machine.CloudService
        IPAddress = $Machine.IP
        VMCredentials = $VMCredentials
        DomainJoinCreds = $DomainJoinCreds
        SubnetName = $Machine.SubnetName
        AvailabilitySet = $Machine.AvailabilitySet
        AffinityGroup = $AffinityGroup
        Size = $Machine.Size
        VnetName = $VnetName
        OperatingSystem = $ImageFamily
      try {
        #Create virtual machine
        New-JDAzureVM @VirtualMachine -ErrorAction Stop
        #Wait for machine to get to the state "ReadyRole" before continuing.
        while ((Get-AzureVM -ServiceName $Machine.CloudService -Name $Machine.Name).status -ne "ReadyRole") {
          Write-Verbose "Waiting for $($Machine.Name) to start..."
          Start-Sleep -Seconds 15
        $VMUri = Get-AzureWinRMUri -ServiceName $machine.CloudService -Name $Machine.Name
        #Create VM Role configuration
        $VMRoleConfiguration = @{
          Role = $Machine.Role
          VMUri = $VMUri
          VMCredentials = $VMCredentials
          VM = $Machine.Name
          CloudService = $Machine.CloudService
          Subnet = $machine.SubnetName
        #Add internal load balancer IP to the role configuration
        if ($Machine.InternalLB) {
          $VMRoleConfiguration["InternalLBIP"] = $Machine.InternalLB
        #Role specific configuration of the virtual machine
        Configure-JDAzureVM @VMRoleConfiguration -ErrorAction Stop
      } catch {
        Write-Warning $_
    Write-Verbose "Finished deployment of machines at: $(Get-date -format u)"

    After some waiting for the provisioning, we now have the infrastructure in place in order to complete the configuration of our SSO infrastructure for Office 365 in Microsoft Azure. In the next part of this series we will put all the pieces together and finish AADSync, ADFS and WAP configuration against Office 365 / Azure Active Directory.
    2014-11-26 15-54-05

    Enjoy, let me know if you have any feedback!


    Office 365: Deploying your SSO Identity Infrastructure in Microsoft Azure – Part 1

    This is part 1 of 3 in a series where we go through how to create a highly available SSO infrastructure for Office 365 in Microsoft Azure.
    Part 2 of the series can be found here.
    Part 3 of the series can be found here.

    Implementing SSO infrastructure with ADFS is something many customers want in order to reduce the amount of logins for their end users. Soon we will even get single sign on in the Outlook client! (YAY 🙂 ) The biggest challenge implementing the infrastructure is that you get dependent on your local server infrastructure and internet connection.
    When it’s not possible to make the solution redundant with you own infrastructure, but still need single signon, my recommendation is to deploy the SSO infrastructure in Microsoft Azure.

    Microsoft has provided a white paper on the topic that gives you an idea on what options you have and important things to consider. The White paper describes two deployment options implementing the infrastructure in Azure.

    1. All Office 365 SSO integration components deployed in Azure. This is cloud-only approach; you deploy directory synchronization and AD FS in Azure. This eliminates the need to deploy on-premises servers.
    2. Some Office 365 SSO integration components deployed in Azure for disaster recovery. This is the mix of on-premises and cloud-deployed components; you deploy directory synchronization and AD FS, primarily on-premises and add redundant components in Azure for disaster recovery.

    Option 1 to put the entire Identity Infrastructure in the cloud has been the choice for most of the implementations I’ve been involved in. This gives you a very flexible solution that will provide you a highly available SSO infrastructure, yet putting the infrastructure near the services you are providing.

    In order to set it up in a highly available SSO infrastructure in Azure, the following components/servers must be deployed in Azure. Recommendations on the high-level architecture is also described on

    First of all, if you’re setting up your Azure Infrastructure from scratch, you need a virtual network and a storage account in the region that is closest/best for you (in my case, North Europe). See the following tutorials on how to create a virtual network, a storage account and an affinity group.

    In my example – the virtual network in Azure is configured as follows:

    2014-11-22 13-42-09

    The network is divided into two subnets except for the gateway subnet. One “Internal” subnet and one “Perimeter” subnet in order to filter traffic based on the source/destination IP, that is now possible through Network Security Groups (NSG’s) in Azure. A local network has also been added for on-premises connectivity.

    In my example, I will deploy six(6) virtual Machines with the following roles:

    Name Role IP Size Cloud Service Availability Set
    AZURE-DC1 Domain Controller Small 365lab-azr01
    AZURE-AADSYNC1 DirSync/AADSync Medium 365lab-azr01
    AZURE-ADFS01 ADFS Server Small 365lab-sts sts-365lab
    AZURE-ADFS02 ADFS Server Small 365lab-sts sts-365lab
    AZURE-WAP01 Web Application Proxy Small 365lab-wap wap-365lab
    AZURE-WAP02 Web Application Proxy Small 365lab-wap wap-365lab

    In this case, I’m choosing to deploy only one domain controller in Azure, and configure that one as the primary dns for the virtual network, and the local network domain controllers as secondary dns servers. Note that the ADFS and WAP servers are placed in separate cloud services/availability sets in order to make them highly available.
    You will find a reference regarding machine sizing here. Estimated costs per month for the above setup will with list pricing be around $500 including network traffic and storage. The exact pricing will of course be depend on your Azure agreement and network traffic.


    The overall high level design of the setup will be as in the following sketch:

    In the next part of the series, we will deploy the virtual Machines with the proper configuration using Azure PowerShell.

    Until next time!