Fixing Public Folder Replication Errors From Exchange 2003 to Exchange 2007 or 2010
In my post Accessing The Information Store From Powershell, I promised I would post a real-world Outlook Object Model example in the future, so here it is.
Back in 2008, I posted part 4 of my public folder replication troubleshooting series on the Exchange Team Blog, with part 4 focused on Exchange 2007. In that post, I described several content conversion issues that prevent items from replicating from Exchange 2003 to Exchange 2007. These same problems prevent replication to Exchange 2010.
We are looking at ways to possibly address this in the product, but for now, we often use a script I wrote to identify and correct some of these problems. The script uses Outlook Object Model from Powershell to dig through the public folder items on the Exchange 2003 side and examine them for known problems. It does not fix every possible problem, but it does fix a few of the common ones and can save you quite a bit of manual work.
If you’ve called in with one of these problems, you may have used an earlier version of this script already. This is the latest version. Enjoy!
# Fix-PFItems.ps1
#
# This script should be run on a workstation where Outlook is installed
# as well as Powershell. You do not need the Exchange Management Console
# installed in order to use this. The Outlook profile you are using should be
# one that will access public folders on the Exchange 2003 server, because
# the whole point is to fix the items on the 2003 side so they will replicate
# to Exchange 2007/2010. If you're not sure which replica of a folder your
# profile is accessing, launch MFCMapi, logon to the same profile, navigate
# to the public folder, and look at the PR_REPLICA_SERVER property. This will
# tell you which replica your client is looking at.
#
# Syntax info:
#
# Examples:
# .\Fix-PFItems -folderPath "Departments\HR" -splitOnBadchar $true -resetEmptyCategories $false -doAppointments $false -doInstanceKey $false -doSubfolders $false
# .\Fix-PFItems "Departments\HR" $true $false $false $false $false
#
# folderPath should be in the form: "TopLevelFolder\Subfolder\Subfolder 2"
# a folderPath of "" will run against all public folders
# splitOnBadChar determines whether we split the category into two names, or just replace badChar
# resetEmptyCategories will clear all categories on items that already appear to have no
# categories. This ensures that the categories value is REALLY empty, and should fix
# items that have an empty array in the categories. Be aware that since this changes ALL
# items that appear to have no categories, it could cause a lot of replication.
# doAppointments determines whether appointment items are processed. Note that if this is $true
# and $commitChanges is set to $true, ALL appointment items in the specified folders
# will be modified by the script. This will change the last modified time and cause
# replication.
# doInstanceKey determines whether we clear PR_INSTANCE_KEY on items. Note that if this is $true
# and $commitChanges is set to $true, ALL items in ALL folders that you specify will be
# be modified.
# doSubfolders determines whether we automatically traverse subfolders of the specified folder
#
# This script will identify 5 types of problems:
#
# 1. Categories that contain a bad character, typically a comma. This error is identified in the
# content conversion tracing by output that states:
#
# PropertyValidationException: Property validation failed. Property = [{GUID}:'Keywords']
# Categories Error = Element 0 in the multivalue property is invalid..
#
# This problem will always be fixed if $commitChanges is $true (see below). The
# -splitOnBadChar parameter lets you choose whether to split the category into two separate
# categories (A category such as "catone,cattwo" would become two separate categories
# "catone" and "cattwo") or to simply get rid of the bad character ("catone,cattwo" becomes
# "catonecattwo").
#
# 2. Categories that contain an emtpy array. This problem is identified by the same tracing
# output as that shown for problem #1. This problem can be fixed if -resetEmptyCategories
# is $true and $commitChanges is changed to $true (see below). Note that this will modify
# all items that appear to have no categories, as the script can't tell the difference
# between an empty array and an item that truly has no categories.
#
# 3. Appointment items with invalid start/end dates. This problem can be fixed if $doAppointments
# is $true and $commitChanges is changed to $true. See below. Note that this does not fix
# every possible problem with appointments.
#
# 4. Address type properties that are longer than 9 characters. This problem can be identified
# by content conversion tracing that states:
#
# Property validation failed. Property = [{GUID}:0x8nnn] Email2AddrType Error =
# Email2AddrType is too long: maximum length is 9, actual length is nn..
#
# This problem cannot be fixed by the script. You can try to fix it manually using
# mfcmapi, but sometimes these properties cannot be changed and the item
# must be deleted.
#
# 5. A bad PR_INSTANCE_KEY. This problem can be identified by an error in the
# content conversion tracing that says MapiExceptionPropsDontMatch. This problem can
# be fixed by the script if -doInstanceKey is $true and $commitChanges is $true (see
# below).
#
param([string]$folderPath, [bool]$splitOnBadchar, [bool]$resetEmptyCategories, [bool]$doAppointments, [bool]$doInstanceKey, [bool]$doSubfolders)
#
# By default, the script does NOT change anything. You must
# change $commitChanges to $true in order for the script to actually
# do anything.
#
# This has the potential to change a lot of items in your public folders!
# Run it in READ ONLY mode first to see what will get changed, and have a
# good backup just in case!
#
$commitChanges = $false
# $badChar is the character we want to get rid of in the category names.
# Normally it's a comma, but it can be changed to anything you want.
$badChar = ","
# $reportAll will make the script report all categories and address types
# that were found. This is just for reporting purposes so you can see what values
# exist.
$reportAll = $false
#
##########################################################################
#
# Be careful changing anything below this point.
#
##########################################################################
#
$allCategories = new-object System.Collections.Specialized.StringCollection
$allAddressTypes = new-object System.Collections.Specialized.StringCollection
function GetNamedFromCollection($name, $collection)
{
foreach ($item in $collection)
{
if ($item.Name -eq $name -or $item.DisplayName -eq $name)
{
return $item
}
}
return $null
}
function RecordAddrType($type)
{
if ($reportAll)
{
if (!($allAddressTypes.Contains($type)))
{
$temp = $allAddressTypes.Add($type)
}
}
}
function DoCategories($item)
{
$categoryArray = $item.PropertyAccessor.GetProperty("urn:schemas-microsoft-com:office:office#Keywords")
if ($categoryArray.Length -eq 0 -and $resetEmptyCategories -eq $true)
{
if ($commitChanges)
{
" Resetting categories..."
$item.PropertyAccessor.SetProperty("urn:schemas-microsoft-com:office:office#Keywords", $null)
$item.Save()
}
else
{
" Would have reset the categories, but we're in Read-Only mode."
}
}
else
{
$fixedCategories = $false
[string[]]$newCategoryArray = @()
foreach ($cat in $categoryArray)
{
if ($reportAll)
{
if (!($allCategories.Contains($cat)))
{
$temp = $allCategories.Add($cat)
}
}
if ($cat.Contains($badChar))
{
if (!($splitOnBadchar))
{
# In this case, we just replace the badChar with nothing
$newCategoryArray += $cat.Replace($badChar, "")
}
else
{
# In this case, we split it into multiple separate categories
$categorySplit = $cat.Split($badChar)
foreach ($newCat in $categorySplit)
{
$newCat = $newCat.Trim()
if ($newCat.Length -gt 0)
{
$newCategoryArray += $newCat
}
}
}
$fixedCategories = $true
}
elseif ($cat.Trim() -eq "")
{
# this category should be deleted from the item
$fixedCategories = $true
}
else
{
$newCategoryArray += $cat
}
}
if ($fixedCategories)
{
" Old category list for this item:"
foreach ($cat in $categoryArray)
{
(" " + $cat)
}
" New category list for this item:"
foreach ($cat in $newCategoryArray)
{
(" " + $cat)
}
if ($commitChanges)
{
$item.PropertyAccessor.SetProperty("urn:schemas-microsoft-com:office:office#Keywords", $newCategoryArray)
$item.Save()
(" Changes saved.")
}
else
{
(" Changes not saved (READ ONLY mode).")
}
}
}
}
function DoAppointmentProps($item)
{
if ($item.Class -eq 26) # olAppointment in the olObjectClass enumeration is 26
{
if ($commitChanges)
{
(" Updating appointment props: " + $item.Subject)
if ($item.IsRecurring)
{
$recurPattern = $item.GetRecurrencePattern()
$recurPattern.StartTime = $recurPattern.StartTime
$recurPattern.EndTime = $recurPattern.EndTime
$item.Save()
}
else
{
$item.Start = $item.Start
$item.End = $item.End
$item.Save()
}
}
else
{
(" Appointment props not updated (READ ONLY mode).")
}
}
}
function DoInstanceKey($item)
{
if ($commitChanges)
{
(" Clearing PR_INSTANCE_KEY: " + $item.Subject)
$item.PropertyAccessor.DeleteProperty("https://schemas.microsoft.com/mapi/proptag/0x0FF60102")
$item.Save()
(" Changes saved.")
}
else
{
(" PR_INSTANCE_KEY not cleared (READ ONLY mode).")
}
}
function DoAddrType($item)
{
$senderAddrType = $item.PropertyAccessor.GetProperty("https://schemas.microsoft.com/mapi/proptag/0x0C1E001E")
RecordAddrType $senderAddrType
if ($senderAddrType.Length -gt 9)
{
(" WARNING! PR_SENDER_ADDRTYPE is too long: " + $senderAddrType)
}
$sentRepresentingAddrType = $item.PropertyAccessor.GetProperty("https://schemas.microsoft.com/mapi/proptag/0x0064001E")
RecordAddrType $sentRepresentingAddrType
if ($sentRepresentingAddrType.Length -gt 9)
{
(" WARNING! PR_SENT_REPRESENTING_ADDRTYPE is too long: " + $sentRepresentingAddrType)
}
$recipients = $item.Recipients
if ($recipients -ne $null)
{
foreach ($recipient in $recipients)
{
$addrType = $recipient.PropertyAccessor.GetProperty("https://schemas.microsoft.com/mapi/proptag/0x3002001E")
if ($addrType.Length -gt 9)
{
(" WARNING! A recipient PR_ADDRTYPE is too long: " + $addrType)
}
}
}
if ($item.Email1AddressType.Length -gt 0)
{
RecordAddrType $item.Email1AddressType
}
if ($item.Email1AddressType.Length -gt 9)
{
(" WARNING! Email1AddressType is too long: " + $item.Email1AddressType)
}
if ($item.Email2AddressType.Length -gt 0)
{
RecordAddrType $item.Email2AddressType
}
if ($item.Email2AddressType.Length -gt 9)
{
(" WARNING! Email2AddressType is too long: " + $item.Email2AddressType)
}
if ($item.Email3AddressType.Length -gt 0)
{
RecordAddrType $item.Email3AddressType
}
if ($item.Email3AddressType.Length -gt 9)
{
(" WARNING! Email3AddressType is too long: " + $item.Email3AddressType)
}
}
function DoFolder($folder)
{
$replicaServer = $folder.PropertyAccessor.GetProperty("https://schemas.microsoft.com/mapi/proptag/0x6644001E")
$items = $folder.Items
($items.Count.ToString() + " items found in folder " + $folder.FolderPath)
("Accessing this folder on server: " + $replicaServer)
foreach ($item in $items)
{
("Checking item: " + $item.Subject)
if ($item.PropertyAccessor -ne $null)
{
DoCategories($item)
DoAddrType($item)
if ($doAppointments)
{
DoAppointmentProps($item)
}
if ($doInstanceKey)
{
DoInstanceKey($item)
}
}
else
{
" No PropertyAccessor on this item. It may be in a conflict state."
}
}
if ($doSubfolders)
{
foreach ($subfolder in $folder.Folders)
{
DoFolder($subfolder)
}
}
}
"Starting..."
if ($commitChanges)
{
"commitChanges has been set to TRUE. The script WILL save changes."
}
else
{
"commitChanges is set to FALSE. Running in READ ONLY mode. Changes will NOT be saved."
}
$outlook = new-object -com Outlook.Application
if (!($outlook.Version -like "12.*" -or $outlook.Version -like "14.*"))
{
("This script requires Outlook 2007 or 2010. Your version: " + $outlook.Version)
return
}
$mapi = $outlook.GetNamespace("MAPI")
#
# First, check the categories list and fix if necessary
#
"Checking Outlook categories list..."
$categories = $mapi.Categories
foreach ($category in $categories)
{
if ($category.Name -ne $null)
{
if ($category.Name.Contains($badChar))
{
[string[]]$newNames = @()
(" Fixing category in Outlook category list: " + $category.Name)
if (!($splitOnBadchar))
{
# In this case, we just replace the badChar with nothing
$newName += $category.Name.Replace($badChar, "")
}
else
{
# In this case, we split it into multiple separate categories
$categorySplit = $category.Name.Split($badChar)
foreach ($newCat in $categorySplit)
{
if ($newCat.Length -gt 0)
{
$newNames += $newCat
}
}
}
if ($commitChanges)
{
$foo = $categories.Remove($category.Name)
foreach ($newName in $newNames)
{
$foo = $categories.Add($newName)
}
}
}
}
}
#
# Categories list should be in good shape now.
# Now find the specified folder.
#
"Finding the specified folder..."
$session = $mapi.Session
$pfStore = $null
foreach ($store in $mapi.Stores)
{
if ($store.ExchangeStoreType -eq 2)
{
$pfStore = $store
}
}
$pfRoot = $pfStore.GetRootFolder()
$allPFs = GetNamedFromCollection "All Public Folders" $pfRoot.Folders
if ($allPFs -eq $null)
{
"Couldn't find All Public Folders folder."
return
}
if ($pfStore -eq $null)
{
"Couldn't find public folder store."
return
}
$pfRoot = $pfStore.GetRootFolder()
$folderPath = $folderPath.Trim("\")
$folderPathSplit = $folderPath.Split("\")
$folder = $allPFs
if ($folderPath.Length -gt 0)
{
"Traversing folder path..."
for ($x = 0; $x -lt $folderPathSplit.Length; $x++)
{
$folder = GetNamedFromCollection $folderPathSplit[$x] $folder.Folders
}
if ($folder -eq $null)
{
("Could not find folder: " + $folderPath)
return
}
("Found folder: " + $folder.FolderPath)
}
#
# Got the folder.
# Start processing the folder and subfolders.
#
DoFolder $folder
"Done!"
if ($reportAll)
{
""
"Categories found:"
$allCategories
""
"Address types found:"
$allAddressTypes
}
Comments
Anonymous
January 01, 2003
The comment has been removedAnonymous
January 01, 2003
I've updated the script above so you should no longer have to manually change it to avoid getting the "Couldn't find public folders in profile". It searches based on store type now instead of name, so it doesn't matter what Outlook decides to display in the GUI.Anonymous
January 01, 2003
Nevermind. Works fine when i run with Outlook 2007.Anonymous
January 01, 2003
The comment has been removedAnonymous
January 01, 2003
Oh, and sorry for the delayed response. I have been on vacation. :)Anonymous
January 01, 2003
Interesting! Since I only have Outlook 2007 in my lab environment right now, I hadn't actually tested it with 2010 yet. Thanks for the heads up. I'll look into it.Anonymous
January 01, 2003
Hey Rabs, this is a little late, but no, there's currently no easy way to fix conflicts in bulk. That would be a handy script though, if possible. I will look into that.Anonymous
January 01, 2003
Hi Hutch, Figuring out which folders are causing the error is a bit of work. First, you need to turn up logging on Replication Incoming Messages and Replication Outgoing Messages to Expert on the 2010 server. Then, just watch your application log for the store driver error. When you see that error, shortly before that event you should see an outgoing backfill request for a particular folder, event ID 3016. The folder path in that event is probably one of the folders causing the problem. To verify that really is a problem folder, you can make it try to backfill again. Run "Update-PublicFolder MyFolderMySubfolder -Server My2010PFServer", and watch the application log. You should see another 3016, and if it is really a problem folder, it will be shortly followed by the store driver error. At that point you know it's a problem folder for sure.Anonymous
January 01, 2003
The comment has been removedAnonymous
January 01, 2003
Awesome! I'm glad it was able to help you out.Anonymous
January 01, 2003
Good point! Thanks for that tip Marc.Anonymous
January 01, 2003
Hutch, that's actually a very common issue. Exchange 2007 and 2010 transport will not allow a Category name to be 0 characters long. It must be at least 1, or not present at all. When you see your two separate groups of None, one of those groups is a group of items that truly have no Category. The others have a Category value present, but the value is an empty string (0 characters long). Unfortunately, my Fix-PFItems script can't seem to fix these. The workaround you're using is the best approach I know of. We've been looking at ways to address this in the product itself, but I don't know yet if or when we'll have a solution. In any case, it's a common problem and I wouldn't worry about corruption. We likely had some old Outlook clients that would set categories that way or something.Anonymous
January 01, 2003
The comment has been removedAnonymous
June 02, 2010
i am getting a couldn't find public folder in profile error.... i can see them in outlook with out a problem.. can someone please help?Anonymous
June 04, 2010
I'm having the exact same problem here: "Couldn't find Public Folders in profile." Windows 2008 R2 + Outlook 2007 Ent. (Dutch). I've setup a mailbox which is hosted on the Exchange 2003 server.Anonymous
August 30, 2010
Bill Long, you saved my job with your PF troubleshooting guide and this script. Even my mpss engineer was throwing up her hands at my replication problem. Thanks!Anonymous
September 21, 2010
If your public folder store is not named "All Public Folders". The script fails. I replaced it with my German name and now it seems working: $allPFs = GetNamedFromCollection "Alle Ă–ffentlichen Ordner" $pfRoot.FoldersAnonymous
March 21, 2011
The comment has been removedAnonymous
June 01, 2011
Bill some great articles. I found this page because of some "Property validation failed. Property = [{00020329-0000-0000-c000-000000000046}:'Keywords'] Categories" errors that pop up when trying to replicate my 2003 folders to 2010. The fix might work but with thousands of public folders how do I find out which ones are actually causing the error please? Thanks!Anonymous
July 25, 2011
Bill sorry for not thanking you for replying - I didn't notice that you had! Anyway, today I'm going through the outstanding PF stuff as ever after waiting a long time there's still stuff that never made it across. I've settled on "Get-PublicFolder "folder-name" -recurse | Update-Public-Folder -Server 2010Server" which seems a suitable way to reset/start backfill for a given top level folder, and if I get issues I can then bump up the logging and narrow things down. Does that seem sane/sensible?Anonymous
July 25, 2011
Thanks Bill, seems to be working. The issue I appear to be running into is that somehow over time we have lots of folders, and some, and they all seem to be Contact folders, show two sets of "None" if I do a "Group By" on Category. I can select both groups and set it to some value then clear it, but of course this is a bit of a pain with dozens of folders. I don't suppose you know of a tool to help automate this? Also some sort of basic explanation would be really good just to help me understand whether something has gotten corrupted or whether Exchange 2010 is just a lot more fussy than 5.5 and 2003 were as these folders and their contents would have started life a long time back. Thanks again, much appreciated.Anonymous
July 25, 2011
Thanks Bill. Looks like I'm in for a fun and repetitive day tomorrow then :-) Appreciate the help, thanks again.Anonymous
July 26, 2011
Just as a follow-up in case anyone else ends up here with the same problem, I cheated but it seems to have worked: On my machine (Outlook 2010 if that matters) I used a profile on the old server, so all the items are visible, and dumped the affected public folders to a PST file. I checked the numbers matched up between the public folders and the PST. I then deleted the public folders, and re-created and imported the PST contents and it seems to have done the trick. I'm guessing/assuming that Outlook does some validation or maybe Exchange can somehow "fix" the problem items when they're pulled into it this way vs. being replicated across.Anonymous
January 19, 2012
Hi Bill, I know this post has been idle for a while but I've been asked to run the script against our Public Folder hierachy as part of an investigation into Offline Address Book replication issues and exceeded the Powershell 2.0 "call depth overflow" limit. Is there any way to override this limit or is it simply a case of breaking the folder hierachy down into more manageable chunks? Cheers.Anonymous
February 01, 2012
Anyone getting this when they try to run the script? Starting... commitChanges is set to FALSE. Running in READ ONLY mode. Changes will NOT be saved. Checking Outlook categories list... Finding the specified folder... You cannot call a method on a null-valued expression. At C:Fix-PFItems.ps1:447 char:31
- $folderPath = $folderPath.Trim( <<<< "") You cannot call a method on a null-valued expression. At C:Fix-PFItems.ps1:448 char:37
- $folderPathSplit = $folderPath.Split( <<<< "") 0 items found in folder \Public Folders - administratorAll Public Folders Accessing this folder on server: CORPMAIL1 Done! Not sure whats going on here.
- Anonymous
November 08, 2013
The comment has been removed - Anonymous
December 17, 2013
Pingback from Wiederhergestellte ??ffentliche Ordner Synchronisieren nicht mehr(Public Folder Replication Problem [entwurf] | Stiki - Anonymous
December 17, 2013
Pingback from Wiederhergestellte ??ffentliche Ordner Synchronisieren nicht mehr (Public Folder Replication Problem [entwurf] | Stiki - Anonymous
December 17, 2013
Pingback from Wiederhergestellte ??ffentliche Ordner Synchronisieren nicht mehr (Public Folder Replication Problem [entwurf] | Stiki - Anonymous
December 17, 2013
Pingback from Wiederhergestellte ??ffentliche Ordner Synchronisieren nicht mehr (Public Folder Replication Problem [entwurf] | Stiki - Anonymous
December 17, 2013
Pingback from Wiederhergestellte ??ffentliche Ordner Synchronisieren nicht mehr (Public Folder Replication Problem [entwurf] | Stiki - Anonymous
December 17, 2013
Pingback from Wiederhergestellte ??ffentliche Ordner Synchronisieren nicht mehr (Public Folder Replication Problem [entwurf] | Stiki - Anonymous
December 17, 2013
Pingback from Wiederhergestellte ??ffentliche Ordner Synchronisieren nicht mehr (Public Folder Replication Problem [entwurf] | Stiki - Anonymous
December 17, 2013
Pingback from Wiederhergestellte ??ffentliche Ordner Synchronisieren nicht mehr (Public Folder Replication Problem [entwurf] | Stiki - Anonymous
December 17, 2013
Pingback from Wiederhergestellte ??ffentliche Ordner Synchronisieren nicht mehr (Public Folder Replication Problem [entwurf] | Stiki - Anonymous
December 17, 2013
Pingback from Wiederhergestellte ??ffentliche Ordner Synchronisieren nicht mehr (Public Folder Replication Problem [entwurf] | Stiki - Anonymous
December 17, 2013
Pingback from Wiederhergestellte ??ffentliche Ordner Synchronisieren nicht mehr (Public Folder Replication Problem [entwurf] | Stiki - Anonymous
December 19, 2013
Pingback from Wiederhergestellte ??ffentliche Ordner Synchronisieren nicht mehr (Public Folder Replication Problem [entwurf] | Stiki - Anonymous
July 24, 2014
Exchange 2003 > 2010, 2007 > 2010 Public Folder Replication / Migration