Fermer

mai 6, 2025

Plan et stratégie / blogs / perficient

Plan et stratégie / blogs / perficient


La migration du contenu du nuage XM Sitecore peut être abordé de diverses manières. La première chose qui vous vient à l’esprit lorsque vous travaillez sur le projet de mise à niveau Sitecore vers XM Cloud est la migration de contenu. Le choix de la bonne méthode aide à réduire le temps de migration et à éviter les erreurs. Je vais décrire l’approche qui répond le mieux aux besoins de nos clients dans ce blog. On nous a fourni un maître sauvegarde de la base de données qui avait tout le contenu. Notre tâche était de déplacer les données Sitecore, y compris les médias, de Sitecore 9.3 au cloud XM.

Nous avons connecté la base de données Legacy Sitecore à l’instance Cloud XM pour la migration. Mon collègue Martin Miles a écrit un blog détaillé sur Migration de contenu XM Cloud: connexion base de données

Migration des données sur les éléments Sitecore vers XM Cloud

L’extraction des éléments et leurs données sur le terrain était relativement simple. Nous avons utilisé des scripts PowerShell pour exporter des données de champ d’élément dans des fichiers CSV. Ces CSV ont ensuite été importés dans le cloud XM à l’aide de scripts d’importation, construire différents types de pages.

Nous avons choisi cette approche en raison d’une refonte complète des modèles et des rendus. La nouvelle configuration XM Cloud n’a pas réutilisé d’anciens modèles, champs ou rendus. Le nuage XM comprenait des modèles, des champs et des rendus frais. Cela nous a permis de cartographier les modèles et les champs de page hérités au nouveau Modèles de nuages ​​XM. Ce mappage nous a aidés à importer des valeurs de champ pour différents types de pages en conséquence.

Nos scripts d’importation ont créé des types de pages de clés et ont rempli les champs à l’aide de mappages de champs. Nous avons créé des rendus standardisés pour les modèles de pages de clés comme les textes riches, l’accordéon, etc. Les scripts ont attribué automatiquement les sources de données pour économiser le temps des auteurs de contenu. Seules quelques pages, comme la maison, l’atterrissage et la recherche, ont été créées manuellement, qui étaient uniques. La plupart des autres pages ont été créées à l’aide de scripts d’automatisation.

XM Cloud s’exécute dans des conteneurs Docker dans l’environnement local. Nous n’avons donc pas pu accéder aux fichiers CSV directement à partir du système de fichiers local. Cependant, nous l’avons couvert par la copie des CSV dans \docker\deploy\platform\App_Data. Ce dossier fait partie du répertoire racine du projet. Ces CSV sont ensuite devenus accessibles dans l’instance de cloud XM local. Nous avons exploité Concepteur de packages Sitecore Pour emballer les fichiers CSV à être installé dans des environnements plus élevés pour exécuter des scripts d’importation.

Concepteur de packages Sitecore - Fichiers statiquement

Migration des médias vers XM Cloud

Nous avons déjà une base de données Legacy Sitecore connectée à l’instance de cloud XM. En utilisant l’interface utilisateur prêt à l’emploi de Sitecore pour la migration de contenu en vrac entre les bases de données, nous avons transféré localement les supports de la base de données hérités vers le cloud XM.

ControlPanel MoveItemTootherDB

Une fois que le dossier multimédia avec tous les sous-dossiers est passé à XM Cloud, nous avons utilisé un script pour créer des packages multimédias. Mon collègue Nick a écrit un blog complet sur Exportation d’articles multimédias avec des extensions de PowerShell Sitecore Pour créer des packages de médias.

Le nombre de packages multimédias dépend de la taille de votre base de données. Nous avions environ 5 Go d’éléments médiatiques à migrer, nous avons donc créé environ 190 packages multimédias pour cette migration. Le nombre de packages dépend également de la taille de chaque package multimédia. Nous avons limité chaque taille de package multimédia à 25 Mo. Les packages multimédias plus grands prennent plus de temps à télécharger et à installer. Parfois, les grands packages sont coincés sans montrer les progrès de l’installation, ce qui est frustrant.

Dans des environnements supérieurs, nous avons utilisé Le script ci-dessous pour télécharger et installer le packages créés localement.

$filepath = Receive-File -Path "C:\inetpub\wwwroot\App_Data\packages\"
Write-Host "Uploaded package Succesfully : " $filepath

