Friday, August 27, 2021

PowerShell 7 with Sitecore PowerShell Module

 Recently one of my project required a migration process to be run on a large number of records. Ideally, I wanted to run the processing for individual Sitecore items in parallel. Unfortunately, SPE doesn't support "-Parallel" parameter on ForEach-Object, so I had to come up with a different approach. I learnt that PowerShell 7 supports this parameter and figured that it might be possible to combine the PowerShell 7 script with execution of SPE script using remoting. Below is the result of this effort, if you are interested.

There is a function that makes a call to a URL to retrieve an image, downloads it into a folder in Sitecore Data Folder, and uploads it into the Media Library. If the process is successful, the function returns the created media item and image field gets updated with the reference to the newly created image. 

Import-Module -Name SPE
$credential = Get-Credential
$session = New-ScriptSession -Username admin -Password b -ConnectionUri https://www-local-9sc.dev.local -Credential $credential 

$apiBaseUrl = "https://[service base url]"
$groupEndpoint = $apiBaseUrl
$headers = @{"X-AUTH-TOKEN" = "#############"}

Write-Output "###############################################################"
Write-Output "################# Starting Import #####################"
Write-Output "###############################################################"

while $groupEndpoint -ne $null) {
    Write-Output "______________________________________________________________________"
    Write-Output $groupEndpoint 
    Write-Output "__________________________________________________________________________"

    $Response = Invoke-RestMethod -Uri $groupEndpoint -Method "GET" -Headers $headers -UseBasicParsing
    $groupEndpoint = $Response.meta.next_link   
        
    $Response.data | ForEach-Object -Parallel {

        $script = {            
            Write-Output "###############################################################"
            Write-Output "$($params.id)"
            Write-Output "---------------------------------------------------------------"

            Write-Log "###############################################################"
            Write-Log "$($params.id)"
            Write-Log "---------------------------------------------------------------"

            Set-Location -Path "master:"

            function New-MediaItem{
                [CmdletBinding()]
                param(
                    [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
                    [ValidateNotNullOrEmpty()]
                    [string]$filePath,

                    [Parameter(Position=1, Mandatory=$true)]
                    [ValidateNotNullOrEmpty()]
                    [string]$mediaPath)

                $mco = New-Object Sitecore.Resources.Media.MediaCreatorOptions
                $mco.Database = [Sitecore.Configuration.Factory]::GetDatabase("master");
                $mco.Language = [Sitecore.Globalization.Language]::Parse("en");
                $mco.Versioned = [Sitecore.Configuration.Settings+Media]::UploadAsVersionableByDefault;
                $mco.Destination = "$($mediaPath)/$([System.IO.Path]::GetFileNameWithoutExtension($filePath))";

                $mc = New-Object Sitecore.Resources.Media.MediaCreator
                return $mc.CreateFromFile($filepath, $mco);
            }

            function Retrieve-Media{
                [CmdletBinding()]
                param(
                    [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
                    [ValidateNotNullOrEmpty()]
                    [string]$sourceUrl,
        
                    [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
                    [ValidateNotNullOrEmpty()]
                    [string]$profileId)
        
                $imageFilename = [System.IO.Path]::GetFileName($sourceUrl)
                $imageFileDest = "$AppPath$dataFolder\images_temp\$profileId\$imageFilename"
                #Write-Host $sourceUrl  $imageFileDest
                    
                New-Item -ItemType Directory -Path "$AppPath$dataFolder\images_temp\$profileId" -Force
    
                $wc = New-Object System.Net.WebClient
                $imageFile = $wc.DownloadFile($sourceUrl, $imageFileDest);
                $imageName = $imageFilename.TrimEnd(".jpg")
                $imagePath = "$mediaFolderPath/$mediaFolderName/$imageFilename"
                    
                $imageMediaItem = $null
                if(Test-Path $imagePath) {
                   Write-Host "Image $imagePath already exists... skipping"
                   $imageMediaItem = Get-Item -Path $imagePath
                   #Write-Host "Existing media item id is " $imageMediaItem.ID
                }
                else
                {       
                   #Write-Host "Uploading Image $imagePath ..."
                   $imageMediaItem = New-MediaItem $imageFileDest "$mediaFolderPath/$mediaFolderName"
                   #Write-Host "Uploading Image $imagePath ... done. " $imageMediaItem.ID
                }
                        
                #Write-Host "Removing file $imageFileDest"
                Remove-Item -Path $imageFileDest
                        
                return $imageMediaItem
            }

            $websiteItem = [Custom.Feature.Agents.Services.AgentWebsiteDashboardService]::GetExistingItem($rootItem, $params.id)

            if ($websiteItem -eq $null) {
                Write-Output "There is no agent website item for this id value. Creating new agent website for $($params.id)"
                Write-Log "Creating new agent website for $($params.id)"
                $websiteItem = [Custom.Feature.Agents.Helpers.PowershellHelper]::CreateAgentWebsite($params.id)
            }

            if ($websiteItem -ne $null) {
                Write-Output "Agent Site ID $($websiteItem.ID.Guid)"
                Write-Log "Agent Site ID $($websiteItem.ID.Guid)"
#### Profile Photo Start
                $mediaFolderName = $_.first_name + " " + $_.last_name
                $mediaFolderPath = $mediaLibraryPath
                    
                $mediaFolderExists = Test-Path -Path $mediaFolderPath
    
# Creating the folder for media assets in Media Library
                if ($mediaFolderExists -eq $false) {
                   New-Item -Path $mediaFolderPath -Name $mediaFolderName -ItemType "System/Media/Media folder"
                   Write-Output "Created Media Folder $($mediaFolderPath/$mediaFolderName)"
                   Write-Log "Created Media Folder $($mediaFolderPath/$mediaFolderName)"
                } 

                if ($_.profile_photo -ne $null) {
                    $profilePhotoMediaItem = Retrieve-Media -sourceUrl $_.profile_photo -profileId $profileId 
                } else {
                    Write-Output "Profile Photo is empty in response"
                    Write-Log "Profile Photo is empty in response" -Log Error
                }
                    
                if ($profilePhotoMediaItem -ne $null -and $primaryAgentItem -ne $null) {
                    $primaryAgentItem.Editing.BeginEdit()
                    $primaryAgentItem.Fields["Profile Photo"].Value = [string]::Format("<image mediaid='{0}' />",$profilePhotoMediaItem.ID)
                    $primaryAgentItem.Editing.EndEdit()

                    Write-Output "Profile Photo Updated"
                    Write-Log "Profile Photo Updated"
                 }
#### Profile Photo End

                 #### Website Item Update Start
                 $websiteItem.Editing.BeginEdit()                    
                 ### field updates got here
                 $websiteItem.Editing.EndEdit()

                 Write-Output "Website Item updated"
                 Write-Log "Website Item updated"
                        
                }
            }
            else {
                Write-Output "There is no agent website item for this gname value - $($params.id) and it wasn't created by this script"
                Write-Log "There is no agent website item for this gname value - $($params.id) and it wasn't created by this script."  -Log Error
            }
        }

        $args = @{ "id" = $_.id; "apiBaseUrl" = $using:apiBaseUrl; "headers" = $using:headers }

        Invoke-RemoteScript -Session $using:session -ScriptBlock $script -ArgumentList $args #-AsJob
              
    }  -ThrottleLimit 4
}

Stop-ScriptSession -Session $session

Write-Output "###############################################################"
Write-Output "################# Finished Import #####################"
Write-Output "###############################################################"