Author Archives: Andreas Lindahl

Office 365: Migrating DirSync to new AD domain

Disclaimer
This blog post was written for older versions of Office 365 and Azure AD, 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.

In a migration scenario you might need to replace the Active Directory domain used to sync your users to Office 365. I will go through the steps you need to change AD domain.

In this scenario I assume that ADFS is not used. If it is, you first have to disable federation. I suggest looking at my previous post where I described how to switch from ADFS to Password Sync.

The steps we have to go through are:

  1. Disable DirSync in old domain
  2. Populate new AD domain with users and attributes
  3. Prepare Office 365 users for new domain
  4. Install DirSync in the new domain.
  5. Verify Sync

First, disabling DirSync is very easy. Just go to the Office 365 admin center and click the Deativate link under users and groups.

dirsync-change-domain-1This will take up to 72 hours. When this process is over all user accounts are managed in the Office 365 portal, and there is no connection to your old domain. This change will not cause any service interruption, all users will be able to use their services as normal.

In the meantime you can uninstall the Azure Active Directory Sync tool on the old DirSync server.

The second step is to populate your new AD domain with all user accounts. It is now important that you copy all information from the old domain, (i.e. phone numbers, titles etc), and for Exchange Online it is especially important that these attributes are copied:

  • userPrincipalName
  • proxyAddresses
  • legacyExchangeDN

UserPrincipalName is your login name to Office 365. I don’t think I have to explain why this attribute is important 🙂 . ProxyAddresses are all your email addresses, both primary and alias. The last attribute, legacyExchangeDN, is used if you previously have migrated from an Exchange on-premises to Office 365. It is used for internal addressing in Exchange. If it is removed you will not be able to reply to old emails, meeting invitations, and your Suggested Contacts will also fail.

I will not go through here how to migrate these attributes here, check out our post on how to sync attributes from cloud identities to active directory.

The third step is where the magic happens. Office 365 uses the ObjectGUID attribute to keep track of the user accounts in in your on-premises Active Directory. It is translated to an ImmutableID in Azure Active Directory. If you rename your users, the ObjectGUID is untouched. Hence the name ImmutableID.

dirsync-change-domain-2The problem is that when you move to a new domain, all ObjectGUIDs are changed, and we need to generate a new ImmutableID.

Office 365 generates these IDs for us, we just have to clear the attribute on all users in Office 365. This is easily done with PowerShell:

Set-MsolUser -UserPrincipalName "aaron.beverly@365lab.net" -ImmutableId "$null"

The next step is to activate DirSync in the Office 365 portal again, and then reinstall the Azure Active Directory Sync tool on a server in the new domain. I strongly recommend using a new server for this step. Re-using the old server (after joining it to the new domain) might break your sync.

After the installation a full sync is done. The Sync tool will identify and match the users in Office 365 and Active Directory by the primary email address. If a match is found a new ImmutableID is created and written to Azure Active Directory.

Finally, after the initial sync is done we can look in Synchronization Service Manager to check that all users were matched and that we don’t have any sync errors.

/ Andreas

Advertisements

WMI Eventing: Watch folder and send files as email attachments, Part II

In my previous post I described how to create a WMI Event to watch a folder for changes. In this post I will make the WMI Event permanent so that it will work also after a computer restart.

Our script will have three parts:

  1. A definition of an event filter. I will reuse the same filter I used in Part I of this post.
  2. An event consumer which handles our event, in this case I will use a CommandLine Event Consumer.
  3. A part that binds the filter and the consumer together.

Let’s look at our WMI Event Query again:

$Query = @"
Select * from __InstanceCreationEvent within 10
where targetInstance isa 'Cim_DirectoryContainsFile'
and targetInstance.GroupComponent = 'Win32_Directory.Name="C:\\\\Data"'
"@

We have defined a query that monitors the C:\Data folder for changes, with an interval of 10 seconds. Lets put this into a Event Filter:

$WMIEventFilter = Set-WmiInstance -Class __EventFilter `
    -NameSpace "root\subscription" `
    -Arguments @{Name="WatchFolder2EmailFilter";
                 EventNameSpace="root\cimv2";
                 QueryLanguage="WQL";
                 Query=$Query
                }

