Steve Goodman's Exchange Blog
2Feb/123

iPhone with Exchange 2010 – Business Integration and Deployment available for pre-order now

imageI am pleased to announce that my forthcoming book, iPhone with Exchange Server 2010 - Business Integration and Deployment is now available for pre-order, after what seems like an endless wait!

Right now, we're finishing off the technical reviewing process, where feedback from Exchange MCMs and MVPs, primarily Henrik Walther and Jeff Guillet has been invaluable (and given me a lot of confidence that it's a great book!)  - so once again a massive thanks for their help, as well as Packt Publishing.

Available for pre-order on the Packt website right now, the book is aimed at both IT professionals with little Exchange experience who have been tasked with implementing Exchange 2010 (or Office 365) and iPhones or iPads into their employer's business. With more complex topics such as provisioning and certificate-based authentication, seasoned Exchange admins will also find a lot of in-depth information filling in the large gaps between the Exchange Server and Apple documentation.

Taken from the "What you will learn" overview I put together for Packt, here's a quick bullet-point list of the types of topics covered:

  • The roles and features of Exchange Server 2010
  • Capacity planning for an Exchange environment and how to plan a new installation
  • Certificate and namespace requirements for an external facing Exchange organization
  • Install Exchange Server 2010 and build a database availability group
  • Why you should consider Office 365 and what's involved in getting it set up
  • Use policies to control what users can do with iDevices connected to Exchange
  • Why certificate authentication can make your life easier and how to set it up
  • Use Apple's iPhone Configuration Utility to create and deploy configuration profiles to mobile devices
  • What's involved with sharing mailboxes and calendars with Apple devices
  • Troubleshooting and managing devices in use

Read more about the book and pre-order on the Packt website..

5Dec/110

Exporting Exchange 2010 ActiveSync statistics for iOS Devices

image

When exporting ActiveSync statistics from your Exchange Server 2010 environment, you've got a number of options, including using the Export-ActiveSyncLogs cmdlet to parse IIS log files and produce a number of CSV reports to help understand the way your ActiveSync devices are being used, the use of Device troubleshooting logs to retrieve client-side logs on a user-by-user basis to help diagnose issues, and use of the Get-ActiveSyncDeviceStatistics cmdlet to interrogate the information stored by Exchange and Active Directory about each ActiveSync device partnership.

The final option is what provides the foundation for this script, however when just exporting information on it's own the built in cmdlet doesn't interpret the information encoded in the User Agent string that helps understand what versions of iOS are in use across your business. Therefore as well as exporting the data from Exchange, this script maps the information stored in Exchange to iOS versions.

Usage:

.\Export-MessageTrackingLogsForRecipient.ps1 -OutputCSVFile C:\output.csv

Example Output:

1482-09-08

Script (downloadable below)

#    .SYNOPSIS
#    Generates a CSV file containing ActiveSync Device Statistics with iOS Specific Information.
#  
#    .PARAMETER OutputCSVFile
#    Filename to save the output CSV file as
#  
#    .EXAMPLE
#    .\Export-MessageTrackingLogsForRecipient.ps1 -OutputCSVFile C:\output.csv
param(
    [parameter(Position=1,Mandatory=$true,ValueFromPipeline=$false,HelpMessage="Output CSV File Name")][string]$OutputCSVFile
    )
# The following is a Hash Table containing information used to
# map the Apple Device User Agent to it's corresponding iOS version
$iOSVersions=@{"508.11"="2.2.1";
        "701.341"="3.0.0";
        "701.400"="3.0.1";
        "702.367"="3.2";
        "702.405"="3.21";
        "702.5"="3.3";
        "703.144"="3.1";
        "704.11"="3.1.2";
        "705.18"="3.1.3";
        "801.293"="4.0.0";
        "801.306"="4.0.1";
        "801.400"="4.0.2";
        "802.117"="4.1";
        "803.148"="4.2.1";
        "806.190"="4.3";
        "806.191"="4.3";
        "807.4"="4.3.1";
        "808.7"="4.3.2";
        "810.2"="4.3.3";
        "811.2"="4.3.4";
        "812.1"="4.3.5";
        "901.334"="5";
        "901.403"="5.0.1";
        "901.405"="5.0.1"}
