The other day I was working on a case where the customer was using VBScript and CDO 1.21 to make some changes in some mailboxes, and I couldn’t help but feel that we were working in the past. Don’t get me wrong, there’s a lot of tried and tested VBScript and CDO 1.21 code out there, but I found myself wondering what the “new” way to do this would be.
I started looking at using Powershell to automate a .Net HttpWebRequest to post to an EWS endpoint, and just build the SOAP requests myself (or leverage an XmlDocument), but while I was researching this I stumbled upon something slightly more interesting.
The version of Powershell that is included with the Windows 7 Beta includes a new cmdlet called new-webserviceproxy and it does exactly what it sounds like. You can point it at a wsdl file, and it auto-generates a proxy in a way that seems very similar to the Visual Studio auto-generated proxies.
The following is the result of a good deal of trial and error, but what you end up with is a output that displays some basic information about all of the emails in your inbox.
<# Untested sample code, use at your own risk! #>
## Setup the Exchange Service Binding
$uri = "https://exchangeserver/ews/exchange.asmx"
$exchangeservicebinding = new-webserviceproxy -NameSpace "EWS" -URI $uri -UseDefaultCredential -Class EWS
$exchangeservicebinding.RequestServerVersionValue = new-object EWS.RequestServerVersion
$exchangeservicebinding.RequestServerVersionValue.Version = [EWS.ExchangeVersionType]::Exchange2007_SP1
$exchangeservicebinding.Url = $uri
## Create and Populate the Parent Folder ID Collection
[EWS.DistinguishedFolderIdType]$parentfolderid = new-object EWS.DistinguishedFolderIdType
$parentfolderid.Id = [EWS.DistinguishedFolderIdNameType]::inbox
[EWS.BaseFolderIdType[]]$parentfolderids = $parentfolderid
## Create an ItemShape and set it to return All Properties
[EWS.ItemResponseShapeType]$itemshape = new-object EWS.ItemResponseShapeType
$itemshape.BaseShape = [EWS.DefaultShapeNamesType]::AllProperties
## Create the FindItemType object and populate with the Parent Folder Ids and Item Shape
[EWS.FindItemType]$finditemtype = new-object EWS.FindItemType
$finditemtype.ParentFolderIds = $parentfolderids
$finditemtype.ItemShape = $itemshape
## Make the call to the webservice
[EWS.FindItemResponseType]$finditemresponses = $exchangeservicebinding.FindItem($finditemtype)
## Loop through the returned messages and print some basic info
[EWS.MessageType]$messagetype
foreach($messagetype in $finditemresponses.ResponseMessages.Items[0].RootFolder.Item.Items)
{
Write-Host "From: " $messagetype.From.Item.Name
Write-Host "Subject: " $messagetype.Subject
Write-Host "Received: " $messagetype.DateTimeReceived.Date
Write-Host "-------------------------------------------------------"
Write-Host "ItemId: " $messagetype.ItemId.Id
Write-Host "======================================================="
Write-Host
}
<# download code here #>
The above code obviously has no error handling, but hopefully it shows a basic example of how this new cmdlet can be used.
If you want to dig in further, make use of the get-member cmdlet to examine objects of the types that are generated by the proxy, and you can also inspect the object using format-custom (alias fc) as follows (This can be done for any of the object types in the above code):
1: $exchangeservicebinding | get-member
2: $exchangeservicebinding | fc
We seem to get a fair amount of questions and cases where the supportability of certain OWA customizations isn’t clear. In the past, many organizations have found ingenuous ways to modify the OWA files on the server in order to make them behave in different ways. Unfortunately, changing these files takes you into uncharted territory. The OWA web application undergoes extensive testing, and when Microsoft makes any changed to these files, it has to go through testing again. If you modify a javascript or aspx file, then you are running OWA in an untested and unsupported configuration.
Now, many companies have gone outside of the “supported” realm and made these types of changes anyway, and hopefully they do their own testing to make sure that they aren’t introducing errors. Unfortunately this isn’t quite enough. Prior to Exchange Server 2007 SP1 RU5, the update installer would not update files that had been altered. This meant that if you made a change to a file such as logon.aspx, the file wouldn’t get replaced with the latest version. For many installations, this behavior simply meant that the customizations would “stick” through the update. The note above about being in an untested configuration still applies, and you have the added consequence of negating any testing that you did when you initially made the change. Things can get worse though. There were some changes made to OWA with SP1 RU3 that depend on an updated logon.aspx file. If you had made changes to that file prior to SP1 RU3 and you apply RU3 or RU4, OWA ceases to function. This behavior is noted in the release notes for SP1 RU3 and SP1 RU4, and documented in KB 956582. Exchange Server 2007 SP1 RU5 changed this behavior to always replace files even if they have been changed, so that an update doesn’t cause OWA to stop working, but this means that modified files can be replaced.
So, what are the supported ways to customize OWA? The short answer is: “If you can find documentation about how to do it on MSDN or Technet, then it is supported.”
Here’s some information on the supported scenarios:
Outlook Web Access Customization Architecture – Overview of how OWA Customization works.
Outlook Web Access User Interface Customization – You can add elements to the navigation bar and the “New” dropdown.
Outlook Web Access Forms Registry – You can register new custom forms to be used for specific item classes (Note: this only works for the default mail folder view in OWA, and won’t work for other folders like the Calendar).
How to Create a Theme for Outlook Web Access – You can customize the look and feel of the OWA client, including the logon and logoff pages*.
* A special note for branding of the logon and logoff pages: These changes are limited to the image files and the logon.css file in the Base theme directory. This is necessary because without a logged in session, the server has no way to check what theme or experience (Basic or Premium) it should use. Updates to Exchange create a new version-based folder for themes and the modified files should be copied forward. You should plan to test logon and logoff screen customizations after an update to make sure that the non-modified files haven’t changed in a way that causes a problem with your customizations.
I have seen a few cases now where people are trying to implement custom forms for Public Folder Items under Outlook Web Access (Exchange 2007 Sp1, see here for more details on this functionality).
One problem arises when you run into an item that includes “%2B” in the value for the Id that is passed to the custom form on the Query String. When you use the typical methods of retrieving the value of this field, such as retrieving the value from the QueryString collection that is a member of the System.Web.HttpRequest object, .NET helpfully UrlDecodes the value that is returned. When you turn around and use this value to populate an AlternatePublicFolderItemIdType, which in turn is used to populate a ConvertIdType, which is then used to call ConvertId, you will receive an error indicating that the item could not be found.
The simple solution for this issue is to UrlEncode the Id value before placing it into the AlternatePublicFolderItemIdType, like this:
AlternatePublicFolderItemIdType id = new AlternatePublicFolderItemIdType(); id.Format = IdFormatType.OwaId; id.ItemId = HttpUtility.UrlEncode(Request.QueryString["Id"]);
One of my colleagues correctly pointed out that this round trip UrlDecode/UrlEncode could possibly return a different value than was originally on the raw query string, but I have not found any cases yet where this happens.
A second nuance of this scenario is that the schema for the AlternatePublicFolderItemId Type requires a value for the FolderId field, but when the source type is IdFormatType.OwaId, this value is ignored. Simply populate this field with an empty string and it will pass schema validation.
Recently I had the opportunity to dig into an interesting failure with the Exchange Legacy Backup API (ESEBCLI2.DLL). A backup vendor was seeing a reproducible error when they tried to remotely backup certain Exchange servers from their backup software when running on Windows Server 2008. They found that their call to HrESEBackupReadFile was returning 0xC7FF1004 instead of the expected S_OK but they could not figure out why.
Let me start by pointing out that the Legacy Backup API is a technology that is on its way out. Exchange 2010 no longer supports legacy API backups, and there are many caveats to using the API against Exchange 2007. With all that said up front, there are still quite a few backup applications that make use of this interface.
At its core, the legacy backup API uses Remote Procedure Calls to connect to the Exchange server and control the flow of backups and restores. When you actually go to read a file, it does something a bit different though. Instead of trying to package the data up and deliver it via returns to RPCs, the server actually creates a TCP socket connection back to the Backup Client.
If something prevents the return socket connection, such as the Windows Firewall, Network Address Translation or an External Firewall, you will receive the 0xC7FF1004 result code when you call HrESEBackupReadFile. In this particular case, we ruled out all of these “external” factors, but still were seeing the error. Furthermore, looking at a Netmon trace of the issue showed that the server was never even attempting (on the wire at least) to connect back to the client.
The mechanism by which this return socket connection is established relies on an address that is provided by the client. This would seem to be a valid process, but the introduction of changes to support IPv6 in Exchange Server 2007 SP1, and the inclusion of an IPv6 aware network stack on Windows Server 2008 collided to induce a failure.
The simplified sequence of events is:
From here, you will see an Event logged in the Exchange Server’s Application Log that reads:
Source: ESE BACKUP Event ID: 909 Description: Information Store (PID) Backup data transfer method is RPC
Source: ESE BACKUP
Event ID: 909
Description: Information Store (PID) Backup data transfer method is RPC
And subsequently when the client calls HrESEBackupReadFile, the result you get back is 0xC7FF1004, which is a generic ESE backup error code.
So how do you fix it? One way is to follow the instructions in the following KB article to really disable IPv6 on the backup client:
http://support.microsoft.com/kb/929852
It is not sufficient to unbind IPv6 from the interface or disable IPv6 through other means. Note that the DisabledComponents Value should be 0xFFFFFFFF to completely disable IPv6.
This method has been noted previously in relation to MAPI issues with IPv6, and it appears that it is relevant here as well. Remember that you have to reboot for these changes to take effect, and also that changes to your network configuration may impact the Windows Firewall, as well as Network Location Awareness. Since 0xC7FF1004 is the same error that will occur if your backup client blocks the return socket connection, make certain that this change doesn’t end up blocking the return socket connection for a different reason (Such as a network connection that was previously identified as Private or Domain defaulting to Public).
In an Exchange cluster environment with multiple active nodes, Exchange instantiates one IIS virtual server (And therefore one SMTP Virtual Server Instance) for each external interface of the cluster. This configuration is duplicated on each node of the cluster.
For example, if you have an Active/Active/Passive cluster, then the external interfaces might map to the individual nodes like so:
Ext. Node-------------------------EXV1 -> Node1EXV2 -> Node2 Node3 (Passive)
On each node, there will be two SMTP Virtual Server Instances (VSI), labeled EXV1 and EXV2. Whenever a node becomes active, it starts the VSI that is associated with the external interface that is pointing to it. So if Node1 fails, and Node3 takes over servicing EXV1, then Node3 will start up its EXV1 VSI. Likewise if Node2 fails instead, Node3 will start its EXV2 VSI.
Since each SMTP instance has its own event bindings, you must register bindings that apply to any SMTP VSI that could become active at any time. For a clustered Exchange setup this means that you should register the SMTP Sink on every SMTP VSI that exists on every box.
Using the simple example in this KB Article (How to add a disclaimer to outgoing SMTP messages in Visual Basic script), instead of registering with just this:
cscript smtpreg.vbs /add 1 onarrival SMTPScriptingHost CDO.SS_SMTPOnArrivalSink "mail from=*@your-domain-here.com"cscript smtpreg.vbs /setprop 1 onarrival SMTPScriptingHost Sink ScriptName "C:\EventSinkScript.vbs"
You would have to register additional bindings for all other instances (one for each active node). So for our Active/Active/Passive cluster mentioned above, you would register the sink like this:
cscript smtpreg.vbs /add 1 onarrival SMTPScriptingHost CDO.SS_SMTPOnArrivalSink "mail from=*@your-domain-here.com"cscript smtpreg.vbs /setprop 1 onarrival SMTPScriptingHost Sink ScriptName "C:\EventSinkScript.vbs"cscript smtpreg.vbs /add 2 onarrival SMTPScriptingHost CDO.SS_SMTPOnArrivalSink "mail from=*@your-domain-here.com"cscript smtpreg.vbs /setprop 2 onarrival SMTPScriptingHost Sink ScriptName "C:\EventSinkScript.vbs"
If you look at the code in the SMTPREG.VBS file's "DisplaySinks" method you can see some hints on how to do this enumeration programmatically.