Next it is time for the Event Consumer. This is where we define what to do when the event is triggered. I will use the CommandLineEventConsumer that runs a command line. Another option would be to use ActiveScriptEventConsumer to run a script, but it only supports VBScript and we need PowerShell for our script.

If you want to read more about the different Event Consumers there’s more information on MSDN.

I have to define the Executable, CommandLine and the parameters to use for powershell.exe.

$WMIEventConsumer = Set-WmiInstance -Class CommandLineEventConsumer `
    -Namespace "root\subscription" `
    -Arguments @{Name="WatchFolder2EmailConsumer";
                 ExecutablePath = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe";
                 CommandLineTemplate =" C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe –ExecutionPolicy Bypass -File c:\\WatchFolder2Email.ps1"
                }

As you can see I run the script file WatchFolder2Email.ps1 which we created in Part I of this post. I also specify ExecutionPolicy on the command line to make sure that I can run the script file.

Finally we bind our Filter and Consumer together:

Set-WmiInstance -Class __FilterToConsumerBinding `
                -Namespace "root\subscription" `
                -Arguments @{Filter=$WMIEventFilter;
                             Consumer=$WMIEventConsumer
                            }

There we go! Now we have a persistent WMI Event that watches our folder for changes, and then triggers our script that sends an email with the files as attachments.

Now we know how to create a persistent WMI Event. To remove it just run the following lines:

Get-WmiObject __EventFilter -namespace root\subscription -filter "name='WatchFolder2EmailFilter'" | Remove-WmiObject
Get-WmiObject CommandLineEventConsumer -Namespace root\subscription -filter "name='WatchFolder2EmailConsumer'" | Remove-WmiObject
Get-WmiObject __FilterToConsumerBinding -Namespace root\subscription -Filter "Filter = ""__eventfilter.name='WatchFolder2EmailFilter'""" | Remove-WmiObject

/ Andreas

Change from ADFS to Password Sync in Office 365

Disclaimer
This blog post is reflecting how the process was made in early 2014, and has since then been replaced with built-in tools in ADConnect.

Some time ago Johan wrote a post about how to convert a federated domain to a standard domain in Office 365. Much has happened since, and one feature that has been added is to synchronize the passwords between on-premises Active Directory and Office 365.

An important reason to implement ADFS and federation is to keep all user passwords the same, to reduce support and Helpdesk calls. The drawback is that a high availability server (or even better, an ADFS farm distributed over several locations) is needed on-premises. If your internet connection is dropping, no-one will able to access Exchange or SharePoint even though it is located “in the Cloud”.

Now when Password Sync is available, some organizations choose to retire their ADFS servers to implement Password Sync instead. It is now possible to have the same password in all systems without those high availability servers.

The process to move to a non-federated state with Password Sync is similar to how Johan described the process, but to minimize the service interruption for your end-users there are a few things to think of. All steps below are performed on the server hosting the Azure Active Directory Sync tool.

  1. First of all you need to upgrade to the latest version of Azure Active Directory Sync tool. The upgrade process is simple; just uninstall the old version and install the new one. The installation may take a few minutes. Make sure that you check the Password Sync option during installation. End by running the Configuration Wizard.
  2. Check the Synchronization Service Manager (miisclient.exe) to make sure that the synchronization was successful. You may have to log off and log in again to be able to access Synchronization Service Manager.
  3. Time for PowerShell 🙂
    #Connect to MSOnline
    Import-Module MSOnline
    
    #Enter credentials
    Connect-MsolService
    
    #Set context and credentials
    Set-MsolADFSContext -Computer srv01
    
    #From here on the Office 365 services will be unavailable 
    #for the end-users until all passwords are in in sync.
    
    #This is where we change our federated domain to a standard domain.
    #This command generates a text file with new random passwords for all users.
    #In the next step we will replace these passwords with passwords synced from AD.
    Convert-MSOLDomainToStandard -DomainName "mydomain.com" -SkipUserConversion $false -PasswordFile C:\userpwd.txt
    
    #Make sure that all user passwords has ForceChangePassword flag set to False
    Get-MsolUser -Synchronized | Set-MsolUserPassword -ForceChangePassword $false
    
    
  4. Now all users are converted. A file with new temporary passwords for each user is saved to the path you specified, but we will not use it. Instead, we now have to initialize a full password sync.

