Wait-Chain Traversal: Or, How We Can Use PowerShell to JIT C# to Load an Unmanaged Assembly (C++), InterOping With Windows APIs, Returning that Data Back to PowerShell

So, as the long-winded title infers, today I’ll be covering something that I wrote a long time ago and have recently re-written (albeit, probably badly) for you to use and/or learn from.

In this case, we’re using a PowerShell script, which JITs C# code, the C# code calls into an unmanaged DLL (C++), and that calls into an Windows API. Once the data has been obtained from the Windows API, we pass the data back from the unmanaged assembly to the managed code (via Marshal) and then return that back to the PowerShell instance to be displayed to the user.

Before we dive into the what we’re doing, we should cover some key concepts. The first is JIT’ing. JIT stands for “Just-In-Time” (Compilation) and the name is slightly a misnomer but we’ll cover that in a second. So, in JIT, what happens is that the run time precompiles the code before it’s ran. This is important because a key-concept in exception handling is the runtime’s seek operation to find a handler for an exception that is thrown. You’ll often see this as a FirstChanceException in a dump file. In PowerShell, we have the added ability to leverage the JIT compilation by passing source code as a type into the App’s Domain. It’s important to distinguish that once the App Domain has been disposed of, the type specified is lost and has to be re-instantiated again.

So, what – exactly – is this code going to be doing? Well, since Windows Vista, the Windows Operating System exposes the Wait Chain Traversal API. You can see a demonstration of this API in Task Manager: Go to the Details tab, right click on a process and click “Analyze Wait Chain”.

Since Windows Server 2016 Core doesn’t include a desktop or any GUI interfaces, a more robust way was needed to obtain the same information in production, to determine if the reason an application wasn’t responding or performing work was because the threads were blocked.

When you run the code, you can tell if this is the case or not by something like the following:

12972   [7464:12972:blocked]->[ThreadWait]->[7464:12968:blocked]->[End]

Where the first thread is blocked by a thread wait on the the second thread, which is also (itself) blocked.

So, first things first, the PowerShell code. Take a peek here to see that. Note that the Source code is contained with a specific character-delimited string @”<code>”@. After that we add a Type, pointing it to the source code we’ve defined and referencing the assemblies that we’ll need for this type to work. Worthy of noting is that when we add this type, it is exposed in PowerShell the same way any normal .NET type is, via the []:: convention.

Note that in the C# source we import the unmanaged DLL and reference the exposed method. In the body of the code, we also construct an IntPtr to reference for the return. So, now, we get to Marshalling.

An IntPtr is, quite literally, a pointer or handle to an object. A pointer is a reference to memory where an object exists and the object is typically delimited by characters to signify the termination of it (e.g.: the end of a string is null-terminated). A handle is roughly the same premise but the handle abstracts memory management from the caller. So, at 200 ticks, the handle could point to address 0x000001 and at 369 ticks, it could point to 0x34778.

Alright, so why this matters is because when we pass from unmanaged code back to managed code, there’s nothing that implicitly tells the managed code where to find the object in native memory; so, we have to pass a pointer to the object back to managed (I believe managed creates it’s own copy and creates an address for that object) and, using that, we can then try to convert the passed object from a native string into a managed string (via Marshalling).

What about this unmanaged code I keep hearing about? Oh, well… You can find that here. Don’t forget to add the headers referenced in stdafx.h, or your compiler will cry bloody murder.

So, how this works is: The script is called via PowerShell. After some magic to verify that we weren’t given junk data, then PowerShell JIT’s the C# code and performs the runtime operations. The compiler loads the unmanaged DLL into memory. The C# code then calls into the unmanaged DLL via the exposed method (declspec/dllexport/cdecl). The unmanaged code performs it’s work and returns the string back to the caller – unaware that the caller is managed code. The managed code creates an IntPtr to reference the return and then Marshal is called to convert the native string into a managed string. This is then returned back to the user. In the case of multiple instances of a process, the managed string is added to an array and that array is returned.

While it may not seem like much and seem like overglorified complication just to check for threads on wait chaings, it was code that was written to be used in production where we had no desktop environments and we had to determine if this was the case.

I hope that someone gets some kind of use out of it. 🙂

Until next time.

Windows 10: Removing Groove, Facebook, and Other Unwanted Apps

