Sitecore Lift and Shift Scripts for XM Cloud and Sitecore AI
March 6, 2026 • Nona Dzhurkova • 14 min read

Sitecore Lift and Shift Scripts for XM Cloud and Sitecore AI

I recently worked on a migration project from Sitecore 10 to Sitecore AI where the goal was to keep components and design the same—no full redesign, just lift and shift. We had a copy of the prod database, and to move templates and renderings, the option was to attach it to Docker ( Docker container SQL or a local SQL instance) .

To do that, few steps are required to attach database into the docker.
- add the connection string config in the container deploy folder, containing new line for the database
- add database config definition for the new database
- create a specific IAR for it - just copy the one for master and rename it to match your legacy database name in the connectionstring config file.
Once spin up the containers, I can switch to the new database from Sitecore Desktop. From there we used Sitecore’s move-item-to-another-database feature and moved templates, renderings, placeholder settings, content, and images into the Docker master database locally. There are plenty of linnks over internet how to do it, most of them are for versions of Sitecore with docker before 10 but the only difference is the database config structure and IAR files.

I've decided to use Sitecore CLI to pull and push items into Sitecore AI dev environment.

Bellow is a list of powershell scripts I used to analyze what content is used and how and some other powershel scripts to convert renderings and variants.

Sections:

1. Get Page Renderings, Placeholders, and Parents

This one produces a layout report for a single page—placeholder, rendering name, datasource, rendering UID, dynamic placeholder IDs, and parent rendering when you have nested dynamic placeholders. Handy for understanding how a page is built before you move it to headless.

# =========================
# CONFIG
# =========================
$pagePath = "/sitecore/content/tenant/site/Home"
$useFinal = $true  # true = Final Layout, false = Shared Layout

# =========================
# HELPERS
# =========================
function Get-ParentKeyFromPlaceholder {
  param([string]$placeholder)

  if ([string]::IsNullOrWhiteSpace($placeholder)) { return $null }

  $segments = $placeholder.Trim("/").Split("/")
  if ($segments.Length -lt 2) { return $null }

  $last = $segments[$segments.Length - 1]
  $m = [regex]::Match($last, '(\d+)$')
  if (-not $m.Success) { return $null }

  $parentDynId = [int]$m.Groups[1].Value
  $parentPlaceholder = "/" + ($segments[0..($segments.Length - 2)] -join "/")

  return [pscustomobject]@{
    ParentDynId = $parentDynId
    ParentPlaceholder = $parentPlaceholder
  }
}

# =========================
# LOAD ITEM
# =========================
$item = Get-Item "master:$pagePath"

# =========================
# GET RENDERINGS (SPE)
# =========================
$device = Get-LayoutDevice -Default
$renderings = if ($useFinal) {
  Get-Rendering -Item $item -Device $device -FinalLayout
} else {
  Get-Rendering -Item $item -Device $device
}

# =========================
# BUILD REPORT ROWS
# =========================
$rows = foreach ($r in $renderings) {

  $renderingItem = $null
  $renderingName = "(missing rendering)"
  $renderingPath = ""

  if ($r.ItemID) {
    $renderingItem = Get-Item -Path "master:" -ID  $r.ItemID -ErrorAction SilentlyContinue
    if ($renderingItem) {
      $renderingName = $renderingItem.Name
      $renderingPath = $renderingItem.Paths.FullPath
    }
  }

  $dynId = $null
  if ($r.Parameters -match "DynamicPlaceholderId=(\d+)") { $dynId = [int]$Matches[1] }

  $parentInfo = Get-ParentKeyFromPlaceholder $r.Placeholder

  [pscustomobject]@{
    Placeholder          = $r.Placeholder
    RenderingName        = $renderingName
    RenderingItemPath    = $renderingPath
    Datasource           = $r.Datasource
    RenderingUid         = $r.UniqueId
    DynamicPlaceholderId = $dynId

    ParentPlaceholder    = if ($parentInfo) { $parentInfo.ParentPlaceholder } else { $null }
    ParentDynId          = if ($parentInfo) { $parentInfo.ParentDynId } else { $null }
    ParentRenderingName  = $null
    ParentRenderingUid   = $null
  }
}