To see the status of the password sync you should check the Application Log in Event Viewer for event id 657. When all passwords are synchronised the users will be able to access their services again. This will take a few minutes depending on the number of users in your environment.

/ Andreas

WMI Eventing: Watch folder and send files as email attachments, Part I

I got a question from one of my colleagues if I knew an application that watched a folder for files, and then emailed all files to a specific address. My immediate answer was that this can very easily be achieved with PowerShell.

Short version

First we define the action to trigger when a file is found. We simply enumerate all files with Get-ChildItem and then use the standard Send-MailMessage cmdlet to send them to the recipient.

WatchFolder2Email.ps1

$WatchFolder = "C:\Data"
$To = "admin@365lab.net"
$From = "noreply@365lab.net"
$Subject = "File transfer"
$Body = "Please check the attached file(s)."
$SMTPServer = "smtp.cloud.net"
 
$Files = (Get-ChildItem $WatchFolder).FullName
if ($Files) {
    Send-MailMessage -From $From -To $To -Subject $Subject -Body $Body -SmtpServer $SMTPServer -Attachments $Files
    Remove-Item $Files
}

Now we could call our problem solved. Just put the code in a .ps1 file and use Task Scheduler to run it every 10 minutes.

Advanced version

Another way of triggering the script is to use WMI Eventing. There are different types of events, I will use an intrinsic event which are events triggered on a change to the CIM database, for example a change in the file system. This is defined in a WQL Query that also defines an interval and which folder to watch.

SELECT * FROM __InstanceCreationEvent WITHIN 10 
WHERE TargetInstance ISA "CIM_DirectoryContainsFile" 
AND TargetInstance.GroupComponent = "Win32_Directory.Name=\"c:\\\\Data\""

Let’s put the script together and register the event.

$action = {
    $WatchFolder = "C:\Data"
    $To = "admin@365lab.net"
    $From = "noreply@365lab.net"
    $Subject = "File transfer"
    $Body = "Please check the attached file(s)."
    $SMTPServer = "smtp.cloud.net"
        
    $Files = (Get-ChildItem $WatchFolder).FullName 
    if ($Files) {
        Send-MailMessage -From $From -To $To -Subject $Subject -Body $Body -SmtpServer $SMTPServer -Attachments $Files 
        Remove-Item $Files
    }
}

$WQLQuery = 'SELECT * FROM __InstanceCreationEvent WITHIN 10 
             WHERE TargetInstance ISA "CIM_DirectoryContainsFile"
             AND TargetInstance.GroupComponent = "Win32_Directory.Name=\"c:\\\\Data\""'

Register-WmiEvent -Query $WQLQuery -SourceIdentifier "WatchFolder2Email" -Action $action  

Now we can put a file in the C:\Data folder. Within seconds it will be sent by email and then deleted.

To remove the Job we simply run

Unregister-Event -SourceIdentifier "WatchFolder2Email" 

Unfortunately this solution is limited to your PowerShell session only. When you close the window the job is terminated. In Part II of this blog post I will show how to create a Permanent WMI Consumer that will work also after computer restarts.

/ Andreas

Synchronizing users between two AD Domains without trust

AD LDS SyncRecently I had an interesting job at a customer, who used a web application with an address book. The web application used LDAP queries to Active Directory to get all users addresses and phone numbers. This information was then searchable on a public web site located in DMZ.

The problem here is that we had an internet facing server. We could not allow it to make queries directly to the internal Active Directory. We decided to install a new AD LDS (Lightweight Directory Services) partition in the DMZ where we could ask our queries.

Next problem: We have to populate our new LDS partition with users. Normally this would be done with a system designed for it, like Forefront Identity Manager. Here we needed a quick-and-dirty solution to get started, and I therefore wrote a PowerShell script that copied the attributes we needed from our Active Directory.