# get file size in MB in PowerShell   
$size = (Get-Item -Path $filepath).Length/1MB
Write-Host $size "MB"
Write-Host "Installation Started......"
Install-Package -Path $filepath -InstallMode Merge -MergeMode Merge
Write-Host "Installation Completed - Package installed successfully"

De cette façon, nous avions migré une bibliothèque de médias entière vers XM Cloud. Ce processus était fastidieux et a pris une journée pour créer et télécharger tous les packages.

Je partage des exemples de scripts PowerShell pour extraire et importer des données d’élément Sitecore. Les ID Sitecore utilisées dans les scripts ne sont pas valides; Vous devez les remplacer conformément aux ID d’élément Sitecore de votre projet.

Script d’extraction des données de l’élément Sitecore

<#
    .SYNOPSIS
       Generic Page data extraction
        
    .DESCRIPTION
        Gets Sitecore Items from legacy database and extracts field values.
        List could be exported to csv by OOTB feature of List View in SPE.
        
    .NOTES
        Akash Borkar | Perficient 
#>

function Write-LogExtended {
    param(
        [string]$Message,
        [System.ConsoleColor]$ForegroundColor = $host.UI.RawUI.ForegroundColor,
        [System.ConsoleColor]$BackgroundColor = $host.UI.RawUI.BackgroundColor
    )

    Write-Log -Object $message
    Write-Host -Object $message -ForegroundColor $ForegroundColor -BackgroundColor $backgroundColor
}

# Function for getting item names from pipe-separated item ids
function GetItemNamesFromIds {
    param(
        [System.String] $ids
    )
    
    if($ids.Contains("|"))
    {
        # Split the string by pipe and clean up each GUID
        $guids = $ids.Split("|") | ForEach-Object { $_.Trim(' {}') }
        [Sitecore.Text.ListString]$nameArr = ""

        foreach ($id in $guids) {
            $formattedId = "{0}{1}{2}" -f '{', $id, '}'
    
            $Id = [Sitecore.Data.ID]::Parse($formattedId) 
            $item = Get-Item -Path xyz: -ID $Id
                    
            if ($item -ne $null -and !$nameArr.Contains($item.Name)) {
                $nameArr.Add($item.Name) | Out-Null
            }
        }
        
        # Join the names with pipe separator and return the result
        $names = [System.String]::Join("|", $nameArr)
        return $names
    }
    else
    {
        $item = Get-Item -Path xyz: -ID $ids
            return $item.Name
    }
}

#Function for getting datasource item, which is further used for extracting field value & assigning to new rendering
function Get-DatasourceItem{
    param(
         [System.String] $path
        )
        $datasourceItem = $sourceDatabase.GetItem($path)
        return $datasourceItem
}


Write-LogExtended " "
Write-LogExtended "Generic Pages Data Extraction"
Write-LogExtended "-----------------------------------------------------------------"

$processedItems = [System.Collections.Generic.List[PSCustomObject]]::new()
$nonprocessedItems = [System.Collections.Generic.List[PSCustomObject]]::new()
$sourceDatabase = Get-Database "xyz"

