Handling Non-Default Device Renderings During Migration to Sitecore XM Cloud From Sitecore XP.

When migrating from Sitecore XP to XM Cloud we encountered some issues – particularly when dealing with device-specific renderings. While Sitecore XP supported multiple devices (Mobile, Desktop, Custom devices, etc.) for rendering different layouts, Sitecore XM Cloud only supports the ‘Default’ device in GraphQL queries and Layout Service responses.

This limitation created a migration challenge: how do we preserve device-specific rendering logic when moving content from XP to XM Cloud?

The Migration Problem

During our content migration project, we encountered a common scenario where legacy XP implementations had:

  • Renderings assigned to the ‘Mobile’ device for mobile-specific layouts.
  • Renderings assigned to custom devices like ‘Services’ or ‘CustomData’ for specialized content delivery.
  • Different rendering configurations across devices for the same content items.

Since XM Cloud’s headless architecture only recognizes the ‘Default’ device, all these device-specific renderings never showed up in XM Cloud’s GraphQL responses.

The Solution: PowerShell-Based Device Migration

First I created a Rendering Parameter Template having shared checkbox type fields

  • IsUsedInMobileDevice – Set to true if rendering moved / updated from Mobile device to Default device
  • IsUsedInServicesDevice- Set to true if rendering moved / updated from Service device to Default device
  • IsUsedInCustomDataDevice – Set to true if rendering moved / updated from Custom device to Default device

And attached this Rendering Parameter Template with all the renderings.

Secondly, I developed a comprehensive PowerShell script that intelligently migrates device-specific renderings to the Default device while preserving their original intent through parameter-based flags.

How It Works

The script follows a smart migration strategy:

  1. Identifies Source Renderings: Scans specified device types (Mobile, CustomData, Services) for existing renderings
  2. Compares with Default Device: Checks if similar renderings exist on the Default device
  3. Updates or Adds: Either updates existing renderings or adds new ones based on matching criteria
  4. Preserves Context: Maintains device-specific behavior through parameter flags

Key Features

1. Multi-Language Support

The script processes all target languages defined in the configuration:

$targetLanguages = @("en", "ar")

2. Device Parameter Mapping

Each device type is mapped to a specific parameter that indicates its original usage:

$DeviceParameterMapping = @{
    "Mobile" = "IsUsedInMobileDevice"
    "CustomData" = "IsUsedInCustomDataDevice"
    "Services" = "IsUsedInServicesDevice"
}

3. Intelligent Matching Logic

The script uses below matching:

  • Basic Match: Same rendering, placeholder, and datasource.

4. Layout Service Integration

When new renderings are added, the script automatically updates the Layout Service Placeholders field to ensure proper headless rendering support.

Migration Process Flow

  1. Scan Source Device: Identify all renderings on the specified device (Mobile, CustomData, Services)
  2. Compare with Default: Check existing renderings on the Default device
  3. Decision Logic:
    • If a match exists: Update the Default device rendering with the device-specific parameter (e.g., IsUsedInMobileDevice=1)
    • If no match exists: Add the rendering to the Default device with the appropriate parameter (e.g., IsUsedInMobileDevice=1)
  4. Placeholder Management: Ensure placeholder definitions exist in the layout for new renderings
  5. Validation: Verify parameter template support for device-specific parameters

Example Usage

# Migrate Mobile device renderings
Migrate-RenderingsToDefault -SourceDeviceType "Mobile" -ParentItemPath "master:/sitecore/content/<SITE_COLLECTION_NAME>/<SITE_NAME>/Home"

# Migrate Custom Data device renderings
Migrate-RenderingsToDefault -SourceDeviceType "CustomData" -ParentItemPath "master:/sitecore/content/<SITE_COLLECTION_NAME>/<SITE_NAME>/Home"

# Migrate Services device renderings
Migrate-RenderingsToDefault -SourceDeviceType "Services" -ParentItemPath "master:/sitecore/content/<SITE_COLLECTION_NAME>/<SITE_NAME>/Home"

Technical Implementation Highlights

Safe Parameter Handling

The script includes robust parameter cloning to avoid reference issues:

function Clone-Parameters {
    param($originalParameters)
    
    if ($originalParameters -eq $null) {
        return @{}
    }
    
    $clonedParams = @{}
    foreach ($key in $originalParameters.Keys) {
        $clonedParams[$key] = $originalParameters[$key]
    }
    return $clonedParams
}