# Retrieve mailboxes of users who have a connected ActiveSync Device
$CASMailboxes = Get-CASMailbox -Filter {hasactivesyncdevicepartnership -eq $true -and -not displayname -like "CAS_{*"} -ResultSize Unlimited;
[array]$Mailboxes = $CASMailboxes | Get-Mailbox;
# Create an array to store the output
$Output=@();
# Perform a set of actions against each mailbox retrieved
foreach ($Mailbox in $Mailboxes)
{
    # Retrieve the ActiveSync Device Statistics for the associated user mailbox
    [array]$ActiveSyncDeviceStatistics = Get-ActiveSyncDeviceStatistics -Mailbox $Mailbox;
    # Use the information retrieved above to store information one by one about each ActiveSync Device
    foreach ($Device in $ActiveSyncDeviceStatistics)
    {
        # Where possible use the information stored in the Device User Agent to understand the iOS device version in use
        $iOSVersion = "N/A";
        if ($Device.DeviceUserAgent -like "*/*") {
            $rawiOSVersion = ($Device.DeviceUserAgent).Substring(($Device.DeviceUserAgent).IndexOf("/")+1);
            if ($iOSVersions[$rawiOSVersion])
            {
                $iOSVersion = $iOSVersions[$rawiOSVersion];
            }
        }
        # Create a new object to store this ActiveSync device information in our CSV file
        $OutputItem = New-Object Object;
        # Add information to the object
        $OutputItem | Add-Member NoteProperty Username $Mailbox.SamAccountName;
        $OutputItem | Add-Member NoteProperty "Display Name" $Mailbox.DisplayName;
        $OutputItem | Add-Member NoteProperty "Device Type" $Device.DeviceType;
        $OutputItem | Add-Member NoteProperty "Device Model" $Device.DeviceModel;
        $OutputItem | Add-Member NoteProperty "iOS Version" $iOSVersion;
        $OutputItem | Add-Member NoteProperty "Device ID" $Device.DeviceID
        $OutputItem | Add-Member NoteProperty "Status" $Device.Status
        $OutputItem | Add-Member NoteProperty "ActiveSync Policy" $Device.DevicePolicyApplied
        $OutputItem | Add-Member NoteProperty "ActiveSync Policy Status" $Device.DevicePolicyApplicationStatus
        $OutputItem | Add-Member NoteProperty "Last Sync" $Device.LastSuccessSync
        $OutputItem | Add-Member NoteProperty "Last Sync Attempt" $Device.LastSyncAttemptTime
        $OutputItem | Add-Member NoteProperty "Last Policy Update" $Device.LastPolicyUpdateTime
        $OutputItem | Add-Member NoteProperty "First Sync" $Device.FirstSyncTime
        # Add the object to our array of output objects
        $Output += $OutputItem;
    }
}
# Print the output object to the screen in a table format with a subset of details for ease of reading
$Output | Format-Table Username,"Device Type","Device ID","Last Sync"
# Export the full set of data to the specified CSV file
$Output | Export-CSV -Path $OutputCSVFile -NoTypeInformation

Version 1.0, 5th December 2011

Download ExportActiveSyncDeviceStatistics.zip

8Nov/112

Quiet on the blog, busy behind the scenes…

It’s been a few weeks since my last post on the blog, and although I haven’t published any articles over the last few weeks, I thought I should check in and share what I’ve been up to.

First and foremost, I’m now into my second month at my new employer, ICM (formerly known as Servo), working in their Microsoft Practice as a Technical Architect. My new role is both design and implementation, working with the same kind of technology I write about on my blog, Exchange and Office 365; along with other technologies I specialise in including Active Directory and Virtualization.

Whilst it’s a bit of a change to go back to working for a reseller after spending a large chunk of my IT career managing a team of system administrators and large server infrastructure, I’m glad to have made the move back to the reseller side, which is after all where it all began for me over ten years ago. Over the last month or so it’s been refreshing to see a lot more of the UK and work with a variety of different customers on some very interesting projects.

