Bulk Move Item with SPE Script

Hey folks!

Moving items on the Content Tree of Sitecore XM/XP is something frequently asked by customers, and although it’s covered by many other blog posts — and you can easily write an SPE script to do it — I found a scenario where nothing fit my solution:

It will not work.

When moving items from one location to another, Sitecore doesn’t replicate the same structure and will throw an error if it doesn’t identify a pre-existing folder.

For example, imagine your Content Tree looks like this::

PowerShell
/sitecore  
  /media library  
    /Project  
      /Folder1  
        ItemA  
        ItemB  
      /Folder2  
        ItemC  
    /Web  
      /Project  

Now, if you want to move all items under /media library/Project/ to /media library/Web/Project/, the desired outcome should be:

PowerShell
/sitecore  
  /media library
    /Project  
    /Web  
      /Project  
        /Folder1  
          ItemA  
          ItemB  
        /Folder2  
          ItemC  

But Sitecore doesn’t automatically create Folder1 and Folder2 under /Web/Project/. Instead, it will throw an error when trying to move ItemA, ItemB, or ItemC, because their parent folders don’t exist in the target location.

Based on this challenge, I decided to create a PowerShell script to handle the job. In my case, I applied it to move media items within the Media Library, but you can adapt it to any other situation.

Let’s take a look. ⬇️

The script’s purpose is to move items from a source folder to a target folder in the Media Library. It also ensures that the target folder exists before moving any items. Let’s break down how each part of the code works and how it comes together.

The script starts with some configuration variables:

PowerShell
$sourcePathRoot = "/sitecore/media library/Project/"
$targetPathRoot = "/sitecore/media library/Web/"
$folderMediaTemplateId= "{FE5DD826-48C6-436D-B87A-7C4210C7413B}"
$global:counter = 0  
$maxItemsToMove = 100
  • $sourcePathRoot and $targetPathRoot define the source and target paths. These are the directories the script will use to look for and move the items.
  • $folderMediaTemplateId is the ID of the folder template.
  • $global:counter is a global counter that will be used to track how many items have been moved. If you are concerned about moving thousands of items, use it to move by batches.

This function checks if the path for the target folder exists. If the folder doesn’t exist, it will be created. The cool part here is that it will create any intermediate folders that don’t exist, ensuring the final path is intact.

PowerShell
function EnsureFolderExists {
    param (
        [string]$folderPath
    )

    # Check if the folder already exists
    $folderItem = Get-Item -Path $folderPath -ErrorAction SilentlyContinue
    if (-not $folderItem) {
        # Get the parent path
        $parentPath = Split-Path -Path $folderPath -Parent
        EnsureFolderExists -folderPath $parentPath # Ensures parent exists

        # Create the current folder
        $folderName = Split-Path -Path $folderPath -Leaf
        Write-Host "Creating folder: $folderPath" -ForegroundColor Yellow
        New-Item -Path $parentPath -Name $folderName -ItemType $folderMediaTemplateId
    }
}

The function first checks if the folder exists. If not, it recursively calls the function to create any parent folders and finally creates the target folder. No matter how deep the target path is, the script guarantees everything will be created properly.

Now comes the part that actually does the trick. The MoveItems function takes the source and target paths and begins moving the media items.

PowerShell
function MoveItems {
    param (
        [string]$sourcePath,
        [string]$targetPath
    )

    # Get the source folder
    $sourceItem = Get-Item -Path $sourcePath -ErrorAction SilentlyContinue
    if (-not $sourceItem) {
        Write-Host "Source path not found: $sourcePath" -ForegroundColor Red
        return
    }

    # Ensure the destination exists
    EnsureFolderExists -folderPath $targetPath

    # Iterate over the child items in the source folder
    Get-ChildItem -Path $sourcePath | ForEach-Object {
        $child = $_
        $childTargetPath = Join-Path $targetPath $child.Name

        if ($child.TemplateID -eq $folderMediaTemplateId) {
            # If it's a folder, process it recursively
            MoveItems -sourcePath $child.ItemPath -targetPath $childTargetPath
        } else {
            # If it's a media item, move it to the target folder
            if ($global:counter -lt $maxItemsToMove) {
                Write-Host "Moving item: $($child.ItemPath) -> $childTargetPath" -ForegroundColor Green
                Move-Item -Path $child.ItemPath -Destination $targetPath -Force
                $global:counter++
            } else {
                Write-Host "Item limit reached. Stopping further moves." -ForegroundColor Red
                return
            }
        }
    }
}
  1. Getting the Source Item: First, it checks if the source folder exists. If not, it displays an error message.
  2. Ensuring the Destination: Before moving anything, it calls the EnsureFolderExists function to ensure that the target folder is ready to receive the items.
  3. Moving the Items: It then loops through all the items in the source folder and checks whether they are folders or files. If it’s a folder, it calls the MoveItems function recursively to move its contents. If it’s a media item, it moves it to the target folder, as long as the item limit hasn’t been reached.
  4. Item Count: The global $global:counter keeps track of how many items have been moved. Once the limit is reached, the script stops moving items.