One of the first things to solve was to find who and what to sync. With ten thousands of users in Active Directory we needed a solution to select users, even though they were spread across several OUs. Also, Swedish laws regulate what information to publish, so we had to filter sensitive information.

We ended up with a solution where only users in a specific OU were synced, but they also needed to be member of a specific group. We also filtered disabled users.

$ADUsers = Get-ADGroupMember -Identity "grp_ADSyncUsers" -Recursive | Get-ADUser -Properties `
     SamAccountName,GivenName,Surname,DisplayName,Title,Office,Department,Company,EmailAddress,Mobile,OfficePhone,Modified,Enabled | `
     Where-Object { $_.Enabled -eq $true -and $_.DistinguishedName -like "*$sourceou" }

We now have too much data. Our sync will be too slow, so we need to filter again.

There is no need to process accounts that haven’t been changed recently. We simply checked the Modified attribute in active directory and skipped these.

if ((get-date $modified) -ge ($(get-date).AddDays(-1.5))) {
    ...
}

We also needed a method to detect deleted accounts. Here we simply got all users in AD LDS and compared it with the users in AD DS. Any extra users were removed.

Compare-Object $PhoneBookUsers.Name $ADUserNames | Where-Object { $_.SideIndicator -eq "<=" } | ForEach-Object {
    $id="cn=$($_.InputObject),$destou"
    Remove-ADUser -Identity $id -Server $destserver -Credential $cred -Partition $destpartition -Confirm:$false
}

Below is the entire script, but as I wrote, it is a quick-and-dirty solution just to get started.

$destserver = "10.10.10.5"
$destpartition = "dc=cloud,dc=net"
$destou = "ou=PhoneBook,dc=cloud,dc=net"
$sourceou = "ou=MyBusiness,dc=contoso,dc=com"

$cred = Get-Credential
$i = 0

Import-Module ActiveDirectory

# Define users to process

    Write-Host "Collecting user information..."

    $ADUsers = Get-ADGroupMember -Identity "grp_ADSyncUsers" -Recursive | Get-ADUser -Properties `
            SamAccountName,GivenName,Surname,DisplayName,Title,Office,Department,Company,EmailAddress,Mobile,OfficePhone,Modified,Enabled | `
            Where-Object { $_.Enabled -eq $true -and $_.DistinguishedName -like "*$sourceou" }

    Write-Host "$($ADUsers.Count) user(s) found in Active Directory."

# Create/update users

    $ADUsers | ForEach-Object {

        $sam = $_.Name
        $givenname = $_.GivenName
        $surname = $_.Surname
        $displayname = $_.DisplayName
        $email = $_.EmailAddress
        $department = $_.Department
        $office = $_.Office
        $title = $_.Title
        $company = $_.Company
        $tele = $_.OfficePhone
        $mobil = $_.Mobile
        $modified = $_.Modified

        if ((get-date $modified) -ge ($(get-date).AddDays(-1.5))) {
            # Process user accounts modified the last 36 hours

            $i++;

            try {
                # Create new account in AD LDS
                New-ADUser -SamAccountName $sam -Name $sam -DisplayName $displayname -GivenName $givenname `
                    -SurName $surname -EmailAddress $email -Title $title -Office $office -Department $department `
                    -Company $company -OfficePhone $tele -MobilePhone $mobil `
                    -Enabled $false -Path $destou –Type iNetOrgPerson `
                    -Server $destserver `
                    -Credential $cred

                Write-Host "Creating user $sam"

            } catch [System.Exception] {
                switch($_.Exception.GetType().FullName) {
                    'Microsoft.ActiveDirectory.Management.ADException' {
                        # User already exists. Update existing account
                        Set-ADUser -Identity "cn=$sam,$destou" -DisplayName $displayname -GivenName $givenname -SurName $surname `
                            -EmailAddress $email -Title $title -Office $office -Department $department `
                            -Company $company -OfficePhone $tele -MobilePhone $mobil `
                            -Server $destserver `
                            -Credential $cred `
                            -Partition $destpartition
                        Write-Host "Updating user $sam"
                    }
                }
            }
        }
    }