# Resolve parents
$lookup = @{}
foreach ($row in $rows) {
  if ($row.Placeholder -and $row.DynamicPlaceholderId) {
    $key = "$($row.Placeholder)|$($row.DynamicPlaceholderId)"
    $lookup[$key] = $row
  }
}

foreach ($row in $rows) {
  if ($row.ParentPlaceholder -and $row.ParentDynId) {
    $pkey = "$($row.ParentPlaceholder)|$($row.ParentDynId)"
    if ($lookup.ContainsKey($pkey)) {
      $parent = $lookup[$pkey]
      $row.ParentRenderingName = $parent.RenderingName
      $row.ParentRenderingUid  = $parent.RenderingUid
    }
  }
}

$rows |
  Sort-Object Placeholder, RenderingName |
  Show-ListView -Title "Layout report: $pagePath" `
    -Property Placeholder, RenderingName, ParentRenderingName, Datasource, RenderingUid, ParentRenderingUid, DynamicPlaceholderId, ParentDynId, RenderingItemPath

2. Get Page Renderings and Placeholders (simple)

A simpler layout report for one page—placeholder, rendering name, datasource, rendering UID, and dynamic placeholder ID. It falls back to layout XML when SPE can’t resolve the rendering item.

# =========================
# CONFIG
# =========================
$pagePath = "/sitecore/content/tenant/site/Home"
$useFinal = $true

$item = Get-Item "master:$pagePath"

$fieldName = if ($useFinal) { "__Final Renderings" } else { "__Renderings" }
$layoutXmlRaw = $item.Fields[$fieldName].Value
[xml]$layoutXml = $layoutXmlRaw

$uidToRid = @{}
foreach ($d in $layoutXml.SelectNodes("//d")) {
  foreach ($r in $d.SelectNodes("./r")) {
    $uid = $r.GetAttribute("uid")
    $rid = $r.'s:id'
    if ($uid -and $rid) {
      $uidToRid[$uid] = $rid
    }
  }
}

$device = Get-LayoutDevice -Default
$renderings = if ($useFinal) {
  Get-Rendering -Item $item -Device $device -FinalLayout
} else {
  Get-Rendering -Item $item -Device $device
}

$rows = foreach ($r in $renderings) {
  $renderingItem = Get-Item -Path "master:" -ID $r.ItemID -ErrorAction SilentlyContinue
  $renderingName = if ($renderingItem) { $renderingItem.Name } else { "(missing rendering)" }
  $dynId = $null
  if ($r.Parameters -match "DynamicPlaceholderId=(\d+)") { $dynId = $Matches[1] }

  [pscustomobject]@{
    Placeholder          = $r.Placeholder
    RenderingName        = $renderingName
    RenderingItemPath    = ""
    Datasource           = $r.Datasource
    RenderingUid         = $r.UniqueId
    DynamicPlaceholderId = $dynId
  }
}

$rows |
  Sort-Object Placeholder, RenderingName |
  Show-ListView -Title "Layout report: $pagePath" `
    -Property Placeholder, RenderingName, RenderingItemPath, Datasource, RenderingUid, DynamicPlaceholderId

3. Get Single Page Rendering and Datasource Fields

For a single page, this lists each rendering, its datasource item, and all non-system field names and values. Good for mapping what your components actually use before you rebuild them as headless.

# CONFIG: set your page here
$pagePath = "master:/sitecore/content/tenant/site/Home"
$device   = Get-LayoutDevice -Default

function Resolve-DataSourceItem {
    param(
        [Parameter(Mandatory=$false)][string]$Datasource,
        [Parameter(Mandatory=$true)]$PageItem
    )

    if ([string]::IsNullOrWhiteSpace($Datasource)) { return $null }

    $dsPath = $Datasource

    if ($dsPath.StartsWith("local:")) {
        $dsPath = $dsPath.Replace("local:", $PageItem.Paths.FullPath)
        return Get-Item -Path ("master:" + $dsPath) -ErrorAction SilentlyContinue
    }

    $guidRegex = '^\{?[A-F0-9]{8}(?:-[A-F0-9]{4}){3}-[A-F0-9]{12}\}?$'
    if ($dsPath -match $guidRegex) {
        $id = $dsPath
        if ($id[0] -ne '{') { $id = '{' + $id.Trim('{}') + '}' }
        return Get-Item master: -ID $id -ErrorAction SilentlyContinue
    }

    return Get-Item -Path ("master:" + $dsPath) -ErrorAction SilentlyContinue
}

