MediaSyncWatcher.ps1
function ShouldSkip { param( [string]$Path, [array]$SkippedPaths ) $SkippedPaths | ForEach-Object { if ($Path -like $_) { return $true } } return $false } function GetRelativePath { param( [string]$FullPath, [string]$ThemeFolder ) [System.Uri]$fileUri = [System.IO.Path]::GetDirectoryName($FullPath) [System.Uri]$themeUri = "$ThemeFolder\" [string]$relativePath = $themeUri.MakeRelativeUri($fileUri) return $relativePath } function GetConfigNodeValue { param( [string]$Path, [string]$XPath ) $config = [xml](Get-Content $Path) return $config.SelectSingleNode($XPath).InnerText } function GetConfigNodeValues { param( [string]$Path, [string]$XPath ) $nodes = Select-Xml -Path $Path -XPath $XPath foreach ($node in $nodes) { $value = $node.Node.Value if ([string]::IsNullOrWhiteSpace($value)) { $value = $node.Node.InnerText } $value } } function GenerateIdentifier { param( [string]$MediaPath, [string]$ThemeFolder ) return [string]::Join("_", $MediaPath.Split([System.IO.Path]::GetInvalidFileNameChars())) } function Start-FolderMediaSync { param( [Parameter(Mandatory = $true,Position = 0)] [ValidateScript({ Test-Path -Path $_ })] [string]$Path, [Parameter(Mandatory = $true,Position = 1)] [ValidateScript({ Test-Path -Path $_ })] [string]$ThemeFolder ) $hostXpath = "mediaSync/folder[@path='$ThemeFolder']/site/host" $protocolHost = GetConfigNodeValue $Path $hostXpath $userNameXpath = "mediaSync/folder[@path='$ThemeFolder']/site/credentials/login" $userName = GetConfigNodeValue $Path $userNameXpath $passwordXpath = "mediaSync/folder[@path='$ThemeFolder']/site/credentials/password" $password = GetConfigNodeValue $Path $passwordXpath $mediaLibaryPathXpath = "mediaSync/folder[@path='$ThemeFolder']/media/mediaFolder" $MediaLibaryPath = GetConfigNodeValue $Path $mediaLibaryPathXpath $skippedPathsXpath = "mediaSync/folder[@path='$ThemeFolder']/media/skippedPaths/mask" $skippedPaths = GetConfigNodeValues $Path $skippedPathsXpath $filter = '*.*' $session = New-ScriptSession -Username $userName -Password $password -ConnectionUri $protocolHost $MessageData = New-Object PSObject -Property @{ MediaLibaryPath = $MediaLibaryPath.Trim("/"); logfile = $logfile; session = $session; ThemeFolder = $ThemeFolder.Trim("\") } $fsw = New-Object IO.FileSystemWatcher $ThemeFolder,$filter -Property @{ IncludeSubdirectories = $true; NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite' } Write-Host "Watching '$ThemeFolder'" -ForegroundColor Green $idf = GenerateIdentifier $ThemeFolder $identifier = "FileCreated_$idf" Register-ObjectEvent $fsw Created -SourceIdentifier $identifier -MessageData $MessageData -Action { $name = $Event.SourceEventArgs.Name $changeType = $Event.SourceEventArgs.ChangeType $timeStamp = $Event.TimeGenerated $extension = [System.IO.Path]::GetExtension($fullPath).Replace(".","") if (ShouldSkip $name $skippedPaths) { return } Write-Host "[$timeStamp] [$extension] [$changeType] : '$name'" -ForegroundColor DarkGreen } $identifier = "FileRenamed_$idf" Register-ObjectEvent $fsw Renamed -SourceIdentifier $identifier -MessageData $MessageData -Action { $name = $Event.SourceEventArgs.Name $changeType = $Event.SourceEventArgs.ChangeType $timeStamp = $Event.TimeGenerated $fullPath = $Event.SourceEventArgs.FullPath $session = $Event.MessageData.session $MediaLibaryPath = $Event.MessageData.MediaLibaryPath $newFilenameWithoutExtension = [System.IO.Path]::GetFileNameWithoutExtension($fullPath) $filenameWithoutExtension = [System.IO.Path]::GetFileNameWithoutExtension($Event.SourceEventArgs.OldName) $extension = [System.IO.Path]::GetExtension($fullPath).Replace(".","") $ThemeFolder = $Event.MessageData.ThemeFolder if (ShouldSkip $name $skippedPaths) { return } [string]$relativePath = GetRelativePath $fullPath $ThemeFolder $mediaPath = "master:/media library/$MediaLibaryPath/$relativePath/$filenameWithoutExtension" Write-Host "[$timeStamp] [$extension] [$changeType] : '$name'" -ForegroundColor DarkCyan Invoke-RemoteScript -Session $session -ScriptBlock { if (Test-Path $using:mediaPath) { Rename-Item -Path $using:mediaPath -NewName $using:newFilenameWithoutExtension } } Write-Host "[$timeStamp] [$extension] [SERVER:$changeType] : '$mediaPath'" -ForegroundColor Cyan } $identifier = "FileDeleted_$idf" Register-ObjectEvent $fsw Deleted -SourceIdentifier $identifier -MessageData $MessageData -Action { $name = $Event.SourceEventArgs.Name $changeType = $Event.SourceEventArgs.ChangeType $timeStamp = $Event.TimeGenerated $fullPath = $Event.SourceEventArgs.FullPath $session = $Event.MessageData.session $MediaLibaryPath = $Event.MessageData.MediaLibaryPath; $filenameWithoutExtension = [System.IO.Path]::GetFileNameWithoutExtension($fullPath) $extension = [System.IO.Path]::GetExtension($fullPath).Replace(".","") $ThemeFolder = $Event.MessageData.ThemeFolder [string]$relativePath = GetRelativePath $fullPath $ThemeFolder if (ShouldSkip $name $skippedPaths) { return } $mediaPath = "master:/media library/$MediaLibaryPath/$relativePath/$filenameWithoutExtension" Write-Host "[$timeStamp] [$extension] [$changeType] : '$name'" -ForegroundColor DarkRed Invoke-RemoteScript -Session $session -ScriptBlock { if (Test-Path $using:mediaPath) { Remove-Item -Path $using:mediaPath -Force } } Write-Host "[$timeStamp] [$extension] [SERVER:$changeType] : '$mediaPath'" -ForegroundColor Red } $identifier = "FileChanged_$idf" Register-ObjectEvent $fsw Changed -SourceIdentifier $identifier -MessageData $MessageData -Action { $name = $Event.SourceEventArgs.Name $changeType = $Event.SourceEventArgs.ChangeType $timeStamp = $Event.TimeGenerated $fullPath = $Event.SourceEventArgs.FullPath $session = $Event.MessageData.session $MediaLibaryPath = $Event.MessageData.MediaLibaryPath $filenameWithoutExtension = [System.IO.Path]::GetFileNameWithoutExtension($fullPath) $extension = [System.IO.Path]::GetExtension($fullPath).Replace(".","") $ThemeFolder = $Event.MessageData.ThemeFolder if (ShouldSkip $name $skippedPaths) { return } if ((Get-Item $fullPath) -is [System.IO.DirectoryInfo]) { Write-Host "'$name' is a folder - skipping upload." -ForegroundColor White return } [string]$relativePath = GetRelativePath $fullPath $ThemeFolder Write-Host "[$timeStamp] [$extension] [$changeType] : '$name'" -ForegroundColor DarkYellow Get-Item -Path $fullPath | Send-RemoteItem -Session $session -RootPath Media -Destination "$MediaLibaryPath/$relativePath" $mediaPath = "master:/media library/$MediaLibaryPath/$relativePath/$filenameWithoutExtension" Write-Host "[$timeStamp] [$extension] [SERVER:$changeType] : '$mediaPath'" -ForegroundColor Yellow } Write-Host "Uploaded to '$protocolHost/-/media/$MediaLibaryPath'" -ForegroundColor Green Write-Host "Watcher started." -ForegroundColor Green } function Stop-FolderMediaSync { param( [Parameter(Mandatory = $true,Position = 0)] [ValidateScript({ Test-Path -Path $_ })] [string]$Path, [Parameter(Mandatory = $true,Position = 1)] [ValidateScript({ Test-Path -Path $_ })] [string]$ThemeFolder ) $idf = GenerateIdentifier $ThemeFolder $identifiers = @("FileCreated_$idf", "FileRenamed_$idf", "FileDeleted_$idf", "FileChanged_$idf") foreach ($identifier in $identifiers) { Unregister-Event -SourceIdentifier $identifier Write-Host "Event subscription with identifier '$identifier' has been removed" -ForegroundColor Cyan } } <# .SYNOPSIS Stops synchronization of local folder (with subfolders) with Media Library (matching structure) on a remote Sitecore instance .DESCRIPTION The script allows you to work with SXA Themes in a more comfortable way. You can need specify the instance hostname, a folder to watch and the location of your Theme in Media Library in the variables directly below and run the script that will monitor and upload your changes. The script requires that SPE Remoting is deployed and enabled on the Sitecore Server. The script requires that SPE Remoting PowerShell Module is installed on your local machine. The following endpoints must be enabled: - remoting - mediaUpload The script requires the same xml config that was profided for Start-MediaSyncWatcher .NOTES Author PowerShell: Adam Najmanowicz Version: 1.0.0 29.March.2017 #> function Stop-MediaSyncWatcher { param( [Parameter(Mandatory = $true,Position = 0)] [Alias("configPath")] [ValidateScript({ Test-Path -Path $_ })] [string]$Path ) $themeFolders = GetConfigNodeValues $Path "mediaSync/folder/@path" foreach ($themeFolder in $themeFolders) { Stop-FolderMediaSync -Path $Path -ThemeFolder $themeFolder } } <# .SYNOPSIS Synchronize local folder (with subfolders) with Media Library (matching structure) on a remote Sitecore instance .DESCRIPTION The script allows you to stops synchronization of local folder (with subfolders) with Media Library (matching structure) on a remote Sitecore instance The script requires that SPE Remoting is deployed and enabled on the Sitecore Server. The script requires that SPE Remoting PowerShell Module is installed on your local machine. The following endpoints must be enabled: - remoting - mediaUpload The script requires xml config file with a following structure <?xml version="1.0" encoding="utf-8"?> <mediaSync> <folder path="C:\Projects\Showcase"> <site> <host>http://sxa</host> <credentials> <login>sitecore\admin</login> <password>b</password> </credentials> </site> <media> <mediaFolder>Project/Showcase</mediaFolder> <skippedPaths> <mask>.sass-cache*</mask> <mask>*optimized*</mask> <mask>Styles.css</mask> <mask>node_modules*</mask> <mask>Showcase.zip</mask> <mask>Scripts\concat.js</mask> </skippedPaths> </media> </folder> <!-- it's possible to add here few more folders for sync --> </mediaSync> .NOTES Author PowerShell: Adam Najmanowicz Version: 1.0.0 29.March.2017 #> function Start-MediaSyncWatcher { param( [Parameter(Mandatory = $true,Position = 0)] [Alias("configPath")] [ValidateScript({ Test-Path -Path $_ })] [string]$Path ) $themeFolders = GetConfigNodeValues $Path "mediaSync/folder/@path" foreach ($themeFolder in $themeFolders) { Start-FolderMediaSync -Path $Path -ThemeFolder $themeFolder } } |