$parentPath = Show-Input -Prompt "Please enter the Sitecore item path for getting children"
if($parentPath -ne $null)
{
    Write-Host "Selected path: $parentPath"

    #Get all child items based on provided path. /sitecore/content/XYZ/Home/Generic
    $nodePath = "xyz:" + $parentPath
    $items = Get-ChildItem -Path $nodePath -Recurse | Where-Object { $_.TemplateName -eq 'Generic Page'}
    
    Write-LogExtended "Total child items: $($items.Count)  Path: $($nodePath)" -ForegroundColor Green
    
    foreach($sourceItem in $items)
    {
        if($sourceItem -ne $null){
            
            #Retrieve RTE
            $rts = Get-Rendering -Item $sourceItem -Device $defaultLayout -FinalLayout | Where-Object { $_.ItemID -eq "{278F7B0D-98F4-4873-9B7B-940082158E4A}"}
            [Sitecore.Text.ListString]$rtArr = ""
            
            if($rts -ne $null)
            {
                foreach($rt in $rts)
                { 
                  $item = $sourceDatabase.GetItem($rt.Datasource)
                  if($item -ne $null -and $item["Text"] -ne "")
                  {
                      $rtArr.Add($item["Text"]) | Out-Null
                  }
                }
            }
            
            
            #Retrieve Accordion
            $accordion = Get-Rendering -Item $sourceItem -Device $defaultLayout -FinalLayout | Where-Object { $_.ItemID -eq "{165B5ECC-E6A0-4B41-AA23-D28FA5A9BF68}"}
            $accordionCount = 0
            [Sitecore.Text.ListString]$titleArr = ""
            [Sitecore.Text.ListString]$descArr = ""
            
            if($accordion -ne $null)
            {
                foreach ($renderingItem in $accordion) 
                {
                    if($renderingItem.Datasource -ne "")
                    {
                        $rendering = Get-Item -Path xyz: -Id $renderingItem.Datasource
                        if($rendering.HasChildren)
                        {
                            $accdChildItems = Get-ChildItem -Path xyz: -ID $rendering.ID
                            foreach($item in $accdChildItems)
                            {
                                if($item["Title"] -ne "" -and $item["Description"] -ne "")
                                {
                                      $titleArr.Add($item["Title"]) | Out-Null
                                      $descArr.Add($item["Description"]) | Out-Null
                                }
                            }
                        $accordionCount++;
                        }
                    }
                }
            }
                
            #Retrieve values of multilist field named Categories
            $categories = $sourceitem["Categories"]
            $categoriesnames = ""
            if($categories -ne "" -and $categories -ne $null)
            {
                $categoriesnames = GetItemNamesFromIds -ids $categories
            }
            
            
            try{
                $processedItems.Add(
                    [PSCustomObject]@{
                        Name = $sourceItem.Name
                        Id = $sourceItem.Id
                        Path = $sourceItem.Paths.FullPath
                        Title = $sourceItem["Title"]
                        HeaderTitle = $sourceItem["Header Title"]
                        Summary = $sourceItem["Summary"]
                        Image = $sourceItem["Image"]
                        OGImage = $sourceItem["Media Image"]
                        Categories = $categoriesnames
                        HasRTE = $rtArr.Count
                        RichText1 = $richText
                        RichText2 = $rtArr[1]
                        RichText3 = $rtArr[2]
                        
                        HasAccordion = $accordionCount
                        AccTitle1 = $titleArr[0]
                        AccDesc1 = $descArr[0]
                        AccTitle2 = $titleArr[1]
                        AccDesc2 = $descArr[1]
                        AccTitle3 = $titleArr[2]
                        AccDesc3 = $descArr[2]
                        AccTitle4 = $titleArr[3]
                        AccDesc4 = $descArr[3]
                        
                    }
                )
                        
            Write-LogExtended "Added data for $($sourceItem.Name), Path: $($sourceItem.Paths.FullPath) " -ForegroundColor Green
            }
            catch{
                Write-Host "Error occured" -BackgroundColor DarkRed
                            
                $nonprocessedItems.Add(
                    [PSCustomObject]@{
                        Name = $sourceItem.Name
                        Id = $sourceItem.Id
                        Path = $sourceItem.Paths.FullPath
                    }
                )
            }
        }
        else
        {
            Write-LogExtended "No Item found in csv for SourceURL: $($row.SourceURL)" -ForegroundColor RED
        }
    }
    
    $processedItems | Show-ListView -PageSize 15000 -Property  Name, Id, Path, Title, HeaderTitle, Summary, Categories, Image, OGImage, 
                                                                HasRTE, RichText1, RichText2, RichText3, HasAccordion, AccTitle1, AccDesc1, AccTitle2, AccDesc2, AccTitle3, 
 AccDesc3, AccTitle4, AccDesc4
                                                             
    $processedItems | export-csv -Path "C:\inetpub\wwwroot\App_Data\Process.csv" -NoTypeInformation                                                  
    $nonprocessedItems | Show-ListView -PageSize 15000 -Property  Name, Id, Path
}
else
{
        Write-Host "Path is not provided : $parentPath"
}

Script d’importation d’élément Sitecore

<#
    .SYNOPSIS
       Generic Pages Import
        
    .DESCRIPTION
        Iterate through CSV, create siteore items for generic pages and Populate fields. Generate List of processed and Non processed items.
        
    .NOTES
        Akash Borkar | Perficient 
#>

