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
- 2. Get Page Renderings and Placeholders (simple)
- 3. Get Single Page Rendering and Datasource Fields
- 4. Get Controller Renderings
- 5. Get Used Rendering Variants
- 6. Convert SXA Rendering Variants to Headless
- 7. Copy SXA Variants as Headless Variants
- 8. Convert Controller Renderings to JSON Renderings
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.




