Submitted By: Gregory J. DeCecco

Verifies public folder migration between Exchange 2000 and Exchange 2007.

PowerShell
Edit|Remove
#--------------------==--------------------#
set-psdebug –strict        # Require declaration of powershell variables
# Set-PSDebug -Off        # Do not require declaration of variables

#------------ Exchange Servers ------------#
#
# Populate $NewServer and $OldServer with your
# Exchange 2007 and Exchange 2000 server names.  
# Don't use <server name>.<domain>.com notation.
$NewServer = "<Exchange 2007 server name>";
$OldServer = "<Exchange 2000 server name>";


#---------- Public Folder Access ----------#
#
# Please use a fresh account for this script.  It does not need
# any administrative rights but I did make it a member of the new
# Exchange Public Folder Administrators security group.  I 
# suggest a fresh account because access permissions to Public 
# Folders will be added AND removed for this account.  You don't 
# want to screw up a legitimate production account, do you?  This
# account will also need a mailbox.
$ConnectAs = "<domain>\<userID>";    
$Password  = "<password>";


#-------- Security Permission Mode --------#
#
# This section needs a little explanation before you're ready to use it.  It it very likely # that $ConnectAs will be unable
# to view every Public Folder hosted on $OldServer.  At first this script would check permissions, add 'FolderVisible' if
# necessary, wait for the permissions to replicate to $OldServer, then compare Public Folder size and item count between
# $OldServer and $NewServer.  This was it was taking upwards of 20 minutes for the security permission update to replicate
# to $OldServer each and every time an Access Denied error was encountered.  This is why I broke out the functionality into
# four modes.  Each mode has a description after it.  I recommend that you run this script under $Security_Permission_Mode_Add
# by setting $Current_SPM = $Security_Permission_Mode_Add; wait a couple hours or a day for Public Folder Replication to 
# work its magic; re-run this script using $Security_Permission_Mode_Add_Only_As_Necessary; then when you have all your results, 
# run it once more using $Security_Permission_Mode_Remove to clean up the security changes.
$Security_Permission_Mode_Add = 1;    # Don't perform any mailbox compares.  Just add 'FolderVisible' permissions for $ConnectAs where necessary.
$Security_Permission_Mode_Remove = 2;  # Don't perform any mailbox compares.  Just remove any 'FolderVisible' permissions for $ConnectAs.
$Security_Permission_Mode_No_Changes = 3;  # Compare Public Folders using the security permissions available at the time of the run.
$Security_Permission_Mode_Add_Only_As_Necessary = 4;  # Add 'FolderVisible' permissions for $ConnectAs where necessary, 
                                                     # wait for Public Folder Replication, then compare folders.
$Security_Permission_Mode_Add_Remove_As_Necessary = 5;  # Add 'FolderVisible' permissions for $ConnectAs where necessary, 
                                                       # wait for Public Folder Replication, compare folders, then remove added permissions.
$Current_SPM = $Security_Permission_Mode_Add_Only_As_Necessary;
$TimeConstant = [int32]1800; # How many seconds to wait for security permissions to replicate from $OldServer to $NewServer per folder.
                           # This value is neede only during $Security_Permission_Mode_Add_Only_As_Necessary

#-------------- Quit On Error -------------#
#
# If an error is encountered while attempting to 
# access the Public Folders via the web site hosted by 
# $OldServer, the script will halt or not based on the config below.
# This flag is active during modes $Security_Permission_Mode_No_Changes,
# $Security_Permission_Mode_Add_Only_As_Necessary, and
# $Security_Permission_Mode_Add_Remove_As_Necessary.
$Quit_On_Error = $True;