So, that’s the first thing that’s been keeping me busy. Second up is something I’ve been keeping close to my chest for a while, but it’s time to let the cat out of the bag. Since July this year I’ve been quietly beavering away on my first book, iPhone with Exchange Server 2010 – Business Integration and Deployment.

The book’s being published by Packt publishing, the same technology publisher behind Mike Pfeiffer’s Exchange 2010 PowerShell Cookbook, and is scheduled for release in a couple of months time. Before then, Packt are planning an early release through their RAW program. RAW allows early access to new books from the publisher, along with a final copy once the editorial process is complete. As you might expect, when it’s available for pre-order I’ll be making sure it’s publicised here.

And finally, the blog! Well, don’t worry – I have a few good blog posts up my sleeve coming this November, and once Exchange Server 2010 SP2 is released (approximately 4-6 weeks from now) you’ll be able to find out more about some of the new features, and how to use them right here.

Till later..

5Apr/1114

How to get info about your ActiveSync, EWS and WebDAV clients before migrating to Exchange 2010

imageWhen you're moving from Exchange 2007 to Exchange 2010 there are a few gotchas that it's worth watching out for in the planning stages before you change over Client Access Namespaces or start migrating Mailboxes. If you don't you might find you have broken Exchange 2007 clients, due to the CAS changes, and find some clients can't connect after their mailboxes are migrated.

When it comes to the Windows version of Outlook, you can find out about currently logged-in clients using the Get-LogonStatistics command; however you also need to be aware of other clients using protocols like ActiveSync, Exchange Web Services and of course WebDAV.

ActiveSync clients will mostly be fine with CAS Namespace changes, as depending on version they should be automatically proxied from Exchange 2010 back to Exchange 2007 or should autodiscover; however some clients like the iPhone don't work properly and you need to consider a workaround.

Exchange Web Services clients, for the most part, shouldn't have issues thanks to AutoDiscover, however it's good to understand what clients you have out there already so you can test and plan for any issues. There's a number of iPhone apps that use EWS out there that your users may have bought and I've seen some funny issues myself with the EWS version Mac Mail on Snow Leopard that may require a client visit.

Finally, WebDAV. There's little to explain about WebDAV apart from it's not supported in Exchange 2010! You need to find these clients (think Entourage 2004 and 2008) and upgrade them.

Unfortunately there isn't anything built-in to Exchange 2007 or 2010 to examine this data, but the good news is it should all be available to you via the IIS log files. Whilst logparser is pretty good, personally I wanted the data collected and grouped all in one go ready to use. And that's where this script came from…

What information does the script output provide?

The output from the script is in CSV format, so it's easy to use in Excel for further data processing. The CSV file itself has the following fields:

Username: Logon name of the user
ActiveSyncUser: If the user uses an ActiveSync mobile device
ActiveSyncProxyUser: If the user is currently being proxied through to this client access server
ActiveSyncClients: A semi-colon separated list of the clients in use, eg. iPad;htchero;
ActiveSyncLastAccess: Last date found for ActiveSync use
EWSUser: If the user uses some sort of Exchange Web Services client
EWSPCOutlook: Version information if the user has the Windows version of Outlook 2007 or 2010
EWSMacMail: Version information if user has the EWS version of Mac Mail
EWSMacOutlook: Version information if the user has Mac Outlook 2011
EWSEntourage: Version information if the user has Entourage 2008, Web Services Edition
EWSOther: A semi-colon separated list of any other EWS clients the user has
EWSLastAccess: Last date found for EWS use
WebDavUser: If the user uses some sort of WebDAV Exchange client
WebDavClient: A semi-colon separated list of WebDAV client software and versions in use
WebDavLastAccess: Last date found for WebDAV use

In an actual export, the above looks a little bit like this (apart from the blurry usernames!)

image

How to use the script

Example one - Parses log files from the default log directory "C:\WINDOWS\system32\LogFiles\W3SVC1" to "C:\output.csv"