Placeholder GUID Management

Automatic placeholder resolution ensures proper layout service integration:

function Get-PlaceholderGuid {
    param($placeholderName)
    
    $placeholderItems = Get-ChildItem -Path "master:/sitecore/layout/Placeholder Settings/Project/<SITE_COLLECTION_NAME>/<SITE_NAME>" -Recurse | 
                       Where-Object { $_.TemplateID -eq "{5C547D4E-7111-4995-95B0-6B561751BF2E}" }
    
    $matchingPlaceholder = $placeholderItems | Where-Object { $_.Name -eq $placeholderName }
    
    if ($matchingPlaceholder) {
        return $matchingPlaceholder.ID.ToString()
    }
    
    return $null
}

Error Handling and Logging

Comprehensive error handling ensures migration reliability:

  • Detailed logging for each operation
  • Rollback capabilities for failed updates
  • Status tracking for all processed items

Benefits of This Approach

  1. Preserves Device Logic: Device-specific rendering behavior is maintained through parameter flags
  2. Maintains Content Integrity: All renderings are preserved, nothing is lost during migration
  3. Automated Process: Reduces manual migration effort and human error
  4. Multi-Language Compatible: Handles complex multi-language Sitecore implementations
  5. Comprehensive Reporting: Provides detailed migration reports for validation

Integration

After migration, any platform can utilize the device parameters:

// Example: Check if rendering should be displayed on mobile

if (rendering.parameters.IsUsedInMobileDevice === '1') {

    // Render mobile-specific layout

}

// Example: Check for custom data rendering

if (rendering.parameters.IsUsedInCustomDataDevice === '1') {

    // Apply custom data formatting

}

Complete Script

# This script migrates device renderings to the default device.
# It checks for existing renderings and updates them if they match, or adds new ones if they don't.
# For the match we are checking the rendering ID, placeholder and datasource.

# *** NEW: Define the target languages ***
$targetLanguages = @("en", "ar")

# Device configuration mapping
$DeviceParameterMapping = @{
    "Mobile" = "IsUsedInMobileDevice"
    "CustomData" = "IsUsedInCustomDataDevice"
    "Services" = "IsUsedInServicesDevice"
}

# Helper function to clone parameters hashtable
function Clone-Parameters {
    param($originalParameters)
    
    if ($originalParameters -eq $null) {
        return @{}
    }
    
    $clonedParams = @{}
    foreach ($key in $originalParameters.Keys) {
        $clonedParams[$key] = $originalParameters[$key]
    }
    return $clonedParams
}

# Helper function to create a unique key for exact rendering comparison (including parameters)
function Get-ExactRenderingKey {
    param($rendering)
    
    # Sort parameters to ensure consistent comparison
    $paramString = ""
    if ($rendering.AllParameters -and $rendering.AllParameters.Count -gt 0) {
        $sortedParams = $rendering.AllParameters.GetEnumerator() | Sort-Object Key
        $paramString = ($sortedParams | ForEach-Object { "$($_.Key)=$($_.Value)" }) -join ";"
    }
    
    return "$($rendering.RenderingId)|$($rendering.Placeholder)|$($rendering.Datasource)|$paramString"
}

# Helper function to create a basic key for rendering identification (without parameters)
function Get-BasicRenderingKey {
    param($rendering)
    return "$($rendering.RenderingId)|$($rendering.Placeholder)|$($rendering.Datasource)"
}

# Helper function to get placeholder GUID by name
function Get-PlaceholderGuid {
    param($placeholderName)
    
    try {
        # Get all placeholder settings under the specified path
        $placeholderItems = Get-ChildItem -Path "master:/sitecore/layout/Placeholder Settings/Project/<SITE_COLLECTION_NAME>/<SITE_NAME>" -Recurse | 
                           Where-Object { $_.TemplateID -eq "{5C547D4E-7111-4995-95B0-6B561751BF2E}" }
        
        # Find the placeholder by name
        $matchingPlaceholder = $placeholderItems | Where-Object { $_.Name -eq $placeholderName }
        
        if ($matchingPlaceholder) {
            return $matchingPlaceholder.ID.ToString()
        }
        
        Write-Warning "Placeholder '$placeholderName' not found in placeholder settings"
        return $null
    }
    catch {
        Write-Error "Error getting placeholder GUID: $($_.Exception.Message)"
        return $null
    }
}

