Category Archives: Windows Server

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.

PROMOTING THE DOMAIN CONTROLLER
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

AZURE AD CONNECT?
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?

GETTING THROUGH THE WIZARD
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.
2015-02-07_12-38-53
2015-02-07_12-48-09
3. Since we want to deploy ADFS and WAP during the installation, we click customize to be able to do that.
2015-02-07_12-48-53
4. Single sign on it is! In this example we’ll use the federation service name of sts.adfs.guru.
2015-02-07_12-49-16
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.
2015-02-07_12-51-12
2015-02-07_12-50-45
6. In my case I’ll have both an Exchange Hybrid deployment and Password Write back through AADP enabled.
2015-02-07_12-51-41
7. Since I have only have one forest/domain, I’m just using the default settings for the next two steps.
2015-02-07_12-51-59
2015-02-07_12-52-13
8. Import the .pfx file for your service. In my case I have a certificate with the CN sts.adfs.guru that will be imported to all ADFS and WAP machines. If you want to set up this in a lab environment, you can use startssl.com (gives you 1 year free single name certificates trusted by most browsers).
2015-02-07_13-23-56
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 :))
2015-02-07_12-58-17
The server with only lower case letters will be the primary ADFS server in the farm.
2015-02-07_13-03-23
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.
2015-02-07_13-03-55
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.
2015-02-07_13-04-18
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.
2015-02-07_13-05-57
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.
2015-02-07_13-06-38
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.
2015-02-07_13-31-32
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 (10.255.255.10) internally, and to the WAP Cloud service name externally (365lab-wap1.cloudapp.net), as seen below:
2015-02-07_13-42-52
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.
2015-02-07_13-36-56
And my DNS records turned out to be OK according to the wizard! 🙂