NOTE: This post – drafted, composed, written, and published by me – originally appeared on https://blogs.technet.microsoft.com/johnbai and is potentially (c) Microsoft.

A few months ago, someone very dear to me asked why they couldn’t remove unwanted apps from Windows 10 – mainly over privacy and right to decline concerns. I took it up as a sort of personal challenge to find a way to rid these apps for other people, as well, whilst I was attempting to figure out how to do this for them specifically. So, I give you the result – which is still a work in progress.


function Remove-UnwantedApps
{
# Attempt to remove Facebook (if it exists)
$facebookPkg = Get-AppxPackage -AllUsers | Where{$_.Name -like '*Facebook*'}
if($facebookPkg -ne $null)
{
Remove-AppxPackage $facebookPkg.PackageFullName -Confirm:$FALSE
}

# Attempt to remove Groove Music
$grooveMusicPkg = Get-AppxPackage -AllUsers | Where{$_.PackageFullName -ilike ‘*ZuneMusic*’}
if($grooveMusicPkg -ne $null)
{
Remove-AppxPackage $grooveMusicPkg.PackageFullName -Confirm:$FALSE
}

# Attempt to remove Groove Video
$grooveVideoPkg = Get-AppxPackage -AllUsers | Where{$_.Name -like ‘*ZuneVideo*’}
if($grooveVideoPkg -ne $null)
{
Remove-AppxPackage $grooveVideoPkg.PackageFullName -Confirm:$FALSE
}

# Attempt to remove all things XBox.
$xboxPkgs = Get-AppxPackage -AllUsers | Where{$_.Name -like ‘*Xbox*’}
foreach($xboxPkg in $xboxPkgs)
{
Remove-AppxPackage $xboxPkg.PackageFullName -Confirm:$FALSE
}

# Attempt to remove the Advertising apps
$advertPkgs = Get-AppxPackage -AllUsers | Where{$_.Name -like ‘*Microsoft.Advertising*’}
foreach($advertPkg in $advertPkgs)
{
Remove-AppxPackage $advertPkg.PackageFullName -Confirm:$FALSE
}

# Attempt to remove the Bing apps
$bingPkgs = Get-AppxPackage -AllUsers | Where{$_.Name -like ‘*Microsoft.Bing*’}
foreach($bingPkg in $bingPkgs)
{
Remove-AppxPackage $bingPkg.PackageFullName -Confirm:$FALSE
}

# Attempt to remove the Messaging package
$msgPkg = Get-AppxPackage -AllUsers | Where{$_.Name -like ‘*Microsoft.Messaging*’}
Remove-AppxPackage $msgPkg.PackageFullName -Confirm:$FALSE

# Attempt to remove Windows Store dependent packages
$storeDepPkgs = Get-AppxPackage -AllUsers | Where{$_.Dependencies -ilike ‘*Store*’ -and $_.Name -inotlike ‘*WindowsStore*’}
foreach($sdPkg in $storeDepPkgs)
{
Remove-AppxPackage $sdPkg.PackageFullName -Confirm:$FALSE
}

# Attempt to remove Windows Store package
$storePkg = Get-AppxPackage -AllUsers | Where{$_.Name -ilike ‘*WindowsStore*’}
if($storePkg -ne $null)
{
Remove-AppxPackage $storePkg.PackageFullName -Confirm:$FALSE
}

# Attempt to disable Cortana via the Registry
$searchRegistyPath = Test-Path “HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Search”
if($searchRegistyPath -eq $FALSE)
{
# Create the Windows Search Registry Key
New-Item -Path “HKLM:\SOFTWARE\Policies\Microsoft\Windows” -Name “Windows Search”

# Create the AllowCortana Registry Key
New-Item -Path “HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Search” -Name “AllowCortana”

# Disable Cortana via the path
Set-Item -Path “HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Search\AllowCortana” -Value 0
}
else
{
# Let’s ensure the child item exists.
$allowCortanaRegistryPath = Test-Path “HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Search\AllowCortana”
if($allowCortanaRegistryPath -eq $FALSE)
{
# Create the AllowCortana Registry Key
New-Item -Path “HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Search” -Name “AllowCortana”
}

# Disable Cortana via the path
Set-Item -Path “HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Search\AllowCortana” -Value 0
}

# Stop the Windows Search Service
$searchService = Get-Service WSearch
Stop-Service -InputObject $searchService -Force

# Attempt to remove Cortana via the App Package
$cortanaPkg = Get-AppxPackage -AllUsers | Where{$_.PackageFullName -ilike ‘*Cort*’}
foreach($cPkg in $cortanaPkg)
{
Remove-AppxPackage $cPkg.PackageFullName -Confirm:$FALSE
}

# Restart the system, so the registy changes take effect in the Windows Search Service’s OnStart() method body.
}