# Helper function to update Layout Service Placeholders field
function Update-LayoutServicePlaceholders {
    param(
        $LayoutItem,
        $PlaceholderGuid
    )
    
    try {
        if (-not $LayoutItem -or -not $PlaceholderGuid) {
            return $false
        }
        
        # Get current Layout Service Placeholders field value
        $currentValue = $LayoutItem.Fields["Placeholders"].Value
        
        # Check if the GUID is already present
        if ($currentValue -and $currentValue.Contains($PlaceholderGuid)) {
            Write-Host "    Placeholder GUID already exists in Layout Service Placeholders field" -ForegroundColor Yellow
            return $true
        }
        
        # Append the GUID with pipe separator
        $newValue = if ([string]::IsNullOrEmpty($currentValue)) {
            $PlaceholderGuid
        } else {
            "$currentValue|$PlaceholderGuid"
        }
        
        # Update the field
        $LayoutItem.Editing.BeginEdit()
        $LayoutItem.Fields["Placeholders"].Value = $newValue
        $LayoutItem.Editing.EndEdit()
        
        Write-Host "    Successfully updated Layout Service Placeholders field" -ForegroundColor Green
        return $true
    }
    catch {
        Write-Error "    Failed to update Layout Service Placeholders: $($_.Exception.Message)"
        if ($LayoutItem.Editing.IsEditing) {
            $LayoutItem.Editing.CancelEdit()
        }
        return $false
    }
}

# Helper function to find matching layout and update placeholders
function Process-LayoutPlaceholders {
    param(
        $TargetItem,
        $PlaceholderName
    )
    
    try {
        Write-Host "  Processing layout placeholders for: $PlaceholderName" -ForegroundColor Cyan
        
        # Get the default device (usually the first one in /sitecore/layout/Devices)
        $defaultDevice = Get-Item "master:/sitecore/layout/Devices/Default"
        
        # Load existing layout definition or create a new one
        $layoutField = [Sitecore.Data.Fields.LayoutField]$TargetItem.Fields["__Renderings"]
        $layoutDefinition = if ($layoutField.Value -ne "") {
            [Sitecore.Layouts.LayoutDefinition]::Parse($layoutField.Value)
        } else {
            New-Object Sitecore.Layouts.LayoutDefinition
        }
        
        # Create or update the device definition
        $deviceDefinition = $layoutDefinition.GetDevice($defaultDevice.ID.ToString())
        
        if (-not $deviceDefinition) {
            Write-Warning "  No device definition found for Default device"
            return $false
        }
        
        # Get the layout ID from the device definition
        $layoutId = $deviceDefinition.Layout
        
        if (-not $layoutId -or $layoutId -eq "{00000000-0000-0000-0000-000000000000}") {
            Write-Warning "  No layout ID found in device definition"
            return $false
        }
        
        Write-Host "  Found layout ID: $layoutId" -ForegroundColor Gray
        
        # Get all layout items under the specified path
        $layoutItems = Get-ChildItem -Path "master:/sitecore/layout/Layouts/Project/<SITE_COLLECTION_NAME>/<SITE_NAME>" -Recurse
        
        # Get placeholder GUID
        $placeholderGuid = Get-PlaceholderGuid -placeholderName $PlaceholderName
        
        if (-not $placeholderGuid) {
            Write-Warning "  Could not find GUID for placeholder: $PlaceholderName"
            return $false
        }
        
        Write-Host "  Found placeholder GUID: $placeholderGuid" -ForegroundColor Gray
        
        # Find matching layout item
        $matchingLayout = $layoutItems | Where-Object { $_.ID.ToString() -eq $layoutId }
        
        if ($matchingLayout) {
            Write-Host "  Found matching layout: $($matchingLayout.Name)" -ForegroundColor Blue
            
            # Update the Layout Service Placeholders field
            if (Update-LayoutServicePlaceholders -LayoutItem $matchingLayout -PlaceholderGuid $placeholderGuid) {
                Write-Host "  Successfully updated layout placeholders" -ForegroundColor Green
                return $true
            } else {
                Write-Warning "  Failed to update layout placeholders"
                return $false
            }
        } else {
            Write-Warning "  No matching layout found with ID: $layoutId"
            return $false
        }
    }
    catch {
        Write-Error "  Error processing layout placeholders: $($_.Exception.Message)"
        return $false
    }
}

