Skip to content

Instantly share code, notes, and snippets.

@IvanLieckens
Last active September 17, 2020 09:22
Show Gist options
  • Save IvanLieckens/f99277e4ff99b8666cd2d34ca088eee9 to your computer and use it in GitHub Desktop.
Save IvanLieckens/f99277e4ff99b8666cd2d34ca088eee9 to your computer and use it in GitHub Desktop.
Powershell to sync SXA themes with TDS serialized items
Param(
[string]$ThemePath = "sitecore\media library\Themes\Project\Shared\Project",
[string]$FilesPath = ".\-\media\Themes\Project\Shared\Project",
[string]$TdsPath = "..\Project\TDS.Master",
[string]$TdsProject = "TDS.Master.scproj",
[string]$TypeLoadPath = ".\Binaries",
[string]$UpdateFile = "",
[string]$DeleteFile = "",
[string]$ItemId = ""
)
# Load Sitecore Kernel for Serialization
try
{
Add-Type -Path (Join-Path $TypeLoadPath "HtmlAgilityPack.dll")
Add-Type -Path (Join-Path $TypeLoadPath "ITHit.WebDAV.Server.dll")
Add-Type -Path (Join-Path $TypeLoadPath "Lucene.Net.dll")
Add-Type -Path (Join-Path $TypeLoadPath "Microsoft.Extensions.DependencyInjection.Abstractions.dll")
Add-Type -Path (Join-Path $TypeLoadPath "Mvp.Xml.dll")
Add-Type -Path (Join-Path $TypeLoadPath "Newtonsoft.Json.dll")
Add-Type -Path (Join-Path $TypeLoadPath "Sitecore.Logging.dll")
Add-Type -Path (Join-Path $TypeLoadPath "Sitecore.Kernel.dll")
# Load System.Xml.Linq for manipulating project XML
Add-Type -AssemblyName "System.Xml.Linq"
}
catch [System.Reflection.ReflectionTypeLoadException]
{
Write-Verbose "Message: $($_.Exception.Message)" -Verbose
Write-Verbose "StackTrace: $($_.Exception.StackTrace)" -Verbose
Write-Debug "LoaderExceptions: $($_.Exception.LoaderExceptions)" -Debug
}
function Get-Base64FileContents([string]$Path)
{
$stream = New-Object System.IO.FileStream $Path, ([System.IO.FileMode]::Open), ([System.IO.FileAccess]::Read), ([System.IO.FileShare]::Read)
$bytes = New-Object Byte[] $stream.Length
$stream.Read($bytes, 0, $stream.Length)
$stream.Dispose()
return [System.Convert]::ToBase64String($bytes, [System.Base64FormattingOptions]::InsertLineBreaks)
}
function Get-MimeType([string]$Extension)
{
$mimeType = $null
if ($null -ne $Extension)
{
$drive = Get-PSDrive HKCR -ErrorAction SilentlyContinue;
if ($null -eq $drive)
{
$drive = New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT
}
$mimeType = (Get-ItemProperty HKCR:$Extension)."Content Type"
if([string]::IsNullOrWhiteSpace($mimeType))
{
$mimeType = "application/octet-stream"
}
}
return $mimeType
}
function Get-SyncItem([string]$Path)
{
$itemFile = [System.IO.File]::OpenText($Path)
$tokenizer = New-Object Sitecore.Data.Serialization.ObjectModel.Tokenizer $itemFile
[Sitecore.Data.Serialization.ObjectModel.SyncItem]$syncItem = [Sitecore.Data.Serialization.ObjectModel.SyncItem]::ReadItem($tokenizer)
$itemFile.Dispose()
return $syncItem
}
function Set-SharedSyncItemField([Sitecore.Data.Serialization.ObjectModel.SyncItem]$Item, [string]$Name, [string]$Value)
{
[Sitecore.Data.Serialization.ObjectModel.SyncField]$sharedField = $Item.SharedFields.Find({ param($field) [String]::Equals($field.FieldName, $Name, [StringComparison]::OrdinalIgnoreCase) })
$sharedField.FieldValue = $Value
}
function Get-SharedSyncItemField([Sitecore.Data.Serialization.ObjectModel.SyncItem]$Item, [string]$Name)
{
[Sitecore.Data.Serialization.ObjectModel.SyncField]$sharedField = $Item.SharedFields.Find({ param($field) [String]::Equals($field.FieldName, $Name, [StringComparison]::OrdinalIgnoreCase) })
return $sharedField.FieldValue
}
# /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// VALIDATE!!!!!
function Set-VersionedSynItemField([Sitecore.Data.Serialization.ObjectModel.SyncItem]$Item, [string]$Language, [int]$VersionNumber, [string]$Name, [string]$Value)
{
[Sitecore.Data.Serialization.ObjectModel.SyncVersion]$version = $Item.Versions.Find({ param($version) [String]::Equals($version.Language, $Language, [StringComparison]::OrdinalIgnoreCase) -and $version.Version -eq $VersionNumber })
[Sitecore.Data.Serialization.ObjectModel.SyncField]$versionedField = $version.Fields.Find({ param($field) [String]::Equals($field.FieldName, $Name, [StringComparison]::OrdinalIgnoreCase) })
$versionedField.FieldValue = $Value
}
function Get-VersionedSynItemField([Sitecore.Data.Serialization.ObjectModel.SyncItem]$Item, [string]$Language, [int]$VersionNumber, [string]$Name)
{
[Sitecore.Data.Serialization.ObjectModel.SyncVersion]$version = $Item.Versions.Find({ param($version) [String]::Equals($version.Language, $Language, [StringComparison]::OrdinalIgnoreCase) -and $version.Version -eq $VersionNumber })
[Sitecore.Data.Serialization.ObjectModel.SyncField]$versionedField = $version.Fields.Find({ param($field) [String]::Equals($field.FieldName, $Name, [StringComparison]::OrdinalIgnoreCase) })
return $versionedField.FieldValue
}
# ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function Create-MediaFolder([string]$Path, [string]$Name)
{
Write-Verbose "Creating folder $Name in $Path" -Verbose
# Load Parent
[Sitecore.Data.Serialization.ObjectModel.SyncItem]$parent = Get-SyncItem "$(Resolve-Path (Join-Path $Path "..")).item"
$sitecorePath = "$($parent.ItemPath)/$Name"
# Build new media folder
[Sitecore.Data.Serialization.ObjectModel.SyncItem]$mediaFolder = New-Object Sitecore.Data.Serialization.ObjectModel.SyncItem
$mediaFolder.ID = ([Guid]::NewGuid()).ToString("B").ToUpper()
$mediaFolder.DatabaseName = "master"
$mediaFolder.ItemPath = $sitecorePath
$mediaFolder.ParentID = $parent.ID
$mediaFolder.Name = $Name
$mediaFolder.MasterID = ([Guid]::Empty).ToString("B").ToUpper()
$mediaFolder.TemplateID = "{FE5DD826-48C6-436D-B87A-7C4210C7413B}"
$mediaFolder.TemplateName = "Media folder"
$isoNow = [Sitecore.DateUtil]::IsoNow
$mediaFolder.Created = $isoNow
$revision = [Guid]::NewGuid()
[Sitecore.Data.Serialization.ObjectModel.SyncVersion]$enVersion = $mediaFolder.AddVersion("en", "1", $revision)
$enVersion.AddField("{52807595-0F8F-4B20-8D2A-CB71D28C6103}", "__Owner", "__owner", "sitecore\syncscript", $true)
$enVersion.AddField("{25BED78C-4957-4165-998A-CA1B52F67497}", "__Created", "__created", $isoNow, $true)
$enVersion.AddField("{5DD74568-4D4B-44C1-B513-0AF5F4CDA34F}", "__Created by", "__created by", "sitecore\syncscript", $true)
$enVersion.AddField("{8CDC337E-A112-42FB-BBB4-4143751E123F}", "__Revision", "__revision", $revision, $true)
$enVersion.AddField("{D9CF14B1-FA16-4BA6-9288-E8A174D4D522}", "__Updated", "__updated", $isoNow, $true)
$enVersion.AddField("{BADD9CF9-53E0-4D0C-BCC0-2D784C282F6A}", "__Updated by", "__updated by", "sitecore\syncscript", $true)
# Create folder if needed and file
if(-not (Test-Path $Path))
{
New-Item -Path $Path -ItemType Directory
}
$itemWriter = [System.IO.File]::CreateText("$Path.item")
$mediaFolder.Serialize($itemWriter)
$itemWriter.Dispose()
}
function Update-MediaFile([string]$Path, [System.IO.FileInfo]$File)
{
$syncItem = Get-SyncItem -Path $Path
$fileContent = Get-Base64FileContents -Path $File.FullName
$originalContent = Get-SharedSyncItemField -Item $syncItem -Name "Blob"
if()
{
}
Set-SharedSyncItemField -Item $syncItem -Name "Blob" -Value $fileContent[1]
Set-SharedSyncItemField -Item $syncItem -Name "Size" -Value $fileContent[0]
$itemWriter = [System.IO.File]::CreateText($Path)
$syncItem.Serialize($itemWriter)
$itemWriter.Dispose()
}
function Create-MediaFile([string]$Path, [System.IO.FileInfo]$File, [string]$ItemId)
{
Write-Verbose "Creating $($File.Name) in $Path" -Verbose
# Create clean sitecore name
$name = [System.IO.Path]::ChangeExtension($File.Name, [string]::Empty)
$name = $name -replace "\.", [string]::Empty
# Load Parent
[Sitecore.Data.Serialization.ObjectModel.SyncItem]$parent = Get-SyncItem "$(Resolve-Path (Join-Path $Path "..")).item"
$sitecorePath = "$($parent.ItemPath)/$name"
# Build new media file
[Sitecore.Data.Serialization.ObjectModel.SyncItem]$mediaFile = New-Object Sitecore.Data.Serialization.ObjectModel.SyncItem
$mediaFile.ID = $ItemId
$mediaFile.DatabaseName = "master"
$mediaFile.ItemPath = $sitecorePath
$mediaFile.ParentID = $parent.ID
$mediaFile.Name = $name
$mediaFile.MasterID = ([Guid]::Empty).ToString("B").ToUpper()
$mediaFile.TemplateID = "{962B53C4-F93B-4DF9-9821-415C867B8903}"
$mediaFile.TemplateName = "File"
$isoNow = [Sitecore.DateUtil]::IsoNow
$mediaFile.Created = $isoNow
$revision = [Guid]::NewGuid()
# Shared Fields
$fileContent = Get-Base64FileContents -Path $File.FullName
$mediaFile.AddSharedField("{40E50ED9-BA07-4702-992E-A912738D32DC}", "Blob", "blob", $fileContent[1], $true)
$extension = $File.Extension -replace "\.", [string]::Empty
$mediaFile.AddSharedField("{C06867FE-9A43-4C7D-B739-48780492D06F}", "Extension", "extension", $extension, $true)
$mediaFile.AddSharedField("{6F47A0A5-9C94-4B48-ABEB-42D38DEF6054}", "Mime Type", "mime type", (Get-MimeType -Extension ".$extension"), $true)
$mediaFile.AddSharedField("{6954B7C7-2487-423F-8600-436CB3B6DC0E}", "Size", "size", $fileContent[0], $true)
$mediaFile.AddSharedField("{06D5295C-ED2F-4A54-9BF2-26228D113318}", "__Icon", "__icon", "-/media/7EC7101143E14304933C27348BF70466.ashx?h=16&thn=1&w=16", $true)
# Version Fields
[Sitecore.Data.Serialization.ObjectModel.SyncVersion]$enVersion = $mediaFile.AddVersion("en", "1", $revision)
$enVersion.AddField("{52807595-0F8F-4B20-8D2A-CB71D28C6103}", "__Owner", "__owner", "sitecore\syncscript", $true)
$enVersion.AddField("{25BED78C-4957-4165-998A-CA1B52F67497}", "__Created", "__created", $isoNow, $true)
$enVersion.AddField("{5DD74568-4D4B-44C1-B513-0AF5F4CDA34F}", "__Created by", "__created by", "sitecore\syncscript", $true)
$enVersion.AddField("{8CDC337E-A112-42FB-BBB4-4143751E123F}", "__Revision", "__revision", $revision, $true)
$enVersion.AddField("{D9CF14B1-FA16-4BA6-9288-E8A174D4D522}", "__Updated", "__updated", $isoNow, $true)
$enVersion.AddField("{BADD9CF9-53E0-4D0C-BCC0-2D784C282F6A}", "__Updated by", "__updated by", "sitecore\syncscript", $true)
# Create file
$itemWriter = [System.IO.File]::CreateText($Path)
$mediaFile.Serialize($itemWriter)
$itemWriter.Dispose()
}
function Add-ScprojReference([string]$Path, [string]$ScprojPath)
{
[System.Xml.Linq.XDocument]$ProjFile = [System.Xml.Linq.XDocument]::Load($ScprojPath)
$syncElementName = [System.Xml.Linq.XName]::Get("ChildItemSynchronization", "http://schemas.microsoft.com/developer/msbuild/2003")
$syncElement = New-Object System.Xml.Linq.XElement $syncElementName, "KeepAllChildrenSynchronized"
$deployElementName = [System.Xml.Linq.XName]::Get("ItemDeployment", "http://schemas.microsoft.com/developer/msbuild/2003")
$deployElement = New-Object System.Xml.Linq.XElement $deployElementName, "AlwaysUpdate"
$includeAttribute = New-Object System.Xml.Linq.XAttribute "Include", $Path
$sitecoreItemName = [System.Xml.Linq.XName]::Get("SitecoreItem", "http://schemas.microsoft.com/developer/msbuild/2003")
$sitecoreItem = New-Object System.Xml.Linq.XElement $sitecoreItemName, $includeAttribute
$sitecoreItem.Add($syncElement)
$sitecoreItem.Add($deployElement)
[System.Xml.Linq.XElement]$itemGroup = [System.Xml.XPath.Extensions]::XPathSelectElement($ProjFile, "./*[local-name()='Project']/*[local-name()='ItemGroup']/*[local-name()='SitecoreItem']/..")
$itemGroup.Add($sitecoreItem)
#Save scproj file
$ProjFile.Save($ScprojPath, [System.Xml.Linq.SaveOptions]::OmitDuplicateNamespaces)
}
function Remove-ScprojReference([string]$Path, [string]$ScprojPath)
{
[System.Xml.Linq.XDocument]$ProjFile = [System.Xml.Linq.XDocument]::Load($ScprojPath)
[System.Xml.Linq.XElement]$sitecoreItem = [System.Xml.XPath.Extensions]::XPathSelectElement($ProjFile, "./*[local-name()='Project']/*[local-name()='ItemGroup']/*[local-name()='SitecoreItem' and @Include='$Path']")
$sitecoreItem.Remove()
$ProjFile.Save($ScprojPath, [System.Xml.Linq.SaveOptions]::OmitDuplicateNamespaces)
}
function Get-TdsFilePath([string]$FilePath, [string]$FileBasePath, [string]$TdsBasePath, [string]$ThemePath)
{
$localPathLength = $FilePath.Length - $FileBasePath.Length
$tdsFilePath = $FilePath.Substring($FileBasePath.Length, $localPathLength)
# Dots in the path are actually removed so we need to strip the extension before clearing them and then add the item extension
# Also this needs to be done only to the part that is actually in Sitecore, not the base path
$tdsFilePath = [System.IO.Path]::ChangeExtension($tdsFilePath, [string]::Empty)
$tdsFilePath = $tdsFilePath -replace "\.", [string]::Empty
$tdsFilePath = "$tdsFilePath.item"
$tdsIncludePath = "$ThemePath$tdsFilePath"
$tdsFilePath = Join-Path $TdsBasePath $tdsFilePath
return New-Object PsObject -Property @{ FullPath = $tdsFilePath; IncludePath = $tdsIncludePath }
}
function Process-File([System.IO.FileInfo]$File, [string]$FileBasePath, [string]$TdsBasePath, [string]$ScprojPath, [string]$ThemePath, [string]$ItemId)
{
$tdsPath = Get-TdsFilePath -FilePath $File.FullName -FileBasePath $FileBasePath -TdsBasePath $TdsBasePath -ThemePath $ThemePath
$checkPath = $tdsPath.FullPath
$itemPath = $tdsPath.IncludePath
if(Test-Path -Path $checkPath -PathType Leaf)
{
Update-MediaFile -Path $checkPath -File $File
Write-Verbose "$File has been updated." -Verbose
}
else
{
Create-MediaFile -Path $checkPath -File $File -ItemId $ItemId
Add-ScprojReference -Path $itemPath -ScprojPath $ScprojPath
Write-Verbose "$File has been created." -Verbose
}
}
function Process-Directory([System.IO.DirectoryInfo]$Directory, [string]$FileBasePath, [string]$TdsBasePath, [string]$ScprojPath, [string]$ThemePath)
{
$localPathLength = $Directory.FullName.Length - $FileBasePath.Length
$checkPath = $Directory.FullName.Substring($FileBasePath.Length, $localPathLength)
$itemPath = "$ThemePath$checkPath.item"
$checkFolderItemPath = Join-Path $TdsBasePath "$checkPath.item"
$checkPath = Join-Path $TdsBasePath $checkPath
if((Test-Path $checkPath) -and (Test-Path $checkFolderItemPath))
{
Write-Verbose "$Directory exists already" -Verbose
}
else
{
Create-MediaFolder -Path $checkPath -Name $Directory.Name
Add-ScprojReference -Path $itemPath -ScprojPath $ScprojPath
Write-Verbose "$Directory has been created." -Verbose
}
}
# ----- ----- -----
# Main Code
# ----- ----- -----
$scprojPath = (Resolve-Path (Join-Path $TdsPath $TdsProject)).Path.ToString()
$tdsBasePath = (Resolve-Path (Join-Path $TdsPath $ThemePath)).Path.ToString()
$fileBasePath = (Resolve-Path $FilesPath).Path.ToString()
if(-not [string]::IsNullOrWhiteSpace($UpdateFile))
{
$UpdateFile = Resolve-Path $UpdateFile
# Check parents
[System.IO.DirectoryInfo]$parent = (Get-Item -Path $UpdateFile).Parent
while($null -ne $parent)
{
Process-Directory -Directory $file -FileBasePath $fileBasePath -TdsBasePath $tdsBasePath -ScprojPath $scprojPath -ThemePath $ThemePath
$parent = $parent.Parent
}
# Update 1 file
$file = New-Object System.IO.FileInfo $UpdateFile
Process-File -File $file -FileBasePath $fileBasePath -TdsBasePath $tdsBasePath -ScprojPath $scprojPath -ThemePath $ThemePath -ItemId $ItemId
}
elseif(-not [string]::IsNullOrWhiteSpace($DeleteFile))
{
$deleteTdsPath = Get-TdsFilePath -FilePath $DeleteFile -FileBasePath $fileBasePath -TdsBasePath $tdsBasePath -ThemePath $ThemePath
Remove-Item -Path $deleteTdsPath.FullPath
Remove-ScprojReference -Path $deleteTdsPath.IncludePath -ScprojPath $ScprojPath
Write-Verbose "Deleted from project" -Verbose
}
else
{
# Load all directory and file information excluding the node_modules and process them
$files = Get-ChildItem $FilesPath -Recurse | Where-Object { $_.FullName -inotmatch 'node_modules' }
foreach($file in $files)
{
if($file -is [System.IO.FileInfo])
{
Process-File -File $file -FileBasePath $fileBasePath -TdsBasePath $tdsBasePath -ScprojPath $scprojPath -ThemePath $ThemePath -ItemId ([Guid]::NewGuid()).ToString("B").ToUpper()
}
if($file -is [System.IO.DirectoryInfo])
{
Process-Directory -Directory $file -FileBasePath $fileBasePath -TdsBasePath $tdsBasePath -ScprojPath $scprojPath -ThemePath $ThemePath
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment