January 12th, 2012

Use PowerShell to Troubleshoot Exchange Server Public Folders

Doctor Scripto
Scripter

Summary: Microsoft PFE, Seth Brandes, discusses using Windows PowerShell to troubleshoot a customer problem with Exchange Server public folders. Microsoft Scripting Guy, Ed Wilson, is here. Today we are happy to have Seth Brandes as our guest blogger.

Seth Brandes is a premier field engineer who works in Microsoft Services. He began his foray into programming as a young lad punching the keys in BASIC on a Tandy TRS-80 back in the late 80s, and he gradually progressed through Pascal, VisualBasic, and C/C++/C#. He has most recently latched on to Windows PowerShell v2. He currently specializes in the Microsoft Exchange Server platform, providing Microsoft premier customers in the midwest guidance and support. He is currently gearing up his blog site and hopes to have it online in the near future. Take it away Seth… I was attending an internal Microsoft event a couple months ago, and one of my peers introduced me to Ed Wilson, THE Scripting Guy. Yes, I did get an autograph! He extended an invitation to me to write a guest blog illustrating an Windows PowerShell example related to Exchange Server, and needless to say, I could not refuse. Recently I had an interesting inquiry posed by one of my customers regarding an apparent inconsistency with the Get-PublicFolderStatistics cmdlet in their Exchange Servedr 2010 environment. They had users calling their Help Desk complaining that certain public folders appeared empty of all content. Running the Get-PublicFolderStatistics cmdlet against the folders in the Exchange Management Shell returned non-zero metrics as if there was content present! I directed them to run the cmdlet with the -server switch against each server that contained replicas of the affected folder. They found discrepancies between the replicas because some were lacking content. We tied it back to a 2003 to 2010 routing group connector outage when they were migrating public folders. They updated the replicas and were placated. Great. Problem solved. Or so I thought…  Shortly after we wrapped that up, they had recurrences of the issue in several other folders. Therefore, they wanted a repeatable method that they could easily invoke to determine which folders had “mismatched” replica content in their hierarchy. I promptly informed them they already had a great monitoring system, the best ever designed in fact: the end user.  That didn’t go over so well; so, I thought this would be a great opportunity to create a simple little script sample in Windows PowerShell that can display replica metrics for any folder (or folder tree) to determine if any replicas have mismatched metrics. Note that any high-volume folder will likely have slightly off numbers when comparing replicas because source data needs a little time to get to replica copies—even under the best of circumstances. This script should be a piece of cake.  I didn’t realize at the onset just how big the cake was going to be and what flavor. But I managed to cobble together a working script sample. Is it the best approach ever? Don’t know, probably not. Did I learn some things I didn’t know before? Sure. Is it a decent enough script to point out and review some cool Windows PowerShell concepts? I would say, “Yes.” Therefore, I decided to write this blog. Because I had a preconceived picture about how I wanted the output to look, I started by sketching a visual output of the content on the whiteboard, thinking to myself that this should be doable. I envisioned a table with each row containing the folder and the item count of each replica, where the server names of the replicas would be the columns. Easy right? Let us get a mockup of how this should look according to my imagination. This mockup is shown in the following table.

Folder Path      Replica1  Replica2   ReplicaX

———–      ——–  ——–   ——–

Level1Sub1               2      2

Level1Sub1SubSub1           3      3

Level2           68 Not too shabby. It’s pretty much a formatted table output. Let us get that filed away—check. Next, I wrote a brief outline of the overall flow:

  1. Obtain folder as input so I know what to look for.
    Wait…do I want to get all fancy and do recursion to look at a portion of the tree hierarchy too?  Umm…sure, but let’s leave it up to user to decide.
  2. Obtain input as whether or not to recurse. I mean, really, who doesn’t curse, recurse, and recurse again public folders? Seriously.
  3. Grab the properties of the folder(s) in the folder list.
  4. Because we are interested in only a couple of folder properties, we’ll specifically target the folder path, a unique identifier of the folder, and the list of replicas.
  5. For each replica, call the Get-PublicFolderStatistics cmdlet to obtain the item count metric…

DOH! This won’t work for replicas that are homed on Exchange Server 2003. What to do, what to do? The usual suspects for getting at 2003 are DAV, CDO, or WMI. I don’t really know DAV or CDO programming, and I know there are built in WMI-based cmdlets in Windows PowerShell, so I’ll give WMI a whirl. Let’s rewrite the logic for step 5:

5. For each replica, determine if is being hosted on a server running Exchange Server 2003 or on a server running Exchange Server 2007 or Exchange Server 2010. If it is 2003, make a WMI query via the Get-WMIObject cmdlet to the server to obtain the item count, else use the Get-PublicFolderStatistics cmdlet to obtain the item count. Store the results somewhere. Where? How? Why? Patience my young Padawan, patience. We’ll get to it.
6. Rinse and repeat for each folder (ala steps 4-5).
7. When all the information has been gathered and stored in a meaningful place, spit out the output as described earlier.
8. Exit chair, open fridge, grab a cold drink.  Eight steps ain’t so bad—especially step 8! Let’s get coding!!! Here is the opening to the script:

#get our input variables from the command line

Param(

  #full PF path

  [Parameter(Mandatory=$true)]

  [ValidateNOtNullOrEmpty()]

  [string] $Path,

  #do we recurse?

  [switch] $Recurse

  ) We start by defining the input parameters of the script. The first is the folder path that will be stored as a string, and because we can’t do anything without it, we throw in a parameter on the parameter (er, pun intended?) to make it mandatory, along with a function to ensure it is not a null value. The second is the recurse switch, which when defined with the type [switch], does not include a value; rather its presence alone is enough to use in a Boolean manner. So far, so good. Now add a bit of logic…

if($recurse) #recurse was requested, get the folder and all of its descendants

  {

  $pfCollection = Get-PublicFolder $Path -ErrorAction silentlycontinue -recurse | Select identity,replicas,@{Name=”LegPFDN”; Expression = {$_.identity.legacyDistinguishedName}}

  }

else #recurse was not requested, get only the folder defined

  {

  $pfCollection = Get-PublicFolder $Path -ErrorAction silentlycontinue | Select identity,replicas,@{Name=”LegPFDN”; Expression = {$_.identity.legacyDistinguishedName}}

  }

if(!$pfcollection) #validate a folder path was actually found, if not exit the script

  {

  Write-Host “ERROR! Could not find folder path: $path”

  Write-Host “Please use a valid folder path.”

  exit

  }  In the previous code, we are doing three things:

  1. Validating whether or not the user specified to use recursion.
  2. Grabbing the properties that interest us about the folder(s) in question via the Get-PublicFolder cmdlet.
  3. Validating that we actually found at least the root folder specified—else, what’s the point right? Note that we are suppressing error output because we don’t care if the command fails to find a folder. We do our own validation after the fact, and keep the output nice and clean.

Note that we are grabbing three properties specifically:

  1. Identity. This is the full name of the folder including the folder path. This is useful for our final output later. Stay tuned.
  2. Replicas. This is a collection of one or more replicas, which we will use shortly to figure out which server they are on to use in querying for its replica’s item count stats/metrics. Again, stay tuned.
  3. LegPFDN. Wha??? Huh?

That’s not a property of Get-PublicFolder! Here we are actually grabbing the LegacyDistinguishedName of the public folder, but because that’s a lot to type (and more importantly, I’m lazy), I elected to “rename” it to LegPFDN by using a simple hash table definition to assign a custom “name” for our purposes. I love this technique because it can be used in so many places and the expression can include calculated values. So cool! Anywho…it’s important to note that the results will include the property name of LegPFDN and not LegacyDistinguishedName. Got it? Good. I know your burning to know why this specific property—read on and find out my friend, read on… Before we continue with the code, let’s take a quick timeout to explain my chosen data storage methodology. I burned many a brain cell trying to figure out a cool, but useful, way to store the results in a manner that would be easy to manipulate the output that I envisioned and described up above. I decided to store all the replica item counts of a given folder into a custom object, aka record. Each “folder” record would contain the following properties:

  • Folder Path, which contains the identity of the folder, including its full path.
  • A separate property for each replica of the folder with the name of the replica’s server as the property name and its value, which contains the item count.