# Helper function to safely add rendering variant
function Add-RenderingVariant {
    param(
        $TargetItem,
        $RenderingId,
        $Placeholder,
        $Datasource,
        $Parameters,
        $DeviceParameterName,
        $DeviceName = "Default"
    )    
    try {
        Write-Host "Adding a NEW rendering variant: $RenderingId to placeholder: $Placeholder" -ForegroundColor Green
        Write-Host "  Datasource: $Datasource" -ForegroundColor Gray
        
        # Get the rendering definition item        
        $renderingDefItem = Get-Item -Path "master:" -ID $RenderingId -ErrorAction Stop
        
        # Create new rendering instance
        $newRenderingInstance = New-Rendering -Item $renderingDefItem
        
        # Clone parameters to avoid reference issues
        $safeParameters = Clone-Parameters -originalParameters $Parameters
        
        # Add the device indicator parameter
        $safeParameters[$DeviceParameterName] = "1"
        
        # Get the target device for the new rendering
        $devices = Get-ChildItem "master:/sitecore/layout/Devices" -Recurse | Where-Object { $_.Name -eq 'Default' }
        $targetDevice = $devices | Where-Object { $_.Name -eq $DeviceName }
        if ($null -eq $targetDevice) {
            $targetDevice = $devices | Where-Object { $_.Name -eq "Default" }
        }
        
        # Add the rendering to the item
        Add-Rendering -Item $TargetItem -Instance $newRenderingInstance -PlaceHolder $Placeholder -Parameter $safeParameters -DataSource $Datasource -Device $targetDevice -FinalLayout
        
        Write-Host "Successfully added rendering variant to $DeviceName device" -ForegroundColor Green
        
        # Process layout placeholders if rendering was added successfully
        $placeholderProcessed = Process-LayoutPlaceholders -TargetItem $TargetItem -PlaceholderName $Placeholder
        
        if ($placeholderProcessed) {
            Write-Host "Layout placeholders processed successfully" -ForegroundColor Green
        } else {
            Write-Warning "Layout placeholders processing failed or no updates needed"
        }
        
        return $true
    }
    catch {
        Write-Error "Failed to add rendering variant: $($_.Exception.Message)"
        return $false
    }
}

# Helper function to update existing rendering
function Update-ExistingRendering {
    param(
        $SourceRenderingInstance,
        $RenderingItem,
        $TargetItem,
        $RenderingId,
        $DeviceParameterName,
        $MatchingDefaultRendering
    )    
    try {
        $setDeviceParameter = "false"
        
        if (-not ([string]::IsNullOrEmpty($RenderingItem.Fields["Parameters Template"].Value))) {
            # Get the parameter template
            $parameterTemplateItem = Get-Item -Path "master:" -ID $RenderingItem.Fields["Parameters Template"].Value -ErrorAction SilentlyContinue
            
            if ($null -ne $parameterTemplateItem) {
                # if this is not used directly
                if ($parameterTemplateItem.ID -ne "{492CEE00-CB5D-47DE-9C35-E29FB6445F17}") {
                    # if it has inherited
                    if ($parameterTemplateItem.Fields["__Base template"].Value.Contains("{492CEE00-CB5D-47DE-9C35-E29FB6445F17}")) {
                        $setDeviceParameter = "true"
                    }
                }
                elseif ($parameterTemplateItem.ID -eq "{492CEE00-CB5D-47DE-9C35-E29FB6445F17}") { # if this is used directly
                    $setDeviceParameter = "true"
                }
            }
        }
        
        if ($setDeviceParameter -eq "true") {
            Write-Host "Updating existing rendering: $($RenderingItem.ID)" -ForegroundColor Blue
            
            # Use the matching default rendering instance instead of searching again
            $defaultRenderingInstance = $MatchingDefaultRendering.rendering
            
            # Get current parameters from the DEFAULT device rendering
            $allParameters = Get-RenderingParameter -Rendering $defaultRenderingInstance
            
            # Clone existing parameters to avoid reference issues
            $updateParameters = Clone-Parameters -originalParameters $allParameters
            
            # Add or update the device indicator while preserving existing parameters
            $updateParameters[$DeviceParameterName] = "1"
            
            # Update the DEFAULT device rendering parameters (not the source device rendering)
            $defaultRenderingInstance | Set-RenderingParameter -Parameter $updateParameters
            Set-Rendering -Item $TargetItem -Instance $defaultRenderingInstance -FinalLayout
            
            Write-Host "Successfully updated DEFAULT device rendering parameters with $DeviceParameterName = 1" -ForegroundColor Green
            
            return $true
        } else {
            Write-Host "Skipping update - rendering doesn't support device parameters" -ForegroundColor Yellow
            return $false
        }
    }
    catch {
        Write-Warning "Failed to update rendering: $($_.Exception.Message)"
        return $false
    }
}

