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.
2015-04-05_16-56-21
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
2015-04-05_17-04-13
2015-04-05_17-05-11
3. Create a runbook in the automation account you created earlier, in my case I called it ‘Sync-EXOSharedMailboxPermissions’.
2015-04-05_17-15-35
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.
2015-04-05_19-11-27
Below you’ll find the code I’ve used:

Sync-EXOSharedMailboxPermissions

workflow Sync-EXOSharedMailboxPermissions {
<#
    .SYNOPSIS
    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
    .NOTES
    File Name: SharedMailboxViaGroups.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!
#>
    #Credentials to connect to Exchange Online
    $Credentials = Get-AutomationPSCredential -Name 'EXOCreds'
    #Prefix to search for
    $Prefix = 'SM-'
    function Connect-ExchangeOnline {
    param (
        $Creds
    )
        #Clean up existing PowerShell Sessions
        Get-PSSession | Remove-PSSession
        #Connect to Exchange Online
        $Session = New-PSSession –ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -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 {
            param(
                [string]$Identity,
                [string]$SharedMailboxName
            )
            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 {
            param(
                [string]$Identity,
                [string]$SharedMailboxName
            )
            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 {
            [CmdletBinding(SupportsShouldProcess=$true)]
            param(
                [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. 🙂
2015-04-05_20-12-47
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!)
2015-04-05_20-12-472015-04-06_13-40-14
2015-04-06_13-45-53
2015-04-06_13-46-56
2015-04-06_13-47-44
2015-04-06_13-48-13
2015-04-06_13-48-39

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

/Johan

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.
365lab-Azure-NSG
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

2015-02-17_23-40-27

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.
2015-04-05_11-44-17

#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.
2015-04-05_12-15-05

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!

/Johan

Awarded MVP in Office 365!

Yesterday was a very nervous day for me and the F5 button went warm pretty much the entire day. New quarter means new possibilities to get a Most Valueable Professional Award (MVP) from Microsoft. 16:26 did the email I was hoping for arrive – that I have got an MVP award for my contributions in the Office 365 community! Capture Thanks for all the support – I am looking forward continuing this journey and will do what I can to keep up with our contributions here and on other platforms together with my fellow MVP’s. /Johan

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 @365lab.net

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 "@365lab.net" `
                           -Operator NOTENDSWITH `
                           -Precedence 50

2015-02-09_22-52-50
Please note that the filter is not quite as forgiving as most things usually are nowdays when it comes to case-sensitivity.

New-JDAADSyncFilteringRule

function New-JDAADSyncFilteringRule {
<#
    .SYNOPSIS
        The function will create AADSync filtering rules based on attributes and conditions
    .EXAMPLE
        New-JDAADSyncFilteringRule "Inbound from AD" -Attribute "userPrincipalName" -Value "@365labf.net" -Operator ENDSWITH -Precedence 50
    .NOTES
        File Name: New-JDAADSyncFilteringRule
        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=$true)]
        [String]
        $Name,
        [ValidateScript({Get-ADSyncConnector -Name $_})]
        [string]
        $DomainName,
        [Parameter(Mandatory=$true)]
        [String]
        $Attribute,
        [Parameter(Mandatory=$true)]
        [String]
        $Value,
        [Parameter(Mandatory=$true)]
        [ValidateSet("EQUAL","NOTEQUAL","NOTENDSWITH","ENDSWITH","CONTAINS","NOTCONTAINS")]
        $Operator,
        [Parameter(Mandatory=$true)]
        [int]
        $Precedence

    )
    #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
            $ScopeFilterGroup.ScopeConditionList.Add($Scopefilter)

            $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 "$_"
        }
    }
}

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

/Johan

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.

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

2015-02-08_20-33-58

SOLUTION
As of now, in order to be able to manage Azure AD Premium, you need to have licenses assigned for each Admin.
2015-02-08_20-04-47
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.
2015-02-08_20-40-14

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

/Johan

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

    Export legacyExchangeDN from Global Address List in Outlook

    In some migration scenarios it is necessary to export all legacyExchangeDN addresses from your Exchange environment. This is for example the case if you are doing a mail migration with .pst files. If you are migrating from a Hosted Exchange environment this information can sometimes be hard to get. However, there are solutions.

    It is quite easy to export all legacyExchangeDN addresses from the Global Address List using Access and Outlook on a client that is connected to the Exchange environment.

    Start by opening Access and create a new empty database.
    On the External Data tab select Outlook Folder

    legacydn-access_1

    In the Import Guide select to import data into a new table, and click OK

    legacydn-access_2

    Select your Outlook profile

    legacydn-access_3

    Now select the address list to export from, in this case I select the Global Address List to get all addresses.

    legacydn-access_4

    In the next step it is possible to adjust the data, just click Finish.

    legacydn-access_5

    Now we’re done! A new table has been added to the database, and in the E-mail address column you will see the legacyExchangeDN for all users

    legacydn-access_6

    Now it is easy to add these addresses as a x500 alias addresses in your migrated mailboxes.

    / Andreas

    Office 365: Assign individual parts of licenses based on groups using PowerShell

    Important note: The end of an era with licensing scripts is near… and the beginning of a new one with Azure AD Group Based Licensing is here. Group Based Licensing is now in preview and currently requires a paid Azure AD Subscription. Try it out and give Microsoft your feedback on how they can make it even better! 

    The story continues… After numerous requests regarding handling parts of licenses in my older licensing scripts , I’ve now created an example on how to do this using security groups.

    Please note that this is of course not a complete solution, just an example on how to incorporate this in to one of the earlier solutions created.

    SCENARIO
    In this example, I will have three groups each assigning different parts of an E3 License. One assiging the Full E3, one assigning Exchange Online and one assigning Lync Online + Office 365 ProPlus.
    In order to assign them with PowerShell, we need the ServicePlan name of each part we want to assign. Those can be found with the following command for an E3-license:

    (Get-MsolAccountSku | Where-Object {$_.SkuPartNumber -eq "ENTERPRISEPACK"}).ServiceStatus
    

    2014-12-16_20-32-02
    As seen above the names are pretty easy to recognise, if you are unsure which one is which, you will find a good translation table here.

    Disabling plans with PowerShell requires you to to select the ones that you want to disable rather than the ones you want to enable, just like in the portal. In my example I’m choosing the parts I want to assign per group, which means I’m disabling all other parts than just the ones you want.
    See the example below how I’ve configured my $Licenses hashtable for my scenario:

    $Licenses = @{
                     'E3-ExchangeOnline' = @{
                              LicenseSKU = 'tenant:ENTERPRISEPACK'
                              EnabledPlans = 'EXCHANGE_S_ENTERPRISE'
                              Group = 'E3-ExchangeOnline-Users'
                            }
                     'E3-LyncO365ProPlus' = @{
                              LicenseSKU = 'tenant:ENTERPRISEPACK'
                              EnabledPlans = 'MCOSTANDARD','OFFICESUBSCRIPTION'
                              Group = 'E3-LyncO365ProPlus-Users'
                            }
                     'E3' = @{
                              LicenseSKU = 'tenant:ENTERPRISEPACK'
                              Group = 'E3-Users'
                            }
                }
    

    RUNNING THE SCRIPT
    After editing the $Licenses hashtable, $UsageLocation and tenant credentials, you’re ready to run the script as in the screenshot below.
    2014-12-16_21-52-24

    2014-12-16_21-40-47
    A user that have been assigned licenses with the E3-LyncO365ProPlus-Users group in the example

    LicenseO365Users.ps1

    &amp;amp;lt;# .SYNOPSIS     Script that assigns Office 365 licenses based on Group membership in AAD. .NOTES     Author: Johan Dahlbom      Blog: 365lab.net      Email: johan[at]dahlbom.eu      The script are provided “AS IS” with no guarantees, no warranties, and they confer no rights.     Requires PowerShell Version 3.0! #&amp;amp;gt;
    
    #Import Required PowerShell Modules
    Import-Module MSOnline
    #Office 365 Admin Credentials
    $CloudUsername = 'admin@tenant.onmicrosoft.com'
    $CloudPassword = ConvertTo-SecureString 'password' -AsPlainText -Force
    $CloudCred = New-Object System.Management.Automation.PSCredential $CloudUsername, $CloudPassword
    #Connect to Office 365
    Connect-MsolService -Credential $CloudCred
    $UsageLocation = 'SE'
    
    $Licenses = @{
                     'E3-ExchangeOnline' = @{
                              LicenseSKU = 'tenant:ENTERPRISEPACK'
                              EnabledPlans = 'EXCHANGE_S_ENTERPRISE'
                              Group = 'E3-ExchangeOnline-Users'
                            }
                     'E3-LyncO365ProPlus' = @{
                              LicenseSKU = 'tenant:ENTERPRISEPACK'
                              EnabledPlans = 'MCOSTANDARD','OFFICESUBSCRIPTION'
                              Group = 'E3-LyncO365ProPlus-Users'
                            }
                     'E3' = @{
                              LicenseSKU = 'tenant:ENTERPRISEPACK'
                              Group = 'E3-Users'
                            }
                }
    
    foreach ($license in $Licenses.Keys) {
        $GroupName = $Licenses[$license].Group
        $GroupID = (Get-MsolGroup -All | Where-Object {$_.DisplayName -eq $GroupName}).ObjectId
        $AccountSKU = Get-MsolAccountSku | Where-Object {$_.AccountSKUID -eq $Licenses[$license].LicenseSKU}
    
        Write-Output "Checking for unlicensed $license users in group $GroupName"
    
        #region Disable non specific plans
        $EnabledPlans = $Licenses[$license].EnabledPlans
        if ($EnabledPlans) {
            $LicenseOptionHt = @{
                AccountSkuId = $AccountSKU.AccountSkuId
                DisabledPlans =  (Compare-Object -ReferenceObject $AccountSKU.ServiceStatus.ServicePlan.ServiceName -DifferenceObject $EnabledPlans).InputObject
            }
            $LicenseOptions = New-MsolLicenseOptions @LicenseOptionHt
        }
        #endregion Disable non specific plans
    
        #Get all unlicensed group members - needs to be changed if a user should be able to have more than one license
        $GroupMembers = (Get-MsolGroupMember -GroupObjectId $GroupID -All | Where-Object {$_.IsLicensed -eq $false}).EmailAddress
        #Warn if not enough licenses are available
        if ($AccountSKU.ActiveUnits - $AccountSKU.consumedunits -lt $GroupMembers.Count) {
            Write-Warning 'Not enough licenses for all users, please remove user licenses or buy more licenses'
        }
    
        foreach ($User in $GroupMembers) {
            try {
                #Set UsageLocation
                Set-MsolUser -UserPrincipalName $User -UsageLocation $UsageLocation -ErrorAction Stop -WarningAction Stop
                $LicenseConfig = @{
                    UserPrincipalName = $User
                    AddLicenses = $AccountSKU.AccountSkuId
                }
                if ($EnabledPlans) {
                    $LicenseConfig['LicenseOptions'] = $LicenseOptions
                }
                Set-MsolUserLicense @LicenseConfig -ErrorAction Stop -WarningAction Stop
                Write-Output "SUCCESS: licensed $User with $license"
            } catch {
                Write-Warning "Error when licensing $User`r`n$_"
            }
        }
     }
    

    Let me know if you have any questions or feedback!

    /Johan

    Back to Basics: Creating new users in an Exchange Hybrid Deployment

    Some of the most common questions I get from my clients are related to how to create new users in an Exchange Hybrid environment. I have seen users set up in an incorrect way in most Hybrid Environments. Some common problems resulting from this include missing Autodiscover functionality or bouncing emails from external users. The reason for these kind of problems is that your on-premises Exchange server doesn’t recognize the Exchange Online mailbox.

    The best way of creating new users during the Hybrid is to use the New Remote Mailbox guide in Exchange Management Console (this can of course also be done in an on-premises Exchange 2013 server, the principle is the same). This will create both the AD account, and populate all necessary attributes for Office 365. It will also enable you to continue using Exchange Management Console for managing all Exchange attributes.

    The key is to use the Mail Contact pane in Exchange Management Console. Start by right-clicking and select the New Remote Mailbox guide.

    remotemailbox1

    Select the type of mailbox you are creating, normally a User Mailbox.

    remotemailbox2

    Fill in all the information that you would normally fill in when creating a new AD user.

    remotemailbox3

    If you are not using Archive Mailboxes just click Next.

    remotemailbox4

    Review the summary and click New.

    remotemailbox5

    All done! Close the guide by clicking Finish.

    remotemailbox6

    If we right-click on our new user and check the Email Addresses tab, we will see that it is already populated with all addresses according to our E-mail Address Policy. We even have a Routing E-mail Address set, pointing to our Office 365 tenant.

    remotemailbox7

    This means that we are all set, our on-premises Exchange server is now aware of the Mailbox in Office 365! The final step is to wait for DirSync (or force a sync) then assign a license to the user.

    / Andreas

    Exchange Online: Adding a secondary e-mail domain to all users

    I thought that I would share with you a simple script that will add an email domain to your users. This script is handy if you for example would like to add contoso.info to your already existing contoso.com environment. If you are using the email address aaron@contoso.com you want the script to automatically add aaron@contoso.info.

    Please note that the domain name that you are adding first has to be added and verified in your Office 365 tenant.

    DirSync version (modifying Active Directory using ADSI):

    $activeDomain = New-Object DirectoryServices.DirectoryEntry
    $domain = $activeDomain.distinguishedName
    $searcher = [System.DirectoryServices.DirectorySearcher]"[adsi]LDAP://$domain"
    $searcher.filter = '(proxyaddresses=*@contoso.com*)'
    $result = $searcher.findall()
    $users = $result.Path
    
    $users | ForEach-Object {
        $user = [adsi]"$_"
        $proxyaddresses = $user.proxyaddresses.Value | Where-Object { $_ -like 'smtp:*@contoso.com' }
        foreach ($proxyaddress in $proxyaddresses) {
            $newaddress = ($proxyaddress.split ':')[1] -replace '@contoso.com', '@contoso.info'
            $user.proxyaddresses.add("smtp:$newaddress")
        }
        $user.setinfo()
    }
    

    Cloud version:

    $cred = Get-Credential -Message 'Please enter your Office 365 admin crendentials'
    $O365 = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri 'https://outlook.office365.com/powershell-liveid/' -Credential $cred -Authentication Basic -AllowRedirection 
    $importcmd = Import-PSSession $O365 -CommandName @('Get-Mailbox','Set-Mailbox') -AllowClobber
    
    Get-Mailbox -ResultSize Unlimited -Filter { EmailAddresses -like '*@contoso.com' } | Select-Object Identity,EmailAddresses | ForEach-Object {
        $proxyaddresses = $_.EmailAddresses | Where-Object { $_ -like 'smtp:*@contoso.com' }
        foreach ($proxyaddress in $proxyaddresses) {
            $newaddress = ($proxyaddress -split ':')[1] -replace '@contoso.com','@contoso.info'
            Set-Mailbox -Identity $_.Identity -EmailAddresses @{Add="smtp:$newaddress"}    
        }
    }
    

    / Andreas