AZURE AD CONNECT – VERDICT
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.
  • SUMMARY
    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!

    /Johan

    Advertisement

    Updating Windows Management Framework breaking DirSync: “Invalid namespace”

    Disclaimer
    This blog post was written for an older version of the Azure AD Connect Synchronization Service, and has not been tested in the latest version. Always make sure that you have a valid backup before making any changes to your system.

    After updating Windows Management Framework you might get an error message in the Event Viewer, “Invalid namespace”. The symptoms includes stopped scheduled synchronizations, and event 0 logged in the Event Viewer when trying to run the synchronization manually.

    dirsync-event-0

    The problem has to do with the WMI Performance Counters being updated when updating Windows Management Framework.

    SOLUTION
    The solution is to rebuild and recompile the MOF files used for the Performance Counters.

    First, open a command prompt and change directory to %Program Files%\Windows Azure Active Directory Sync\SYNCBUS\Synchronization Service\Bin

     cd "%Program Files%\Windows Azure Active Directory Sync\SYNCBUS\Synchronization Service\Bin"

    Open mmswmi.mof in Notepad.exe, and add the following text at the top in the file

    #PRAGMA AUTORECOVER

    Load the modified MOF file into the WMI repository by running the following command:

    mofcomp mmswmi.mof

    mofcomp

    Register the Microsoft Identity Integration Server dll

    regsvr32 /s mmswmi.dll

    Restart Windows Management Instrumentation

    net stop winmgmt
    net start winmgmt

    Re-run the DirSync Configuration Wizard, by running %Program Files%\Windows Azure Active Directory Sync\ConfigWizard.exe

    Check the Event Viewer again and make sure that the Syncroniation was successful!

    / Andreas

    Change DNS settings on multiple servers

    A common change that is required to be done when doing major infrastructure upgrades is changing dns server settings on clients and servers. Something that I often see is that the changes on the servers are handled manually by logging on with RDP, changing DNS and logging of. Therefore I wanted to share a basic script that does this job for you, for all or a set of servers in Active Directory through WMI. It can of course be customized indefinitely according to your DNS topology and site structure.

    By default it will target all servers that are not domain controllers, respond to ping and WMI.
    If you have a simple environment with example only two or three DNS servers it’s just to change the $NewDNS array to your specific DNS servers and run the script.

    ChangeDNSServers.ps1

    Import-Module ActiveDirectory
    #New DNS Servers in order
    $NewDNS = @("10.255.255.4","10.255.255.5")
    $NewDNS | ForEach-Object -Begin { Write-Output "The following DNS servers in order will be set on the machines:" ; $index = 1} -Process { Write-Output "#$($index): $_" ; $index++ }
    #Get all servers in active directory (excluding domain controllers)
    $ServersToChange = (Get-ADComputer -Filter {operatingsystem -like "*Server*" -and enabled -eq $true -and primarygroupid -ne '516'}).Name
    #Loop through all servers and change to the new DNS servers
    foreach ($Server in $ServersToChange) {
        if (Test-Connection -ComputerName $Server -Count 1 -Quiet -ErrorAction Ignore -WarningAction Ignore) {
            try {
                $wmi = Get-WmiObject -Class win32_networkadapterconfiguration -Filter "ipenabled = 'true'" -ComputerName $Server
                $wmi.SetDNSServerSearchOrder($NewDNS) | Out-Null
                Write-Output "SUCCESS: Changed DNS on $Server"
            } catch {
                Write-Warning "Error changing DNS on $Server"
            }
        } else {
            Write-Warning "Error contacting $Server`r`nDoes it respond to ping/wmi?"
        }
    }
    

    2014-11-29_12-25-52
    WHAT COULD POSSIBLY GO WRONG?
    Changing DNS server settings on a machine is not something that you usually think could break anything. In 11/10 times there are no issues at all. But the 12th time you do it, you might get in trouble. So, if you have Windows Server 2008/2008 R2 machines in your environment, please read KB2520155 make sure to apply proper countermeasure prior to changing DNS servers on those.

    /Johan

    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

    GETTING STARTED

    Assumptions
    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.

    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

    CreateO365AzureEnvironment.ps1

    <#
    .SYNOPSIS
        The scripts creates and configures virtual machines in Microsoft Azure based on an input CSV file.
    .EXAMPLE
       .\CreateO365AzureEnvironment.ps1 -InputFile '.\AzureMachines.csv' -Verbose
    .NOTES
        File Name: CreateO365AzureEnvironment.ps1
        Author   : Johan Dahlbom, johan[at]dahlbom.eu
        Blog     : 365lab.net
        The script are provided “AS IS” with no guarantees, no warranties, and they confer no rights.
        Requires PowerShell Version 3.0!
    #>
    [CmdletBinding(SupportsShouldProcess=$true)]
    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!"
      break
    }
    #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 {
      [CmdletBinding(SupportsShouldProcess=$true)]
      Param(
        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$Role,
        [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()]$VMCredentials,
        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$VM,
        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$CloudService,
        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$VMUri,
        [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][string]$InternalLBIP,
        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$Subnet
      )
      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 $CloudService.cloudapp.net"
            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 {
      [CmdletBinding(SupportsShouldProcess=$true)]
      Param(
        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$VMName = '',
        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$CloudService,
        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$IPAddress,
        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$Size,
        $VMCredentials,
        $DomainJoinCreds,
        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$SubnetName,
        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$OperatingSystem,
        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$VnetName,
        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$AvailabilitySet,
        [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$AffinityGroup
      )
    
      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
      Connect-Azure
      $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)"
    

    RESULTS
    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!

    /Johan

    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.

    THE OPTIONS
    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.

    SETTING IT UP – NETWORK AND STORAGE
    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 http://technet.microsoft.com/en-us/library/dn509538.aspx.

    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 10.255.255.0/24 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.

    SETTING IT UP – VIRTUAL MACHINES
    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 10.255.255.4 Small 365lab-azr01
    AZURE-AADSYNC1 DirSync/AADSync 10.255.255.5 Medium 365lab-azr01
    AZURE-ADFS01 ADFS Server 10.255.255.6 Small 365lab-sts sts-365lab
    AZURE-ADFS02 ADFS Server 10.255.255.7 Small 365lab-sts sts-365lab
    AZURE-WAP01 Web Application Proxy 10.255.255.132 Small 365lab-wap wap-365lab
    AZURE-WAP02 Web Application Proxy 10.255.255.133 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:
    Azure-Reference-Test

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

    Until next time!

    /Johan

    How to handle SMTP Relay after migrating to Exchange Online

    When decomissioning your on-premises Exchange server after moving to Office 365 you need a new solution for SMTP relay to use with for example multi-functional printers. In some cases your internet provider can offer this service, but if you want control over your mail flow I recommend using Office 365 also for outgoing e-mail.

    Normally you need a licensed user to be able to send e-mails using SMTP with Office 365. Your applications also need support for TLS encryption. If your application doesn’t support TLS, or if you need to send e-mails from another address than the licensed user’s address you need another solution. Luckily Office 365 can help you. The solution is to set up an inbound connector in Exchange Online Protection.

    Setting up an Inbound Connector

    An Inbound Connector is easily set up with just a few lines of PowerShell code. First we have to connect to Exchange Online.

    $UserCredential = Get-Credential
    $Session = New-PSSession -ConfigurationName Microsoft.Exchange `
        -ConnectionUri 'https://outlook.office365.com/powershell-liveid/' `
        -Credential $UserCredential -Authentication Basic -AllowRedirection
    Import-PSSession $Session -CommandName 'New-InboundConnector'
    

    Now we can create the Inbound Connector. All we have to specify is a Name for our connector, SenderIPAddresses which is the IP addresses to allow relaying from (your external IP address), and SenderDomains which is the domains to accept messages from.

    You can also get your external IP address with PowerShell, here I will use a free IP detection tool from Dyn.

    $ip = (Invoke-WebRequest -Uri http://checkip.dyndns.com).content `
        -replace '[^\d\.]'
    New-InboundConnector `
        -Name 'SMTP Relay' `
        -SenderIPAddresses $ip `
        -SenderDomains '365lab.net'
    

    The settings for Mail Flow are also found in Exchange admin center, navigate to mail flow, and then go to the connectors section. Of course you can also use the GUI to create your connector.

    smtprelay

    When configuring your application to connect to this SMTP server, use your MX server name as SMTP server and connect using port 25. The server name can be found in Office 365 admin center, and looks something like domain-com.mail.protection.outlook.com.

    Increasing security

    With this method all computers on your network sharing the same external IP address can use your Inbound Connector. To strengthen the security I recommend only opening port 25 in the firewall from your devices that actually sends email. An even better alternative would be to set up an internal SMTP server that only accept connections from approved devices, and then uses your Inbound Connector as smart host. This can be achieved with the SMTP Service provided with Internet Information Services (IIS) in Windows Server.

    Configuring IIS SMTP Service

    First we have to install the SMTP Service in Windows. This can be done with PowerShell.

    Note that the Add-WindowsFeature cmdlet in Windows Server 2012 is called Install-WindowsFeature, but Add-WindowsFeature still exists as alias for backward compatibility.

    Import-Module ServerManager
    Add-WindowsFeature SMTP-Server
    

    This will also install the required dependencies for example the IIS 6 Management Console. When the installation is done we are ready to start configuring the service. Let’s set the service to start automatically when Windows starts:

    Set-Service SMTPSVC -StartupType Automatic
    

    The next step is to set Office 365 as Smart Host. Replace the Smart Host name with your own MX server.

    $smtpsvc = [ADSI]'IIS://localhost/smtpsvc/1'
    $smtpsvc.SmartHost = '365lab-net.mail.protection.outlook.com'
    $smtpsvc.SetInfo()
    

    We also have to set a Fully Qualified Domain Name to identify our SMTP Server.

    $hostname = (Resolve-DnsName $ip).NameHost
    $smtpsvc.FullyQualifiedDomainName = $hostname
    $smtpsvc.SetInfo()
    

    Finally we have to add our internal IP addresses that are allowed to use the SMTP Service. I prefer using the GUI for this. You will find these settings in the Internet Information Services (IIS) 6.0 Manager. Expand the local computer node, and right click on “SMTP Virtual Server #1” and choose Properties. On the Access tab click on Relay restrictions, and then add your local IP addresses that are allowed to use the SMTP Server. While in the GUI settings I also suggest having a look at the message size limits and logging settings.

    Now we are all set. Don’t forget to configure your perimeter firewall to block outgoing SMTP traffic from all your computers except the SMTP Server.

    / Andreas

    Logging on as Domain Admin to end user workstations? Think again!

    We’ve all been there.
    An end user has a problem with their computer or needs manual installation of some software. Either we elevate with an admin account logged on as the end user, or we switch user and are logging on with our admin account.
    Far too often, the admin accounts used to help out end users, are very privileged accounts, 2/5 times I see this at my customers those accounts are Domain Admins.

    Why is this a problem? Well, first of all, you should never expose very privileged credentials to “non trusted” computers. Secondly, at the time you log on, your credentials are exposed and can with Benjamin “gentilkiwi” Delpy’s tool mimikatz be extracted in clear text through the lsass process.

    Ok, but the user is not local admin, so they will not be able to do it anyway? That is true, but far too many let end users be local admins, and of course there are ways to get around that if you’re a non admin user as well.

    Example:
    The end user Aaron that has been privileged enough to get local admin rights on his computer. He puts the powershell-script Invoke-Mimikatz.ps1 as a task to run at logon for all users.
    2014-03-12 13-33-19

    2014-03-12 13-44-04
    In this example, I’ve just put in the row below in at the end of the script to dump the credentials to a file on the c-disk. This could of course be dumped anywhere if you wanted to.

    Invoke-Mimikatz -dumpcreds | 
        Out-File -Append  c:\evilplace\$env:computername.txt

    Effect:
    Now, when his fellow administrator logs on to his computer to help him with some software installation, the admin credentials will be dumped to a file and Aaron is from now on Domain Admin!

    2014-03-12 13-54-02

    Scary? Yes!
    Lesson learned: Think both once and twice before logging on with privileged credentials to non trusted computers.

    This is of course nothing new, it’s been out for quite a while. Look in to the following matrix that clarifies when this is an issue or not (in most cases it is…:( )

    /Johan

    Fix broken shortcuts during file server migrations

    During a file server migration several problems arise. Most of them are eliminated by using DFS Namespaces. With DFS all paths are kept the same even though the file server itself is changed. Having that in mind, I really hope that everyone is using DFS for file servers.

    In scenarios where UNC paths that points directly to the file server are used we have to find a way to minimize problems for the end users when the paths are changed.

    It is very common that end users have shortcuts on their desktops that point to folders and documents on the server. I will give you a simple script that will replace the path for you.

    This script is written in VBScript. The first two lines defines the old path, and what to replace it with.

    'Define paths
    strFind = "\\oldserver\share1\"
    strReplace = "\\newserver\share2\"
    
    Set objWsh = CreateObject("WScript.Shell")
    Set objWshSysEnv = objWsh.Environment("PROCESS")
    strProfilePath = objWshSysEnv("USERPROFILE")
    
    Set objFSO = CreateObject("Scripting.FileSystemObject")
    Set objFolder = objFSO.GetFolder(strProfilePath & "\Desktop")
    
    'Loop through all files on the desktop
    For Each file In objFolder.Files
    	if right(file.Name,3)="lnk" then
    		'If a shortcut is found
    		Set objLink = objWsh.CreateShortcut(file.Path)
    
    		objLink.Arguments = Replace(objLink.Arguments,strFind,strReplace,1,-1,1)
    		objLink.TargetPath = Replace(objLink.TargetPath,strFind,strReplace,1,-1,1)
    		objLink.WorkingDirectory = Replace(objLink.WorkingDirectory,strFind,strReplace,1,-1,1)
    
    		objLink.Save
    	end if
    Next
    

    Run this script as a login script on your clients and you will have one problem less the day after your file server migration.

    / Andreas

    Quick Tip: Why are you still using nslookup?

    Still using nslookup when troubleshooting and verifying DNS records?
    Since Windows 8 and Windows Server 2012 arrived, I’ve been switching to the cmdlet Resolve-DnsName instead.
    Two of the benefits are:

    • It gives ju way nicer and much more structured data that easily can be used in scripting scenarios.
    • It’s faster to write, just type Reso<tab> in your Powershell window.

    Examples on output below:

    2014-02-08 21-08-09

    /Johan

    DirSync Configuration Error: “There is no such object on the server”

    Recently, I ran across an issue deploying DirSync at a Customer.
    The installation went well, but running the “Directory Sync Configuration Wizard” failed with the error message “There is no such object on the server”.
    2014-02-03 09-14-26

    Investigating the event logs, the configuration seemed to stop at the following event:
    2014-02-03 09-19-06
    Looking in to AD after the wizard had failed, no account had been created.
    I then noticed that the Users container in the domain root was missing.
    Since it is there DirSync by default tries to create the sync account and the container didn’t exist, it failed.

    After recreating the Users container (can be done with either AdsiEdit or PowerShell), the Configuration Wizard completed successfully.

    Recreate the users container with PowerShell

    New-ADObject -Description "Default container for upgraded user accounts" ` 
                 -Name Users ` 
                 -Type Container `
                 -ProtectedFromAccidentalDeletion $true
    

    Hope this helps you if you are running in to this problem!

    /Johan