Please note that this has only been tested on a local machine and, so far, validates. This can produce undesired results, such as not being able to install packages from the Windows Store in the future, since the Windows Store is removed via this script. YOU HAVE BEEN WARNED.

To use: Copy the red text into an Administrative session in PowerShell (right-click the PowerShell icon –> Run as Administrator). Run ‘Remove-UnwantedApps’ at the new line, after the red code lines from above have been pasted into the PowerShell session. Restart your machine. Profit.

O365 and Exchange 2016/Exchange 2013: Understanding the UserPhoto API

NOTE: This post – drafted, composed, written, and published by me – originally appeared on https://blogs.technet.microsoft.com/johnbai and is potentially (c) Microsoft.

We recently had an issue for an Enterprise Cloud customer, in which the photo was not rendering for the user – which was uploaded to AD (and synced over via MMSSPP to the managed environment). It was sussed that the issue was customer-caused, as the customer was modifying the photo via the PowerShell commandlets and had deleted the photo.

Despite the fact that the customer had disabled the OWA functionality to change the user photo in OWA, the customer was using the PowerShell commandlets to modify this object, which is the same interface/commands that OWA utilises behind the scenes.

To explain this behaviour in further detail: When you delete a photo from a mailbox in Exchange (or Exchange Online), it changes the UserPhotoCacheId MAPI property value to ‘0’ and this signifies to the UserPhoto API that the photo has been deleted. When this occurs, we will not fall-back to the ADPhotoHandler to call the photo from AD because the user explicitly deleted the photo. This scenario is by-design of the UserPhoto API.

To rectify this behaviour, clear the MAPI property by running ‘Remove-UserPhoto -ClearMailboxPhotoRecord’.

Keep in mind, as well, that Exchange uses a CachingPhotoHandler and that the photos stored on disk have a TTL of 7 days.

I have written a script to help ascertain the MAPI properties on a given mailbox, obtain the location of the cached photos on disk, and obtain the IPM.UserPhoto item from the user’s mailbox (on-premises):


