Tuesday, December 28, 2010

Testing AutoDiscover and the OAB age using Powershell and the EWS Managed API

One useful alternative to using the ubiquitous test-outlookwebservice cmdlet or https://www.testexchangeconnectivity.com/ for testing Autodiscover from powershell is to make use of the EWS Managed API and the verbose tracing feature this client side API offers. When you enable tracing on the ExchangeService object with the EWS Managed API you will then see the discovery process and the requests that are made to Autodiscover echo'd to the cmdline. This can add a new dimension to trying to diagnose an autodiscover problem that some of these other tools don't offer as you get to see both sides of the conversation.

A simple autodiscover test script for the EWS 1.1 Managed API looks like

$emailAddress = "Glen@domain.com"
$dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll"
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2007_SP1)
$service.traceenabled = $true
$service.Credentials = New-Object System.Net.NetworkCredential("username","password")

The credential line is optional if you can use the currently logged on credentials just get rid of this line.

An example using the EWS Powershell module i posted about here we can reduce everything to one line

New-MessageOps.EWSProfile -id Glen@domain.com -EnableTracing $true

One other thing we've done with the module is to create specific cmdlets that make an autodiscover request outside of using the Managed API and the parse the result into a custom object so this information can then be easily displayed, exported or used in another application or test. One example where this can be useful is for seeing when the last time the Outlook Address Book was modified. In a normal powershell script you could do this using a HEAD command against the OABurl that shouldn't change much and get the lastmodified header. eg

$url = New-Object System.Uri("https://autodiscover-red001.mail.microsoftonline.com/OAB/4fa96c9b-8232-46a3-b329-d03662dc9484/oab.xml")
$htmlRequest = [System.Net.WebRequest]::Create($url)
$htmlRequest.Method = "HEAD"
$htmlRequest.Credentials = New-Object System.Net.NetworkCredential("user@domain","password")
$htmlRequest.timeout = 100000
$htmlRequest.ContentType = "text/xml"
$htmlRequest.Headers.Add("Translate", "F")
$htmlResponse = $htmlRequest.GetResponse()
if ($htmlResponse.StatusCode -eq "OK"){
$oabLastMod = $htmlResponse.GetResponseHeader("Last-Modified")
$rsResponseStream = $htmlResponse.GetResponseStream();
$htmlRequest = $null

With the module this can be made simpler and dynamic to run in most environments by making use of autodiscover eg a two-liner

$credential = get-credential
Get-MessageOps.OABSetting -id Glen@domaim.com -credential $credential -oaburl (Get-MessageOps.AutoDiscover -id Glen@domaim.com -credential $credential | where-object {$_.type -eq "EXPR" -band $_.oaburl -ne $null}).oaburl

If you want something that runs against BPOS there is a slightly different cmdlet which handles the different EntryPoints have a look at the sample post here. There are also different cmdlets for the different autodiscover entry points for liveEdu

To get a copy of the powershell module I've talked about this can be downloaded from http://www.messageops.com/downloads/MessageOps-Exchange-Module.zip


Marc said...

Hello Glen,

I'd like to use MessageOps to query OAB info for monitoring purposes (like last update), but I cannot access OAB through http as it is currently not published that way in our company (don't ask ...). So I must list OAB contents through public folder.
Get-MessageOps.Messages -fp looks convenient but how do I query under NON_IPM_SUBTREE ? it looks like your cmdlet can only query under IPM_SUBTREE, am I wrong ?
What would it take to query under NON_IPM_SUBTREE ?

Thanks in advance.

Glen said...

Its going to hard with the Module I would suggest you just use EWS instead eg you should be able to use something like this to get to those folders

$PFRoot = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::PublicFoldersRoot)
$NonIPMPfRoot = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$PFRoot.ParentFolderId)
$fvFolderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)
$sfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,"NON_IPM_SUBTREE")
$folders = $NonIPMPfRoot.Findfolders($sfSearchFilter,$fvFolderView)
foreach($folder in $folders.Folders){
$sfSearchFilter1 = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,"Offline Address Book")
$fvFolderView1 = New-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)
$fvFolderView1.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Shallow;
$sfSearchFilter1 =
$OABFolder = $folder.Findfolders($sfSearchFilter1,$fvFolderView1).Folders[0]
$OABFolders = $OABFolder.Findfolders($fvFolderView1)
foreach($OABSubFolder in $OABFolders.Folders){