# Remove deleted accounts

Write-Host "$i user(s) processed."
Write-Host "Searching for users to delete..."
$i=0

    $PhoneBookUsers = Get-ADUser -Server $destserver -SearchBase $destou -Filter * -Credential $cred | Select -Property Name
    $ADUserNames += $ADUsers.Name

    Compare-Object $PhoneBookUsers.Name $ADUserNames | Where-Object { $_.SideIndicator -eq "<=" } | ForEach-Object {
        $id="cn=$($_.InputObject),$destou"
        Remove-ADUser -Identity $id -Server $destserver -Credential $cred -Partition $destpartition -Confirm:$false
        Write-Host "Deleting user $($_.InputObject)"
        $i++
    }

Write-Host "$i user(s) deleted."
Write-Host "Done!"

/ Andreas

Dealing with drive letters in PowerShell

Have you ever wondered why the newly installed disk gets a drive letter in the middle of the alphabet even though E: is free? Why that network drive refuses to connect with the drive letter you want? The reason is most probably that the drive letter you want has been reserved for removable drives even though no media is inserted. This has been an problem for some time now since many computers are shipped with built-in multimedia card readers.

W8-storage-management

In the above example, if you for example try to map a network drive as F: it will fail because it is already reserved by a SD card reader, even when no card is inserted. The solution: change the drive letters to something that you know will never conflict.

Here’s a PowerShell script that automatically reassigns drive letters on removable drives to a predefined set of “approved” drive letters. The script uses the standard mountvol tool, so it also works in older Windows versions.

Replace-DriveLetters.ps1

$ForbiddenDrives = @("E:","F:","G:","H:")
$AllowedDrives = @("W:","X:","Y:","Z:")

$RemovableDrives = Get-WmiObject -Class Win32_Volume -Filter "DriveType=2"
$DrivesToReassign = $RemovableDrives | Where-Object { $ForbiddenDrives -contains $_.DriveLetter }
$i=0

foreach ($Drive in $DrivesToReassign) {
     &mountvol $Drive.DriveLetter /D
     &mountvol $AllowedDrives[$i++] $Drive.DeviceID
}

In an Enterprise Environment this is easy to implement in for example Microsoft Deployment Toolkit. That way your clients will never fail mapping network drives.

MDT

/ Andreas

Managing Office 365 e-mail addresses easy with PowerShell when using DirSync

In most cases you uninstall your local Exchange server after migrating your e-mail to Exchange Online. If you also choose to implement DirSync you place the administration in your local domain instead of the Office 365 Administration Portal.

This means that there is no longer a GUI tool to handle some of the settings related to your mailboxes. Of course you can keep a machine with your old Exchange 2010 Management Console, but often the reason for moving to the cloud is to reduce the complexity and minimize the number of machines.

We don’t have the same problem with other attributes, for example displayname or phone numbers that you’ll find in Users and Computers or the Administrative Center introduced in Windows Server 2008. It does get a bit trickier when you are about to modify primary email address or add alias addresses.

Currently there are no available GUI tools from Microsoft to handle these types of updates easy (but keep your eyes open, things might change 😉 ). However, there are a few third-party tools to achieve this, unless you want to use the Attribute Editor in Users and Computers, or Adsiedit.

Since I’m a big fan of PowerShell I wanted to build a small set of tools to handle simple changes of email addresses. These functions are easy for you to use in your own scripts.

First of all, a function that lists all email addresses of a user:

