Get ActiveSync device statistics faster

Get-MobileDeviceStatistics is an “expensive” cmdlet, in that it requires Exchange to connect to the server where the mailbox is located and get information that is stored in the mailbox. And for whatever reason, it is more expensive than other cmdlets that do the same thing, like Get-MailboxFolderStatistics. If you have Exchange servers in multiple sites, running Get-MobileDeviceStatistics from a server in a different site than the target mailbox, it can take minutes to get data back.

A customer has about 25,000 mailboxes on servers in the US, Europe, and APAC. They wanted to find out which users’ mobile devices have synced in the last 30 days and information about them. Running something like Get-Mailbox -resultsize unlimited | %{Get-MobileDeviceStatistics -Mailbox $_.Identity} is not efficient in such an environment. You end up making expensive calls to remote servers for users that don’t even have a mobile device. The customer’s script would eventually just time out because of network issues with disconnected sessions.

I wrote a script that completes in far less time because it connects directly to a user’s mailbox server to execute the cmdlet. To be efficient processing tens of thousands of users, the script checks whether a user has any ActiveSync partnerships so it doesn’t waste time querying a mailbox that will return no data, keeps track of which server a mailbox database is mounted, establishes a connection to a server only as needed, and imports only the one cmdlet that will be executed (which completes much faster than importing every cmdlet the admin’s RBAC roles provide).

Getting mobile device statistics for this many users still takes several hours to complete, but it does complete. The script uses progress bars to indicate how far along it is in processing all mailboxes, as indicating when it is looking up the active server for a database and when connecting to a server (though the latter two only take a few seconds each).

#Requires -Version 3
$activeServer = @{}
function Get-DatabaseActiveServer ($db)
	{
	if (-not($activeServer[$db]))
		{
		Write-Progress -Activity "Getting active server for $db" -Status " " -Id 2
		$mounted = Get-MailboxDatabaseCopyStatus -Identity $db | Where-Object {$_.Status -eq 'Mounted'}
		$activeServer[$db] = $mounted.ActiveDatabaseCopy
		Write-Progress -Activity "Getting active server for $db" -Id 2 -Completed
		}
	$activeServer[$db]
	}

#Get list of mailboxes to query
Write-Host "Getting list of mailboxes..."
$mb = Get-Mailbox -RecipientTypeDetails UserMailbox -ResultSize unlimited

#Loop through mailboxes and get mobile devices
for ($i=0;$i -lt $mb.Count;$i++)
	{
	Write-Progress -Activity "Processing mailbox for $($mb[$i].DisplayName)" -PercentComplete ($i/$mb.Count*100) `
		-Id 1 -Status "Overall progress:"
	
	#Check if mailbox has any EAS partnerships; avoids needing to query mailbox that will return no data
	if ((Get-CasMailbox -Identity $mb[$i].Identity).HasActiveSyncDevicePartnership)
		{
		$dbServer = Get-DatabaseActiveServer -db $mb[$i].Database
		
		#Determine if connection to remote server already exists by checking for server-prefixed command
		if (-not(Get-Command -Name Get-$($dbServer)MobileDeviceStatistics -ErrorAction SilentlyContinue))
			{
			Write-Progress -Activity "Connecting to $dbServer" -Status " " -Id 2
			#Limit imported session to only the needed command; avoids local session maximum function count and is faster
			Import-PSSession -Session (New-PSSession -ConfigurationName Microsoft.Exchange `
				-ConnectionUri http://$dbServer/powershell -Name 'ExchangeMDStat') -Prefix $dbServer `
				-DisableNameChecking -CommandName Get-MobileDeviceStatistics | Out-Null
			Write-Progress -Activity "Connecting to $dbServer" -Id 2 -Completed
			}
		
		#Build remote command to run as a string because the cmdlet name includes a variable
		$command = "Get-$($dbServer)MobileDeviceStatistics -Mailbox `"$($mb[$i].Identity)`""
		Invoke-Expression -Command $command | Where-Object {$_.LastSuccessSync -gt (Get-Date).AddDays(-30)} |
			Select-Object Identity,DeviceType,DeviceUserAgent,LastSuccessSync | Export-Csv -NoTypeInformation -Append `
			-Path c:\temp\MobileDevices-$(Get-Date -Format yyyy-MM-dd).csv
		}
	}
#Cleanup
Get-PSSession -Name 'ExchangeMDStat' -ErrorAction SilentlyContinue | Remove-PSSession

For those that wonder why I didn’t use Invoke-Command and avoid wasting time importing the remote session, I originally wrote this in the customer’s VDI on a Windows 7 workstation with PowerShell 2, which wouldn’t let me execute the command directly in the remote session because of the Exchange WinRM configuration of restricted language mode when sending local variables to be used in a remote session. This only happened with Powershell 2. On a Windows 10 client with PowerShell 5.1, it works fine. I measured the run time of the script several times with a sample of users and compared it to doing the same with Invoke-Command, and the difference was negligible. However, if you prefer to do it that way, here is a version that uses it:

