Fix duplication in default folders KB3031133 & KB3069501 & kb2991934

 

In exchange 2013 prior to CU9, if you migrate your mailbox to exchange 2013 you might see duplicates in the default folders

The same issue you may face if you are moving the mailbox from one server to another server using the –IgnoreRuleLimitErrors switch

The update will fix the issue for new migration/mailbox move and our job is to fix it for the migrated/moved mailboxes Smile

The ultimate objective is to move old items to the new default folders and after that delete the folders

Let’s take that a bit by bit:

1- The new default folder is empty and old data is stored in the old folder which is no more a default

2- You can delete the old folder

3- The new default folder usually have the same name suffixed with 1/2/3, for example drafts1 or drafts2

So far, it’s doable to check the WellKnownFolderName and see which one is default and which one is not the default but we still have more details yet to cover Winking smile

4- In some cases if the default language used to be French, the new default folder change to English
for example, the brouillons folder change to be drafts

This point forced me to rethink and reconsider to write a map in the folder names for different languages.
As I was working with a French customer I will share the French map and you can follow the same if you have the same symptoms.

The source folder is the French folder and the target is the one with English, you can apply any combination you want.

folderlist.csv
"Source","Target","Type"
"Calendrier","Calendar","Default"
"Contacts","Contacts","Default"
"boîte de réception","Inbox","Default"
"brouillons","Drafts","Default"
"Journal","Journal","Default"
"tâches","Tasks","Default"
"courrier indésirable","JunkEmail","Default"
"boîte d'envoy","sent items","Default"
"éléments supprimés","deleted items","Default"
"RSS Feeds","RSS Feeds1","Search"

5- Some folders are created by outlook and are duplicated also after the move and are not default folders, like the “RSS Feeds”
if you want to change that you can add them to the list and change the type to “search”

6- What about Folder Names ?

Outlook.exe /ResetFolderNamesis the best way to get the folders back to the default name after we delete the old folders for sure.
That will change the folder to the default settings in your computer, so if you run it from a computer with French language you will have Calendrier instead of Calendar for example.

Now, let’s talk about the script.

The script will move all old items from the old folders to the new default folders and once that completed, the script will remove the old folders.

You need impersonation or full access rights to all mailboxes you would like to fix

The script will ask you to provide a csv file with the list of mailboxes in the following format

Identity
TestE15sv05@myzoo.dev

Example:

.\Fix-DefaulltFolders.ps1 -UseAutodiscover $false -UseDefaultCredentials $true -EwsUrl https://mye15cas01.mylab.dev/ews/exchange.asmx -Verbose -CsvFile ListOfMbx.csv

<# 
.SYNOPSIS 
Fix-DefaultFolders.ps1

.DESCRIPTION
    You need to be assigned full mailbox access or impersonation permissions before you can run this script.
    Moving mailboxes using “-IgnoreRuleLimitErrors” will cause duplicate folders, issue is descriped on kb/2991934
    The duplicate folders are created with different languages
    This script will move all items from the old folders to the new default folders
    The script will delete the old folder if it has 0 items.
.NOTES 
    Author       :      Ahmed Ashour - ahmed.ashour@hotmail.fr
    Creation Date:         v0.1 08 June 2015
    Last Update  :      v0.4 02 July 2015
    Update       :      Add the ability to fix the RSS Feeds based on Search
                        Remove duplicates for working set and conversation action settings
    Customer     :         ------------------
    Ticket Ref   :        ------------------

#  DISCLAIMER:
# THIS CODE IS SAMPLE CODE. THESE SAMPLES ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
# MICROSOFT FURTHER DISCLAIMS ALL IMPLIED WARRANTIES INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR OF FITNESS FOR
# A PARTICULAR PURPOSE. THE ENTIRE RISK ARISING OUT OF THE USE OR PERFORMANCE OF THE SAMPLES REMAINS WITH YOU. IN NO EVENT SHALL
# MICROSOFT OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS,
# BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THE
# SAMPLES, EVEN IF MICROSOFT HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. BECAUSE SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION
# OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES, THE ABOVE LIMITATION MAY NOT APPLY TO YOU.