function Get-O365AliasAddress {
<#
.SYNOPSIS
    Displays all email addresses assigned to a user.
.PARAMETER Identity
    The user to query.
.EXAMPLE
    Get-O365AliasAddress -Identity user01
.EXAMPLE
    Get-ADUser user01 | Get-O365AliasAddress
.NOTES
    Author: Andreas Lindahl
    Blog: 365lab.net
    Email: andreas.lindahl[at]jsc.se
    The script are provided “AS IS” with no guarantees, no warranties, and they confer no rights.
#>

    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipelineByPropertyName=$true,Mandatory=$True,ValueFromPipeline=$True,
        HelpMessage="The name of the user to get addresses of")]
        [string]$Identity
    )

    Import-Module ActiveDirectory

    $result = @()

    (Get-ADUser -Identity $Identity -Properties proxyAddresses).proxyAddresses | foreach {
        $proxy =  $_.split(":")
        $object = New-Object System.Object
        $object | Add-Member –Type NoteProperty –Name Type –Value $proxy[0]
        $object | Add-Member –Type NoteProperty –Name Address –Value $proxy[1]
        $object | Add-Member –Type NoteProperty –Name IsPrimary –Value ($proxy[0] -ceq $($proxy[0].ToUpper()))
        $result += $object
    }
    return $result
}

Next, a function that adds an alias to an existing user

function Add-O365AliasAddress {
<#
.SYNOPSIS
    Adds an alias address to a user.
.PARAMETER Identity
    The user to modify.
.PARAMETER Address
    The address to add.
.PARAMETER Type
    The type of address. Default is smtp.
.PARAMETER SetAsDefault
    Indicates if the address should be de default address of the user
.EXAMPLE
    Add-O365AliasAddress -Identity user01 -Address test@365lab.net -SetAsPrimary
.NOTES
    Author: Andreas Lindahl
    Blog: 365lab.net
    Email: andreas.lindahl[at]jsc.se
    The script are provided “AS IS” with no guarantees, no warranties, and they confer no rights.
#>

    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipelineByPropertyName=$true,Mandatory=$True,ValueFromPipeline=$True,
        HelpMessage="The name of the user")]
        [string]$Identity,

        [Parameter(Mandatory=$True,
        HelpMessage="The address to add")]
        [string]$Address,

        [Parameter(
        HelpMessage="The type of the address to add")]
        [string]$Type="smtp",

        [Parameter(
        HelpMessage="Indicates that the address will be set as the default address")]
        [switch]$SetAsDefault
    )

    Import-Module ActiveDirectory

    $Type = $Type.ToLower()

    $defaultaddress = ''
    $proxyaddresses = ''
    $proxyaddress = ''

    #Get all existing proxyAddresses of the same type
    $proxyaddresses = (Get-ADUser -Identity $Identity -Properties proxyAddresses).proxyAddresses | where-object { $_ -like "$Type*" }

    #Get current default address of this type
    foreach ($proxyaddress in $proxyaddresses) {
        $pa = $proxyaddress.split(':')
        if ($pa[0] -ceq $pa[0].ToUpper()) {
            $defaultaddress = $proxyaddress
        }
    }

    #If this is the first address, it will be the default
    if ($proxyaddresses.count -eq 0) {
        $SetAsDefault = $true
    }

    if ($SetAsDefault) {

        #New default address to set. Start by removing the old one, but keep it as alias.
        if ($defaultaddress) {
            $pa = $defaultaddress.split(':')
            $newdefaultaddress = "$($pa[0].ToLower()):$($pa[1])"
            Set-ADUser -Identity $Identity -Remove @{proxyaddresses=$defaultaddress} -Add @{proxyaddresses=$newdefaultaddress}
        }

        #Set new default address. In case it already exists, remove it first (it might already be used as alias)
        if ($Type -eq 'SMTP') {
            Set-ADUser -Identity $Identity -Remove @{proxyaddresses="$($Type.ToLower()):$Address" } -Add @{proxyaddresses="$($Type.ToUpper()):$Address" } -EmailAddress $Address
        } else {
            Set-ADUser -Identity $Identity -Remove @{proxyaddresses="$($Type.ToLower()):$Address" } -Add @{proxyaddresses="$($Type.ToUpper()):$Address" }
        }

    } else {
        #Just add the new address
        Set-ADUser -Identity $Identity -Add @{proxyaddresses="$($Type):$Address" }
    }
}

Finally we also need a script to delete addresses