function Check_Security([string]$PublicFolderHTTPFormat, [string]$UserID, [string]$Pass)
{
  $strRequest = "<?xml version=""1.0"" ?><D:propfind xmlns:D=""DAV:"" xmlns:E=""http://schemas.microsoft.com/mapi/proptag/"">`
<D:prop><E:x0e080014 /><E:x36020003 /></D:prop></D:propfind>";
  $xmlHTTP = new-object -com msxml2.xmlhttp;
  #Write-Host `t Accessing: $PublicFolderHTTPFormat;
  #Write-Host `t UserID:    $UserID;
  #Write-Host `t Password:  $Pass;
  if ($PublicFolderHTTPFormat.EndsWith(".")) {
      $tempString = $PublicFolderHTTPFormat + "%20";
  } 
  else {
      $tempString = $PublicFolderHTTPFormat
  }
  $xmlHTTP.Open("PROPFIND", $tempString, $False, $UserID, $Pass);
  $xmlHTTP.setRequestHeader("Content-type:", "text/xml");
  $xmlHTTP.setRequestHeader("Depth","0");
  $xmlHTTP.send($strRequest);         
  #Write-Host `t xmlHTTP.Status: $xmlHTTP.Status;
  return $xmlHTTP.Status;
}

[xml] $xmlLog = '<?xml version="1.0" encoding="ISO-8859-1"?> <Public_Folders><x /></Public_Folders>';

# Generate Run Details
$xmlRunDetails = $xmlLog.CreateElement("Run_Details");
$xmlStart =  $xmlLog.createelement("Start");
$xmlFinish = $xmlLog.createelement("Finish");

$xmlTimeStamp = $xmlLog.CreateElement("TimeStamp");
($xmlTimeStamp.AppendChild(($($xmlLog.CreateElement("Year"))))).psBase.InnerText = Get-Date -UFormat %Y;
($xmlTimeStamp.AppendChild(($($xmlLog.CreateElement("Month"))))).psBase.InnerText = Get-Date -Format %M;
($xmlTimeStamp.AppendChild(($($xmlLog.CreateElement("Day"))))).psBase.InnerText = Get-Date -Format %d;
($xmlTimeStamp.AppendChild(($($xmlLog.CreateElement("Time"))))).psBase.InnerText = Get-Date -UFormat %T;

$xmlParameters = $xmlLog.CreateElement("Parameters");
($xmlParameters.AppendChild(($($xmlLog.CreateElement("Connect_As_Identiy"))))).psBase.InnerText = $ConnectAs;
($xmlParameters.AppendChild(($($xmlLog.CreateElement("New_Server"))))).psBase.InnerText = $NewServer;
($xmlParameters.AppendChild(($($xmlLog.CreateElement("Old_Server"))))).psBase.InnerText = $OldServer;

