July 2009 - Posts

First off, I didn't use PowerShell because I use the script in this article as a login script - and while I'm sure that the Windows Scripting Host is installed everywhere, I can't be sure that PowerShell is installed everywhere. It will be a couple of years before I can do that.

Beginning with Exchange 2003 and Outlook 2003 (when running on Windows 2003), you could use RPC/HTTP (the feature was renamed to Outlook Anywhere with Exchange 2007). RPC/HTTP provides the capability of encapsulating the native protocol that Outlook uses to communicate with Exchange Server with a secure HTTP tunnel. Long story short, it's a way of safely getting around firewalls.

The name of the protocol that Outlook prefers to use with Exchange Server is called MAPI - Message Applications Programming Interface. Let's suffice it to say that MAPI is very powerful, very extensible, allows you to do darn near anything with a message or with Exchange that you might ever want to, and as is typical with all that power - MAPI is complicated to use. And you won't be using MAPI in a script!

I can't give MAPI justice in a blog post. But let's talk about a few basic MAPI concepts.

Providers - a MAPI provider is some type of a server process (note: this does NOT mean that it has to run on a server, just that it provides services to clients). A few sample MAPI providers include the Address Book provider, the Spooler (which queues and sends email to Exchange), and the Profile provider (which is responsible for managing Mail profiles on a per-user basis on a client workstation).

Profile - a MAPI profile defines everything that a messaging client (such as Outlook) may need to know about communicating with an email server. A single profile may contain information about all of the various providers available to the client, both locally and on remote servers.

Property - a MAPI property is no different than any other type of property. It contains a value that is relevant to a specific provider and that value is contained within a specific profile. In general, a MAPI property is referred to via something called a PROP_TAG (a property tag) which is a 32 bit binary value. The first 16 bits represent the type of the property (such as integer or string, etc.) plus a special flag bit or two. The second 16 bits of a property is the specific property identifier.

The profile provider, which can be accessed via raw MAPI (of course), uses the system registry to store information about MAPI properties, profiles, providers, and other MAPI objects. Officially, these items are opaque. However, they have been the same since Windows 95, so it's unlikely that they will be changing any time soon.

In the registry, each MAPI service associated with a particular profile has a GUID, which is a string. The particular GUID is assigned to the MAPI service, and is consistent across all MAPI installations. Each MAPI service has a series of property values assigned to it, and those property tags are represented by an 8-character value which is the hexidecimal representation of the binary property tag.

Today, we are particular interested in a single MAPI service - the Exchange Server Details MAPI service. This service is responsible for the connectivity to an Exchange server via the MAPI protocol. It has the following GUID:

Const ExchDetails = "13dbb0c8aa05101a9bb000aa002fc45a"

EVERY MAPI profile which connects to an Exchange server will have that MAPI service defined and its property values populated. The particular MAPI property we are interested in is named PR_ROH_FLAGS. It looks like this:

Const PT_LONG         = "0003"           ' hex MAPI type for a 4-byte binary value

PR_ROH_FLAGS          = PT_LONG & "6623" ' PROP_TAG (PT_LONG, 0x6623)

therefore the resultant value for PR_ROH_FLAGS is 00036623. Any MAPI profile which currently (or ever) was using RPC/HTTP (Outlook Anywhere) will have this property present. This flags value contains a number of documented (and conceivably undocumented) flags. They are:

'' Connect to my Exchange mailbox by using HTTP.
Const ROHFLAGS_USE_ROH            = &h1

'' Connect by using SSL only.
Const ROHFLAGS_SSL_ONLY           = &h2

'' Mutually authenticate the session when connecting by using SSL.
Const ROHFLAGS_MUTUAL_AUTH        = &h4

'' On fast networks, connect by using HTTP first. Then, connect by using TCP/IP.
Const ROHFLAGS_HTTP_FIRST_ON_FAST = &h8

'' On slow networks, connect by using HTTP first. Then, connect by using TCP/IP.
Const ROHFLAGS_HTTP_FIRST_ON_SLOW = &h20

You should note that the first flag (ROHFALGS_USE_ROH) indicates whether or not RPC over HTTP is enabled within this profile. The second flag (ROHFLAGS_SSL_ONLY) will always be set in the current implementation. If the ROHFLAGS_MUTUAL_AUTH flag is set, it means that both the client and the server will authenticate to each other.

The last two flags are the ones we are interested in... ROHFLAGS_HTTP_FIRST_ON_FAST and ROHFLAGS_HTTP_FIRST_ON_SLOW. They define whether HTTP is tried first, or TCP is tried first. If the boxes are checked, the flags are set. Conversely, if the flags are set, the boxes will be checked. Therefore, our goal is to make sure those flags are set.