.PARAMETER CsvFile
The input csv File with primary smtp addresses

.PARAMETER EWSURL
The EWS URL set in exchange

.PARAMETER Impersonate
The default value is [bool]$Impersonate = $false
Use this parameter if you have a service account that can impersonate multiple mailboxes

.PARAMETER UseAutodiscover
The default value is [bool] -UseAutodiscover = $true
If you have issues with autodiscover you can need to set the parameter to $false and use the EWSURL paraemter.

.PARAMETER UseDefaultCredentials
The default value is [bool] -UseDefaultCredentials = $true
If you need to run the script with any other credintials other than the loggedn in powershell user, then you need to set this parameter to $false
and provide the other username, password and domain parameters

.PARAMETER UserName
UserName that will be used for authentication
The userName for O365 has to be the complete UPN.

.PARAMETER Password
Passowrd that will be used for authentication

.PARAMETER Domain
The AD domain name that will be used for authentication if you are running onpremise enviroment only.

.PARAMETER Delete
Delete the Old Folders

.Example

.\Fix-DefaulltFolders.ps1 -UseAutodiscover $false -UseDefaultCredentials $false -EwsUrl https://mye15cas01.mylab.dev/ews/exchange.asmx -Username TestE15Mbx01@mylab.dev -Password Passowrd1 -Verbose -CsvFile ListOfMbx.csv
Run the script for all users in the csv file to move the items from the old folders to the new default folders.

.\Fix-DefaulltFolders.ps1 -UseAutodiscover $false -UseDefaultCredentials $false -EwsUrl https://mye15cas01.mylab.dev/ews/exchange.asmx -Username TestE15Mbx01@mylab.dev -Password Passowrd1 -Verbose -CsvFile ListOfMbx.csv -delete $True
Run the script for all users in the csv file to move the items from the old folders to the new default folders and delete the old one.
#> 
[CmdletBinding(SupportsShouldProcess=$true )]
param (
    [Parameter(Mandatory=$True,HelpMessage="Specifies the input csv file to be accessed")]
    [ValidateNotNullOrEmpty()]
    [string]$CsvFile,
    [bool]$Impersonate = $false,
    [bool]$UseAutodiscover = $true,
    [bool]$UseDefaultCredentials = $true,
    [bool]$TrustAllCertificates = $true,
    [bool]$Delete = $False,
    [string] $Username,
    [string] $Password,
    [string] $Domain,
    [string]$EwsUrl
)

#Common variables
[string]$EWSManagedApiPath = "C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll"
#[string]$EWSManagedApiPath = "C:\Program Files\Microsoft\Exchange\Web Services\1.2\Microsoft.Exchange.WebServices.dll"
$DateStamp = Get-Date -f "dd-MM-yyyy"
$OutFile="Results-"+$DateStamp+".csv"

if ($TrustAllCertificates -eq $true)
{
    ## Code From https://poshcode.org/624
    ## Create a compilation environment
    $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider
    $Compiler=$Provider.CreateCompiler()
    $Params=New-Object System.CodeDom.Compiler.CompilerParameters
    $Params.GenerateExecutable=$False
    $Params.GenerateInMemory=$True
    $Params.IncludeDebugInformation=$False
    $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null
 
$TASource=@'
  namespace Local.ToolkitExtensions.Net.CertificatePolicy{
    public class TrustAll : System.Net.ICertificatePolicy {
      public TrustAll() {
      }
      public bool CheckValidationResult(System.Net.ServicePoint sp,
        System.Security.Cryptography.X509Certificates.X509Certificate cert,
        System.Net.WebRequest req, int problem) {
        return true;
      }
    }
  }
'@
 
    $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)
    $TAAssembly=$TAResults.CompiledAssembly
 
    ## We now create an instance of the TrustAll and attach it to the ServicePointManager
    $TrustAll = $TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")
    [System.Net.ServicePointManager]::CertificatePolicy = $TrustAll
    write-host -ForegroundColor Yellow "Trusting all certificates!"
}
 

