Showing posts with label Exchange2010. Show all posts
Showing posts with label Exchange2010. Show all posts

Sunday, December 29, 2019

Automating the Creation of Linked Mailboxes

Our regional IT teams have been delegated rights to create new user accounts in their own OUs. However, access to the on-premises Exchange org is limited to one or two org admins, as well as recipient admin rights for our Service Desk team.

Since there can sometimes be a significant delay between the creation of a new user account and the creation of that user's mailbox (mostly due to different timezones), I developed the following script to both reduce the load on the Service Desk and accelerate the creation of new mailboxes, which ultimately gives the regional IT teams more control over the provisioning process for their users.

The script is fairly specific to our environment, but hopefully the concepts will help others to create similar automation for their own systems. It currently runs as an hourly scheduled task. The regional IT teams simply need to add the string, "MailboxPlease" to the info attribute (on the Telephones tab) of the new user's account for the script to pick it up and create the linked mailbox.

<#
.SYNOPSIS
    NewLinkedMailbox - Creates a linked mailbox for accounts with MailboxPlease in the info attribute
    Created by Mike Koch on October 29, 2019
.DESCRIPTION
    Queries USERDOMAIN for user accounts that have 'MailboxPlease' in the 'info' attribute
        - this attribute is populated by the regional IT teams, which have been delegated rights to create user accounts in their own OUs
    Creates a matching account in the RESOURCEDOMAIN, in the appropriate region OU
    Sets the Company attribute on the mailbox account, to ensure that the proper email address policy is applied
    Creates the linked mailbox, then copies the primary smtp address back to the user's account in USERDOMAIN
    Clears 'MailboxPlease' from the info attribute