Especially if you are using Basic authentication, having these flags set can result in faster Outlook startup time.

By default, Autodiscover in Exchange 2007 and above will set the ROHFLAGS_HTTP_FIRST_ON_SLOW flag, but not set the ROHFLAGS_HTTP_FIRST_ON_FAST flag. There is no way to change this behavior.

The following script is much longer than an equivalent script in PowerShell would be, and it's longer than it has to be in VBScript, but I wanted to make it easy to understand and provide resources for other scripters wanting to make profile modifications.

Obligatory disclaimers:

Modifying your registry can break your computer.

Modifying MAPI properties by modifying the registry may not be supported, I really don't know. It works for me and my clients, but I can't guarantee that it will for you.

On to the script:

''
'' httpFirstAlways.vbs
''
''
'' Michael B. Smith
'' michael@TheEssentialExchange.com
'' July, 2009
''
'' No warranties express or implied are available.
'' Use at your own risk - but it works for me!
''
'' This routine, for the current user, scans through all Exchange profiles. If
'' the RPC over HTTP (Outlook Anywhere) flags value is present, this means that
'' RPC over HTTP is configured for the profile. If the flag is present, this
'' routine will force "On fast networks, connect using HTTP first" and "On slow
'' networks, connect using HTTP first" to be checked.
''
Const ProfilePath = "Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles"
Const ExchDetails = "13dbb0c8aa05101a9bb000aa002fc45a" ' Exchange Server Details MAPI service

Const PT_LONG     = "0003" ' hex MAPI type for a 4-byte binary value
COnst PT_UNICODE  = "001f" ' hex MAPI type for a Unicode string

' Used to configure cached mode, but not used in this script

Dim PR_CONFIG_FLAGS          ' configuration flags for the Exchange Server Details MAPI service

PR_CONFIG_FLAGS          = PT_LONG    & "6601" ' PROP_TAG (PT_LONG, 0x6601)

'' MAPI properties for RPC over HTTP
'' From KB 898835 - MAPI properties for RPC over HTTP settings
'' http://support.microsoft.com/kb/898835

Dim PR_ROH_FLAGS             ' RPC over HTTP flags
Dim PR_ROH_PROXY_SERVER      ' RPC over HTTP proxy server
Dim PR_ROH_PRINCIPAL_NAME    ' Primary name on the SSL certificate 
Dim PR_ROH_PROXY_AUTH_SCHEME ' Basic or NTLM

PR_ROH_FLAGS             = PT_LONG    & "6623" ' PROP_TAG (PT_LONG,    0x6623)
PR_ROH_PROXY_SERVER      = PT_UNICODE & "6622" ' PROP_TAG (PT_UNICODE, 0x6622)
PR_ROH_PRINCIPAL_NAME    = PT_UNICODE & "6625" ' PROP_TAG (PT_UNICODE, 0x6625)
PR_ROH_PROXY_AUTH_SCHEME = PT_LONG    & "6627" ' PROP_TAG (PT_LONG,    0x6627)

'' Flags that are used in PR_ROH_FLAGS
'' Connect to my Exchange mailbox by using HTTP.
Const ROHFLAGS_USE_ROH            = &h1
'' Connect by using SSL only.
Const ROHFLAGS_SSL_ONLY           = &h2
'' Mutually authenticate the session when connecting by using SSL.
Const ROHFLAGS_MUTUAL_AUTH        = &h4
'' On fast networks, connect by using HTTP first. Then, connect by using TCP/IP.
Const ROHFLAGS_HTTP_FIRST_ON_FAST = &h8
'' On slow networks, connect by using HTTP first. Then, connect by using TCP/IP.
Const ROHFLAGS_HTTP_FIRST_ON_SLOW = &h20

'' Values that are used in PR_ROH_PROXY_AUTH_SCHEME
'' Basic authentication
Const ROHAUTH_BASIC               = &h1
'' NTLM authentication
Const ROHAUTH_NTLM                = &h2

Const HKEY_CURRENT_USER = &h80000001

Set objReg = GetObject ("winmgmts:{impersonationLevel=impersonate}!\\.\root\default:StdRegProv")

objReg.EnumKey HKEY_CURRENT_USER, ProfilePath, arrSubKeys