# Loading ManagedAPI dll
[Void] [Reflection.Assembly]::LoadFile($EWSManagedApiPath)
 
# Setting up the service
$Service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1);

# Setting up credentials
if ($UseDefaultCredentials -eq $true)
{
    $Service.UseDefaultCredentials = $true;
}
else

    $Service.Credentials = New-Object Microsoft.Exchange.WebServices.Data.WebCredentials($UserName, $Password, $Domain);
}
 

#Function to process the folders
Function ProcessFolders()
{
param($Folder)
    $Source=$folder.source
    $Target=$folder.Target
    $type=$folder.Type
    Write-verbose "processing $($Source) & $($target)"
    $View = New-Object  Microsoft.Exchange.WebServices.Data.FolderView(1000);
    #$View.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep
    $SearchFilterCollection = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection([Microsoft.Exchange.WebServices.Data.LogicalOperator]::OR)
    $SearchSource = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+ContainsSubstring([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$Source);
    $SearchTarget = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+ContainsSubstring([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$Target);
    $SearchFilterCollection.Add($SearchSource)
    $SearchFilterCollection.Add($SearchTarget)
    $SourceList=@()
    $SearchFolders = $RootFolder.FindFolders($SearchFilterCollection,$view)
    if($SearchFolders)
    {
        Foreach($FolderObj in $SearchFolders)
        {  
            "Found Folder: $($FolderObj.displayName) : will check if it's default"
            $FolderProps = New-Object -TypeName Microsoft.Exchange.WebServices.Data.PropertySet(
            [Microsoft.Exchange.WebServices.Data.BasePropertySet]::IdOnly,
            [Microsoft.Exchange.WebServices.Data.FolderSchema]::WellKnownFolderName,
            [Microsoft.Exchange.WebServices.Data.FolderSchema]::FolderClass,
            [Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,
            [Microsoft.Exchange.WebServices.Data.FolderSchema]::totalcount
            )

            #if($Folder.WellKnownFolderName.length -lt 1)
            if($type -eq "Search")
            {
                $Default=$False
                $Display=$FolderObj.displayName
                $FolderObj.displayName = "Del-$($FolderObj.displayName)"
                try
                {
                    $FolderObj.update();
                }
                catch [exception]
                {
                    $_.Exception.Message
                    $_.Exception.Message | out-file -Append $OutFile
                    $Default=$True
                }

                #if($Folder.FolderClass)
                if($Default)
                {
                    $FolderObj.displayName = $Display;
                    "Found Default folder: $($FolderObj.displayName)"
                    "Found Default folder: $($FolderObj.displayName) : the default folder" | Out-File -Append $OutFile
                    $TargetFolder=$FolderObj  
                }
                else
                {
                    $SourceList+=$FolderObj
                    "Found Custom folder: $($FolderObj.displayName) : will add it to the source List"
                    "Found Custom folder: $($FolderObj.displayName) : will add it to the source List" | out-file -Append $OutFile
                }

            }
            else
            {
                $Folder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service, $FolderObj.Id, $FolderProps)
                #$Folder.FolderClass
                $Folder.WellKnownFolderName.length
                $Folder.WellKnownFolderName
                if($Folder.WellKnownFolderName.length -eq 1)
                {
                    "Found Default folder: $($FolderObj.displayName)"
                    "Found Default folder: $($FolderObj.displayName)" | out-file -Append $OutFile
                    $TargetFolder=$FolderObj  
                }
                else
                {
                    $SourceList+=$FolderObj
                    "Found Custom folder: $($FolderObj.displayName) : will add it to the source List"
                    "Found Custom folder: $($FolderObj.displayName) : will add it to the source List" | out-file -Append $outfile
                }
            }
            <#if($Folder.FolderClass)
            {
                    Write-Verbose "Found $($FolderObj.displayName) to be the Target folder"
                    $TargetFolder=$Folder
            }
            #>
        }

        #Stage 2 move the items
        #if($SourceList -and $TargetFolder)
        #{
            #$SourceList
            Write-Verbose "Start moving To: $($TargetFolder.DisplayName)"
            foreach($SourceFolder in $SourceList)
            {
                Write-Verbose "Start moving from: $($SourceFolder.displayName) To: $($TargetFolder.DisplayName)"
                "Start moving from: $($SourceFolder.displayName) To: $($TargetFolder.DisplayName)" | out-file -Append $OutFile
                #Set parameters - we will process in batches of 1000 for the FindItems call
                $Offset=0;
                $PageSize=500;
                $ItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView($PageSize, $Offset, [Microsoft.Exchange.Webservices.Data.OffsetBasePoint]::Beginning);
                $ItemView.PropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::IdOnly);                       
                do{ 
                        $FindResults=$SourceFolder.FindItems($ItemView);
                        ForEach ($Item in $FindResults.Items)
                        {
                            try
                            {
                                $Item.Move($TargetFolder.Id) | Out-Null;
                            }
                            catch [Exception]
                            {
                                $_.exception
                                Write-Host "Failed to move item", $Item.Id.UniqueId -foregroundcolor Red
                            }
                        }
                        $ItemView.Offset+=$FindResults.Items.Count 
                        $Offset+=$PageSize
                        "The new offset is: $($ItemView.Offset)"
                }while($FindResults.MoreAvailable -eq $true)

                #Stage3:Delete the old folder
                if($delete -eq $True)
                {
                    if($SourceFolder.totalcount -eq 0)
                    {
                        Try
                        {   
                            [void]$SourceFolder.Delete( [Microsoft.Exchange.WebServices.Data.DeleteMode]::SoftDelete )
                            Write-Verbose "Deleting : $($SourceFolder.displayName)"
                            "Deleting : $($SourceFolder.displayName)" | Out-File -Append $OutFile
                        }
                        catch [Exception]
                        {
                            $_.Exception.Message
                            $_.Exception.Message | out-file -append $OutFile
                        }
                    }
                    else
                    {
                        "skipping the delete: $($SourceFolder.displayName) : $($SourceFolder.totalcount)" | out-file -Append $OutFile
                        Write-Verbose "skipping the delete: $($SourceFolder.displayName)"
                    }
                }
            }
        #}
       
    }
    else
    {
        write-verbose "couldn't find any folders"
    }               
}

 
#Import List Of mailboxes
$ListOfMbx=import-csv $CsvFile
"$($DateStamp)" | Out-File $OutFile

If($ListOfMbx)
{
    Foreach($Mailbox in $ListOfMbx)
    {

        # Connect to the mailboxes
        # If we are using impersonation then setup impersonation
        if ($Impersonate)
        {
            $Service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $Mailbox.identity)
        }

        # Setting Autodiscover settings
        if ($UseAutodiscover -eq $true)
        {
            $service.AutodiscoverUrl($Mailbox, {$True});
        }
        else
        {
            $Service.Url = new-Object Uri($EwsUrl);
        }

        write-host "Accessing mailbox $($Mailbox.identity)"
        "Accessing mailbox $($Mailbox.identity)" | Out-File -append $OutFile

        #Get root folder
        $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$($Mailbox.identity))
        Try
        {
            $RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
            "Sucessfull authentication to mailbox $($Mailbox.identity)" | Out-File -append $OutFile
            $Go=$True
        }
        Catch [Exception]
        {
            $_.exception.message
            $_.exception.message | Out-File -Append $OutFile
            $Go=$False
        }
       
        if($Go -eq $true)
        {
            $FoldersList=import-csv folderslist.csv -Encoding Unicode
            ForEach($Folder in $FoldersList)
            {

                    ProcessFolders $Folder
            }
            "=============================="
        }
        else
        {
            Write-Error "you need FullAccess permissions in order to proceed"
            "you need FullAccess permissions in order to proceed" | Out-File -Append $OutFile
        }
    }
}

Enjoy Winking smile

Ahmed Ashour
Support Escalation Engineer