.NOTES
    Assumes the account running this script has sufficient rights to do the following:
        1. Read user properties in USERDOMAIN
        2. Create user accounts in RESOURCEDOMAIN
        3. Create linked mailboxes in the on-premises Exchange org
        4. Write user properties in USERDOMAIN (to write the primary smtp address back to the user's account)
#>
[CmdletBinding()]
Param()

$USERDOMAINDC = "dc1.USERDOMAIN.local"
$RESOURCEDOMAINDC = "dc1.RESOURCEDOMAIN.local"

###
# Query USERDOMAIN for enabled accounts containing "MailboxPlease" in the info attribute
###
$mbxrequests = @(Get-ADUser -Filter { enabled -eq $true -AND info -like "*MailboxPlease*" } -Server $USERDOMAINDC -SearchBase "ou=All Users,dc=USERDOMAIN,dc=local" -Properties givenname, sn, title, description, department, office, company, manager)

if (!$mbxrequests) {
    Write-Verbose "No mailbox requests detected."
}
else {
    foreach ($mbx in $mbxrequests) {
        Write-Verbose "Attempting mailbox creation for $($mbx.Name)..."
        $resAccountParms = @{ }
        $resAccountParms.Add("displayName", $mbx.Name)
        $resAccountParms.Add("userprincipalname", "$($mbx.SamAccountName)@COMPANYNAME.com")
        switch -Wildcard ($mbx.DistinguishedName) {
            "*Australia Associates*" { 
                $path = "ou=Australia,ou=LinkedMailboxAccounts,dc=RSEOURCEDOMAIN,dc=local"
                $resAccountParms.Add("company", "COMPANYNAME Australia") # required to trigger custom email address policy
            }
            "*Canada Associates*" { 
                $path = "ou=Canada,ou=LinkedMailboxAccounts,dc=RESOURCEDOMAIN,dc=local"
                $resAccountParms.Add("company", "COMPANYNAME Canada") # required to trigger custom email address policy
            }
            "*France Associates*" { 
                $path = "ou=France,ou=LinkedMailboxAccounts,dc=RESOURCEDOMAIN,dc=local"
                $resAccountParms.Add("company", "COMPANYNAME France") # required to trigger custom email address policy
            }
            "*Germany Associates*" { 
                $path = "ou=Germany,ou=LinkedMailboxAccounts,dc=RESOURCEDOMAIN,dc=local"
                if ($mbx.company) { $resAccountParms.Add("company", $mbx.company)}
            }
            "*Ireland Associates*" { 
                $path = "ou=Ireland,ou=LinkedMailboxAccounts,dc=RESOURCEDOMAIN,dc=local"
                if ($mbx.company) { $resAccountParms.Add("company", $mbx.company)}
            }
            "*Italy Associates*" {
                $path = "ou=Italy,ou=LinkedMailboxAccounts,dc=RESOURCEDOMAIN,dc=local"
                if ($mbx.company) { $resAccountParms.Add("company", $mbx.company)}
            }
            Default {
                $path = "ou=LinkedMailboxAccounts,dc=RESOURCEDOMAIN,dc=local"
                if ($mbx.company) { $resAccountParms.Add("company", $mbx.company)}
            }
        }
        if ($mbx.givenname) { $resAccountParms.Add("givenName", $mbx.GivenName) }
        if ($mbx.sn) { $resAccountParms.Add("sn", $mbx.sn) }
        if ($mbx.Department) { $resAccountParms.Add("department", $mbx.Department) }
        if ($mbx.description) { $resAccountParms.Add("description", $mbx.description) }
        if ($mbx.Title) { $resAccountParms.Add("title", $mbx.Title) }
        if ($mbx.office) { $resAccountParms.Add("physicalDeliveryOfficeName", $mbx.office) }
        $resAccountParms.Add("extensionAttribute1", "migrate.me")  # triggers migration script

        ### Let's see if we can locate this user's manager's mailbox account in RESOURCEDOMAIN
        if ($mbx.Manager) {
            $mgrsam = (Get-ADUser $mbx.Manager -Server $USERDOMAINDC).SamAccountName
            $resAccountParms.Add("Manager", (Get-ADUser $mgrsam -Server $RESOURCEDOMAINDC).DistinguishedName)
        }

        ### Make sure this samaccountname doesn't exist in RESOURCEDOMAIN
        if (!(Get-ADUser -Filter "samaccountname -eq '$($mbx.SamAccountName)'" -Server $RESOURCEDOMAINDC)) {
            Write-Verbose "Creating RESOURCEDOMAIN account for $($mbx.Name)..."
            New-ADUser -Name $mbx.Name -SamAccountName $mbx.SamAccountName -Enabled $FALSE -Path $path -Server $RESOURCEDOMAINDC -OtherAttributes $resAccountParms
            $resacct = Get-ADUser $mbx.SamAccountName -Server $RESOURCEDOMAINDC
            if ($resacct) {
                Write-Verbose "Creating linked mailbox for $($mbx.Name)..."
                $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://OnPremExchangeServer/powershell/ -Authentication Kerberos -AllowRedirection
                Import-PSSession $Session -CommandName Enable-Mailbox
                Enable-Mailbox -Identity $resacct.DistinguishedName -DomainController $RESOURCEDOMAINDC -Alias $resacct.SamAccountName -LinkedMasterAccount $mbx.DistinguishedName -LinkedDomainController $USERDOMAINDC
                Get-PSSession | Remove-PSSession

                ### copy email address to the USERDOMAIN account
                $email = (Get-ADUser $mbx.SamAccountName -Server $RESOURCEDOMAINDC -Properties mail).mail
                Set-ADUser $mbx.SamAccountName -Server $USERDOMAINDC -EmailAddress $email -Clear info
            } else {
                Write-Verbose "Creation of RESOURCEDOMAIN account failed $($mbx.SamAccountName)"
            }
        }
        else {
            Write-Verbose "Samaccountname already exists in RESOURCEDOMAIN ($($mbx.SamAccountName))."
        }
    }
}
Write-Verbose "Finished."

Wednesday, November 6, 2019

How to Fix a Linked Mailbox if Exchange Online has Already Created a Mailbox in the Cloud

Problem: In our hybrid Exchange 2010 / Exchange Online environment, a user somehow ends up with a mailbox in both Exchange orgs. After some background on our environment, I'll explain how this problem occurred, and the solution I developed to resolve it.

Background: DOMAIN1 is the domestic (U.S.) domain, and has both users and the on-premises Exchange 2010 servers. DOMAIN2 exists due to a merger, is in a separate forest, but there's a two-way trust between the forests ('cause we're all one big happy family). Azure AD Connect imports accounts from both forests, then exports accounts to a single O365 E5 tenant. Licensing for the O365 features is group-based, using global security groups that are mapped to license options in Azure AD. Not all employees are provisioned with a mailbox, so the Exchange feature license is controlled by its own AD security group.

When all mailboxes were in Exchange 2010, each DOMAIN1 user was provisioned with a regular User Mailbox. Users in DOMAIN2 had to be provisioned with a Linked Mailbox. For those in the audience that haven't experienced Exchange in a multi-forest environment before, it's important to understand that only objects in the same forest as the Exchange org can be mail-enabled. In order to provision a mailbox for a user in another forest, you have to create a Linked Mailbox, which involves creating a new, disabled user account in DOMAIN1, populate all the necessary Exchange attributes on that account, and then give the user in DOMAIN2 permission to use the DOMAIN1 user mailbox as if it were his/her own. The New-Mailbox command, available in both the Exchange Admin Shell and in the Exchange Admin Console, makes creating linked mailboxes a breeze. But it's very important to understand that, in order to have a mailbox in Exchange, every user in DOMAIN2 will end up having TWO accounts - their normal login account in DOMAIN2 and the disabled mailbox account in DOMAIN1. No Exchange attributes get written to the account in DOMAIN2. In fact, provisioning a linked mailbox makes no changes to the account in DOMAIN2 at all. If you review every attribute of the DOMAIN2 account, there is absolutely no indication that this account has a mailbox.

As we began our journey to O365 and Exchange Online, it was nice to discover that, for DOMAIN2 users with linked mailboxes, as Azure AD Connect imports their accounts from both forests, it will recognize and intelligently merge their primary user account data from DOMAIN2 with the Exchange attributes from their account in DOMAIN1, and then export that combined information to create a single account in Azure/O365 containing all the necessary attributes.

Those last two paragraphs may seem to have gone a bit off-topic, but I described all of that just to help you understand that this is still how we're provisioning mailboxes today, even though everyone has been migrated to Exchange Online. So, this is how we currently provision mailboxes for new hires:

  • For new users in DOMAIN1, we simply run the Enable-RemoteMailbox command in the on-premises Exchange org, which stamps the account with all the necessary attributes. The account is also added to the AD security group that is used to assign the Exchange Online feature license, so once the account syncs up into Azure and Exchange, a mailbox will be created and the feature license assigned, automatically. Simple and easy, works every time.
  • For users in DOMAIN2, we can't just enable a remote mailbox since Exchange can't see the user accounts in DOMAIN2, so as before, we have to create a linked mailbox in the on-premises Exchange org, wait for Azure AD Connect to merge those accounts and export the changes to Azure, and then migrate the linked mailbox to Exchange Online. The account in DOMAIN2 also needs to be added to the AD security group that is used to assign the Exchange Online feature license, and it is the timing of this license assignment for linked mailbox users that can cause the issue that is the subject of this article.


Scenario: A help desk technician creates a new user account in DOMAIN2 and immediately adds that account to the O365 Exchange Online security group in Active Directory. Within 30 minutes, that account is imported and synced up into Azure AD by Azure AD Connect, and no further action is taken for several hours or longer. Since the account has no Exchange attributes but has been assigned an Exchange feature license, Exchange Online just goes right ahead and creates a mailbox for the user. Later (sometimes much later), the help desk receives a request to create a mailbox for this user, and so they proceed (correctly) to create a new linked mailbox in the on-premises Exchange org. The on-premises Exchange server allows the mailbox to be created, since it has no knowledge of the mailbox that Exchange Online had already created.

Side effects: As described previously, in creating the linked mailbox, a disabled DOMAIN1 account was created and populated with all the usual Exchange attributes. Azure AD Connect correctly sees this account, sees that it is linked to the DOMAIN2 account, correctly merges the two accounts together and exports the now-modified account data up into Azure AD. The user's Exchange Online mailbox is updated with all the Exchange attributes that were stamped on the user's DOMAIN1 account during the linked mailbox creation, so the Exchange Online mailbox now has all the proper details of a full-blown company mailbox. But...

What's the problem? Well, we still have that linked mailbox that was created back in the on-premises Exchange environment. And that's a big problem because both the linked mailbox and the cloud mailbox have the same SMTP addresses on them (with a few exceptions), and if we leave both of them as-is, any mail received by the on-premises Exchange org will be delivered to the on-premises mailbox (because that's the only one that the on-premises Exchange server knows about), and any mail received by Exchange Online will be delivered to the Exchange Online mailbox (ditto) - and the user, who is probably only aware of the Exchange Online mailbox, will have no idea why they're getting some emails but not others.

So, how do we fix this? In the recent past, I would simply delete the mistakenly-created Exchange Online mailbox and then migrate the linked mailbox, which didn't always go smoothly. Plus, that's a lot of work, so I started looking for a simpler, more reliable alternative. Since we ultimately want to keep the Exchange Online mailbox (which may have been receiving important mail by now) and it's already configured correctly, thanks to Azure AD Connect, I realized that all I really needed to do was to get rid of the linked mailbox and make the on-premises Exchange org aware of the Exchange Online mailbox's presence. The solution I came up with is to modify the Exchange properties on the disabled DOMAIN1 account, by either adding, modifying, or deleting the attributes that control how the on-premises Exchange org "sees" that user object.

This solution requires quite a few attribute changes, and after modifying five or six accounts by bringing up another account and comparing attribute values, it quickly became clear that the manual process was far too labor-intensive and prone to mistakes. It's too easy to forget which attributes need to be modified, and copying values from one account to another is just so boring and slow. So now I've collected all of those attributes into an easily repeatable powershell script. We'll use the Set-ADUser command to make our changes all at once, but we need to do a bit of set up before we can run it.

Caveat: Please consider the attributes and values you see below as specific to my company's Exchange environment. While it is quite likely that your org uses many of the same attributes and possibly even the same attribute values, I strongly urge you to compare these with those on your user accounts and make adjustments as needed before you attempt to run this in your environment. I can only confirm that what follows below seems to work well for our environment.

There's only one attribute that is user-specific, and that's the targetAddress, which is what the on-premises Exchange org uses to route the user's mail up to their Exchange Online mailbox. So our first step is to set a variable to the samaccountname of the user's disabled DOMAIN1 account. We'll use that to retrieve this account's mailNickname (which was set when the linked mailbox was created), then build the targetAddress.

$aduser = ""

$target = "SMTP:" + (get-aduser $aduser -Properties mailnickname).mailnickname + "@COMPANY.mail.onmicrosoft.com"

Some attributes don't exist on this user's account yet, so we'll have to add them. These attributes have the same values for all remote mailboxes, so we'll create a hash table with all the proper values hard-coded right in. Note that the $target variable is used in this hash table, to set the targetAddress.

$addhash = @{ `
    msExchAddressBookFlags=1; `
    msExchArchiveQuota=52428800; `
    msExchArchiveWarnQuota=47185920; `
    msExchBypassAudit=$false; `
    msExchMailboxAuditEnable=$false; `
    msExchMailboxAuditLogAgeLimit=7776000; `
    msExchMDBRulesQuota=64; `
    msExchModerationFlags=6; `
    msExchProvisioningFlags=0; `
    msExchRemoteRecipientType=4; `
    msExchTransportRecipientSettingsFlags=0; `
    targetAddress=$target
}

A couple more attributes already exist but need to be changed, again with values that are common to all remote user mailboxes, so we'll create another hash table for those.

$replhash = @{ `
    msExchRecipientDisplayType=-2147483642; `
    msExchRecipientTypeDetails=2147483648
}

Finally, there are half a dozen attributes that just need to disappear (be cleared), so we'll create a variable containing an array of those attribute names.

$clearargs = `
    'homeMDB', `
    'homeMTA', `
    'mDBUseDefaults', `
    'msExchMailboxSecurityDescriptor', `
    'msExchHomeServerName', `
    'msExchRBACPolicyLink'

That's it for the prep work, so now we can run the command.

Set-ADUser $aduser `
    -Add $addhash `
    -Replace $replhash `
    -Clear $clearargs

If your account does not have rights to make these changes in Active Directory, you can specify alternate credentials:

Set-ADUser $aduser `
    -Add $addhash `
    -Replace $replhash `
    -Clear $clearargs `
    -Credential (Get-Credential)

Assuming the changes apply without error, there will be no output. And if you go back into the on-premises Exchange console, you will see that the linked mailbox object has disappeared, replaced with the familiar Remote User Mailbox object. And once all those changes have synced up into Azure and Exchange Online, everything will work as expected.

Troubleshooting: If something goes wrong and you don't see the Remote User Mailbox object in the Exchange console… first, try refreshing the listing again (remember, it'll be in the Mail Contact node). Exchange could be using a different domain controller than the one that processed your Set-ADUser command, so allow a few minutes for AD replication then refresh the list again. If all else fails, pull up the user's DOMAIN1 account side-by-side with another, working DOMAIN1 account, go to the Attribute Editor tab of both accounts and compare the settings. They won't all match, so just concentrate on the attributes listed in the above hashtables.

Ciao!

Saturday, August 25, 2012

Bulk Creation of Mail-Enabled Security Groups

During our recent implementation of System Center Service Manager 2012, I was asked to create several dozen mail-enabled security groups. After some discussion around naming conventions, I was provided with a CSV file containing the desired display names and aliases, that looked like this (this is only a partial list):


Display Name,Email Alias
ACCOUNTING,smaccounting
AUDIT  COMPLIANCE,smauditcompliance
AUSTRALIA,smaustralia
BACKUP ADMINS,smbackupadmins
CANADA,smcanada
CHANGE MANAGEMENT,smchangemgmt


In order to organize and differentiate these groups from similarly named groups that already existed in our directory, each group's display name would be prefixed with "zSvcMgr". The "z" pushed these entries down to the bottom of the address list where they would be less likely to be seen or used, yet they would still be grouped together alphabetically. The email aliases would also be prefixed by "sm". As you can see from the above sample list, the supplied file included the "sm" prefix on the email aliases, but the "zSvcMgr" prefix is missing from the display names. I'll have to add those in during the processing.

This is one of those situations where a script would be handy, but the single-use nature of the task doesn't really warrant a lot of development time. I decided to just write the code on the fly, but this could easily be turned into a full-fledged script if you need to do this sort of thing often.

I basically needed to do two distinct things - process each line in the CSV file to create the security groups and then mail-enable them. Creating the groups could be done from any standard powershell session, but mail enabling them requires the Exchange shell, so I just performed both tasks from an Exchange 2010 shell session. Here's all the code I needed to complete the job.

$list = Import-CSV SMGroupList.csv

Import-module ActiveDirectory
foreach ($entry in $list) {
$name = "zSvcMgr " + $entry."Display Name"
$alias = $entry."Email Alias"
New-ADGroup –Name $name –Path “ou=ServiceManager,ou=Distribution Lists,dc=mydomain,dc=pvt” –GroupScope universal
Enable-DistributionGroup -Identity "cn=$name,ou=ServiceManager,ou=Distribution Lists,dc=mydomain,dc=pvt" -alias $alias
}


That's all that's required. Let's take a more detailed look at each line.


$list = Import-CSV SMGroupList.csv


This line imports the CSV file into the $list variable. This loads the entire file into memory, and automatically uses the data in the first row as column headings.

Import-module ActiveDirectory

This command loads the Active Directory module into the shell, giving us access to the New-ADGroup command, which we'll use to create our new groups.

foreach ($entry in $list) {

Now we want to start processing the contents of the CSV file, which is stored in the $list variable. The foreach command will loop through the imported data line by line. For each iteration of the foreach loop, the $entry variable will contain the contents of the currently processing line. To access the various fields in the line, just refer to the field by using the column name.

$name = "zSvcMgr " + $entry."Display Name"

In the line above, I build the desired name of the group, taking the contents of the imported Display Name field (note: $entry."Display Name" references the Display Name field in the current $entry variable), append it to the end of the "zSvcMgr " string, then assign the new name to the $name variable.

$alias = $entry."Email Alias"

Next, we grab the Email Alias value from the current line in the $entry variable, and assign it to the $alias variable. Note that the double quotes around Email Alias is only required because the field name has a space in it. If the field name were "EmailAlias" (no spaces), then we could have left off the double quotes and referred to it as simply $entry.EmailAlias.

Now that we've populated our variables, we're ready to create our new group.

New-ADGroup –Name $name –Path “ou=ServiceManager,ou=Distribution Lists,dc=mydomain,dc=pvt” –GroupScope universal

Here, we pass the $name variable, which contains the group name we built above, to the New-ADGroup command. The -Path parameter tells the command where to create the new group object in our Active Directory hierarchy, and the -GroupScope parameter tells it to create a universal group. If you omit the -GroupScope parameter, a global group is created by default.

All that's left to do now is to mail-enable our newly-created group.

Enable-DistributionGroup -Identity "cn=$name,ou=ServiceManager,ou=Distribution Lists,dc=mydomain,dc=pvt" -alias $alias

Note that we're passing both the $name and $alias variables to the Enable-DistributionGroup command. Since we're not specifying a PrimarySmtpAddress parameter, our new group will receive an SMTP address from the default email address policy.


}


The ending curly brace signals the end of our foreach loop, and then the process repeats for the next line in the $list variable. Once all of the lines are processed, all of our shiny, new, mail-enabled security groups will be created. A quick peek inside the ServiceManager OU confirms that the groups exist, and you can confirm that they exist in Exchange by looking in the console, or by issuing a quick, "Get-DistributionGroup zSvcMgr*" command.

That's all there was to it. It took much less to time to write the code and create the groups than it did to write it all down and explain how it worked. I hope someone finds it useful.



Wednesday, June 22, 2011

Mailflow Cutover to Exchange 2010 SP1 (take 1)

Things were looking good. We started the change at 5pm. Out network guy modified the mail flow on the load balancers, then we tested OWA and ActiveSync pointing to both the 2007 and 2010 servers. Modified the external URLs on all the servers and CAS services, swapping the "owa" and "legacy" names as needed. Ran into a strange problem with proxying to our remote sites, but my consultant quickly located an article describing the fix, which worked like a charm. Everything seemed to be working as expected, so we called it a night.

Unrelated to the change, I was awakened at 4am because internet mail had stopped working. First thing I checked was the Forefront services, and found them stopped. Couldn't restart them because the WinHTTP Autodiscover service is disabled. On both servers. Set the startup to Manual, then restarted the Transport service, which brought up everything agin, restoring mail flow.


By the time I got into the office at 7am, reports had started trickling in, complaints that some phones weren't able to connect to Exchange. More investigation revealed a common thread - they were all Android phones. Still have to collect more data, because some Androids are working, while others are not. Shortly after, some Mac clients reported being unable to connect. Seems to be those with older Entourage clients. My own Outlook 2011 client works fine, and supposed the Entourage 2008 client with EWS support will also work. The desktop group is on the job, trying to identify which Macs are having issues, and getting them upgraded.


The phones will be a problem. How do you talk several hundred computer-illiterate people through upgrading their phones to a compatible OS version. And to make matters worse, one of our first upgraded phones still won't connect to 2010 and proxy to a 2007 mailbox. It will connect to either 2007 or 2010 directly, but attempting to proxy from a 2010 CAS to a 2007 mailbox won't work.


Tomorrow I'll pull a report of the ActiveSync client information, in order to identify potential upgrade candidates, so we can come up with a plan to move forward.