function Remove-O365AliasAddress {
<#
.SYNOPSIS
    Removes an alias address from a user.
.PARAMETER Identity
    The user to modify.
.PARAMETER Address
    The address to remove.
.PARAMETER Type
    The type of address. Default is smtp.
.EXAMPLE
    Remove-O365AliasAddress -Identity user01 -Address test@365lab.net
.NOTES
    Author: Andreas Lindahl
    Blog: 365lab.net
    Email: andreas.lindahl[at]jsc.se
    The script are provided “AS IS” with no guarantees, no warranties, and they confer no rights.
#>

    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipelineByPropertyName=$true,Mandatory=$True,ValueFromPipeline=$True,
        HelpMessage="The name of the user")]
        [string]$Identity,

        [Parameter(Mandatory=$True,
        HelpMessage="The address to remove")]
        [string]$Address,

        [Parameter(
        HelpMessage="The type of the address to remove")]
        [string]$Type="smtp"

    )

    Import-Module ActiveDirectory

    $Type = $Type.ToLower()
    $defaultaddress = ''
    $newdefaultaddress = ''
    $proxyaddresses = ''

    #Get all existing proxyAddresses of the same type
    $proxyaddresses = (Get-ADUser -Identity $Identity -Properties proxyAddresses).proxyAddresses | where-object { $_ -like "$Type*" }

    #Get current default address of this type
    foreach ($proxyaddress in $proxyaddresses) {
        $pa = $proxyaddress.split(':')
        if ($pa[0] -ceq $pa[0].ToUpper()) {
            $defaultaddress = $proxyaddress
        }
    }

    if ($defaultaddress -eq "$($Type):$Address") {
        #We are trying to remove the default address. Now it becomes a bit tricky...
        #First, find the next address of the same type that we can use as default address
        foreach ($proxyaddress in $proxyaddresses) {
            if ($proxyaddress -ne "$($Type):$Address") {
                $newdefaultaddress = $proxyaddress
                break
            }
        }
    }

    #Now we can remove the address
    Set-ADUser -Identity $Identity -Remove @{proxyaddresses="$($Type):$Address"}
    if ($Type -eq 'smtp' -and $defaultaddress -eq "$($Type):$Address") {
        Set-ADUser -Identity $Identity -Clear mail
    }

    if ($newdefaultaddress) {
        #Set $newdefaultaddress as new default address
        Write-Warning "New default address set: $newdefaultaddress"
        Add-O365AliasAddress -Identity $Identity -Address $newdefaultaddress.split(":")[1] -Type $Type -SetAsDefault
    }
}

Now we can play with these functions:

O365AliasAddress

I have put together a PowerShell Module for you to import. Just load it with the Import-Module cmdlet.

Import-Module O365ProxyAddresses

The module can be downloaded here.

I hope that you will find these functions useful in your daily user administration tasks. Please leave your comments below and feel free to suggest improvements.

Happy coding!

/ Andreas

Quick Tip: Using a custom domain to connect to Office 365 Webmail

Office365-sign-inAfter migrating to Office 365 it can be hard to communicate the new webmail addresses to all end-users. Especially when performing a cut-over migration all users suddenly need to be informed about the new address to use.

Office 365 uses many different host names, for example portal.microsoftonline.com and login.microsoftonline.com, both taking you to the portal page where users can access all services or download desktop software. But you still have to use the Outlook link in the portal page to access your mail. How can we make this simpler for the end-users?

If your tenant is set up with ADFS you want to use the address outlook.com/yourdomain.com. This automatically logs you on directly to the webmail using your domain credentials, allowing single sign-on to Office 365. Unfortunately with a custom domain this can only be achieved using a local web server that handles the redirect to the correct web page.

So, what to do then? I don’t want to set up a web server just to handle a redirect. Luckily there is an easy workaround to this: Use DNS to create a CNAME (for example mail.mydomain.com) that point to mail.office365.com. This presents a login screen for the users, and they will then be logged on directly to the webmail page.

The CNAME can be created using PowerShell cmdlets for DNS, which was introduced in Windows Server 2012:

Add-DnsServerResourceRecordCName -HostNameAlias mail.office365.com -Name mail -ZoneName mydomain.com

If you are using ADFS the users must check the “Keep me signed in” checkbox to handle single sign-on in the future.

/ Andreas