function Write-LogExtended {
    param(
        [string]$Message,
        [System.ConsoleColor]$ForegroundColor = $host.UI.RawUI.ForegroundColor,
        [System.ConsoleColor]$BackgroundColor = $host.UI.RawUI.BackgroundColor
    )

    Write-Log -Object $message
    Write-Host -Object $message -ForegroundColor $ForegroundColor -BackgroundColor $backgroundColor
}


Write-LogExtended " "
Write-LogExtended "Generic Pages Import"
Write-LogExtended "-----------------------------------------------------------------"

# Prompt for the file path
$filePath = Show-Input -Prompt "Please enter the full path to the CSV file"
if($filePath -ne $null)
{
Write-Host "Selected file: $filePath"

# Import the CSV file
$csvData = Import-Csv -Path $filePath | Where-Object { -join $_.psobject.Properties.Value }

Write-LogExtended "CSV file read successfully" -ForegroundColor Green

    if($csvData -ne $null)
    {
        $processedItems = [System.Collections.Generic.List[PSCustomObject]]::new()
        $NonProcessedItems = [System.Collections.Generic.List[PSCustomObject]]::new()
        $database = Get-Database "master"
        $placeholder = "/headless-main/container-1"
        
        foreach ($row in $csvData){
            
            #Generic Page branch
            $branchTemplateId = [Sitecore.Data.ID]::Parse("{8032FE9E-3CD1-4E80-8377-66BBF74F839E}")
            
            # Extract the desired item name, parent item and template id from the URL
            $itemName = $row.Name
            $parentItemPath = $row.Path -Replace $itemName, ""

            # Get the parent item
            $parentItem = $database.GetItem($parentItemPath)
            
            if ($parentItem){
                # Check if the item already exists
                $existingItemPath = "$($parentItem.Paths.FullPath)/$itemName"
                $itemExists = Test-Path -Path $existingItemPath
                
                if (-not $itemExists){
                    $item = [Sitecore.Data.Managers.ItemManager]::AddFromTemplate($itemName, $branchTemplateId, $parentItem)
                    
                    if ($item -eq $null) {
                    Write-LogExtended "Unable to create new item - $($itemName) - in Language en" -ForegroundColor Red
                    
                        $NonProcessedItems.Add(
                        [PSCustomObject]@{
                            ID = $row.ID
                            Name    = $row.Name
                            Path = $row.Path
                        })
                    }
                    
                    if($item -ne $null){
                        $item.Editing.BeginEdit()
                        
                        $item["Title"] = $row.Title
                        
                        #Meta Properties/OG
                        $item["OpenGraphTitle"] = $row.Title
                        $item["OpenGraphDescription"] = $row.Summary
                        $item["MetaDescription"] = $row.Summary
                        $item["TwitterDescription"] = $row.Summary 
                        $item["TwitterImage"] = $row.OGImage
                        
                        $item.Editing.EndEdit() | Out-Null
                        
                        
                        $datasourcePath =  $item.Paths.FullPath + "/Data/"
                        #Create and Populate Rich Text (Max RT are 3 as we had extracted 3 RTEs)
                        if($row.RichText1 -ne "")
                        {
                             For($i=1; $i -lt 4; $i++) 
                                { 
                                    $propname = "RichText$i"
                                    if($row.$propname -ne "")
                                    {
                                        $dsitem = New-Item -Path $datasourcePath -Name "Text $i" -ItemType "{4FBDBF79-C7D6-42F1-8048-D5E70D6167D5}"
                                        $dsitem.Editing.BeginEdit()
                                        $dsitem.Text =  $row.$propname
                                        $dsitem.Editing.EndEdit() | Out-Null
                                        
                                        #Create and Set Rich text Rendering
                                        $rendering = get-item -path master: -id {EF82E4AE-C274-40D4-837C-B3E1BF180CCC}
                                        $renderinginstance = $rendering | new-rendering -placeholder $placeholder
                                        $renderinginstance.datasource = $dsitem.id
                                        Add-Rendering -Item $item -placeholder $placeholder -instance $renderinginstance -finallayout
                                        $item.Editing.beginedit()
                                        $item.Editing.endedit() | out-null
                                    }
                                }
                        }
                        
                        
                        #Create and Populate Accrd datasource item (Max Acc are 4)
                        if($row.AccTitle1 -ne "" -and $row.AccDesc1 -ne "")
                        {
                            $accDatasourcePath =  $item.Paths.FullPath + "/Data/"
                            #Accordion
                            $Accitem = New-Item -Path $accDatasourcePath -Name "Accordion" -ItemType "{D482D45C-4248-46C8-BDD5-DE7C2255C52A}"
                            $Accitem.Editing.BeginEdit()
                            $Accitem.Title =  "Accordion"
                            $Accitem.Editing.EndEdit() | Out-Null
                            
                            #Create and Set Acc rendering
                            $rendering = Get-Item -Path master: -ID {3341A94D-42C9-4EE3-8A25-51D8B437982B} #Accordion
                            $renderingInstance = $rendering | New-Rendering -Placeholder $placeholder
                            $renderingInstance.Datasource = $Accitem.ID
                            Add-Rendering -Item $item -PlaceHolder $placeholder -Instance $renderingInstance -FinalLayout
                            
                            For($i=1; $i -lt 5; $i++) 
                            { 
                                $titlename = "AccTitle$i"
                                $descname = "AccDesc$i"
                                if($row.$titlename -ne "" -and $row.$descname -ne "")
                                {
                                    #Acc Panel
                                    $dsitem = New-Item -Path $Accitem.Paths.FullPath -Name $row.$titlename -ItemType "{B50C502C-2740-44C8-A63E-E9E4AF4BAA4B}"
                                    $dsitem.Editing.BeginEdit()
                                    $dsitem.Title =  $row.$titlename
                                    $dsitem.Content =  $row.$descname
                                    $dsitem.Editing.EndEdit() | Out-Null
                                    
                                    #Create and Set Acc panel rendering
                                    $rendering = Get-Item -Path master: -ID {7614DFFF-6735-4BA5-929A-A82FBC91DB25} #Acc Panel
                                    $renderingInstance = $rendering | New-Rendering -Placeholder "/headless-main/container-1/accordion-panels-1"
                                    $renderingInstance.Datasource = $dsitem.ID
                                    Add-Rendering -Item $item -PlaceHolder "/headless-main/container-1/accordion-panels-1" -Instance $renderingInstance -FinalLayout
                                    $item.Editing.BeginEdit()
                                    $item.Editing.EndEdit() | Out-Null
                                    
                                    Write-LogExtended "Added Accordion datasource to New Item - $($item.Name) at $($dsitem.Paths.FullPath)" -ForegroundColor Green
                                }
                            }
                        }
                        
                        $ManualWork = "No"
                        if(($row.HasRTE -gt 3) -or ($row.HasAccordion -gt 4))
                        {
                            $ManualWork = "Yes"
                        }
                        
                        Write-LogExtended "Created New Item - $($itemName) at $($parentItemPath)" -ForegroundColor Green
                        $processedItems.Add(
                        [PSCustomObject]@{
                            Name    = $item.Name
                            Id = $item.ID
                            NewPath = $item.Paths.FullPath
                            HasRTE = $row.HasRTE
                            HasAccordion = $row.HasAccordion
                            ManualWork = $ManualWork 
                        })
                    }
                }
                else 
                {
                    Write-LogExtended "Item $($itemName) already exists at $($parentItemPath) " -ForegroundColor Yellow
                }
            }
            else 
            {
                Write-LogExtended "Parent item not found: $parentItemPath" -ForegroundColor Red
            }
        }
        
    $processedItems | Show-ListView -PageSize 2000 -InfoTitle "Processed Items" -Property  Name, Id, NewPath, HasRTE, HasAccordion, ManualWork
        $processedItems | export-csv -Path "C:\inetpub\wwwroot\App_Data\GenericPagesReport.csv" -NoTypeInformation
    $NonProcessedItems | Show-ListView -PageSize 2000 -InfoTitle "Non Processed Items" -Property  ID, Name, Path
    }
}
else
{
    Write-Host "No file selected : $filePath"
}

J’ai gardé le script limité à l’extraction de certains champs et rendus; Cependant, cela donne une idée juste de la façon d’extraire les données et d’importer.

Conclusion

Les scripts PowerShell que vous écrirez seront basés sur les exigences du projet, la dénomination des modèles, le rendu et les mappages de champs pour vos modèles de page de clé. Lorsque nous avons commencé à migrer, nous avons eu du mal à déterminer l’approche et à connecter les points. Dans ce blog, je voulais m’assurer que le plan était clair. Dans mon prochain blog, je donnerai les extraits de code des plus utilisés Commandes PSE utilisées dans la migration du contenu Sitecore.






Source link