function Get-UserPhotoDataOnPremises
{
param([string]$User)
Write-Warning -Message "The source of our powers comes from a Cracker Jack™ box, so this will take a minute. Please be patient..."
if([System.String]::IsNullOrEmpty($User))
{
throw [System.NullReferenceException]::new("The user value cannot be null.")
}
else
{
$mbx = Get-Mailbox $User
}

[string]$scriptPath = $env:ExchangeInstallPath + “Scripts\ManagedStoreDiagnosticFunctions.ps1”
# Dot-load script into function
. $scriptPath

[GUID]$guid = $mbx.ExchangeGuid.Guid
[int]$mbxNumber = Get-StoreQuery -Database $mbx.Database -Query “SELECT MailboxNumber FROM Mailbox WHERE MailboxGuid=’$guid'” | Select -ExpandProperty MailboxNumber
[string]$folderId = Get-StoreQuery -Database $mbx.Database -Query “SELECT FolderId FROM Folder WHERE MailboxNumber=’$mbxNumber’ AND DisplayName=’$([System.String]::Empty)'” | Select -ExpandProperty FolderId
$global:item = Get-StoreQuery -Database $mbx.Database -Query “SELECT * FROM Message WHERE MailboxNumber=’$mbxNumber’ AND FolderId=’$folderId’ AND MessageClass=’IPM.UserPhoto'” -Unlimited
[string]$previewPhotoCachedId = (Get-StoreQuery -Database $mbx.Database -Query “SELECT UserPhotoPreviewCacheId FROM Mailbox WHERE MailboxNumber=’$mbxNumber'” -Unlimited).p7C1B0003
[string]$photoCacheId = (Get-StoreQuery -Database $mbx.Database -Query “SELECT UserPhotoCacheId FROM Mailbox WHERE MailboxNumber=’$mbxNumber'” -Unlimited).p7C1A0003

# Obtain files on disk (if any)
$smtp = $mbx.WindowsEmailAddress.Address
$smtpAtIndex = $smtp.IndexOf(“@”)
$smtpAtIndexPlusOne = $smtpAtIndex + 1
$smtpDotIndex = $smtp.LastIndexOf(“.”)
$smtpNewLength = $smtpDotIndex – $smtpAtIndexPlusOne
$subString = $smtp.Substring($smtpAtIndexPlusOne, $smtpNewLength)
$queryString = “_$subString”
$preSubString = $smtp.Substring(0, $smtpAtIndex)
$srvr = Get-MailboxDatabaseCopyStatus $mbx.Database.Name | Where{$_.Status -contains ‘Mounted’} | Select -ExpandProperty MailboxServer
$folderPathUnc = “\\$($srvr)\” + $env:ExchangeInstallPath.Replace(“:”, “$”) + “\ClientAccess\photos”
$obj = Get-ChildItem -Path $folderPathUnc -Filter “*$($queryString)*”
$folderPath = $obj.FullName
$obj2 = Get-ChildItem -Path $folderPath
$fullPaths = @()
foreach($o in $obj2)
{
$picObj = Get-ChildItem -Path $o.FullName -Filter “*$($preSubString)*”
$fullPaths += New-Object PSobject -Property @{
Name=$picObj.Name
Size=$picObj.Length
Location=$picObj.FullName
}
}

if([System.String]::IsNullOrEmpty($item.MessageId) -eq $FALSE)
{
$string = “IPM.UserPhoto item found in user’s mailbox. The object can be found in ” + “$” + “item”
Write-Host $string
}
if($previewPhotoCachedId)
{
Write-Host “UserPhotoPreviewCacheId found: $previewPhotoCachedId”
}
if($photoCacheId)
{
Write-Host “UserPhotoCacheId found: $photoCacheId”
}
Write-Host -ForegroundColor Green “Photo files found on disk on $($srvr) for $($smtp):”
$fullPaths | FL
}

Here’s an example as run from my lab:


[PS] E:\>Get-UserPhotoDataOnPremises -User Administrator
WARNING: The source of our powers comes from a Cracker Jack™ box, so this will take a minute. Please be patient...
IPM.UserPhoto item found in user's mailbox. The object can be found in $item
UserPhotoPreviewCacheId found: -88512737
UserPhotoCacheId found: -88512737
Photo files found on disk on [REDACTED] for [email protected]:

Name : _Administrator-8EEDD78A2D804372C17E9FABD151BCD5.jpg
Location : \\[REDACTED]\E$\exchsrvr\ClientAccess\photos\_contoso.se-BBED6250E228D4F38F5F19BF4F1A6823\HR648x648\_Administrator-8EEDD78A2D804372C17E9FABD151BCD5.jpg
Size : 79838

Name : _Administrator-8EEDD78A2D804372C17E9FABD151BCD5.jpg
Location : \\[REDACTED]\E$\exchsrvr\ClientAccess\photos\_contoso.se-BBED6250E228D4F38F5F19BF4F1A6823\HR96x96\_Administrator-8EEDD78A2D804372C17E9FABD151BCD5.jpg
Size : 3224

I’ve also written a method in C# to test obtain the photo via the EWS GetUserPhoto REST method:


///
/// Creates an EWS request for a user's photo at each standard size.
///

/// Vanity name of your endpoint.
/// Smtp Address of the user you’re targeting.
private static void GetPhotos(string vanityName, string username)
{
int[] sizeInts = new[] { 48, 64, 96, 120, 240, 360, 432, 504, 648 };
Parallel.ForEach(sizeInts, delegate (int i)
{
Uri targetUri = new Uri($”https://{vanityName}/EWS/Exchange.asmx/s/GetUserPhoto?email={username}&size=HR{i}x{i}”);
Console.WriteLine($”Targeting: {targetUri}”);
try
{
HttpWebRequest newWebRequest = (HttpWebRequest)WebRequest.Create(targetUri);
newWebRequest.UserAgent = “Enterprise Cloud UserPhoto EWS Client”;
newWebRequest.Credentials = new NetworkCredential(“[UserName]”, “[PassWord]”);
using (HttpWebResponse newWebResponse = (HttpWebResponse)newWebRequest.GetResponse())
{
if (newWebResponse.StatusCode == HttpStatusCode.OK)
{
Console.WriteLine($”Size {i} photo found.”);
}
else if(newWebResponse.StatusCode == HttpStatusCode.NotFound)
{
Console.WriteLine($”Photo API states that a photo cannot be found for the size {i}x{i}”);
}
else
{
Console.WriteLine($”Unexpected http response received: {newWebResponse.StatusCode}”);
}
}

}
catch (Exception e)
{
Console.WriteLine(e.Message);
}

});
}

If you run into any problems with the script or have any questions or concerns around the UserPhoto API, feel free to let know! 🙂

Exchange: Non-Discriminant Mailbox Moves

NOTE: This post – drafted, composed, written, and published by me – originally appeared on https://blogs.technet.microsoft.com/johnbai and is potentially (c) Microsoft.

I had to rebuild my lab and in one of the invariable problems of doing this is that the default database is created and by the time you do any administration in Exchange, mailboxes exist on it.

I had to come up with a sure-fire way to balance moves between the two mount-points I had created, so as to not really favor one over the other (plus, manually alternating isn’t much fun).

Enter PowerShell. First, I had to make script to test if the iteration I was on was even or odd:

function Check-IfEvenOrOdd ($digit) {[bool]!($digit%2)}

Once I had the function, the next step was to iterate:

$MBXs = @()
$MBXs += Get-Mailbox -Database “Mailbox Database 1750300935”
$MBXs += Get-Mailbox -Database “Mailbox Database 1750300935” -Monitoring
$MBXs += Get-Mailbox -Database “Mailbox Database 1750300935” -Arbitration
$MBXs += Get-Mailbox -Database “Mailbox Database 1909919934” -Monitoring
$MBXs += Get-Mailbox -Database “Mailbox Database 1909919934” -Arbitration
($MBXs | Measure-Object).Count

for([int]$y = 0; $y -le 18; $y++)
{
 if(Check-IfEvenOrOdd $y = $true)
 {
  New-MoveRequest $MBXs[$y].Identity -TargetDatabase EURPROD01-db001
 }
 else
 {
  New-MoveRequest $MBXs[$y].Identity -TargetDatabase EURPROD01-db002
 }
}

And there you have it: Easy ‘balancing’ of mailbox moves to evacuate databases.

PowerShell Scripting: EWS and IPM.Configuration.Owa.UserOptions

NOTE: This post – drafted, composed, written, and published by me – originally appeared on https://blogs.technet.microsoft.com/johnbai and is potentially (c) Microsoft.

So, to start off, I should explain how this script came about and why it’s, currently, in a ‘legacy’ status.

There was an issue in Exchange 2010 (that, has since, been fixed), where the save method for the IPM.Configuration.Owa.UserOptions item was in an infinite loop due to corruption. This corruption didn’t come into fruition until long after RTM and SP1. (My thoughts are that SP1 was the introduction of the “bad code”, but it’s not really important when it was introduced.)

What would happen is that the Mailbox Assistants would become flooded/backlogged with events, in the event history table – which is kept in memory. The only signs of reproduction of the issue was that OOFs and RBAs would not fire, in response to emails or meeting requests, respectively. This became a tedious issue to deal with because it also caused other problems, during initial attempts of remediation.

For example, mailbox moves would fail, as the Mailbox Replication Service would successfully move the mailbox but the assistants were still churning away at the mailbox. After the failure, and if the new mailbox on the target database was – now – oriented to the user’s object, the new database (a.k.a.: the target database) would quickly fall into repro.

Restarting the service and having it rebuild the event history table in memory was not a viable avenue for remediation, because the table would rebuild with the infinite corruption loop – once the assistants had read in the event history table, tagged the IPM item change to be processed, and the event was interesting to the assistant[s].

The method we would use to find these users, with the affected IPM item, is to use ExMon to see which user was consuming the most CPU. We could, then, use ExTra to obtain an ETL – targeting the ALL of the assistant flags. Then, we could use a program to port the ETL to CSV and see just what those assistants were chomping down on. [The ExTra method was my preferred method, as it provided two things: significant proof of repro and the user we needed to fix.]

Sadly, our only method of remediation (until SP2 + RU2 + IU) was to copy the user’s configuration settings (Get-MailboxMessageConfiguration) and, then, purge the item from the mailbox store. More often, than not, this was done with MFCMAPI and we would pass a hard delete, to prevent the corrupted item from going into the dumpster.

Then, I got a novel idea: if we use EWS, we can poke into the mailbox store and obtain the item for deletion, since it’s at the root level of the container. And the rest is, as they say, history.

I thought, even though this is legacy, it might prove note-worthy or that someone could learn from the interop of .NET with PowerShell. [All of the EWS calls are in C#.NET.] Unless we have a repro of this particular issue in the nigh to immediate future (for those who are concerned, it’s been over a year since this was fixed), the propensity of it ever being used again is $null. (Little PS humor, for ya, there…)

Delete-IPMConfigurationOWAUserOptions.ps1

Using PowerShell and .NET to Construct a DirectorySearcher

NOTE: This post – drafted, composed, written, and published by me – originally appeared on https://blogs.technet.microsoft.com/johnbai and is potentially (c) Microsoft.

PowerShell and .NET are very interoperable and can help to save time, when you’re performing generic, every day tasks. For example, let’s say that I want to create a Date-Time value that’s thirty minutes ago and one for right now, we can do this in one fell swoop in Exchange Management Shell (read: PowerShell):

v2: Get-MessageTrackingLog -Start [System.DateTime]::Now.AddMinutes(-30) -End [System.DateTime]::Now

v3: Get-MessageTrackingLog -Start ([System.DateTime]::Now).AddMinutes(-30) -End ([System.DateTime]::Now)

As you can see, the syntax changes slightly but the methods are the same because they derive from the same .NET class.

Below, I demonstrate constructing a DirectorySearcher object for a specific case. The final script is published on OneScript, here, but I wanted to demonstrate that we can use PowerShell + .NET to solve some complex problems in a rather easy way.

============================================================================================================================================

In rare cases, removal of an Exchange Server from the forest doesn’t go according to plan and, without Exchange Management Shell (EMS), finding servers via Active Directory might be a bit of a pain-point. Enter DirectorySearcher.

Here is an example, finding Exchange 2013 mailbox servers in the forest:

$colMBX = @()
$CurrentDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain()
$ForestName = $CurrentDomain.Forest.Name
$ForestDC = $ForestName.Replace(“.”,”,DC=”)
$ForestLDAP = [ADSI]”LDAP://CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=$ForestDC”
$orgName = $ForestLDAP.psbase.children | where {$_.objectClass -eq ‘msExchOrganizationContainer’}
$Path = “LDAP://CN=Servers,CN=Exchange Administrative Group (FYDIBOHF23SPDLT),CN=Administrative Groups,CN=” + $orgName.Name + “,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=” + $ForestDC
$Searcher = New-Object System.DirectoryServices.DirectorySearcher
$Searcher.Filter = ‘(&(&(&(objectCategory=msExchExchangeServer)(msExchCurrentServerRoles:1.2.840.113556.1.4.803:=54)(serialNumber=*15*))))’
$Searcher.PageSize = 10000
$Searcher.SearchScope = “OneLevel”
$Searcher.PropertiesToLoad.Add(“Name”) | Out-Null
$Searcher.SearchRoot = $Path
$ServerResult = $Searcher.FindAll()
foreach ($result in $ServerResult)
{
       $colMBX += $result.Properties.name[0]
}
$colMBX

You’ll notice that in the filter we’re using the numeric value ‘1.2.840.113556.1.4.803’ between the attribute and the value we’re seeking. This OID is an Extensible Matching Rule for the bitwise operator AND, which may also be referred to as ‘LDAP_MATCHING_RULE_BIT_AND‘. It is not required for use in your filter but does follow RFC convention.

In Exchange 2013, for Mailbox servers we can use the value ’54’ to search and for CAS servers we can use the value ‘16385’.

To explain the values, we can demonstrate via table:

Server role Role value
Mailbox role 2
Client Access role (CAS) 4
Unified Messaging role 16
Hub Transport role 32

The Mailbox role now has the previous roles in one server, so 2 + 4 + 16 + 32 = 54.

You can read more on PowerShell + DirectorySearcher here: http://technet.microsoft.com/en-us/library/ff730967.aspx