function Format-FieldValue {
    param([string]$Value)
    if ($null -eq $Value) { return "" }
    $v = $Value -replace "`r`n|`n|`r", " "
    $v = $v.Trim()
    if ($v.Length -gt 500) { $v = $v.Substring(0,500) + "..." }
    return $v
}

$page = Get-Item -Path $pagePath -ErrorAction SilentlyContinue
if ($null -eq $page) { throw "Page not found: $pagePath" }

$renderings = Get-Rendering -Item $page -Device $device -FinalLayout
$Results = New-Object System.Collections.Generic.List[object]

foreach ($rendering in $renderings) {
    if ($null -eq $rendering.ItemID) { continue }

    $renderingItem = Get-Item master: -ID $rendering.ItemID -ErrorAction SilentlyContinue
    if ($null -eq $renderingItem) { continue }

    $dsItem = Resolve-DataSourceItem -Datasource $rendering.Datasource -PageItem $page

    if ($null -eq $dsItem) {
        $Results.Add([pscustomobject]@{
            PagePath          = $page.Paths.Path
            PageName          = $page.Name
            RenderingName     = $renderingItem.Name
            RenderingPath     = $renderingItem.Paths.Path
            Placeholder       = $rendering.Placeholder
            DataSourceRaw     = $rendering.Datasource
            DataSourcePath    = ""
            DataSourceName    = ""
            DataSourceTemplate= ""
            FieldName         = ""
            FieldValue        = ""
        })
        continue
    }

    foreach ($field in $dsItem.Fields) {
        if ($field.Name -like "__*" ) { continue }

        $Results.Add([pscustomobject]@{
            PagePath           = $page.Paths.Path
            PageName           = $page.Name
            RenderingName      = $renderingItem.Name
            RenderingPath      = $renderingItem.Paths.Path
            Placeholder        = $rendering.Placeholder
            DataSourceRaw      = $rendering.Datasource
            DataSourcePath     = $dsItem.Paths.Path
            DataSourceName     = $dsItem.Name
            DataSourceTemplate = $dsItem.TemplateName
            FieldName          = $field.Name
            FieldValue         = Format-FieldValue -Value $field.Value
        })
    }
}