# Main function to migrate renderings from source device to default
function Migrate-RenderingsToDefault {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet("Mobile", "CustomData", "Services")]
        [string]$SourceDeviceType,
        
        [Parameter(Mandatory = $true)]
        [string]$ParentItemPath        
        
    )
    
    Write-Host "Starting migration from $SourceDeviceType device to Default device" -ForegroundColor Cyan
    Write-Host "Parent item path: $ParentItemPath" -ForegroundColor Gray
    Write-Host "Target item ID: $TargetItemId" -ForegroundColor Gray
    
    # Get the device parameter name for this device type
    $DeviceParameterName = $DeviceParameterMapping[$SourceDeviceType]
    if (-not $DeviceParameterName) {
        Write-Error "Unknown device type: $SourceDeviceType"
        return
    }
    
    Write-Host "Device parameter name: $DeviceParameterName" -ForegroundColor Gray
    
    # *** NEW: Process each target language ***
    foreach ($language in $targetLanguages) {
        Write-Host "`n========================================" -ForegroundColor Magenta
        Write-Host "Processing language: $language" -ForegroundColor Magenta
        Write-Host "========================================" -ForegroundColor Magenta
        
        try {
            # *** NEW: Set the language context ***
            $currentLanguage = [Sitecore.Globalization.Language]::Parse($language)
            [Sitecore.Context]::SetLanguage($currentLanguage, $true)
            
            $finalResults = @()
            
            # *** UPDATED: Get the parent item with language parameter ***
            $parentItem = Get-Item -Path $ParentItemPath -Language $language -ErrorAction SilentlyContinue
            
            if (-not $parentItem) {
                Write-Host "Parent item not found at path: $ParentItemPath for language: $language" -ForegroundColor Red
                Write-Host "Getting Parent item at path: $ParentItemPath for language en" -ForegroundColor Red
                $parentItem = Get-Item -Path $ParentItemPath -Language 'en' -ErrorAction SilentlyContinue
                if (-not $parentItem) {
                    continue
                }
            }
            
            # Get all layout devices
            $devices = Get-ChildItem "master:/sitecore/layout/Devices" -Recurse | Where-Object { $_.Name -eq 'Default' -OR $_.Name -eq $SourceDeviceType }
            
            if ($devices.Count -lt 2) {
                Write-Host "Required devices (Default and $SourceDeviceType) not found for language: $language" -ForegroundColor Red
                continue
            }
            
            # *** UPDATED: Get all child items with language parameter ***
            $childItems = Get-ChildItem -Path $parentItem.Paths.Path -Language $language
            
            foreach ($item in $childItems) {
                # Check if this is the target item
                if ($item) {
                    $itemPath = $item.Paths.FullPath
                    Write-Host "Processing item: $($item.Name) for language: $language" -ForegroundColor Yellow
                    
                    # Get the value of the __Renderings field
                    $renderingsXml = $item."__Renderings"
                    
                    # Check if the __Renderings field has a value
                    if ([string]::IsNullOrEmpty($renderingsXml) -or $renderingsXml -eq "<r />") {
                        Write-Host "Item '$($item.Name)' at '$($item.Paths.FullPath)' does NOT have presentation details for language: $language."
                        continue
                    }
                    
                    # Clear arrays for this item
                    $finalResults = @()
                    
                    # Collect renderings from both devices
                    $defaultRenderings = @()
                    $sourceRenderings = @()
                    
                    foreach ($device in $devices) {
                        $renderings = Get-Rendering -Item $item -Device $device -FinalLayout
                        
                        foreach ($rendering in $renderings) {
                            $allParameters = Get-RenderingParameter -Instance $rendering
                            
                            $renderingObj = [PSCustomObject]@{
                                ItemId = $item.Id
                                ItemPath = $itemPath
                                RenderingId = $rendering.ItemID
                                DeviceName = $device.Name
                                Placeholder = $rendering.Placeholder
                                Datasource = $rendering.Datasource
                                AllParameters = $allParameters
                                rendering = $rendering
                                ExactKey = Get-ExactRenderingKey -rendering @{
                                    RenderingId = $rendering.ItemID
                                    Placeholder = $rendering.Placeholder
                                    Datasource = $rendering.Datasource
                                    AllParameters = $allParameters
                                }
                                BasicKey = Get-BasicRenderingKey -rendering @{
                                    RenderingId = $rendering.ItemID
                                    Placeholder = $rendering.Placeholder
                                    Datasource = $rendering.Datasource
                                }
                            }
                            
                            # Separate by device
                            if ($device.Name -eq "Default") {
                                $defaultRenderings += $renderingObj
                            } elseif ($device.Name -eq $SourceDeviceType) {
                                $sourceRenderings += $renderingObj
                            }
                        }
                    }
                    
                    Write-Host "Found $($defaultRenderings.Count) renderings on Default device for language: $language" -ForegroundColor Cyan
                    Write-Host "Found $($sourceRenderings.Count) renderings on $SourceDeviceType device for language: $language" -ForegroundColor Cyan
                    
                    # Create lookup tables for Default device renderings
                    $defaultExactKeys = @{}  # For exact matches (same parameters)
                    $defaultBasicKeys = @{}  # For basic matches (same rendering+placeholder+datasource)
                    
                    foreach ($defaultRendering in $defaultRenderings) {
                        $defaultExactKeys[$defaultRendering.ExactKey] = $defaultRendering
                        $defaultBasicKeys[$defaultRendering.BasicKey] = $defaultRendering
                    }
                    
                    # Process each source device rendering
                    $updateCount = 0
                    $addCount = 0
                    $skipCount = 0
                    
                    foreach ($sourceRendering in $sourceRenderings) {
                        Write-Host "`nProcessing $SourceDeviceType rendering: $($sourceRendering.RenderingId) for language: $language" -ForegroundColor Magenta
                        Write-Host "  Placeholder: $($sourceRendering.Placeholder)" -ForegroundColor Gray
                        Write-Host "  Datasource: $($sourceRendering.Datasource)" -ForegroundColor Gray
                        
                        # Check for basic match (same rendering + placeholder + datasource)
                        if ($defaultBasicKeys.ContainsKey($sourceRendering.BasicKey)) {
                            Write-Host "  → MATCH found - UPDATING existing DEFAULT device rendering" -ForegroundColor Blue
                            
                            # Get the matching default rendering object
                            $matchingDefaultRendering = $defaultBasicKeys[$sourceRendering.BasicKey]
                            
                            $renderingItem = Get-Item -Path "master:" -ID $sourceRendering.RenderingId -ErrorAction SilentlyContinue
                            
                            if ($renderingItem -and (Update-ExistingRendering -SourceRenderingInstance $sourceRendering.rendering -RenderingItem $renderingItem -TargetItem $item -RenderingId $sourceRendering.RenderingId -DeviceParameterName $DeviceParameterName -MatchingDefaultRendering $matchingDefaultRendering)) {
                                $updateCount++
                                
                                $finalResults += [PSCustomObject]@{
                                    ItemId = $sourceRendering.ItemId
                                    ItemPath = $sourceRendering.ItemPath
                                    RenderingId = $sourceRendering.RenderingId
                                    Placeholder = $sourceRendering.Placeholder
                                    Datasource = $sourceRendering.Datasource
                                    DeviceName = "Default"
                                    Language = $language  # *** NEW: Added language tracking ***
                                    Action = "Updated"
                                    Status = "Success"
                                    Reason = "Match found - parameter $DeviceParameterName set to 1"
                                }
                            } else {
                                $skipCount++
                                $finalResults += [PSCustomObject]@{
                                    ItemId = $sourceRendering.ItemId
                                    ItemPath = $sourceRendering.ItemPath
                                    RenderingId = $sourceRendering.RenderingId
                                    Placeholder = $sourceRendering.Placeholder
                                    Datasource = $sourceRendering.Datasource
                                    DeviceName = "Default"
                                    Language = $language  # *** NEW: Added language tracking ***
                                    Action = "Skipped"
                                    Status = "Update Failed"
                                    Reason = "No device support or error occurred"
                                }
                            }
                        }
                        # Completely new rendering (different placeholder or datasource)
                        else {
                            Write-Host "  → NEW rendering - ADDING to Default device" -ForegroundColor Green
                            
                            $renderingItem = Get-Item -Path "master:" -ID $sourceRendering.RenderingId -ErrorAction SilentlyContinue
                            
                            if ($renderingItem -and (Add-RenderingVariant -TargetItem $item -RenderingId $sourceRendering.RenderingId -Placeholder $sourceRendering.Placeholder -Datasource $sourceRendering.Datasource -Parameters $sourceRendering.AllParameters -DeviceParameterName $DeviceParameterName -DeviceName "Default")) {
                                $addCount++
                                
                                $finalResults += [PSCustomObject]@{
                                    ItemId = $sourceRendering.ItemId
                                    ItemPath = $sourceRendering.ItemPath
                                    RenderingId = $sourceRendering.RenderingId
                                    Placeholder = $sourceRendering.Placeholder
                                    Datasource = $sourceRendering.Datasource
                                    DeviceName = "Default"
                                    Language = $language  # *** NEW: Added language tracking ***
                                    Action = "Added"
                                    Status = "Success"
                                    Reason = "New rendering added with $DeviceParameterName = 1"
                                }
                            } else {
                                $skipCount++
                                $finalResults += [PSCustomObject]@{
                                    ItemId = $sourceRendering.ItemId
                                    ItemPath = $sourceRendering.ItemPath
                                    RenderingId = $sourceRendering.RenderingId
                                    Placeholder = $sourceRendering.Placeholder
                                    Datasource = $sourceRendering.Datasource
                                    DeviceName = "Default"
                                    Language = $language  # *** NEW: Added language tracking ***
                                    Action = "Skipped"
                                    Status = "Add Failed"
                                    Reason = "Error occurred while adding rendering"
                                }
                            }
                        }
                    }
                    
                    Write-Host "`nMigration Summary for $($item.Name) - Language: $language" -ForegroundColor Yellow
                    Write-Host "  - Updated existing renderings: $updateCount" -ForegroundColor Blue
                    Write-Host "  - Added new renderings/variants: $addCount" -ForegroundColor Green
                    Write-Host "  - Skipped (errors): $skipCount" -ForegroundColor Red
                }
            }
            
            # Display results for this language
            if ($finalResults.Count -gt 0) {
                Write-Host "`nFinal Results for Language: $language" -ForegroundColor Yellow
                $finalResults | Format-Table -Property Action, Status, RenderingId, Placeholder, Datasource, Language, Reason -AutoSize
                $finalResults | Show-ListView
            }
            else {
                Write-Host "No operations were performed for language: $language" -ForegroundColor Yellow
            }
        }
        catch {
            Write-Error "Error processing language $language : $($_.Exception.Message)"
            continue
        }
    }
    
    Write-Host "`n========================================" -ForegroundColor Magenta
    Write-Host "Migration from $SourceDeviceType to Default completed for ALL languages." -ForegroundColor Cyan
    Write-Host "========================================" -ForegroundColor Magenta
}