For Each objSubKey in arrSubKeys

	strMAPIpropKey   = ProfilePath  & "\" & _
                           objSubKey    & "\" & _
                           ExchDetails

        strMAPIpropValue = PR_ROH_FLAGS

	e "Checking profile named " & objSubKey

	On Error Resume Next
	Err.Clear

	objReg.GetBinaryValue HKEY_CURRENT_USER, strMAPIpropKey, strMAPIpropValue, arrBinary

	On Error Goto 0

	If IsNull (arrBinary) Or Err Then
		e "...property value not found"
	Else
		'e "Values (" & UBound (arrBinary) & ")"
		'For i = 0 To UBound (arrBinary)
		'	e i & " " & arrBinary(i) & " " & Hex(arrBinary (i))
		'Next

		val = arrBinary (0)
		If (val and ROHFLAGS_HTTP_FIRST_ON_FAST) = ROHFLAGS_HTTP_FIRST_ON_FAST Then
			e "...On fast networks, connect using HTTP first --- IS SET"
		End If

		If (val and ROHFLAGS_HTTP_FIRST_ON_SLOW) = ROHFLAGS_HTTP_FIRST_ON_SLOW Then
			e "...On slow networks, connect using HTTP first --- IS SET"
		End If

		bitVal = ROHFLAGS_HTTP_FIRST_ON_SLOW Or ROHFLAGS_HTTP_FIRST_ON_FAST

		If (val and bitVal) = bitVal Then
			e "...Desired options are already set"
		Else
			e "...Updating"
			arrBinary(0) = arrBinary(0) Or bitVal
			result = objReg.SetBinaryValue (HKEY_CURRENT_USER, strMAPIpropKey, strMAPIpropValue, arrBinary)
			If result <> 0 Then
				e "...Could not update HTTP network value (" & result & ") !!!"
			Else
				e "...update complete"
			End If
		End If

	End If
Next

e "Done"

Set objReg = Nothing

Sub e(str)
	WScript.Echo str
End Sub

Until next time...

If there are things you would like to see written about, please let me know!

P.S. Thanks to Diane Poremsky and John Fullbright for assistance in creating this article.

Posted by michael | with no comments

I'm working with a client this week on migrating them from Exchange Server 2003 to Exchange Server 2007.

Their Exchange 2003 environment is a weird one - a A/A/A/P/P cluster. That's right - Active/Active/Active/Passive/Passive. Whoever sold them that solution made some serious money! :-)

I'm moving them to a more standard solution - dual front end servers (CAS + HT) and a CCR backend for the mailboxes. Obviously, we are going to have coexistence for some period of time - there are thousands of mailboxes involved.

One of the standard steps in a co-existence scenario is to create a inter-server-version (InterOp) routing group connector (RGC) which allows the Exchange 2003 environment to communicate (i.e., transfer email) with the Exchange 2007 environment.

Today I finished setting up the CAS+HT servers and the CCR servers and began to set up the InterOp RGC. I used the same syntax I've used with dozens of other migrations:

New-RoutingGroupConnector -name Interop-RGC -SourceTransportServers server1, server2, server3 -TargetTransportServers ExchCas1, ExchCas2 -BiDirectional $true -PublicFolderReferralsEnabled $true

And... crap. It bombs.

WTF?!?!?

New-RoutingGroupConnector : Active Directory operation failed on dc3.example.com. This error is not retriable. Additional information: The name reference is invalid.
This may be caused by replication latency between Active Directory domain controllers.
Active directory response: 000020B5: AtrErr: DSID-03152392, #1:
    0: 000020B5: DSID-03152392, problem 1005 (CONSTRAINT_ATT_TYPE), data 0, Att 315c30e2 (msExchTargetBridgeheadServersDN)
At line:1 char:26
+ new-routinggroupconnector  <<<< -domaincontroller dc3.example.com -identity "Exchange Administrative Group (FYDIBOHF23SPDLT)\Exchange Routing Group (DWBGZMFD01QNBJR)\InterOp-RGC" -sourcetransportservers server1, server2, server3 -targettransportservers ExchCas1, ExchCas2 -BiDirectional $true -PublicFolderReferralsEnabled $true

Well, it has nothing whatsoever to do with "replication latency between Active Directory domain controllers". I spent far too long heading down that path before I started googling around to see what else might be on the "Interwebs" about this issue.

It turns out that Exchange 2003 assigns indexes to each SMTP Virtual Server on a computer. The New-RoutingGroupConnector command will only connect to SMTP Virtual Servers that have a index value of 1. Is this a bug? Absolutely. Has it been reported? Absolutely. Has it been fixed? Absolutely not. Note that the same error can occur with the attribute msExchangeSourceBridgeheadServersDN - depends on how you construct the cmdlet.

As of the Exchange 2010 beta, this issue still appears.

In a A/A/A/P/P cluster, the indexes for the SMTP Virtual Servers go 1, 2, 3. This means that you can only specify a single SMTP Virtual Server in the New-RoutingGroupConnector cmdlet, and you can only specify the SMTP Virtual Server for the Exchange Server that has the index of 1. This ALSO means that, for the InterOp-RGC to work, that the Exchange 2003 server with the index of 1 must be the routing group master!

Finding those things out cost me several hours of my life. I hope this blog post helps you avoid the same timesink I had!