ExIISLogParser.ps1

Example two - Parses the last 30 days of log files from the current directory  to cas_results.csv in the current directory, and saves the state to state.xml in the current directory (could take a LONG time!)

ExIISLogParser.ps1 -LogFilePath ".\" -Days 30 -OutputCSVFile ".\cas_results.csv" -SaveStateFile ".\state.xml"

Limitations

So far, only tested with Exchange 2007 IIS log files.. Any probs, let me know.

Script Code

<#
    .SYNOPSIS
    Parses IIS Log files for records relating to ActiveSync, Exchange Web Services and WebDAV
   
    Steve Goodman
    .DESCRIPTION
    Looks at logs files and produces a CSV file with summary data listing:
    * Activesync clients, whether it is proxied by another CAS and ActiveSync device type
    * EWS clients with columns for PC Outlook, Mac Outlook and Entourage 2008 EWS Edition and other clients
    * WebDAV clients including client versions.
   
    .PARAMETER LogFilePath
    Path to base directory of IIS Log files, e.g. "C:\WINDOWS\system32\LogFiles\W3SVC1"
   
    .PARAMETER Days
    How many days log files to look back by
   
    .PARAMETER OutputCSVFile
    File to write CSV output to
   
    .PARAMETER SaveStateFile
    File to save or load internal state to. Useful when looking at multiple CAS servers or to update output later on based on only more recent logfiles
   
    .EXAMPLE
    Parses log files from the default log directory "C:\WINDOWS\system32\LogFiles\W3SVC1" to "C:\output.csv"
    .\ExIISLogParser.ps1
   
    .EXAMPLE
    Parses the last 30 days of log files from the current directory  to cas_results.csv in the current directory, and saves the state to state.xml in the current directory
    .\ExIISLogParser.ps1 -LogFilePath ".\" -Days 30 -OutputCSVFile ".\cas_results.csv" -SaveStateFile ".\state.xml"
   
    #>

param(
    [parameter(Position=0,Mandatory=$false,ValueFromPipeline=$false,HelpMessage="Full path to log file directory")][string]$LogFilePath = "C:\WINDOWS\system32\LogFiles\W3SVC1",
    [parameter(Position=1,Mandatory=$false,ValueFromPipeline=$false,HelpMessage="Last write date of logs to search back by")][int]$Days=0,
    [parameter(Position=2,Mandatory=$false,ValueFromPipeline=$false,HelpMessage="CSV file for output")][string]$OutputCSVFile="C:\output.csv",
    [parameter(Position=3,Mandatory=$false,ValueFromPipeline=$false,HelpMessage="Script state XML file")][string]$SaveStateFile
   
    )