#Requires -Version 3
$activeServer = @{}
function Get-DatabaseActiveServer ($db)
	{
	if (-not($activeServer[$db]))
		{
		Write-Progress -Activity "Getting active server for $db" -Status " " -Id 2
		$mounted = Get-MailboxDatabaseCopyStatus -Identity $db | Where-Object {$_.Status -eq 'Mounted'}
		$activeServer[$db] = $mounted.ActiveDatabaseCopy
		Write-Progress -Activity "Getting active server for $db" -Id 2 -Completed
		}
	$activeServer[$db]
	}

#Get list of mailboxes to query
Write-Host "Getting list of mailboxes..."
$mb = Get-Mailbox -RecipientTypeDetails UserMailbox -ResultSize unlimited

#Loop through mailboxes and get mobile devices
for ($i=0;$i -lt $mb.Count;$i++)
	{
	Write-Progress -Activity "Processing mailbox for $($mb[$i].DisplayName)" -PercentComplete ($i/$mb.Count*100) `
		-Id 1 -Status "Overall progress:"
	
	#Check if mailbox has any EAS partnerships; avoids needing to query mailbox that will return no data
	if ((Get-CasMailbox -Identity $mb[$i].Identity).HasActiveSyncDevicePartnership)
		{
		$dbServer = Get-DatabaseActiveServer -db $mb[$i].Database
		
		#Determine if connection to remote server already exists
		$session = Get-PSSession -Name "MDStat-$dbServer" -ErrorAction SilentlyContinue
		if (-not($session))
			{
			Write-Progress -Activity "Connecting to $dbServer" -Status " " -Id 2
			$session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://$dbServer/powershell `
				-Name "MDStat-$dbServer"
			Write-Progress -Activity "Connecting to $dbServer" -Id 2 -Completed
			}
		
		#Put mailbox identity in variable because you can't use expressions with the using: scope modifier
		$mbIdentity = $mb[$i].Identity
		Invoke-Command -Session $session -Command {Get-MobileDeviceStatistics -Mailbox $using:mbIdentity} |
			Select-Object Identity,DeviceType,DeviceUserAgent,LastSuccessSync | Export-Csv -NoTypeInformation -Append `
			-Path c:\temp\MobileDevices-$(Get-Date -Format yyyy-MM-dd).csv
		}
	}
#Cleanup
Get-PSSession  | Where-Object {$_.Name -like "MDStat*"} | Remove-PSSession

My journey from Windows CE to Android

After over 14 years using some kind of Microsoft-powered mobile device, I recently got a Google Nexus 4.  I thought I would look back at the devices I have used over the years.  Most have long been recycled, but a few of them are in a drawer (or perhaps even the garage).

Google Nexus 4 (LG E960)
Released: November, 2012
Google Nexus 4
AT&T Tilt2 (HTC Rhodium)
Released: October, 2009
AT&T Tilt 2
Samsung BlackJack (Samsung SGH-i607)
Released: November, 2006
Samsung BlackJack
Cingular 2125 (HTC Faraday)
Released: March, 2006
Cingular 2125
Cingular 8125 (HTC Wizard)
Released: January, 2006
Cingular 8125
Sprint PPC-6700 (HTC Apache)
Released: October, 2005
Sprint-PPC6700
Siemens SX66 (HTC Blue Angel)
Released: December, 2004
Siemens SX66
Audiovox SMT-5600 (HTC Typhoon)
Releases: November, 2004
Audiovox SMT5600
Audiovox PPC-4100
Released: June, 2004
Audiovox PPC-4100
Hitachi SH-G1000
Released: July, 2003
Hitachi G1000
Siemens SX56 (HTC Wallaby)
Released: October, 2002
Siemens SX56
Casio Cassiopeia E-100
Released: May, 1999
Casio E100
Philips Nino 300
Released: September, 1998
Philips Nino

Use the Exchange Management Shell or LDAP to get a list of quarantined mobile devices

You can use Exchange Control Panel to view the list of Exchange ActiveSync devices that are in a quarantined state.  I wanted to be able to get this list without using ECP, but I didn’t know where this information is stored: Exchange or AD?  Long story short, it is stored in AD.  Every Exchange ActiveSync partnership exists as an AD object (whose class is msExchActiveSyncDevice) located as a child object of the user object whose mailbox has the partnership.  The access state of the device is stored as an integer in the msExchDeviceAccessState attribute of the object.

To use EMS to get the list, run this command:

If you want to use LDAP to get the list, this is the corresponding search filter:

(&(objectclass=msexchactivesyncdevice)(msexchdeviceaccessstate=3))

The device access state values are 1 for allowed, 2 for blocked, 3 for quarantined.

TechEd 2011: WP7 Mango update a snoozer

Microsoft announced more details of the Windows Phone 7 update (Mango), due out later this year.  They hyped the enterprise features, which are nothing to be proud of, IMO.  Support for searching you server-side mailbox.  Already in WM 6.x?  Check.  Support for IRM.  Already in WM 6.x?  Check.  Lync client. Communicator Mobile for WM 6.x to connect to OCS already?  Check.  A mobile client for Lync should have been released with Lync server RTM.  Conversation views.  Already in WM 6.x?  Check.

Where is the at-rest device encryption?  It is already a cliche to say that it is ironic when Microsoft’s own mobile OS doesn’t support all of their Exchange ActiveSync policies, one of them being the very thing whose absence will keep the OS out of the enterprise.  Because my company deals with PHI/PII, we require at-rest encryption, which means the only devices that we allow via EAS are iDevices, Android with Touchdown installed, and Windows Mobile 6.x.

One may argue that WP7 is a consumer device.  But MS has abandoned everything for WM 6.x.  They may say that WP7 is a consumer device, but they treat as their one-and-only OS for home and business.  They didn’t even release a TechEd app for WM for 6.x, only WP7 (and Android).  So if you release an app for 10,000 enterprise geeks at your premier technical conference for IT Pros and Developers on only your consumer-targeted mobile OS, what message are you really sending about who you target audience is?

The correct way to restrict users to specific devices with Exchange ActiveSync

One way to control access to a mailbox with Exchange ActiveSync is to restrict a user to a specific device ID (or more than one), as described in this TechNet help page. The prerequisite listed on the page is only that the user be enabled for EAS. You will find, however, that if you list one or more device IDs in the ActiveSyncAllowedDevicesIDs property and attempt to sync with a device that is not in the list, it will still be able to.

The reason for this is not explained clearly in Microsoft documentation, but can be inferred by this good post on the Exchange blog. It details the flow logic when a device is accessing a mailbox and the order in which permissive and preventive access is handled. This image from the post sums up the flow:

When a device attempts to sync with a mailbox, the Allow/Block list on the user object is checked first, followed by any device access rules defined at the org level, and then the global access rule (the decision point labeled as Anything Unknown).  The default configuration at the org level is a permissive organization, where any device not explicitly blocked at the user level or does not match any device access rule is allowed.

The important distinction, however, is that specifying an allowed device ID at the user level is not exclusive, while specifying a blocked device ID is [effectively] exclusive.  This means that the access logic doesn’t stop at the user level when an allowed device ID is specified, though it does when a blocked device ID is specified.  Therefore, in a default permissive organization, specifying an allowed device ID for a user does not restrict the user to only that device.  In order to restrict a user to the device ID(s) specified on the user object, it is also necessary to define a device access rule and/or change the global access policy so that a device will otherwise be blocked (or quarantined).

To understand the action taken by Exchange when a device attempts to synchronize, I have made my own flowchart (click to zoom):

Script to compare deployed BlackBerry handheld firmware with a local repository

If you use, say, BlackBerry Web Desktop Manager to facilitate firmware upgrades for users, then you have a bunch of them installed somewhere. (I have 49 versions among 15 carriers.) I keep a list of the ones I have installed, but I wanted to be able to compare that to what is actually in use among all the users in the organization. This way I can know which installs are obsolete, when new models are in use, and when newer versions are in use by users.

To use this script, in BlackBerry Manager (I am using 4.1.6) highlight all of your users and right-click on them, then select Export Asset Summary Data. The script will look for BESHandhelds.txt, but you can edit it to be whatever you want. The file that I use to track installed versions is just a csv with the following headers: Carrier, Model, Model Name, Version, Installed. Model Name is not used in the script, but mirrors the fields I use in an internal KB article that users can reference. Here is a sample table of entries:

Carrier Model Model Name Version Installed
AT&T 8320 Curve 4.5.0.182
AT&T 8520 Curve 4.6.1.314 No
KPN 8820 4.5.0.55

I added the Installed field as a way to track models that don’t actually have a local installation either because there is only one release from the carrier or all handhelds are already on that version so there isn’t a reason to have it installed. The script will look for a file called BBFirmware.csv but, again, you can change it to whatever you want.

The switch statement starting in line 17 is to map the carrier name as recorded in BES to how I record it in my file. The output of the script will let you know carriers that are not defined in the statement, but you will want to add or remove carriers as necessary first.

The output will also let you know if a model is in use that is not found in your installation file, if a there is a newer version for a particular model that is in use, and if all users are using the latest version you have installed. What this script does not do is determine if you have the latest version that has been released by the carrier (since that would be much harder to do). Lastly, it will let you know of installations you have for models that are not in use anymore so that you can uninstall them.

I am a perfectionist, so I can already see ways to improve the script, but I would never be done if I didn’t stop somewhere. You can download the ps1 file below.

  BBFirmwareCheck.zip (1.5 KiB)