Until next time...

If there are things you would like to see written about, please let me know!

Posted by michael | with no comments

I have a client who has multiple business units. Each business unit is placed into its own Organizational Unit (OU) in Active Directory and the business unit has its own unique Email Address Policy (EAP) that (using a RecipientFilter) assigns specific email addresses to all users in that OU.

The client has changed the name of one of their business units, and as of today, the old email addresses were to "go away".

There is no in-built functionality for automatically removing email addresses. You can modify the EAP to specify a new address template as the default, but even when you remove the old address template, that email address is not removed from the user object (Exchange has worked this way since at least Exchange 2000, Exchange 5.5 used a somewhat different model).

What do you do? Well, you use PowerShell of course! This "one-liner" does the job:

get-mailbox -organizationalunit ad.example.com/BusinessUnitOU/users |% { $a = $_.emailaddresses; $b = $_.emailaddresses; foreach($e in $a) { if ($e.tostring() -match "old-email-domain") { $b -= $e; } } ; $_ | set-mailbox -emailaddresses $b }

You can enter this as a single line (and I did). However, the command is easier to understand when you separate it out, as shown below:

get-mailbox -organizationalunit ad.example.com/BusinessUnitOU/users |% 
    { 
        $a = $_.emailaddresses; 
        $b = $_.emailaddresses; 
        foreach ($e in $a) 
        { 
            if ($e.tostring() -match "old-email-domain") 
            { 
                $b -= $e; 
            } 
         }

         $_ | set-mailbox -emailaddresses $b 
    }


The Get-Mailbox command specifies the unique organizational unit that we are looking at. In this case, Get-Mailbox will return a mailbox object for all mailboxes that exist within the ad.example.com/BusinessUnitOU/users organizational unit. Note that I have not used the distinguishedName format for the organizational unit, but instead the X.500 format. Either is acceptable. However, the X.500 format only requires quoting when you have spaces in your organizational unit names. The distinguishedName format must always be quoted or escaped due to its use of commas (and how PowerShell interprets commas in open text strings).

The "|%" syntax indicates that the output of the Get-Mailbox command is to be evaluated, for each object returned, based on the following script block. In this case, the script block begins on the next line with "{" in column 4 and terminates 12 lines later with a "}" in column 4.

First, we make two copies of the email address collection for the mailbox that is returned. Note that setting "$b = $a" would have a different behavior! Instead of creating a copy of the collection, it would create a reference - that is, $b would point to the same collection as $a, and the following loop would not work properly, as described in the next paragraph.

Next, we evaluate each email address contained within the email address collection ($a). If a particular email address matches the old email address domain, we remove that particular email address from the second collection ($b). If we removed it from the original collection ($a), then the foreach loop would fail. Not a good thing to have happen - which is why we have two collections!

After examining all of the email addresses in the collection, we pipe the mailbox object to Set-Mailbox using the new/adjusted collection. And we are done!

But you don't need to understand the script in order to use the script. To use the script, replace the -organizationalunit parameter with the one you are wanting to change (or completely remove the parameter if you want to affect all mailboxes). Next, replace old-email-domain with the domain you are want to remove. That's all it takes.

Until next time...

If there are things you would like to see written about, please let me know!

[Edit March 29, 2012]

Mike Crowley, another Exchange MVP, makes the following comment that I thought I would share:

Michael, this was very helpful, thanks again!  I needed to do this for one of my customers, however the address that needed to ‘go away’ was a parent domain of one that needed to stay (e.g. ‘domain.com’ needed to go away, but ‘subdomain.domain.com’ needed to stay).  I modified your script and ran the below:

 

get-mailbox -organizationalunit ad.domain.com/BusinessUnitOU/users | % { $a = $_.emailaddresses; $b = $_.emailaddresses; foreach($e in $a) { if (($e.tostring() -match "domain.com") -and ($e.tostring() -notmatch "subdomain.domain.com")) { $b -= $e; } } ; $_ | set-mailbox -emailaddresses $b }

-Mike

Posted by michael | with no comments
Filed under: , ,

Yesterday I was notified that my MVP was renewed for another year (MVPs are awarded for one-year at a time, and there is a quarterly cycle of Jan/Apr/Jul/Oct). This is my sixth MVP award and part of it is due to you, my loyal readers! Officially, my MVP award is for "Windows Server System - Exchange Server", but that's generally shortened to "Exchange MVP".

If you aren't familiar with the MVP program, check out http://mvp.support.microsoft.com and my personal MVP profile at https://mvp.support.microsoft.com/profile/Michael.B..

Thanks for your ongoing support!

Until next time...

If there are things you would like to see written about, please let me know!

Posted by michael | with no comments
Filed under: ,