if (!(Test-Path $LogFilePath))
{
    throw "LogFilePath does not exist"
}
if ((Test-Path $OutputCSVFile))
{
    throw "OutputCSVFile already exists"
}
[hashtable]$Users = @{}
if ($SaveStateFile)
{
    if ((Test-Path $SaveStateFile))
    {
        $Users = Import-Clixml -Path $SaveStateFile
    }
}
$EarliestLogDate = (Get-Date).Subtract([timespan]"$($Days).00:00").Date
[array]$Files = Get-Item -Path "$($LogFilePath)\*.log" | Where {$_.LastWriteTime -gt $EarliestLogDate}
for ($i = 0; $i -lt $Files.Count; $i++)
{
    Write-Progress -id 1 -activity "Overall Progress" -status "File $($i) of $($Files.Count)" -percentComplete (($i/$Files.Count*100)*0.9);
    Write-Progress -id 2 -activity "Log $($Files[$i].Name)" -status "Loading" -percentComplete 0
    $Log = Get-Content $Files[$i] | Where {$_ -like "*/Microsoft-Server-ActiveSync/*" -or $_ -like "*/EWS/*" -or $_ -like "*/exchange/*"}
    for ($j = 0; $j -lt $Log.Count; $j++)
        {
        Write-Progress -id 2 -activity "Current Log $($Files[$i].Name)" -status "Working" -percentComplete ($j/$Log.Count*100);
        # Clear Username
        $Username=$null
       
        # Split up log line
        $arrLog = $Log[$j].Split(" ");
       
        # Extract username first
        if ($arrLog[5] -eq "/Microsoft-Server-ActiveSync/Proxy")
        {
            foreach ($QSPart in $arrLog[6].Split("&"))
            {
                if ($QSPart -like "User=*")
                {
                    $Username =($QSPart.Split("="))[1]
                }
            }
           
        } else {
            # Not proxied, just get the username provided direct
            $Username = $arrLog[8]  
        }
       
        # Only process if it's an authenticated user
        if ($Username)
        {
            # Take off domain or UPN suffix
            if ($Username -like "*@*")
            {
                $Username = ($Username.Split("@"))[0]
            } elseif ($Username -like "*\*") {
                $Username = ($Username.Split("\"))[1];
            } elseif ($Username -like "*%40*") {
                $Username = ($Username.Split("%40"))[0]
            } elseif ($Username -like "*/*") {
                $Username = ($Username.Split("/"))[1]
            }
           
            # Make username lower case
            $Username = $Username.ToLower()
           
            # Update data for user
            switch -wildcard ($arrLog[5])
            {
                "/EWS/*"
                {
                    # Get EWS Client
                    $EWSClient = $arrLog[10].Replace("+"," ")
                    # Check if already found or create new hashtable item
                    if (!$Users.Contains($Username))
                    {
                        [hashtable]$obj = @{ActiveSyncUser=0; ActiveSyncProxyUser=0; ActiveSyncClients=@{}; ActiveSyncLastAccess=""; EWSUser=0; EWSClients=@{}; EWSLastAccess=""; WebDavUser=0; WebDavClients=@{}; WebDavLastAccess=""}
                        $Users.Add($Username,$obj)
                    }
                    # Set variables
                    $Users[$Username]["EWSUser"]=1
                   
                    if (!$Users[$Username]["EWSClients"].Contains($EWSClient))
                    {
                        $Users[$Username]["EWSClients"].Add($EWSClient,1)
                    }
                    $Users[$Username]["EWSLastAccess"]=$arrLog[0]
                    break
                }
                "/Microsoft-Server-ActiveSync/*"
                {
                    # Check if already found or create new hashtable item
                    if (!$Users.Contains($Username))
                    {
                        [hashtable]$obj = @{ActiveSyncUser=0; ActiveSyncProxyUser=0; ActiveSyncClients=@{}; ActiveSyncLastAccess=""; EWSUser=0; EWSClients=@{}; EWSLastAccess=""; WebDavUser=0; WebDavClients=@{}; WebDavLastAccess=""}
                        $Users.Add($Username,$obj)
                    }
                    # Set variables
                   
                    # Is a ActiveSync user?
                    $Users[$Username]["ActiveSyncUser"]=1
                    # Is a proxy user?
                    if ($arrLog[5] -eq "/Microsoft-Server-ActiveSync/Proxy")
                    {
                        $Users[$Username]["ActiveSyncProxyUser"]=1
                    }
                    # Client Info
                    foreach ($QSPart in $arrLog[6].Split("&"))
                    {
                        if ($QSPart -like "DeviceType=*")
                        {
                            $ASClient = ($QSPart.Split("="))[1]
                            if (!$Users[$Username]["ActiveSyncClients"].Contains($ASClient))
                            {
                                $Users[$Username]["ActiveSyncClients"].Add($ASClient,1)
                            }
                        }
                    }
                    # Last Access Date
                    $Users[$Username]["ActiveSyncLastAccess"]=$arrLog[0]
                    break
                }
                "/exchange/*"
                {
                    # Check if already found or create new hashtable item
                    if (!$Users.Contains($Username))
                    {
                        [hashtable]$obj = @{ActiveSyncUser=0; ActiveSyncProxyUser=0; ActiveSyncClients=@{}; ActiveSyncLastAccess=""; EWSUser=0; EWSClients=@{}; EWSLastAccess=""; WebDavUser=0; WebDavClients=@{}; WebDavLastAccess=""}
                        $Users.Add($Username,$obj)
                    }
                    # Set variables
                    $Users[$Username]["WebDavUser"]=1
                    $WDClient = $arrLog[10].Replace("+"," ")
                    if (!$Users[$Username]["WebDavClients"].Contains($WDClient))
                    {
                        $Users[$Username]["WebDavClients"].Add($WDClient,1)
                    }
                    $Users[$Username]["WebDavLastAccess"]=$arrLog[0]
                    break
                }
            }
        }
    }
}
Write-Progress -id 1 -activity "Overall Progress" -status "Preparing Output" -percentComplete 95;
[array]$Output=$null
$Users.GetEnumerator() | Foreach {
    $OutputItem = New-Object Object
    $OutputItem | Add-Member NoteProperty Username  $_.Key
    $OutputItem | Add-Member NoteProperty ActiveSyncUser $_.Value["ActiveSyncUser"]
    $OutputItem | Add-Member NoteProperty ActiveSyncProxyUser $_.Value["ActiveSyncProxyUser"]
    $ActiveSyncClients = $null
    $_.Value["ActiveSyncClients"].GetEnumerator() | % { $ActiveSyncClients += "$($_.Key); "}
    $OutputItem | Add-Member NoteProperty ActiveSyncClients $ActiveSyncClients
    $OutputItem | Add-Member NoteProperty ActiveSyncLastAccess $_.Value["ActiveSyncLastAccess"]
    $OutputItem | Add-Member NoteProperty EWSUser $_.Value["EWSUser"]
    $EWSPCOutlook = ""
    $EWSMacMail = ""
    $EWSMacOutlook = ""
    $EWSEntourage = ""
    $EWSOther = ""
    $_.Value["EWSClients"].GetEnumerator() | foreach {
        $EWSClient = $_.Key
        switch -wildcard ($EWSClient)
        {
            "Microsoft Office*"
            {
                $EWSPCOutlook=$EWSClient
                break
            }
            "Mac*ExchangeWebServices*"
            {
                $EWSMacMail=$EWSClient
                break
            }
            "MacOutlook*"
            {
                $EWSMacOutlook=$EWSClient
                break
            }
            "Entourage*"
            {
                $EWSEntourage=$EWSClient
                break
            }
            default
            {
               $EWSOther+="$($EWSClient); "
            }
           
        }
    }
    $OutputItem | Add-Member NoteProperty EWSPCOutlook $EWSPCOutlook
    $OutputItem | Add-Member NoteProperty EWSMacMail $EWSMacMail
    $OutputItem | Add-Member NoteProperty EWSMacOutlook $EWSMacOutlook
    $OutputItem | Add-Member NoteProperty EWSEntourage $EWSEntourage
    $OutputItem | Add-Member NoteProperty EWSOther $EWSOther
    $OutputItem | Add-Member NoteProperty EWSLastAccess $_.Value["EWSLastAccess"]
    $OutputItem | Add-Member NoteProperty WebDavUser $_.Value["WebDavUser"]
    $WebDavClients=$null
    $_.Value["WebDavClients"].GetEnumerator() | % { $WebDavClients += "$($_.Key); "}
    $OutputItem | Add-Member NoteProperty WebDavClients $WebDavClients
    $OutputItem | Add-Member NoteProperty WebDavLastAccess $_.Value["WebDavLastAccess"]
    $Output += $OutputItem
}

if ($SaveStateFile)
{
    $Users = Export-Clixml -Path $SaveStateFile
}

$Output[1..($Output.Count)] | Select Username,ActiveSyncUser,ActiveSyncProxyUser,ActiveSyncClients,ActiveSyncLastAccess,EWSUser,EWSPCOutlook,EWSMacMail,EWSMacOutlook,EWSEntourage,EWSOther,EWSLastAccess,WebDavUser,WebDavClients,WebDavLastAccess | Export-Csv -Path $OutputCSVFile -NoClobber -NoTypeInformation

Script Download

Download ExIISLogParser.zip