Each of these records are appended as they are completed to a master array called $pfResults. This manner of storing the data provides a very simple method to display the data the way I want to. However, output of the content completely falls apart when records in the set contain dissimilar property names.   This little nugget drove me crazy until I came up with a way around it. My ego tells me it’s good enough, but my common sense is still telling me that  it’s a hatchet job at best.  Oh well. It works. For now. To illustrate what the heck I’m alluding to here, let’s paint a picture. Here’s what the script actually stores as output by Format-List  against the $pfResults array:

Folder Path   : Level1Sub1

Replica2 : 2

ReplicaX : 2

Folder Path   : Level1Sub1SubSub1

Replica2 : 3

ReplicaX : 3

Folder Path   : Level1Sub2

Replica1 : 68 When you attempt to view this output with Format-Table, the third item’s Replica information will not be written out, and it will look like this instead:

Folder Path      Replica2   ReplicaX

———–      ——–   ——–

Level1Sub1         2      2

Level1Sub1SubSub1     3      3

Level2 The reason is when the first element in the array is used for its defining features. In this case, the first record in the array does not contain any properties named Replica1, so that never makes it to the table formatting “definition.” For my purposes, this is the absolute worst. Instead of just throwing it away, I was determined (my better half would use the word “stubborn” here) to find a way around this. So, in came the hatchet job. I created a one-off custom object, and as I parse through all the replicas of each folder (or just the single folder if not using recursion), I simply add the server name to the object—but not any duplicates. Why am I doing this? Because if I inject this custom record into the very first position in the array, it will be used for the Format-Table output definition! This will force all the columns to be present. Sweet! Is it a hatchet job? Certainly. So Whut?! Back to the code…

#define the array containing our overall folder(s) and metrics results

$pfResults = @()

#Define a custom object which will store the names of all servers

#containing replicas from (all) the folder(s).

$ReplicasObj = new-object system.object

Add-Member -InputObject $ReplicasObj -MemberType NoteProperty “Folder Path” “” Define my master array that will hold the records. Also defined that one-off object I have named $ReplicasObj. Because this object will eventually be the first record in the array, I also throw in a property called “Folder Path” since it is critical to be defined for use as a column header.

#loop through each in folder in the hirarchy collection.