$Results |
    Sort-Object RenderingName, DataSourcePath, FieldName |
    Show-ListView -Title "Renderings + Datasource Fields (Single Page)" `
        -Property PageName, PagePath, RenderingName, Placeholder, DataSourcePath, DataSourceTemplate, FieldName, FieldValue

4. Get Controller Renderings

Scans all pages under a root and lists only controller renderings—name, path, controller, action, area, datasource, placeholder, and which page they’re on. Use it to find the MVC controller renderings you need to turn into headless/JSON.

Set-Location 'master:\sitecore\content\tenant\site'

$pages   = Get-Item 'master:\sitecore\content\tenant\site' | Get-ChildItem -Recurse
$device  = Get-LayoutDevice -Default
$Results = New-Object System.Collections.Generic.List[object]

function Resolve-DataSourceItem {
    param(
        [Parameter(Mandatory=$false)][string]$Datasource,
        [Parameter(Mandatory=$true)]$PageItem
    )

    if ([string]::IsNullOrWhiteSpace($Datasource)) { return $null }

    $dsPath = $Datasource
    if ($dsPath.StartsWith("local:")) {
        $dsPath = $dsPath.Replace("local:", $PageItem.Paths.FullPath)
        return Get-Item -Path ("master:" + $dsPath) -ErrorAction SilentlyContinue
    }

    $guidRegex = '^\{?[A-F0-9]{8}(?:-[A-F0-9]{4}){3}-[A-F0-9]{12}\}?$'
    if ($dsPath -match $guidRegex) {
        $id = $dsPath
        if ($id[0] -ne '{') { $id = '{' + $id.Trim('{}') + '}' }
        return Get-Item master: -ID $id -ErrorAction SilentlyContinue
    }
    return Get-Item -Path ("master:" + $dsPath) -ErrorAction SilentlyContinue
}

function Is-ControllerRenderingItem {
    param([Parameter(Mandatory=$true)]$RenderingItem)
    if ($null -eq $RenderingItem) { return $false }
    if ($RenderingItem.TemplateName -and ($RenderingItem.TemplateName -match 'Controller Rendering')) {
        return $true
    }
    return $false
}

foreach($page in $pages){
    $renderings = Get-Rendering -Item $page -Device $device -FinalLayout

    foreach($rendering in $renderings){
        if($null -eq $rendering.ItemID) { continue }

        $renderingItem = Get-Item master: -ID $rendering.ItemID -ErrorAction SilentlyContinue
        if($null -eq $renderingItem) { continue }

        if(-not (Is-ControllerRenderingItem -RenderingItem $renderingItem)) { continue }

        $dataSourceItem = Resolve-DataSourceItem -Datasource $rendering.Datasource -PageItem $page
        $pageTemplate   = Get-ItemTemplate -Item $page

        $controller = $renderingItem["Controller"]
        $action     = $renderingItem["Controller Action"]
        $area       = $renderingItem["Area"]

        $Results.Add([pscustomobject]@{
            RenderingItemName = $renderingItem.Name
            RenderingItemPath = $renderingItem.Paths.Path
            Controller         = $controller
            ControllerAction   = $action
            Area               = $area
            DataSource         = $rendering.Datasource
            DataSourcePath     = if($dataSourceItem){ $dataSourceItem.Paths.Path } else { '' }
            Placeholder        = $rendering.Placeholder
            UsedOnPage         = $page.Name
            UsedOnPagePath     = $page.Paths.Path
            PageTemplate       = $pageTemplate.Name
        })
    }
}

$Results |
    Sort-Object UsedOnPagePath, RenderingItemName |
    Show-ListView -Property RenderingItemName, RenderingItemPath, Controller, ControllerAction, Area, DataSourcePath, Placeholder, UsedOnPage, UsedOnPagePath, PageTemplate

5. Get Used Rendering Variants

Walks all pages under a root and reports which SXA rendering variants are actually used (from FieldNames in the rendering parameters). You get rendering name, datasource path, variant folder/name, and page. Helps you see what’s in use before you convert to headless variants.

Set-Location 'master:\sitecore\content\tenant\site'

$pages  = Get-Item 'master:\sitecore\content\tenant\site' | Get-ChildItem -Recurse
$device = Get-LayoutDevice -Default
$Results = New-Object System.Collections.Generic.List[object]

function Resolve-DataSourceItem {
    param(
        [Parameter(Mandatory=$false)][string]$Datasource,
        [Parameter(Mandatory=$true)]$PageItem
    )
    if ([string]::IsNullOrWhiteSpace($Datasource)) { return $null }
    $dsPath = $Datasource
    if ($dsPath.StartsWith("local:")) {
        $dsPath = $dsPath.Replace("local:", $PageItem.Paths.FullPath)
        return Get-Item -Path ("master:" + $dsPath) -ErrorAction SilentlyContinue
    }
    $guidRegex = '^\{?[A-F0-9]{8}(?:-[A-F0-9]{4}){3}-[A-F0-9]{12}\}?$'
    if ($dsPath -match $guidRegex) {
        $id = $dsPath
        if ($id[0] -ne '{') { $id = '{' + $id.Trim('{}') + '}' }
        return Get-Item master: -ID $id -ErrorAction SilentlyContinue
    }
    return Get-Item -Path ("master:" + $dsPath) -ErrorAction SilentlyContinue
}

function Resolve-VariantInfoFromRendering {
    param([Parameter(Mandatory=$true)]$Rendering)
    $variantItem = $null
    $variantName = ''
    $variantFolder = ''
    $variantLabel = ''

    if ($Rendering.Parameters -match 'FieldNames=(?[^&]+)') {
        $raw = $matches['value']
        $decoded = [System.Uri]::UnescapeDataString($raw)
        $decoded = $decoded.Trim()
        $decodedGuid = $decoded.Trim('{}')
        $guidRegexInner = '^[A-F0-9]{8}(?:-[A-F0-9]{4}){3}-[A-F0-9]{12}$'
        if ($decodedGuid -match $guidRegexInner) {
            $id = '{' + $decodedGuid + '}'
            $variantItem = Get-Item master: -ID $id -ErrorAction SilentlyContinue
            if ($variantItem) {
                $variantName = $variantItem.DisplayName
                if ($variantItem.Parent) {
                    $variantFolder = $variantItem.Parent.DisplayName
                    $variantLabel = "$variantFolder / $variantName"
                } else { $variantLabel = $variantName }
            }
        }
    }
    return @{ VariantItem = $variantItem; VariantName = $variantName; VariantFolder = $variantFolder; VariantLabel = $variantLabel }
}

foreach($page in $pages){
    $renderings = Get-Rendering -Item $page -Device $device -FinalLayout

    foreach($rendering in $renderings){
        if($null -eq $rendering.ItemID) { continue }

        $renderingItem = Get-Item master: -ID $rendering.ItemID -ErrorAction SilentlyContinue
        if($null -eq $renderingItem) { continue }

        $dataSourceItem = Resolve-DataSourceItem -Datasource $rendering.Datasource -PageItem $page
        $pageTemplate = Get-ItemTemplate -Item $page
        $variantInfo  = Resolve-VariantInfoFromRendering -Rendering $rendering

        $Results.Add([pscustomobject]@{
            RenderingItemName     = $renderingItem.Name
            RenderingItemPath     = $renderingItem.Paths.Path
            DataSourcePath        = if($dataSourceItem){ $dataSourceItem.Paths.Path } else { '' }
            VariantName           = $variantInfo.VariantName
            VariantRenderingFolder= $variantInfo.VariantFolder
            VariantLabel          = $variantInfo.VariantLabel
            UsedOnPage            = $page.Name
            UsedOnPagePath        = $page.Paths.Path
            PageTemplate          = $pageTemplate.Name
        })
    }
}

$Results |
    Sort-Object UsedOnPagePath, RenderingItemName |
    Show-ListView -Property RenderingItemName, RenderingItemPath, DataSourcePath, VariantLabel, UsedOnPage, UsedOnPagePath, PageTemplate

6. Convert SXA Rendering Variants to Headless (template swap)

Converts classic SXA variants (Variant Definition, Variants folder, Variants Grouping) to headless SXA templates in place—swaps template IDs and keeps field values. Run with $reportOnly = $true first so you can see what would change.

# ============================================
# CONFIG
# ============================================
$rootPath   = "master:/sitecore/content/tenant/Common/Presentation/Rendering Variants"
$reportOnly = $true   # set to $false to apply changes

$templateMap = @{
  "{BBB9AE71-C6B4-4201-83E7-AC5B3EF373DC}" = "{DA26C636-96E1-45E4-88D6-3FCEC70D5699}" # Variants Grouping -> HeadlessVariantsGrouping
  "{E1A3B30C-77BC-4F6C-A008-D01B3371235D}" = "{49C111D0-6867-4798-A724-1F103166E6E9}" # Variants -> HeadlessVariants
  "{FB3E3034-33F8-4CE8-BE98-DD05010F4C22}" = "{4D50CDAE-C2D9-4DE8-B080-8F992BFB1B55}" # Variant Definition -> Headless Variant Definition
}

$root = Get-Item -Path $rootPath -ErrorAction Stop
$all = @($root) + @(Get-ChildItem -Path $root.Paths.Path -Recurse)
$targets = $all | Where-Object { $templateMap.ContainsKey($_.TemplateID.ToString().ToUpper()) }
$targets = $targets | Sort-Object { $_.Paths.Path.Split('/').Count } -Descending

function Swap-TemplatePreserveFields {
  param(
    [Parameter(Mandatory=$true)]$Item,
    [Parameter(Mandatory=$true)][string]$NewTemplateId,
    [Parameter(Mandatory=$true)][bool]$ReportOnly
  )

  $snapshot = @{}
  foreach ($f in $Item.Fields) {
    if ($null -eq $f) { continue }
    if ($f.Name -like "__*") { continue }
    $snapshot[$f.Name] = $f.Value
  }

  $oldTemplate = $Item.TemplateID.ToString().ToUpper()

  if ($ReportOnly) {
    Write-Host ($Item.Paths.Path + " :: " + $oldTemplate + " -> " + $NewTemplateId) -ForegroundColor Yellow
    return
  }

  $Item.Editing.BeginEdit()
  try {
    $Item.TemplateID = $NewTemplateId
    foreach ($kvp in $snapshot.GetEnumerator()) {
      $fieldName = $kvp.Key
      $value     = $kvp.Value
      if ($Item.Fields[$fieldName]) { $Item[$fieldName] = $value }
    }
    $Item.Editing.EndEdit()
    Write-Host ("UPDATED: " + $Item.Paths.Path) -ForegroundColor Green
  }
  catch {
    $Item.Editing.CancelEdit()
    Write-Host ("FAILED: " + $Item.Paths.Path + " :: " + $_.Exception.Message) -ForegroundColor Red
  }
}

foreach ($item in $targets) {
  $oldTid = $item.TemplateID.ToString().ToUpper()
  $newTid = $templateMap[$oldTid]
  Swap-TemplatePreserveFields -Item $item -NewTemplateId $newTid -ReportOnly $reportOnly
}

7. Copy SXA Variants as Headless Variants

Copies classic SXA rendering variants from a source tree into a headless variants tree—creates the HeadlessVariantsGrouping, HeadlessVariants folders, and Headless Variant Definition items and copies over the matching fields. You can use $reportOnly and $overwriteFieldValues to control the run.

# =========================
# CONFIG
# =========================
$sourceRoot = "master:/sitecore/content/tenant/Common/Presentation/Rendering Variants"
$targetRoot = "master:/sitecore/content/Main/Core/Presentation/Headless Variants"

$tpl_VariantsClassic         = "{E1A3B30C-77BC-4F6C-A008-D01B3371235D}"  # Variants
$tpl_VariantDefinitionClassic= "{FB3E3034-33F8-4CE8-BE98-DD05010F4C22}"  # Variant Definition
$tpl_HeadlessVariantsGrouping= "{DA26C636-96E1-45E4-88D6-3FCEC70D5699}"  # HeadlessVariantsGrouping
$tpl_HeadlessVariants        = "{49C111D0-6867-4798-A724-1F103166E6E9}"  # HeadlessVariants
$tpl_HeadlessVariantDefinition= "{4D50CDAE-C2D9-4DE8-B080-8F992BFB1B55}" # Headless Variant Definition

$reportOnly = $false
$overwriteFieldValues = $true

function Normalize-Id([string]$id) {
  if ([string]::IsNullOrWhiteSpace($id)) { return "" }
  $u = $id.Trim().ToUpper()
  if ($u[0] -ne "{") { $u = "{"+ $u.Trim("{}") +"}" }
  return $u
}

function Ensure-Item {
  param([string]$Path, [string]$Name, [string]$TemplateId, [bool]$ReportOnly)
  $parent = Get-Item -Path $Path -ErrorAction Stop
  $childPath = "$Path/$Name"
  $existing = Get-Item -Path $childPath -ErrorAction SilentlyContinue
  if ($existing) { return $existing }
  if ($ReportOnly) { return $null }
  return New-Item -Path $parent.Paths.Path -Name $Name -ItemType $TemplateId
}

function Copy-IntersectingFields {
  param($SourceItem, $TargetItem)
  foreach ($sf in $SourceItem.Fields) {
    if ($null -eq $sf) { continue }
    if ($sf.Name -like "__*") { continue }
    $fieldName = $sf.Name
    if ($TargetItem.Fields[$fieldName]) { $TargetItem[$fieldName] = $SourceItem[$fieldName] }
  }
}

$targetRootItem = Get-Item -Path $targetRoot -ErrorAction SilentlyContinue
if (-not $targetRootItem) {
  $targetPresentation = Split-Path $targetRoot -Parent
  $targetName = Split-Path $targetRoot -Leaf
  if (-not $reportOnly) {
    $targetRootItem = New-Item -Path $targetPresentation -Name $targetName -ItemType $tpl_HeadlessVariantsGrouping
  }
}

$sourceRootItem = Get-Item -Path $sourceRoot -ErrorAction Stop
$variantFolders = Get-ChildItem -Path $sourceRootItem.Paths.Path -Recurse |
  Where-Object { (Normalize-Id $_.TemplateID.ToString()) -eq (Normalize-Id $tpl_VariantsClassic) }

foreach ($vf in $variantFolders) {
  $newFolderPath = "$targetRoot/$($vf.Name)"
  $existingNewFolder = Get-Item -Path $newFolderPath -ErrorAction SilentlyContinue

  if (-not $existingNewFolder) {
    if (-not $reportOnly) {
      $existingNewFolder = New-Item -Path $targetRoot -Name $vf.Name -ItemType $tpl_HeadlessVariants
      $existingNewFolder.Editing.BeginEdit()
      try {
        $existingNewFolder.Appearance.DisplayName = $vf.Appearance.DisplayName
        $existingNewFolder.Editing.EndEdit()
      } catch { $existingNewFolder.Editing.CancelEdit(); throw }
    }
  }

  if ($overwriteFieldValues -and -not $reportOnly -and $existingNewFolder) {
    $existingNewFolder.Editing.BeginEdit()
    try {
      Copy-IntersectingFields -SourceItem $vf -TargetItem $existingNewFolder
      $existingNewFolder.Editing.EndEdit()
    } catch { $existingNewFolder.Editing.CancelEdit() }
  }

  $defs = Get-ChildItem -Path $vf.Paths.Path -Recurse |
    Where-Object { (Normalize-Id $_.TemplateID.ToString()) -eq (Normalize-Id $tpl_VariantDefinitionClassic) }

  foreach ($d in $defs) {
    $relative = $d.Paths.Path.Substring($vf.Paths.Path.Length).TrimStart('/')
    $parentRel = Split-Path $relative -Parent
    $name = Split-Path $relative -Leaf
    $destParentPath = if ([string]::IsNullOrWhiteSpace($parentRel)) { $existingNewFolder.Paths.Path } else { ($existingNewFolder.Paths.Path + "/" + $parentRel).Replace("\","/") }
    $destItemPath = ($destParentPath + "/" + $name).Replace("\","/")
    $existingDef = Get-Item -Path ("master:" + $destItemPath.Replace("master:", "")) -ErrorAction SilentlyContinue

    if (-not $existingDef -and -not $reportOnly) {
      $existingDef = New-Item -Path ("master:" + $destParentPath.Replace("master:", "")) -Name $name -ItemType $tpl_HeadlessVariantDefinition
    }
    if (-not $reportOnly -and $existingDef) {
      $existingDef.Editing.BeginEdit()
      try {
        $existingDef.Appearance.DisplayName = $d.Appearance.DisplayName
        if ($overwriteFieldValues) { Copy-IntersectingFields -SourceItem $d -TargetItem $existingDef }
        $existingDef.Editing.EndEdit()
      } catch { $existingDef.Editing.CancelEdit() }
    }
  }
}

8. Convert Controller Renderings to JSON Renderings

Swaps controller rendering items to the JSON rendering template under a given path. It keeps the fields you care about (e.g. Datasource Location, Placeholder, Controller, componentName) and sets componentName from the item name. Useful when you’re moving to headless or XM Cloud.

$contentPath = "master:/sitecore/layout/Renderings/Feature/tenant"
$controllerTemplateId = "{2A3E91A0-7987-44B5-AB34-35C2D9DE83B9}"
$jsonTemplateId       = "{04646A89-996F-4EE7-878A-FFDBF1F0EF0D}"

$items = Get-ChildItem -Path $contentPath -Recurse | Where-Object { $_.TemplateID -eq $controllerTemplateId }

$fieldsToPreserve = @(
  "Parameters Template",
  "Datasource Location",
  "Datasource Template",
  "Rendering Contents Resolver",
  "Placeholder",
  "Controller",
  "Controller Action",
  "componentName"
)

foreach ($item in $items) {
    $snapshot = @{}
    foreach ($f in $fieldsToPreserve) {
        if ($item.Fields[$f]) { $snapshot[$f] = $item[$f] }
    }

    $item.Editing.BeginEdit()
    try {
        $item.TemplateID = $jsonTemplateId
        foreach ($kvp in $snapshot.GetEnumerator()) {
            $fieldName = $kvp.Key
            $value     = $kvp.Value
            if ($item.Fields[$fieldName]) { $item[$fieldName] = $value }
        }
        if ($item.Fields["componentName"]) {
            $item["componentName"] = $item.Name.Replace(" ", "")
        }
        $item.Editing.EndEdit()
        Write-Host ("Updated: " + $item.Paths.Path)
    }
    catch {
        $item.Editing.CancelEdit()
        Write-Host ("FAILED: " + $item.Paths.Path + " :: " + $_.Exception.Message)
    }
}

All of this assumes you have Sitecore PowerShell Extensions installed. Swap tenant and site for your real tenant and site names, and tweak the template GUIDs if yours are different. Run the reporting scripts first, then do conversions with $reportOnly = $true before you actually apply changes.

Featured Blogs