• Fixing Public Folder Directory Objects That Aren’t Linked To The Hierarchy

    In my recent post on the Exchange Team Blog, I briefly mentioned the dangers of forcibly removing an administrative group from ADSI Edit. The most common unintended consequence of this is a deletion of the directory object that represents the Public Folder Hierarchy.

    Deleting this object creates quite a mess, because all of your mail-enabled public folders and all of your public folder stores point to it. Even if you create a new hierarchy object and manually link the stores to it by setting the msExchOwningPFTree value, you still have a ton of public folder directory objects in Microsoft Exchange System Objects with no homeMDB.

    I had a customer today in this situation, and I wrote a quick Powershell script to fix all the directory objects for the mail-enabled public folders. Here it is.

    If you find yourself in this situation, you’ll still need to create a new hierarchy object and link your public folder stores to it. This script does not fix that part of the problem, although it wouldn’t be hard to adapt it to link the stores as well.

    To keep things simple, this only runs against one domain at a time. You’ll need to change the container to point to the Microsoft Exchange System Objects container in each different domain.

    # Link-PFProxies.ps1
    #
    # Change these two values to match your environment.
    # $container should point to the MESO container you want to run against.
    # $pfTreeDN should contain the distinguishedName of the public folder hierarchy object.

    $container = [ADSI]("LDAP://CN=Microsoft Exchange System Objects,DC=contoso,DC=com")
    $pfTreeDN = "CN=Public Folders,CN=Folder Hierarchies,CN=Exchange Administrative Group (FYDIBOHF23SPDLT),CN=Administrative Groups,CN=First Organization,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=contoso,DC=com"

    #################################################

    $filter = "(!(homemdB=*))"
    $propertyList = @("distinguishedName")
    $scope = [System.DirectoryServices.SearchScope]::OneLevel

    $finder = new-object System.DirectoryServices.DirectorySearcher($container, $filter, $propertyList, $scope)
    $finder.PageSize = 100
    $results = $finder.FindAll()

    ("Found " + $results.Count + " folder proxies with no homeMDB...")
    foreach ($result in $results)
    {
        ("Fixing object: " + $result.Path)
        $entry = $result.GetDirectoryEntry()
        $entry.Put("homeMDB", $pfTreeDN)
        $entry.SetInfo()
    }

  • ExFolders for Exchange 2010 Sp1

    A few of you emailed me when you noticed an error running ExFolders on Exchange 2010 SP1, which was recently released. Attempting to navigate into a mailbox would throw an error stating, “Error: Method not found.” This was due to some changes in the DLLs that ExFolders relies on.

    We’ve just posted a new version of ExFolders that works properly with Sp1. You can find it here.

    The RTM version is still available for download as well. Be aware that the RTM version won’t work on Sp1, and the Sp1 version won’t work on RTM, as far as navigating into mailboxes.

  • How To Identify Bad Items In Public Folder Replication

    As I’ve previously discussed here and here, there are various things that will prevent an item from replicating to an Exchange 2007 or Exchange 2010 public folder store – things like bad start/end dates on appointments, category names that contain certain characters, etc. However, once you’ve figured out that replication is failing because of one of these problems, it can still be tricky to identify which particular items are the ‘bad’ items. In this post, I want to share the approach we usually use to track these down.

    Now, you may be thinking, “Well, I can just compare the source and the destination to see which items didn’t make it over, and those are my bad items, right?” Not quite.

    When we replicate changes for a particular folder, we pack up as many changes as we can until we meet the Replication Message Size Limit configured on the public folder store. This applies to both the replication of new data and the backfill of existing data. As a result, when you observe a backfill response for a folder with a bunch of messages, you typically see a big list of items in the event:

    Event Type:    Information
    Event Source:    MSExchangeIS Public Store
    Event Category:    Replication Outgoing Messages
    Event ID:    3021
    Description:
    An outgoing replication message was issued.

    Type: 0x80000004
    Message ID: <003346D6BDE71245954899B0FC2F3702017DCF@bilongtimb1.bilong.test>
    Folder: (1-16771) IPM_SUBTREE\FolderA\Subfolder1

    Database "First Storage Group\Public Folder Store (BILONGTIMB1)".
    CNSET: 1-1,1-1791E

    CNSET (FAI): 1-1,1-1791E

    Message IDs: 90
      1: 1-1687B, 1-16C45
    --- : Test 10 : 5/19/2010 4:16:12 PM
    2: 1-1687C, 1-16C46
    --- : Test 10 : 5/19/2010 4:16:12 PM
    3: 1-16886, 1-16C47
    --- : Test 10 : 5/19/2010 4:16:12 PM
    4: 1-16890, 1-16C48
    --- : Test 10 : 5/19/2010 4:16:12 PM
    5: 1-1689A, 1-16C49
    --- : Test 10 : 5/19/2010 4:16:12 PM
    6: 1-168A4, 1-16C4A
    --- : Test 10 : 5/19/2010 4:16:12 PM
    7: 1-168AE, 1-16C4B
    --- : Test 10 : 5/19/2010 4:16:12 PM
    8: 1-168B8, 1-16C4C
    --- : Test 10 : 5/19/2010 4:16:12 PM
    9: 1-168C2, 1-16C4D
    --- : Test 10 : 5/19/2010 4:16:12 PM
    10: 1-1687A, 1-16C4E
    --- : Test 9 : 5/19/2010 4:16:09 PM
    11: 1-1687D, 1-16C4F
    --- : Test 9 : 5/19/2010 4:16:09 PM
    12: 1-16887, 1-16C50
    --- : Test 9 : 5/19/2010 4:16:09 PM
    13: 1-16891, 1-16C51
    --- : Test 9 : 5/19/2010 4:16:09 PM
    14: 1-1689B, 1-16C52
    --- : Test 9 : 5/19/2010 4:16:09 PM
    15: 1-168A5, 1-16C53
    --- : Test 9 : 5/19/2010 4:16:09 PM
    16: 1-168AF, 1-16C54
    --- : Test 9 : 5/19/2010 4:16:09 PM
    17: 1-168B9, 1-16C55
    --- : Test 9 : 5/19/2010 4:16:09 PM
    18: 1-168C3, 1-16C56
    --- : Test 9 : 5/19/2010 4:16:09 PM
    19: 1-16879, 1-16C57
    --- : Test 8 : 5/19/2010 4:16:05 PM
    20: 1-1687E, 1-16C58
    --- : Test 8 : 5/19/2010 4:16:05 PM
    21: 1-16888, 1-16C59
    --- : Test 8 : 5/19/2010 4:16:05 PM
    22: 1-16892, 1-16C5A
    --- : Test 8 : 5/19/2010 4:16:05 PM
    23: 1-1689C, 1-16C5B
    --- : Test 8 : 5/19/2010 4:16:05 PM
    24: 1-168A6, 1-16C5C
    --- : Test 8 : 5/19/2010 4:16:05 PM
    25: 1-168B0, 1-16C5D
    --- : Test 8 : 5/19/2010 4:16:05 PM
    26: 1-168BA, 1-16C5E
    --- : Test 8 : 5/19/2010 4:16:05 PM
    27: 1-168C4, 1-16C5F
    --- : Test 8 : 5/19/2010 4:16:05 PM
    28: 1-16878, 1-16C60
    --- : Test 7 : 5/19/2010 4:15:59 PM
    29: 1-1687F, 1-16C61
    --- : Test 7 : 5/19/2010 4:15:59 PM
    30: 1-16889, 1-16C62
    --- : Test 7 : 5/19/2010 4:15:59 PM
    31: 1-16893, 1-16C63
    --- : Test 7 : 5/19/2010 4:15:59 PM
    32: 1-1689D, 1-16C64
    --- : Test 7 : 5/19/2010 4:15:59 PM
    33: 1-168A7, 1-16C65
    --- : Test 7 : 5/19/2010 4:15:59 PM
    34: 1-168B1, 1-16C66
    --- : Test 7 : 5/19/2010 4:15:59 PM
    35: 1-168BB, 1-16C67
    --- : Test 7 : 5...
    MIDSET Deleted: 1-1,1-16871

    Server: /O=FIRST ORGANIZATION/OU=EXCHANGE ADMINISTRATIVE GROUP (FYDIBOHF23SPDLT)/CN=CONFIGURATION/CN=SERVERS/CN=BILONGE12MB1/CN=MICROSOFT PUBLIC MDB

    One thing to notice is that the end of the list of items you just have a “…”. In fact, it doesn’t even give the whole date for that last item – the date is truncated. This “…” at the end means there was more stuff there, but we didn’t log the rest. Because of this, you can’t really see the entire list of items that were included in this backfill response.

    Anyway, if any one of these items fails in content conversion, all the other items in this message also fail to replicate. Because of this, a single problem item can prevent many other items from replicating, even when those other items are just fine.

    Fortunately, there’s an easy way to find the problem items. You can let the replication engine narrow down the problem for you just by changing one simple setting.

    On the store that’s sending the backfill response (the store that has the data), set the replication message size limit to 1KB. Here are some example screenshots of the size limit from various tools:

    sizelimit-Ti

    sizelimit-E12 sizelimit-E12cmd

    In the top two screenshots, I had already changed the limit to 1KB. In the bottom one I had not changed it yet, so it was still at its default of 300KB.

    Once you’ve turned up logging, changed the limit, and re-mounted the public store, go to the server that is missing the data and force a backfill timeout on that folder. In Exchange 2007 and 2010, you do this with the Update-PublicFolder cmdlet, or by right-clicking on the folder and choosing “Update Content” in the PF management GUI. In Exchange 2003, you do this from ESM by going to the public folder tree, clicking on the folder in question in the left-hand pane, choosing the Status tab in the right-hand pane, right-clicking on the server that doesn’t have the data, and choosing “Synchronize Content”.

    Because of the tiny replication size limit, instead of a few big replication messages, you’ll see a ton of tiny ones. Assuming you have logging turned up on Replication Incoming and Replication Outgoing, the application log on the server that has the data will look something like this:

    applog1

    That 3026 you see at the bottom is the backfill request from the other server. Then you have a huge series of 3021’s, one for each backfill response. Each of these responses will only contain one or two messages.

    Once this process completes, most of the good messages will have successfully replicated. At that point, you can go and force a backfill timeout one more time. You’ll see another backfill request followed by some responses – but this time, since all the good stuff replicated, you’ll only see a few backfill responses. You can look at each of those response events to see the messages listed therein.

    And with that information, you know which messages to focus on to determine what the problem is! You can force a backfill timeout over and over, and you should see the store repeatedly try to replicate those same problem items every time.

    Once you’re done troubleshooting, you can change the replication message size limit back to 300KB.

  • Public Folder Admin Permissions Versus Client Permissions

    Most Exchange administrators have noticed at one time or another that there are folders they can see and access through admin tools such as Exchange System Manager or PFDAVAdmin, but they can’t see or access those same folders through Outlook or OWA. I see a lot of confusion over this issue, so I want to explain a bit about how this works.

    Each public folder has two sets of permissions – client permissions and admin permissions. These are stored in two separate properties on the folder. Let’s start by taking a look at these two different ACLs.

    Client Permissions

    When you’re thinking about public folder permissions, you’re usually thinking about client permissions. Client permissions are the ones you see when you get properties on a folder in Outlook, or when you use the Get-PublicFolderClientPermission cmdlet, or when you look at Folder Permissions in the ExFolders or PFDAVAdmin tool. Here are some screenshots of looking at client permissions from different interfaces.

    Clientperms-ESM
    Client permissions in Exchange System Manager

    clientperms-Outlook
    Client permissions in Outlook 2007

    clientperms-PFDAVAdmin
    Client permissions in PFDAVAdmin

    clientperms-Powershell
    Client permissions in Powershell

    Since folders in the hierarchy have typically been created by many different users, the client permissions are often different on each folder. The creator of the folder will be the Owner, and will often change the other rights on the folder as well, so there’s no telling what the client permissions will look like throughout the hierarchy.

    Admin Permissions

    Each public folder has a separate ACL that contains admin permissions. Admin permissions can only be seen through admin tools such as ESM or the Get-PublicFolderAdministrativePermission cmdlet. Here are some screenshots of looking at admin permissions.

    adminperms-ESM
    Admin permissions in ESM

    adminperms-Powershell
    Admin permissions in Powershell

    Unlike client permissions, admin permissions are typically the same on every folder in the hierarchy. While it’s possible to change the admin permissions on individual folders, that’s a very rare scenario. This means that in most environments, Exchange admins do have admin permissions to every folder in the public folder hierarchy.

    Am I using admin or client permissions?

    When you access a public folder, we only honor one ACL or the other – either the client OR the admin permissions, but not both! The ACL we use to determine your access to a folder is determined entirely by the tool you are using to access it.

    • If you are using Outlook to access a public folder, we only look at the client permissions.
    • If you are using Outlook Web Access to access a public folder, we only look at the client permissions.
    • If you are using Exchange System Manager to access a public folder, we only look at the admin permissions.
    • If you are using the Exchange Powershell public folder cmdlets or the Exchange 2007 public folder management GUI, we only look at the admin permissions.
    • If you are using PFDAVAdmin or ExFolders to access a public folder, we only look at the admin permissions.

    This means that while an administrator can see and access all the public folders through an admin tool, he does not have the same rights when using a client such as Outlook. The behavior is different between the two types of programs, because we only look at one ACL or the other.

    This is actually controlled by the tool or client you are using. Any program has the option of asserting admin rights when connecting to the public store, but it is typically hard-coded and the user does not get a choice. The client programs never assert admin rights, while the admin programs always do. If you’re an admin using an admin tool, then you get to use the admin ACL instead of the client ACL to access the public folders through that tool, because the tool asserted admin rights when it connected to the public store.

    One way to see this is to use the MFCMAPI tool. After logging on, if you go to the MDB menu and choose Open Public Store, you’ll see the following dialog box:

    mfcmapi-admin 

    This shows a list of optional flags you can pass when opening the public store. Notice the very first one: 0x00000001 OPENSTORE_USE_ADMIN_PRIVILEGE. When a program passes this flag while opening the public store, it’s saying, “I’m an admin so let me use the admin permissions instead of the client permissions.” The store checks to see if the user has Exchange admin rights, and if so, he gets to use the admin ACL. MFCMAPI is the only tool I’m aware of that actually lets you choose whether to use the client or admin ACL by letting you modify the flags passed when opening the public store.

    If you were writing your own tool, such as a migration tool that needs to access all the public folder content, then this is exactly what you would want to do – pass the OPENSTORE_USE_ADMIN_PRIVILEGE flag when opening the public folder store. If you assert admin rights when opening the store, then your tool automatically gets access to all the public folders (assuming it’s being run by an Exchange admin), and you don’t have to worry about the different client permissions on all the folders.

    If you do need to access all public folders from a program that uses client permissions, you’ll need to use an admin tool to grant client permissions to the desired user on all the folders. This is easily accomplished using ESM, PFDAVAdmin, ExFolders, AddUsersToPFRecursive.ps1, etc.

  • Fixing Mail Enabled Public Folders per KB 977921

    I admit it. I have gotten lazy about posting my new scripts, and haven't posted anything in forever. Today, someone emailed me about the KB 977921 problem, where a public folder has a directory object but is not flagged as mail-enabled. I wrote a script for this a year ago, and never posted it. So, just as I was about to fire off an email with the script attached, I figured I would just post it here so others can benefit as well.

    Keep in mind that while this script runs, it will potentially be deleting and recreating directory objects for folders. While any individual folder should only be missing its directory object for a few seconds, it might be a good idea to pause incoming mail from the internet to avoid unwanted NDRs. The script requires an export file which will contain all the old folder email addresses, and it also generates ldif exports as it runs, so if something goes horribly wrong, you already have a backup of that data. It never hurts to make sure you have a good backup of your AD as well, just in case you need to do an authoritative restore of the Microsoft Exchange System Objects container.

    # Fix-MailEnabled.ps1
    #
    # The purpose of this script is to read an ExFolders or PFDAVAdmin
    # property export of public folders, and fix the folders where the
    # mail-enabled state is not consistent.
    #
    # The export must include the following properties:
    # PR_PF_PROXY, PR_PF_PROXY_REQUIRED, DS:proxyAddresses
    #
    # The export can optionally include the following
    # properties, and if they are included, the script will preserve
    # that setting:
    # DS:msExchHideFromAddressLists
    #
    # This script must be run from Exchange Management Shell.

    # File is required, ExchangeServer and DC are optional
    # Example syntax:
    # .\Fix-MailEnabled C:\ExFoldersExport.txt
    # .\Fix-MailEnabled C:\ExFoldersExport.txt CONTOSO1 DC01
    # .\Fix-MailEnabled -File C:\ExFoldersExport.txt -ExchangeServer CONTOSO1 -DC DC01

    param([string]$File, [string]$ExchangeServer, [string]$DC, [string]$emailAddress, [string]$smtpServer)

    # Log file stuff

    $ScriptPath = Split-Path -Path $MyInvocation.MyCommand.Path -Parent
    $Global:LogFileName = "FixMailEnabled"
    $Global:LogFile  = $ScriptPath + "\" + $LogFileName + ".log"
    $Global:ErrorLogFile = $ScriptPath + "\FixMailEnabled-Errors.log"

    $sendEmailOnError = $false
    if ($emailAddress -ne $null -and $emailAddress.Length -gt 0 -and $smtpServer -ne $null -and $smtpServer.Length -gt 0)
    {
        $sendEmailOnError = $true
    }

    function Test-Transcribing
    {

        $externalHost = $host.gettype().getproperty("ExternalHost", [reflection.bindingflags]"NonPublic,Instance").getvalue($host, @())
        try
        {
            $externalHost.gettype().getproperty("IsTranscribing", [reflection.bindingflags]"NonPublic,Instance").getvalue($externalHost, @())
        }
        catch
        {
            write-warning "This host does not support transcription."

        }

    }

    function writelog([string]$value = "")
    {
        $Global:LogDate = Get-Date -uformat "%Y %m-%d %H:%M:%S"
        ("$LogDate $value")
    }

    function writeerror([string]$value = "")
    {
        $Global:LogDate = Get-Date -uformat "%Y %m-%d %H:%M:%S"
        Add-Content -Path $Global:ErrorLogFile -Value ("$LogDate $value")
        if ($sendEmailOnError)
        {
            writelog("Sending email notification...")
            Send-MailMessage -From "Fix-MailEnabled@Fix-MailEnabled" -To $emailAddress -Subject "Fix-MailEnabled script error" `
             -Body $value -SmtpServer $smtpServer
        }
    }

    $isTranscribing = Test-Transcribing
    if (!($isTranscribing))
    {
        $transcript = Start-Transcript $Global:LogFile -Append
        writelog ($transcript)
    }
    else
    {
        writelog ("Transcript already started. Logging to the current file will continue.")
    }

    writelog ("Fix-MailEnabled starting.")

    # Directory objects will be exported prior to deletion. This could
    # potentionally create a lot of export files. By default these are
    # put in the same folder as the script. If you want to put them
    # elsewhere, change this path and make sure the folder exists.
    $ExportPath = $ScriptPath

    if ($ExchangeServer -eq "")
    {
        # Choose a PF server
        $pfdbs = Get-PublicFolderDatabase
        if ($pfdbs.Length -ne $null)
        {
            $ExchangeServer = $pfdbs[0].Server.Name
        }
        else
        {
            $ExchangeServer = $pfdbs.Server.Name
        }
       
        writelog ("ExchangeServer parameter was not supplied. Using server: " + $ExchangeServer)
    }

    if ($DC -eq "")
    {
        # Choose a DC
        $rootDSE = [ADSI]("LDAP://RootDSE")
        $DC = $rootDSE.Properties.dnsHostName
        writelog ("DC parameter was not supplied. Using DC: " + $DC)
    }

    $reader = new-object System.IO.StreamReader($File)

    # The first line in this file must be the header line, so we can
    # figure out which column is which. Folder Path is always the
    # first column in an ExFolders property export.

    $headerLine = $reader.ReadLine()
    if (!($headerLine.StartsWith("Folder Path")))
    {
        writelog "The input file doesn't seem to have the headers on the first line."
        return
    }

    # Figure out which column is which

    $folderPathIndex = -1
    $proxyIndex = -1
    $proxyRequiredIndex = -1
    $proxyAddressesIndex = -1
    $hideFromAddressListsIndex = -1
    $headers = $headerLine.Split("`t")
    for ($x = 0; $x -lt $headers.Length; $x++)
    {
        if ($headers[$x] -eq "Folder Path")
        {
            $folderPathIndex = $x
        }
        elseif ($headers[$x] -eq "PR_PF_PROXY: 0x671D0102")
        {
            $proxyIndex = $x
        }
        elseif ($headers[$x] -eq "PR_PF_PROXY_REQUIRED: 0x671F000B")
        {
            $proxyRequiredIndex = $x
        }
        elseif ($headers[$x] -eq "DS:proxyAddresses")
        {
            $proxyAddressesIndex = $x
        }
        elseif ($headers[$x] -eq "DS:msExchHideFromAddressLists")
        {
            $hideFromAddressListsIndex = $x
        }
    }

    if ($folderPathIndex -lt 0 -or `
        $proxyIndex -lt 0 -or `
        $proxyRequiredIndex -lt 0 -or `
        $proxyAddressesIndex -lt 0)
    {
        writelog "Required columns were not present in the input file."
        writelog "Headers found:"
        writelog $headers
        return
    }

    # Loop through the lines in the file
    while ($null -ne ($buffer = $reader.ReadLine()))
    {
        $columns = $buffer.Split("`t")
        if ($columns.Length -lt 4)
        {
            continue
        }
       
        # Folder paths from ExFolders always start with "Public Folders" or
        # "System Folders", so trim the first 14 characters.
        $folderPath = $columns[$folderPathIndex].Substring(14)
        $guidString = $columns[$proxyIndex]
        $proxyRequired = $columns[$proxyRequiredIndex]
        $proxyAddresses = $columns[$proxyAddressesIndex]
        $hideFromAddressLists = $false
        if ($hideFromAddressListsIndex -gt -1)
        {
            if ($columns[$hideFromAddressListsIndex] -eq "True")
            {
                $hideFromAddressLists = $true
            }
        }
       
        if ($proxyRequired -ne "True" -and $proxyRequired -ne "1" -and $guidString -ne "PropertyError: NotFound" -and $guidString -ne "" -and $guidString -ne $null)
        {
            # does this objectGUID actually exist?
            $proxyObject = [ADSI]("LDAP://" + $DC + "/<GUID=" + $guidString + ">")
            if ($proxyObject.Path -eq $null)
            {
                # It's possible the object is in a different domain than the one
                # held by the specified DC, so let's try again without a specific DC.
                $proxyObject = [ADSI]("LDAP://<GUID=" + $guidString + ">")
            }
           
            if ($proxyObject.Path -ne $null)
            {
                # PR_PF_PROXY_REQUIRED is false or not set, but we have a directory object.
                # This means we need to mail-enable the folder. Ideally it would link up to
                # the existing directory object, but often, that doesn't seem to happen, and
                # we get a duplicate. So, what we're going to do here is delete the existing
                # directory object, mail-enable the folder, and then set the proxy addresses
                # from the old directory object onto the new directory object.
               
                # First, check if it's already mail-enabled. The input file could be out of
                # sync with the actual properties.
                $folder = Get-PublicFolder $folderPath -Server $ExchangeServer
                if ($folder -ne $null)
                {
                    if ($folder.MailEnabled)
                    {
                        writelog ("Skipping folder because it is already mail-enabled: " + $folderPath)
                        continue
                    }
                }
                else
                {
                    writelog ("Skipping folder because it was not found: " + $folderPath)
                    continue
                }
               
                # If we got to this point, we found the PublicFolder object and it is not
                # already mail-enabled.
                writelog ("Found problem folder: " + $folderPath)
               
                # Export the directory object before we delete it, just in case
                $fileName = $ExportPath + "\" + $guidString + ".ldif"
                $ldifoutput = ldifde -d $proxyObject.Properties.distinguishedName -f ($fileName)
                writelog ("    " + $ldifoutput)
                writelog ("    Exported directory object to file: " + $fileName)

                # Save any explicit permissions
                $explicitPerms = Get-MailPublicFolder $proxyObject.Properties.distinguishedName[0] | Get-ADPermission | `
                    WHERE { $_.IsInherited -eq $false -and (!($_.User.ToString().StartsWith("NT AUTHORITY"))) }

                # Save group memberships
                # We need to do this from a GC to make sure we get them all
                $memberOf = ([ADSI]("GC://" + $proxyObject.Properties.distinguishedName[0])).Properties.memberOf
               
                # Delete the current directory object
                # For some reason Parent comes back as a string in Powershell, so
                # we have to go bind to the parent.
                $parent = [ADSI]($proxyObject.Parent.Replace("LDAP://", ("LDAP://" + $DC + "/")))
                if ($parent.Path -eq $null)
                {
                    $parent = [ADSI]($proxyObject.Parent)
                }
               
                if ($parent.Path -eq $null)
                {
                    $proxyObject.Parent
                    writelog ("Skipping folder because bind to parent container failed: " + $folderPath)
                    continue
                }
               
                $parent.Children.Remove($proxyObject)
                writelog ("    Deleted old directory object.")
               
                # Mail-enable the folder
                Enable-MailPublicFolder $folderPath -Server $ExchangeServer
                writelog ("    Mail-enabled the folder.")
               
                # Disable the email address policy and set the addresses.
                # Because we just deleted the directory object a few seconds ago, it's
                # possible that that change has not replicated everywhere yet. If the
                # Exchange server still sees the object, setting the email addresses will
                # fail. The purpose of the following loop is to retry until it succeeds,
                # pausing in between. If this is constantly failing on the first try, it
                # may be helpful to increase the initial pause.
                $initialSleep = 30 # This is the initial pause. Increase it if needed.
                writelog ("    Sleeping for " + $initialSleep.ToString() + " seconds.")
                Start-Sleep $initialSleep
               
                # Filter out any addresses that aren't SMTP, and put the smtp addresses
                # into a single comma-separated string.
                $proxyAddressArray = $proxyAddresses.Split(" ")
                $proxyAddresses = ""
                foreach ($proxy in $proxyAddressArray)
                {
                    if ($proxy.StartsWith("smtp:"))
                    {
                        if ($proxyAddresses.Length -gt 0)
                        {
                            $proxyAddresses += ","
                        }
                       
                        $proxyAddresses += $proxy.Substring(5)
                    }
                    elseif ($proxy.StartsWith("SMTP:"))
                    {
                        if ($proxyAddresses.Length -gt 0)
                        {
                            $proxyAddresses = $proxy.Substring(5) + "," + $proxyAddresses
                        }
                        else
                        {
                            $proxyAddresses = $proxy.Substring(5)
                        }
                    }
                }

                $proxyAddresses = $proxyAddresses.Split(",")
                $retryCount = 0
                $maxRetry = 3 # The maximum number of times we'll retry
                $succeeded = $false
                while (!($succeeded))
                {
                    writelog ("    Setting proxy addresses...")
                    # Retrieve the new proxy object
                    $newMailPublicFolder = Get-MailPublicFolder $folderPath -Server $ExchangeServer

                    # Now set the properties
                    $Error.Clear()
                    Set-MailPublicFolder $newMailPublicFolder.Identity -EmailAddressPolicyEnabled $false -EmailAddresses $proxyAddresses `
                        -HiddenFromAddressListsEnabled $hideFromAddressLists -Server $ExchangeServer
                    if ($Error[0] -eq $null)
                    {
                        $succeeded = $true
                    }
                    else
                    {
                        writelog ("    Error encountered in Set-MailPublicFolder: " + $Error[0].ToString())
                        if ($retryCount -lt $maxRetry)
                        {
                            $retryCount++
                            writelog ("    Pausing before retry. This will be retry number " `
                                + $retryCount.ToString() + ". Max retry attempts is " + $maxRetry.ToString() + ".")
                            Start-Sleep 60 # This is how long we'll pause before trying again
                        }
                        else
                        {
                            writelog ("    Max retries reached. You must manually set the properties.")
                            writelog ("    See the error log for more details.")
                            writeerror ("Failed to set proxyAddresses on folder.`r`nFolder: " + $folderPath + `
                                "`r`nProxy Addresses:`r`n" + $proxyAddresses + `
                                "`r`nGroup membership:`r`n" + $memberOf + `
                                "`r`nExplicit Permissions:`r`n" + ($explicitPerms | Select-Object User,AccessRights | out-string) + "`r`n")
                           
                            break
                        }
                    }
                }

                if ($succeeded -and $explicitPerms -ne $null)
                {
                    $succeeded = $true
                    writelog ("    Setting explicit permissions on new directory object...")
                    $newMailPublicFolder = Get-MailPublicFolder $folderPath -Server $ExchangeServer
                    foreach ($permission in $explicitPerms)
                    {
                        $Error.Clear()
                        $temp = Add-ADPermission $newMailPublicFolder.Identity -User $permission.User -AccessRights $permission.AccessRights
                        if ($Error[0] -ne $null)
                        {
                            $succeeded = $false
                            writelog ("    Error setting explicit permissions. You must manually set the permissions:")
                            writelog ($explicitPerms)
                            writeerror ("Failed to set explicit permissions on folder.`r`nFolder: " + $folderPath + `
                            "`r`nExplicit Permissions:`r`n" + ($explicitPerms | Select-Object User,AccessRights | out-string) + "`r`n")

                            break
                        }
                    }
                }

                if ($succeeded -and $memberOf -ne $null)
                {
                    writelog ("    Setting group memberships...")
                    $newMailPublicFolder = Get-MailPublicFolder $folderPath -Server $ExchangeServer
                    $proxy = [ADSI]("LDAP://<GUID=" + $newMailPublicFolder.Guid + ">")
                    $proxyDn = $proxy.Properties.distinguishedName[0]
                    $succeeded = $true
                    foreach ($group in $memberOf)
                    {
                        $Error.Clear()
                        $groupObject = [ADSI]("LDAP://" + $group)
                        $temp = $groupObject.Properties.member.Add($proxyDn)
                        $groupObject.CommitChanges()
                        if ($Error[0] -ne $null)
                        {
                            writelog ("    Error setting group memberships. You must add the folder to these groups:")
                            writelog ($memberOf)
                            writeerror ("Failed to set group memberships on folder.`r`nFolder: " + $folderPath + `
                            "`r`nGroup memberships:`r`n" + $memberOf + "`r`n")
                            $succeeded = $false
                            break
                        }
                    }
                }
               
                writelog ("    Set the properties on the new directory object.")
                writelog ("    Done with this folder.")  
            }
            else
            {
                # This means the input file said this was a bad folder, but when we tried
                # to bind to the objectGUID from PR_PF_PROXY, we failed. Either the file
                # is out of sync with the folder settings, or something else went wrong.
                # Do we want to generate any output here?
                writelog ("Skipping folder because the objectGUID was not found: " + $folderPath)
            }

        }
        else
        {
            # If we got here, it means that according to the input file, the folder is
            # not in a state where it has a directory object but is not mail-enabled.
            # Nothing we need to do in that case. The folder is good.
        }
       
    }

    $reader.Close()
    writelog "Done!"
    if (!($isTranscribing))
    {
        Stop-Transcript
    }