At the end, the MoveItems function is called with the source and target paths as parameters, kicking off the entire item-moving process.

PowerShell
Write-Host "Operation started..." -ForegroundColor Cyan
MoveItems -sourcePath $sourcePathRoot -targetPath $targetPathRoot
Write-Host "Operation completed. Total items moved: $global:counter" -ForegroundColor Cyan

It’s always a good idea to test this in a dev environment before running it in production, but this script can save you a lot of time by automating the process.

✨ The Final Code :

PowerShell
$sourcePathRoot = "/sitecore/media library/Project/"
$targetPathRoot = "/sitecore/media library/Web/"
$folderMediaTemplateId = "{FE5DD826-48C6-436D-B87A-7C4210C7413B}"
$global:counter = 0 
$maxItemsToMove = 100

function EnsureFolderExists {
    param (
        [string]$folderPath
    )
    
    $folderItem = Get-Item -Path $folderPath -ErrorAction SilentlyContinue
	
    if (-not $folderItem) {
        
        $parentPath = Split-Path -Path $folderPath -Parent
        EnsureFolderExists -folderPath $parentPath 

        $folderName = Split-Path -Path $folderPath -Leaf
        Write-Host "Creating folder: $folderPath" -ForegroundColor Yellow
        New-Item -Path $parentPath -Name $folderName -ItemType $folderMediaTemplateId
    }
}

function MoveItems {
    param (
        [string]$sourcePath,
        [string]$targetPath
    )
    
    $sourceItem = Get-Item -Path $sourcePath -ErrorAction SilentlyContinue
    if (-not $sourceItem) {
        Write-Host "Source path not found: $sourcePath" -ForegroundColor Red
        return
    }

    EnsureFolderExists -folderPath $targetPath

    Get-ChildItem -Path $sourcePath | ForEach-Object {
        $child = $_
        $childTargetPath = Join-Path $targetPath $child.Name

        if ($child.TemplateID -eq $folderMediaTemplateId) {
            # If it is a folder, process recursively
            MoveItems -sourcePath $child.ItemPath -targetPath $childTargetPath
        } else {
            
            if ($global:counter -lt $maxItemsToMove) {
                Write-Host "Moving item: $($child.ItemPath) -> $childTargetPath" -ForegroundColor Green
                Move-Item -Path $child.ItemPath -Destination $targetPath -Force
                $global:counter++
            } else {
                Write-Host "Item limit reached. Stopping further moves." -ForegroundColor Red
                return
            }
        }
    }
}

Write-Host "Operation started..." -ForegroundColor Cyan
MoveItems -sourcePath $sourcePathRoot -targetPath $targetPathRoot
Write-Host "Operation completed. Total items moved: $global:counter" -ForegroundColor Cyan

Feel free to modify it as needed, and if you find any error or improvement, let me know!

That’s all, keep Sitecoring! 🔥

Category:

2 responses

  1. I think this is all you need:
    “`
    $sourcePathRoot = “/sitecore/media library/Project/”
    $targetPathRoot = “/sitecore/media library/Web/”
    Get-ChildItem -Path $sourcePathRoot | Move-Item -Destination $targetPathRoot
    “`

    1. Hi Alan,

      This script will move all items under $sourcePathRoot to the $targetPathRoot but does not replicate the tree structure that defines the relationship between parents and children. To properly move it following the tree structure you will need to dynamically change the $targetPathRoot or create a new folder (suggested by me in my approach).

Leave a Reply

Your email address will not be published. Required fields are marked *