switch ($Current_SPM) 
{ 
  $Security_Permission_Mode_Add {($xmlParameters.AppendChild(($($xmlLog.CreateElement("Security_Permission_Mode"))))).psBase.InnerText = "Add";}
  $Security_Permission_Mode_Remove {($xmlParameters.AppendChild(($($xmlLog.CreateElement`
("Security_Permission_Mode"))))).psBase.InnerText = "Remove";}
  $Security_Permission_Mode_No_Changes {($xmlParameters.AppendChild`
(($($xmlLog.CreateElement("Security_Permission_Mode"))))).psBase.InnerText = "No Changes";}
  $Security_Permission_Mode_Add_Only_As_Necessary {($xmlParameters.AppendChild`
(($($xmlLog.CreateElement("Security_Permission_Mode"))))).psBase.InnerText = "Add Only As Necessary";}
  $Security_Permission_Mode_Add_Remove_As_Necessary {($xmlParameters.AppendChild`
(($($xmlLog.CreateElement("Security_Permission_Mode"))))).psBase.InnerText = "Add Remove As Necessary";}
}

$Catch_Output = $xmlStart.AppendChild($xmlTimeStamp);
$Catch_Output = $xmlRunDetails.AppendChild($xmlStart);
$Catch_Output = $xmlRunDetails.AppendChild($xmlFinish);
$Catch_Output = $xmlRunDetails.AppendChild($xmlParameters);
$Catch_Output = $xmlLog.Public_Folders.AppendChild($xmlRunDetails);

$intCounter = 0;
Get-PublicFolder -recurse | 
foreach {
  if ($_.ParentPath -ne $null) 
  { 
     if ([int]$($_.ParentPath.get_length()) -gt 1)
     {
         $PublicFolderPath = $_.ParentPath + "\" + $_.name
     }
     else 
     {
         $PublicFolderPath = "\" + $_.name
     }
  }
  else
  {
      $PublicFolderPath = "\" + $_.name
  }
  # NOTE: Microsoft allows the character "/" to be used in Public Folder Names.
  # Try to reference a Public Folder named "Pizza/Toppings" via HTTP.  You can't.  The web server 
  # will come back with a HTTP 409 error.  So we have to do a replace with "_xF8FF_".
  $PublicFolderWebPath = "HTTP://" + $OldServer + "/public" + $PublicFolderPath.replace("/","_xF8FF_").replace("\","/");
  $PublicFolderWebPathProper = $PublicFolderWebPath.replace(" ","%20");
  
  $ReplicaSet = $_.Replicas;
  
  $intCounter += 1; 
  $xmlFolder = $xmlLog.CreateElement("Public_Folder");
  $xmlFolder.SetAttribute("ID",[string]$intCounter);
  ($xmlFolder.AppendChild(($($xmlLog.CreateElement("Name"))))).psBase.InnerText = $_.name;
  ($xmlFolder.AppendChild(($($xmlLog.CreateElement("Path"))))).psBase.InnerText = $PublicFolderPath;
  ($xmlFolder.AppendChild(($($xmlLog.CreateElement("Friendly_Web_Address"))))).psBase.InnerText = $PublicFolderWebPath;
  ($xmlFolder.AppendChild(($($xmlLog.CreateElement("Proper_Web_Address"))))).psBase.InnerText = $PublicFolderWebPathProper;    
  Write-Host $_.name;
  $Error_Flag = $False;
  forEach ( $Replica in $ReplicaSet ) {
      if ( ($([string]$Replica).ToLower().Contains($OldServer.ToLower()) -eq $true))
      {
          # The code that deals with XML came from this location:
          # [link=http://www.infinitec.de/post/2004/12/Retrieve-the-size-of-a-Microsoft-Exchange-20002003-public-folder.aspx]`
http://www.infinitec.de/post/2004/12/Retrieve-the-size-of-a-Microsoft-Exchange-20002003-public-folder.aspx[/link]
          # It was written by Henning Krause.  Many thanks for the excelent code.
          # Visit Henning's Blog at [link=http://www.infinitec.de/author/hkrause.aspx]http://www.infinitec.de/author/hkrause.aspx[/link]
          # I translated it into PowerShell 
          # A list of additional (and sometimes available) MAPI properties are available at:
          # [link=http://svn.opengroupware.org/OGoProjects/evolution-groupdav/trunk/utils/mapi-properties]`
http://svn.opengroupware.org/OGoProjects/evolution-groupdav/trunk/utils/mapi-properties[/link]
  
          $strRequest = "<?xml version=""1.0"" ?><D:propfind xmlns:D=""DAV:"" xmlns:E=""http://schemas.microsoft.com/mapi/proptag/""><D:prop><E:x0e080014 /><E:x36020003 /></D:prop></D:propfind>";
          $xmlHTTP = new-object -com msxml2.xmlhttp;
          $xmlDOC = new-object -com msxml2.DOMDocument;
          $return = $xmlDOC.loadXML($strRequest);
          Write-Host Inspecting: $PublicFolderWebPath;
          
          $xmlReplica = $xmlLog.CreateElement("Replica");
          ($xmlReplica.AppendChild(($($xmlLog.CreateElement("Server_Name"))))).psBase.InnerText = $OldServer;
          ($xmlReplica.AppendChild(($($xmlLog.CreateElement("Web_Address_Header"))))).psBase.InnerText = 'http://' + $OldServer;
          
          # I was getting a lot of HTTP 401 Access Denied errors while attempting to access
          # some public folders.  In the end, I discovered that this is because the account
          # I'm using to troll through the Public Folders does not have 'FolderVisible'
          # rights for that particular Public Folder after ALL the folder permissions have
          # been compiled and compared against the user $ConnectAs.
          # So I got the bright idea to check for a denied condition ("401" error) and
          # grant 'FolderVisible' access rights using the "Add-PublicFolderClientPermission"
          # cmdlet that you'll see a couple lines below.  Initially I didn't use the "-Server"
          # parameter.  This caused some heartache.  The security changes were being made on 
          # $NewServer and would eventually take effect on $OldServer through the Public
          # Folder replication.  This was not fast enough.  I tried to grant rights on 
          # $OldServer manually using the Exchange System Manager (remember: $OldServer is
          # running Exchange 2000) and after a few seconds, I got this email in my inbox:
          #   'A folder design conflict has occurred in "Public Folder Store (<$OldServer>)". 
          #    The design of this folder has been simultaneously modified on two or more 
          #    folder replicas. Only the set of changes made last have been saved.'
          # Having read this, I decided to attempt to use the "-Server $OldServer" parameter
          # of the cmdlet "Add-PublicFolderClientPermission".
          # This failed miserably since $OldServer is running Exchange 2000.  My only option
          # left was to grant permissions on $NewServer then wait for the predefined replication
          # period before continuing. Through repeated testing, I found that I had to keep
          # keep extending the wait time stored in $Time for the security replication to take place.
          
          # Test to see if $ConnectAs can access $PublicFolderPath.
          # If not, add an Access Control Entry (ACE) into the Access Control Llist (ACL) 
          # for $PublicFolderPath.
          $Time = $TimeConstant;
          $TimeCounter = $Time;
          If (( $(Check_Security $PublicFolderWebPathProper $ConnectAs $Password) -eq 401 ) -and `
                 (($Current_SPM -eq $Security_Permission_Mode_Add) -or `
                  ($Current_SPM -eq $Security_Permission_Mode_Add_Only_As_Necessary) -or `
                  ($Current_SPM -eq $Security_Permission_Mode_Add_Remove_As_Necessary)) ) {
              Write-Host `t Adding FolderVisible Access Permissions to $NewServer;
              ($xmlFolder.AppendChild(($($xmlLog.CreateElement("Security_Action"))))).psBase.InnerText = `
                  "Adding FolderVisible Access Permissions to " + $NewServer + " for " + $ConnectAs;
              $return = Add-PublicFolderClientPermission -Identity $PublicFolderPath -User $ConnectAs -AccessRights "FolderVisible" -Confirm:$False;
              If (($Current_SPM -eq $Security_Permission_Mode_Add) -or `
                  ($Current_SPM -eq $Security_Permission_Mode_Add_Only_As_Necessary)) {
                  $strActivity = 'Waiting up to ' + $Time.tostring() + ' Seconds for Public Folder replication to push permissions to ' + $OldServer;
                  $Show = $True;
                  While (($Time -gt 0) -and ($(Check_Security $PublicFolderWebPathProper $ConnectAs $Password) -eq 401)){
                      Write-Progress -Activity $strActivity -Status "Polling in 10 Second Intervals" -SecondsRemaining $TimeCounter -Completed $Show;
                      Start-Sleep -Seconds 10;
                      $TimeCounter = $TimeCounter - 10;
                      if ($Show -eq $True) {$Show = $False} else {$Show = $True}
                  }
                  Write-Progress -Activity $strActivity -Status "Polling in 10 Second Intervals" `
-SecondsRemaining $Time; #-PercentComplete (0) -Completed:$True;
                  Write-Progress -Activity $strActivity -Status "Polling in 10 Second Intervals" -Completed:$True;
              }
          }
          # We've passed through the section of code that makes security changes on the fly (if necessary).
          # Hopefully by this point everything is in order to allow a successful read attempt when we access
          # the HTTP directory that represents the Public Folder located on $OldServer.
          if ( ($Current_SPM -eq $Security_Permission_Mode_Add_Remove_As_Necessary) -or `
               ($Current_SPM -eq $Security_Permission_Mode_Add_Only_As_Necessary) -or `
               ($Current_SPM -eq $Security_Permission_Mode_No_Changes) ) {
              # Some of the folders in my organization ended with a period (".") and a space.
              # I tried removing just the space but that frequently didn't work; the space would come back.
              # If I removed both the space and the period, the change stuck.  So when I encounter
              # a public folder that ends with a period, I append a space to the end of the URL.
              # Then IIS can access the URL.  This is explained again in greater detail several
              # lines below.  Search for "404_Error" and look for the big blob of text above it.
              if ($PublicFolderWebPathProper.EndsWith(".")) {
                  $tempString = $PublicFolderWebPathProper + "%20";
              } 
              else {
                  $tempString = $PublicFolderWebPathProper
              }
              $xmlHTTP.Open("PROPFIND", $tempString, $False, $ConnectAs, $Password);
              $xmlHTTP.setRequestHeader("Content-type:", "text/xml");
              $xmlHTTP.setRequestHeader("Depth","0");
              $xmlHTTP.send($strRequest);
              $xmlDOC = $xmlHTTP.ResponseXML;
              if ( $xmlHTTP.Status -eq 207 ) {
                  $xmlDOC.SetProperty("SelectionNamespaces", "xmlns:ex='http://schemas.microsoft.com/mapi/proptag/'");
                  # To see the properties & methods for a single XML Node (Msxml2.DOMDocument), uncomment this next line
                  # forEach ( $property in $($xmlDOC.selectSingleNode("//ex:x36020003") | get-member)) { Write-Host $property }
                  
                  Write-Host `t Items: $xmlDOC.selectSingleNode("//ex:x36020003").text vs `
                      $(Get-PublicFolderStatistics -identity $PublicFolderPath).ItemCount.ToString();
                  Write-Host `t Bytes: $xmlDOC.selectSingleNode("//ex:x0e080014").text vs `
                      $(Get-PublicFolderStatistics -identity $PublicFolderPath).TotalItemSize.ToString().Replace("B","");
                  ($xmlReplica.AppendChild(($($xmlLog.CreateElement("Item_Count")))))`
.psBase.InnerText = $xmlDOC.selectSingleNode("//ex:x36020003").text;
                  ($xmlReplica.AppendChild(($($xmlLog.CreateElement("Byte_Count")))))`
.psBase.InnerText = $xmlDOC.selectSingleNode("//ex:x0e080014").text;
              }
              else {
                  Write-Host `t Failed: $xmlHTTP.Status `n`t Actual HTTP path attempted: $PublicFolderWebPathProper
                  if ($TimeCounter -eq 0) { 
                      Write-Host '`tTime expired while waiting for security permissions to replicate to $oldServer';
                      ($xmlReplica.AppendChild(($($xmlLog.CreateElement("Time_Error"))))).psBase.InnerText = `
                          'Time expired while waiting for security permissions to replicate to $oldServer';
                  }
                  if ($xmlHTTP.Status.ToString() -eq "404") { 
                      $string =   "`t A 'Not Found' error was encountered.  Please check the public folder `
                                  `n`t " + $PublicFolderPath + "`n and make sure there are no trailing spaces. `
                                  `n`t The easiest way to do this is to open Exchange System Manager `
                                  `n`t on the Exchange 2000 server an navigate to 'Administrative `
                                  `n`t Groups' -> 'First Administrative Group' -> 'Folders' -> `
                                  `n`t 'Public Folders' -> " + $PublicFolderPath + `
                                  "`n`n`t Before you continue running this script, I recommend that `
                                  `n`t you visit [link=http://visualbasicscript.com/m_61934/tm.htm]http://visualbasicscript.com/m_61934/tm.htm[/link] and run `
                                  `n`t this script to discover which Public Folders have trailing spaces. `
                                  `n`n`t If the above does not work, try this method:`
                                  `n`t Using PFDavAdmin.exe, grant yourself 'Owner' Rights to the Public Folder `
                                  `n`t then go to that folder within Outlook.  Change the Name of the troublesome `
                                  `n`t folder.  Verify the name change in both Exchange 2000's Exchange System  `
                                  `n`t Manager and Exchange 2007 SP1's Public Folder Management Console.  When `
                                  `n`t both GUI's show the new name, then rerun this script. `
                                  `n`t NOTE: I encountered one particular folder that had a period and a space `
                                  `n`t at the end of its name ('. ').  Outlook displayed the period but not the `
                                  `n`t trailing space.  Removing the period also removed the space in Exchange `
                                  `n`t 2000's Exchange System Manager.  Using Outlook to add the period back `
                                  `n`t to the end of the Public Folder name also added the trailing space `
                                  `n`t automatically.  In the end, I decided to remove the trailing space `
                                  `n`t and period to make sure it worked.";
                      Write-Host $string;
                      ($xmlReplica.AppendChild(($($xmlLog.CreateElement("404_Error"))))).psBase.InnerText = $string;
                  }
                  $Error_Flag = $True;
              }
              $Catch_Output = $xmlFolder.AppendChild($xmlReplica);
          }
          # Look for the Access Control Entry for the user $ConnectAs and remove it.
          if ( ($Current_SPM -eq $Security_Permission_Mode_Add_Remove_As_Necessary) -or `
               ($Current_SPM -eq $Security_Permission_Mode_Remove) ) {
              foreach ($ACE in $(get-publicfolderclientpermission -Identity $PublicFolderPath)) {
                  #Write-Host `t ACE:. $([string]$ACE.User) 
                  #Write-Host `t User: $([string]$(get-user $ConnectAs).Identity)
                  #Write-Host `t $([string]$ACE.User).CompareTo( $([string]$(get-user $ConnectAs).Identity) )
                  if ($([string]$ACE.User).CompareTo( $([string]$(get-user $ConnectAs).Identity) ) -eq 0) {
                      ($xmlFolder.AppendChild(($($xmlLog.CreateElement("Security_Action"))))).psBase.InnerText = `
                          "Removing all Access Permissions to " + $NewServer + " for " + $ConnectAs;
                      Remove-PublicFolderClientPermission -Identity $PublicFolderPath -User $ACE.User -AccessRights $ACE.AccessRights -Confirm:$False;
                      Write-Host `t Removing ACE for $ConnectAs;
                  }
              }
          }
          Write-host -------------------------------
          Write-host -------------------------------
          $Catch_Output = $xmlFolder.AppendChild($xmlReplica);
      }
      # This code only centers upon $NewServer and $OldServer; which must be explicitly
      # defined at the beginning of the script.  A good project for someone else would
      # be to modify this script to handle 3+ Exchange servers and NOT require $OldServer
      # be explicitly defined.
      if ( ($([string]$Replica).ToLower().Contains($NewServer.ToLower()) -eq $true))
      {
          $xmlReplica = $xmlLog.CreateElement("Replica");
          ($xmlReplica.AppendChild(($($xmlLog.CreateElement("Server_Name"))))).psBase.InnerText = $NewServer;
          ($xmlReplica.AppendChild(($($xmlLog.CreateElement("Web_Address_Header"))))).psBase.InnerText = 'https://' + $NewServer;
          ($xmlReplica.AppendChild(($($xmlLog.CreateElement("Item_Count"))))).psBase.InnerText = `
              $(Get-PublicFolderStatistics -identity $PublicFolderPath).ItemCount.ToString();
          ($xmlReplica.AppendChild(($($xmlLog.CreateElement("Byte_Count"))))).psBase.InnerText = `
              $(Get-PublicFolderStatistics -identity $PublicFolderPath).TotalItemSize.ToString().Replace("B","");
          $Catch_Output = $xmlFolder.AppendChild($xmlReplica);
      }
      $Catch_Output = $xmlLog.Public_Folders.AppendChild($xmlFolder);
      if (($Quit_On_Error -eq $True) -and ($Error_Flag -eq $True)) {exit;}
  }
}
Write-Host "Done Processing Folders.`nWriting Log to file.";
$xmlTimeStamp = $xmlLog.CreateElement("TimeStamp");
($xmlTimeStamp.AppendChild(($($xmlLog.CreateElement("Year"))))).psBase.InnerText = Get-Date -UFormat %Y;
($xmlTimeStamp.AppendChild(($($xmlLog.CreateElement("Month"))))).psBase.InnerText = Get-Date -Format %M;
($xmlTimeStamp.AppendChild(($($xmlLog.CreateElement("Day"))))).psBase.InnerText = Get-Date -Format %d;
($xmlTimeStamp.AppendChild(($($xmlLog.CreateElement("Time"))))).psBase.InnerText = Get-Date -UFormat %T;
$Catch_Output = $xmlFinish.AppendChild($xmlTimeStamp);
$xmlLog.Save($(join-path -path $(get-location).tostring() ((get-date -UFormat `
  "%Y %m %d %A %r Ex2000 vs Ex2007 Public Folders.xml").replace(" ","_").replace(",","").replace(":","_"))));



Example of the XML log file:
<?xml version="1.0" encoding="ISO-8859-1"?>
<Public_Folders>
 <x />
 <Run_Details>
 <Start>
  <TimeStamp>
    <Year>2008</Year>
    <Month>7</Month>
    <Day>14</Day>
    <Time>15:03:08</Time>
  </TimeStamp>
 </Start>
 <Finish>
  <TimeStamp>
    <Year>2008</Year>
    <Month>7</Month>
    <Day>14</Day>
    <Time>20:35:07</Time>
  </TimeStamp>
 </Finish>
 <Parameters>
  <Connect_As_Identity>UserID</Connect_As_Identity>
  <New_Server>Exchange2007</New_Server>
  <Old_Server>Exchange2000</Old_Server>
  <Security_Permission_Mode>No Changes</Security_Permission_Mode>
 </Parameters>
 </Run_Details>
 <Public_Folder ID="2">
 <Name>Access</Name>
 <Path>\Access</Path>
 <Friendly_Web_Address>HTTP://OldServer/public/Access</Friendly_Web_Address>
 <Proper_Web_Address>HTTP://OldServer/public/Access</Proper_Web_Address>
 <Replica>
  <Server_Name>NewServer</Server_Name>
  <Web_Address_Header>https://NewServer</Web_Address_Header>
  <Item_Count>0</Item_Count>
  <Byte_Count>0</Byte_Count>
 </Replica>
 </Public_Folder>
 <Public_Folder ID="3">
 <Name>Access Issues</Name>
 <Path>\Access\Access Issues</Path>
 <Friendly_Web_Address>HTTP://OldServer/public/Access/Access Issues</Friendly_Web_Address>
 <Proper_Web_Address>HTTP://OldServer/public/Access/Access%20Issues</Proper_Web_Address>
 <Replica>
  <Server_Name>OldServer</Server_Name>
  <Web_Address_Header>http://OldServer</Web_Address_Header>
  <Item_Count>26</Item_Count>
  <Byte_Count>529033</Byte_Count>
 </Replica>
 <Replica>
  <Server_Name>NewServer</Server_Name>
  <Web_Address_Header>https://NewServer</Web_Address_Header>
  <Item_Count>26</Item_Count>
  <Byte_Count>540999</Byte_Count>
 </Replica>
 </Public_Folder>
...



To parse the output file and display Public Folders with mismatched counts:
#--------------------==--------------------#
#--------------  Description --------------#
#
# This script takes an input file (example shown below) and looks
# for any Public Folders where the items counts for all the
# replicas are not identical.

#--------------- Input File ---------------#
# 
# The input file is generated from the output of my PowerShell script
# Compare_Public_Folders_Between_Ex2000_and_Ex2007.ps1.  An excerpt of
# this file is shown below.
# <?xml version="1.0" encoding="ISO-8859-1"?>
# <Public_Folders>
#   <x />
#   <Run_Details>
#     <Start>
#       <TimeStamp>
#         <Year>2008</Year>
#         <Month>7</Month>
#         <Day>15</Day>
#         <Time>13:28:23</Time>
#       </TimeStamp>
#     </Start>
#     <Finish>
#       <TimeStamp>
#         <Year>2008</Year>
#         <Month>7</Month>
#         <Day>15</Day>
#         <Time>15:23:26</Time>
#       </TimeStamp>
#     </Finish>
#     <Parameters>
#       <Connect_As_Identiy>UserID</Connect_As_Identiy>
#       <New_Server>NewMailServer</New_Server>
#       <Old_Server>OldMailServer</Old_Server>
#       <Security_Permission_Mode>Add</Security_Permission_Mode>
#     </Parameters>
#   </Run_Details>
#   <Public_Folder ID="3">
#     <Name>Access Issues</Name>
#     <Path>\Access\Access Issues</Path>
#     <Friendly_Web_Address>HTTP://OldMailServer/public/Access/Access Issues</Friendly_Web_Address>
#     <Proper_Web_Address>HTTP://OldMailServer/public/Access/Access%20Issues</Proper_Web_Address>
#     <Replica>
#       <Server_Name>cocsxchng01</Server_Name>
#       <Web_Address_Header>http://OldMailServer</Web_Address_Header>
#     </Replica>
#     <Replica>
#       <Server_Name>NewMailServer</Server_Name>
#       <Web_Address_Header>https://NewMailServer</Web_Address_Header>
#       <Item_Count>26</Item_Count>
#       <Byte_Count>540999</Byte_Count>
#     </Replica>
#   </Public_Folder>


#----------------- Output -----------------#
#
# This is an example of the output that is displayed on screen.
#
# Item Count mismatch for folder 
#     \Finance\Conference Call Calendar
#     OldMailServer :  71
#     NewMailServer :  55
# Item Count mismatch for folder
#     \Marketing\Administration\Team A
#     OldMailServer :  2655
#     NewMailServer :  2653
# Done.


set-psdebug –strict;    # Require declaration of powershell variables
# Set-PSDebug -Off;        # Do not require declaration of variables
$strPathToFile         = "U:\Development";
$strFileName         = "XMLDoc.old.txt";
$xmlLog             = [xml]( Get-Content (join-path -path $strPathToFile $strFileName));
$colPublicFolders     = $xmlLog.Public_Folders.Public_Folder;

foreach ($objPublicFolder in $colPublicFolders) {
   # If there was only one replica for a folder, then 
   # ($objPublicFolder.Replica -is [array]) would return false
   if ($objPublicFolder.Replica -is [array]) {
       $Item_Count_for_First_Replica = $objPublicFolder.Replica[0].Item_Count;
       $colReplicas = $objPublicFolder.Replica;
       $mismatch = $False;
       # Compare the item count for each replica against
       # the first replica item count.
       foreach ($objReplica in $colReplicas) {
           If ($objReplica.Item_Count -ne $Item_Count_for_First_Replica) {
               $mismatch = $True;
           }
       }
       if ($mismatch -eq $True) {
           Write-Host "Item Count mismatch for folder`n`t" $objPublicFolder.Path;
           foreach ($objReplica in $colReplicas) {
               Write-Host "`t" $objReplica.Server_Name ": " $objReplica.Item_Count;}
       }
   }
   $colReplicas = $Null;
   $Item_Count_for_First_Replica = $Null;
   $objReplica = $Null;
}
$strPathToFile = $Null
$strFileName = $Null
$objPublicFolder = $Null;
$colPublicFolders = $Null;
$xmlLog = $Null;
Write-Host "Done.";