# Example usage:
# Migrate-RenderingsToDefault -SourceDeviceType "Mobile" -ParentItemPath "master:/sitecore/content/<SITE_COLLECTION_NAME>/<SITE_NAME>/Home"
# Migrate-RenderingsToDefault -SourceDeviceType "CustomData" -ParentItemPath "master:/sitecore/content/<SITE_COLLECTION_NAME>/<SITE_NAME>/Home"
# Migrate-RenderingsToDefault -SourceDeviceType "Services" -ParentItemPath "master:/sitecore/content/<SITE_COLLECTION_NAME>/<SITE_NAME>/Home"

After running the script, below are the result




This rendering will be moved to Default device having parameter “Is Used in Mobile Device” set to true.

Conclusion

Migrating from Sitecore XP to XM Cloud requires careful consideration of architectural differences, particularly around device support. This PowerShell solution provides a robust, automated approach to preserve device-specific rendering logic while conforming to XM Cloud’s ‘Default device only’ architecture.

The script demonstrates how thoughtful parameter management and intelligent matching logic can solve complex migration challenges while maintaining the integrity of your content and user experience.

By transforming device-specific renderings into parameter-driven logic, we successfully bridge the gap between XP’s multi-device architecture and XM Cloud’s streamlined headless approach, ensuring a smooth migration path for enterprise Sitecore implementations.

Leave a comment