foreach ($pf in $pfCollection)

  { There is an overall loop occurring for all the folders in the original collection. If recursion was not used, it will simply exit after processing the single folder. Neato.

#create temp object to store information about replica

  $tmpObj = New-Object system.object

  #add the name of the folder including the full path

  Add-Member -InputObject $tmpObj -MemberType NoteProperty “Folder Path” $pf.Identity We create a new custom temporary object that will hold this iteration’s folder replicas info. Again, we are storing at least two or more properties: the folder identity and each replica’s information. First, we add the “Folder Path” in the form of the folder identity.

foreach ($replica in $pf.replicas) #step through each replica of the folder

    { We then start a new loop inside the current loop to cycle through all the replicas of the folder and start processing the replica information.

#store replica’s server name into variable for use below.

    $replicaServer = (Get-PublicFolderDatabase $replica.distinguishedname).server.name

            #add server to Replicas object to be used as the column headers for the $pfresults array

    Add-Member -InputObject $ReplicasObj -MemberType NoteProperty $replicaServer “” -ErrorAction SilentlyContinue Get the replica’s server name and store it into the one-off object that will be used for the column headers in the first array element. Note: To prevent getting duplicate server names into the record, I simply suppress the error handling because by default, you cannot have two properties with the same name in an object using the Add-Member cmdlet (it normally throws an error if a duplicate property is added to the record). If I wanted to allow duplicates (which I don’t, so this is just informational), I would use the -force switch as an override to create a duplicate property.

#determine which version of exchange the replica is homed on (as defined in KB158530)

   if ( (get-exchangeserver $replicaServer).admindisplayversion.major -lt 8)

      { #Exchange 2003 server

      #grab item count via WMI since get-publicfolderstatistics will not work against a 2003 server. Store result in the temp object.

      add-member -inputObject $tmpObj -membertype NoteProperty $replicaserver (Get-WmiObject -ComputerName $replicaServer -Namespace “rootMicrosoftExchangeV2” -Class “Exchange_PublicFolder” -Filter “targetaddress = “”$($pf.legPFDN)”””).messagecount

      } We are finally ready to gather the metrics for the current replica, but we need to invoke the proper command, so we query the Exchange Server properties that the replica is homed on to figure out what its major version is. Per KB158530, if the administrative version is less than 8, it is not going to be Exchange Server 2007 or Exchange Server 2010, so the Get-PublicFolderStatistics cmdlet goes out the window. I am making an assumption that Exchange Server 5.5 and Exchange Server 2000 are not in play, and I am therefore finding Exchange Server 2003. As I mentioned earlier, because there are precanned cmdlets for WMI—and I happen to know a WMI call to query public folder information—I’m going with it! Here is where that pesky long named property of LegacyDistinguishedName (which we are calling LegPFDN) comes in. I use it as the filter criteria for the WMI call (and I’ll use it also for the 2007/2010 search below…stay tuned). Why this property specifically? Er, we’re gonna go on a slight tangent here to explain… <TANGENT>Basically, I ran into some logistical issues in the past when calling public folders via WMI where I wanted to perform built-in recursion, for example, with a filter using the Path attribute. Such a filter would be structured like “path>=’/level1/Sub1/’”. The problem is that if the name of another folder at the same level of “Sub1” is literally greater than from a comparison perspective (such as “Sub2”), Sub2’s folder (and all of its descendant folders) would get caught up and returned in the results. Not good. So, at the time, I elected to use the LegacyDistinguishedName. Plus using the Path attribute requires further monkeying around because with WMI, the path is returned with forward slashes instead of backslashes, and so on, I’ve just found it kinda messy.</TANGENT> Is there a better attribute to use for the folder identity that is searchable via WMI and Get-PublicFolderStatistics? I don’t know. Please tell me so I can repent and learn the error of my ways. In any case, we store the results of the WMI call that contains the item count into a new property of the temporary object, and we name the property the server name that is hosting the replica.

else

      { #Exchange 2007 or 2010 server

      #grab item count via get-publicfolderstatistics since replica is on 2007 or 2010. Store result in the temp object.

      add-member -inputObject $tmpObj -membertype NoteProperty $replicaserver (Get-PublicFolderStatistics $pf.legPFDN -Server $replicaserver).itemcount

      } #end of the else

    } #end of the replica loop  If the version dictates Exchange Server 2007 or Exchange Server 2010, we simply apply the exact same logic as previously, only this time we get the item count by using the very versatile Get-PublicFolderStatistics cmdlet. Rinse-and-repeat for any remaining replicas. The end result is a record that contains the folder identity and all the replicas with their item counts all stored as properties. Yay!

  #dump the content of the temp object into the results array

  $pfResults += $tmpObj

  } #end of the folder loop  Back in the main folder loop, we take the newly completed temporary object and append it to the master array that is holding our collection of objects. Double yay!

#prepend the ReplicasObject to the beginning of the array to allow a format-table to display all columns

$pfResults = @($replicasObj,$pfResults)

#output the results of the

$pfResults We finally arrive at the tail end of the script. All of the objects have been loaded into the array, but if we attempt to display a table with the array elements, we’d start potentially missing entire chunks due to that pesky “first element properties defines the output definition” business we explained earlier. So, we simply inject that one-off object that contains all the properties we need containing all the server names. We also add the “Folder Path” entry, which will be used for all of our column headers. Nice. Then, we simply call the array, and it paints itself to the console in all its glory in a table format.  Note: There may be a need to pipe it to Format-Table if there are enough columns in play or if you want to punch it through to an Export-CSV cmdlet (for example). But because this is only a sample script, I’m taking the easy way out. Customize it to your heart’s content… To summarize… In my opinion, we covered some pretty cool Windows PowerShell concepts:

  • Using hash tables to customize output
  • Using custom objects to store data
  • Using an array as a record set to store other data types
  • Manipulating output of an array that contains dissimilar objects
  • Determining the version of Exchange Server that is running
  • Illustrating different ways to access public folder statistics information, depending on the version of Exchange Server that is hosting the folder replica
  • Using different types of parameters for script input

 The complete script can be found in the Script Center Repository. Seth~ Thanks, Seth, for sharing your knowledge and time. Join us tomorrow as Bhargav Shukla shares another blog about Exchange Server. I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

Author

The "Scripting Guys" is a historical title passed from scripter to scripter. The current revision has morphed into our good friend Doctor Scripto who has been with us since the very beginning.

0 comments

Discussion are closed.