Last active
October 14, 2024 18:16
-
-
Save alan-null/23f74896e1540bba3e407d3509aea320 to your computer and use it in GitHub Desktop.
Generate unicorn dependency graph using PowerShell. Analyse and improve your configurations or easily fix existing issues
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Get-ComputedDependencyGraphUrl { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory = $true, Position = 0 )] | |
[Hashtable]$allDependenciesClean | |
) | |
begin { | |
Write-Verbose "Cmdlet Get-ComputedDependencyGraphUrl - Begin" | |
} | |
process { | |
$r = "" | |
$allDependenciesClean.Keys | ? { $allDependenciesClean[$_] -ne $null } | % { | |
$configName = $_ | |
$computedDependencies = $allDependenciesClean[$configName] | ? { $_ -ne $configName } | |
$configReport = ($computedDependencies | % { "[$configName]-->[$_]" }) -join "," | |
$r = $r, $configReport -join "," | |
} | |
Save-ChartImage $r | |
} | |
end { | |
Write-Verbose "Cmdlet Get-ComputedDependencyGraphUrl - End" | |
} | |
} | |
function Get-ConfigDependencyGraphUrl { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory = $true, Position = 0 )] | |
[Hashtable]$allDependenciesClean, | |
[Parameter(Mandatory = $true, Position = 1 )] | |
[object[]]$allUnicornConfigs | |
) | |
begin { | |
Write-Verbose "Cmdlet Get-ConfigDependencyGraphUrl - Begin" | |
} | |
process { | |
$r = "" | |
$allDependenciesClean.Keys | ? { $allDependenciesClean[$_] -ne $null } | % { | |
$configName = $_ | |
$configDependencies = ($allUnicornConfigs | ? { $_.Name -eq $configName } | Select-Object -First 1).Dependencies | |
$configReport = ($configDependencies | % { "[$configName]-->[$_]" }) -join "," | |
$r = $r, $configReport -join "," | |
} | |
Save-ChartImage $r | |
} | |
end { | |
Write-Verbose "Cmdlet Get-ConfigDependencyGraphUrl - End" | |
} | |
} | |
function Get-DiffDependencyGraphUrl { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory = $true, Position = 0 )] | |
[Hashtable]$allDependenciesClean, | |
[Parameter(Mandatory = $true, Position = 1 )] | |
[object[]]$allUnicornConfigs | |
) | |
begin { | |
Write-Verbose "Cmdlet Get-DiffDependencyGraphUrl - Begin" | |
} | |
process { | |
$r = "" | |
$allDependenciesClean.Keys | ? { $allDependenciesClean[$_] -ne $null } | % { | |
$configName = $_ | |
$computedDependencies = $allDependenciesClean[$configName] | ? { $_ -ne $configName } | |
$configDependencies = ($allUnicornConfigs | ? { $_.Name -eq $configName } | Select-Object -First 1).Dependencies | |
$missing = Compare-Object $configDependencies $computedDependencies -IncludeEqual | ? { $_.SideIndicator -eq "=>" } | % { $_.InputObject } | |
$missingFormated = ($missing | % { "[$configName]-->[$_]" }) -join "," | |
$redundant = Compare-Object $configDependencies $computedDependencies -IncludeEqual | ? { $_.SideIndicator -eq "<=" } | % { $_.InputObject } | |
$redundantFormated = ($redundant | % { "[$configName]-.-^[$_]" }) -join "," | |
$configReport = $missingFormated, $redundantFormated -join "," | |
$r = $r, $configReport -join "," | |
} | |
Save-ChartImage $r | |
} | |
end { | |
Write-Verbose "Cmdlet Get-DiffDependencyGraphUrl - End" | |
} | |
} | |
function Save-ChartImage { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory = $true, Position = 0 )] | |
[string]$r | |
) | |
begin { | |
Write-Verbose "Cmdlet Save-ChartImage - Begin" | |
} | |
process { | |
$chartsFolder = "$SitecoreTempFolder\yuml.me" | |
if (!(Test-Path $chartsFolder)) { | |
mkdir $chartsFolder | Out-Null | |
} | |
Write-Progress -Activity "Saving chart image" -Status "Computing data hash" | |
Write-Host "Computing data hash" | |
$md5 = [System.Security.Cryptography.MD5]::Create() | |
$byteArray = [System.Text.Encoding]::UTF8.GetBytes($r) | |
$hash = $md5.ComputeHash($byteArray) | |
$hashString = -join ($hash | ForEach-Object { $_.ToString("x2") }) | |
$fileName = $hashString.Substring(0, 32) + ".svg" | |
if ((Test-Path "$chartsFolder\$fileName")) { | |
Write-Progress -Activity "Saving chart image" -Status "Found local copy of the chart" | |
Write-Host "Found local copy of the chart" | |
return "/temp/yuml.me/$fileName" | |
} | |
Write-Progress -Activity "Saving chart image" -Status "Generating chart on yuml.me" | |
Write-Host "Generating chart on yuml.me" | |
$payload = [System.Net.WebUtility]::UrlEncode($r) | |
$response = Invoke-WebRequest -UseBasicParsing -Uri "https://yuml.me/diagram/scruffy/class/" ` | |
-Method "POST" ` | |
-ContentType "application/x-www-form-urlencoded; charset=UTF-8" ` | |
-Body "dsl_text=$payload" | |
$imageName = $response.Content | |
Write-Progress -Activity "Saving chart image" -Status "Downloading image" | |
Write-Host "Downloading image" | |
Invoke-WebRequest -Uri "https://yuml.me/$imageName" -OutFile "$chartsFolder\$fileName" | |
return "/temp/yuml.me/$fileName" | |
} | |
end { | |
Write-Verbose "Cmdlet Save-ChartImage - End" | |
} | |
} | |
function Get-ItemDataChildren { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory = $true, Position = 0 )] | |
[Rainbow.Model.IItemData]$IItemData, | |
[Parameter(Mandatory = $true, Position = 1 )] | |
[Unicorn.Data.ISourceDataStore]$ISourceDataStore, | |
[Parameter(Mandatory = $true, Position = 2 )] | |
[Unicorn.Predicates.IPredicate]$IPredicate | |
) | |
begin { | |
Write-Verbose "Cmdlet Get-ItemDataChildren - Begin" | |
} | |
process { | |
Write-Verbose "Cmdlet Get-ItemDataChildren - Process" | |
$ISourceDataStore.GetChildren($IItemData) | % { | |
if ($IPredicate.Includes($_).IsIncluded) { | |
$_ | |
Get-ItemDataChildren $_ $ISourceDataStore $IPredicate | |
} | |
} | |
} | |
end { | |
Write-Verbose "Cmdlet Get-ItemDataChildren - End" | |
} | |
} | |
function Get-ItemsData { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory = $true, Position = 0 )] | |
[Unicorn.Configuration.IConfiguration]$Configuration | |
) | |
begin { | |
Write-Verbose "Cmdlet Get-ItemsData - Begin" | |
} | |
process { | |
Write-Verbose "Cmdlet Get-ItemsData - Process" | |
$method = $Configuration.GetType().GetMethods() | ? { $_.Name -eq "Resolve" } | ? { $_.ReturnType.Name -eq "T" } | Select-Object -First 1 | |
$predicateRootPathResolver = $method.MakeGenericMethod([Unicorn.Predicates.PredicateRootPathResolver]).Invoke($Configuration, $null) | |
$sourceDataStore = $method.MakeGenericMethod([Unicorn.Data.ISourceDataStore]).Invoke($Configuration, $null) | |
$predicate = $method.MakeGenericMethod([Unicorn.Predicates.IPredicate]).Invoke($Configuration, $null) | |
foreach ($IItemData in $predicateRootPathResolver.GetRootSourceItems()) { | |
$IItemData | |
Get-ItemDataChildren $IItemData $sourceDataStore $predicate | |
} | |
} | |
end { | |
Write-Verbose "Cmdlet Get-ItemsData - End" | |
} | |
} | |
function Get-UnicornConfiguration { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory = $false, Position = 0 )] | |
[string]$ConfigurationName = "" | |
) | |
begin { | |
Write-Verbose "Cmdlet Get-UnicornConfiguration - Begin" | |
} | |
process { | |
Write-Verbose "Cmdlet Get-UnicornConfiguration - Process" | |
[Unicorn.ControlPanel.ControlPanelUtility]::ResolveConfigurationsFromQueryParameter($ConfigurationName) | |
} | |
end { | |
Write-Verbose "Cmdlet Get-UnicornConfiguration - End" | |
} | |
} | |
function Get-PredicateIncludes { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory = $true, Position = 0 )] | |
[Unicorn.Configuration.IConfiguration]$Configuration | |
) | |
begin { | |
Write-Verbose "Cmdlet Get-PredicateIncludes - Begin" | |
} | |
process { | |
Write-Verbose "Cmdlet Get-PredicateIncludes - Process" | |
$method = $Configuration.GetType().GetMethods() | ? { $_.Name -eq "Resolve" } | ? { $_.ReturnType.Name -eq "T" } | Select-Object -First 1 | |
$predicateRootPathResolver = $method.MakeGenericMethod([Unicorn.Predicates.PredicateRootPathResolver]).Invoke($Configuration, $null) | |
$predicateRootPathResolver.GetRootPaths() | |
} | |
end { | |
Write-Verbose "Cmdlet Get-PredicateIncludes - End" | |
} | |
} | |
function Get-ComparableObjects { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory = $true, Position = 0 )] | |
[Unicorn.Configuration.IConfiguration[]]$Configurations | |
) | |
begin { | |
Write-Verbose "Cmdlet Add-Files - Begin" | |
} | |
process { | |
Write-Verbose "Cmdlet Add-Files - Process" | |
$result = New-Object System.Collections.ArrayList | |
$Configurations | ForEach-Object { | |
$configName = $_ | |
Get-ItemsData $configName | ForEach-Object { | |
$obj = @{} | |
$obj.ItemData = $_ | |
$obj.Config = $configName | |
$result.Add($obj) | Out-Null | |
} | |
} | |
$result | |
} | |
end { | |
Write-Verbose "Cmdlet Add-Files - End" | |
} | |
} | |
function Get-TemplateDependency { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory = $true, Position = 0 )] | |
$currentItemData | |
) | |
begin { | |
Write-Verbose "Cmdlet Add-Files - Begin" | |
} | |
process { | |
Write-Verbose "Cmdlet Add-Files - Process" | |
$templateIdKey = $currentItemData.ItemData.TemplateId.ToString() | |
if ($global_invalidTemplateIDs[$templateIdKey] -eq $null) { | |
$result = $global_templateIdToItemDataMapping[$templateIdKey] | |
if ($result -ne $null) { | |
$result | |
} | |
else { | |
$objectForDifferentConfigs = $global_allObjects | Where-Object { $_.Config.Name -ne $currentItemData.Config.Name } | |
$result = $objectForDifferentConfigs | Where-Object { $_.ItemData.ID -eq $currentItemData.ItemData.TemplateId } | Select-Object -First 1 | |
if ($result -ne $null) { | |
$global_templateIdToItemDataMapping.Add($templateIdKey, $result) | |
$result | |
} | |
else { | |
$global_invalidTemplateIDs.Add($templateIdKey, 1) | |
} | |
} | |
} | |
} | |
end { | |
Write-Verbose "Cmdlet Add-Files - End" | |
} | |
} | |
function Get-Depependencies($configName, $allDependencies) { | |
$start2 = Get-Date | |
# check depndencies by path include | |
$global_predicateIncludes[$configName].Path | % { | |
$currentPath = $_ | |
$global_predicateIncludes.Keys | % { | |
$globalConfig = $_ | |
if ($globalConfig -ne $configName) { | |
$global_predicateIncludes[$globalConfig].Path | % { | |
if ($currentPath.Contains($_)) { | |
$allDependencies[$configName].Add($globalConfig) | Out-Null | |
} | |
} | |
} | |
} | |
} | |
# check depndencies by template | |
$configurationObjects = $global_allObjects | Where-Object { $_.Config.Name -eq $configName } | |
$configurationObjects | ForEach-Object { | |
$result = Get-TemplateDependency $_ | |
if ($result -ne $null) { | |
$allDependencies[$configName].Add($result.Config.Name) | Out-Null | |
} | |
} | |
Write-Verbose "Processing | |
[$((Get-Date) - $start2)] $configName" | |
} | |
function Show-Dialog { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory = $true, Position = 0 )] | |
[Unicorn.Configuration.IConfiguration[]]$Configurations | |
) | |
begin { | |
Write-Verbose "Cmdlet Add-Files - Begin" | |
} | |
process { | |
Write-Verbose "Cmdlet Add-Files - Process" | |
$dialogOptions = @{} | |
$Configurations.Name | ForEach-Object { $dialogOptions.Add($_, $_) } | |
$preSelectedConfigurations = $Configurations.Name | |
$result = Read-Variable -Parameters ` | |
@{ Name = "preSelectedConfigurations"; Title = "Configurations"; Options = $dialogOptions; Editor = "checklist"; Tip = "Select which configuration should be used to generate dependency graph"; Height = "330px"; Tab = "General"; } ` | |
-Description "This script will go through the selected unicorn configurations and generate dependency report" ` | |
-Title "Generate Unicorn configurations dependency graph" -Width 500 -Height 600 ` | |
-OkButtonName "OK" -CancelButtonName "Cancel" ` | |
if ($result -ne "ok") { | |
Close-Window | |
exit | |
} | |
$Configurations | Where-Object { $preSelectedConfigurations.Contains($_.Name) } | |
} | |
end { | |
Write-Verbose "Cmdlet Add-Files - End" | |
} | |
} | |
function Show-ResultsDialog { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory = $true, Position = 0 )] | |
[Hashtable]$allDependenciesClean, | |
[Parameter(Mandatory = $true, Position = 1 )] | |
[object[]]$allUnicornConfigs | |
) | |
begin { | |
Write-Verbose "Cmdlet Show-ResultsDialog - Begin" | |
} | |
process { | |
$result = "ok" | |
$graphModes = [ordered]@{} | |
$graphModes['Mixed dependency graph (redundant, missing dependencies)'] = "mixed" | |
$graphModes['Computed dependency graph'] = "computed" | |
$graphModes['Configuration based dependency graph'] = "config" | |
while ($result -eq "ok") { | |
$result = Read-Variable -Parameters ` | |
@{ Name = "Description"; Title = " "; Value = "Please select graph you want to see and click Show button"; editor = "info"; }, ` | |
@{ Name = "graphMode"; Options = $graphModes; Value = ""; Title = "Graph mode"; } ` | |
-Description "This script will go through the selected unicorn configurations and generate dependency report" ` | |
-Title "Generate Unicorn configurations dependency graph" -Width 500 -Height 300 ` | |
-OkButtonName "Show" -CancelButtonName "Exit" | |
if ($result -eq "cancel") { | |
Exit | |
} | |
switch ($graphMode) { | |
"computed" { | |
$url = Get-ComputedDependencyGraphUrl $allDependenciesClean | |
$description = "Dependency graph based on item path/id dependency" | |
$legend = "This graph represents desired dependency determined based on item's id/path dependencies between each module." | |
} | |
"config" { | |
$url = Get-ConfigDependencyGraphUrl $allDependenciesClean $allUnicornConfigs | |
$description = "Configuration based dependency graph" | |
$legend = "This graph represents dependency that is currently set in your unicorn configuration files" | |
} | |
"mixed" { | |
$url = Get-DiffDependencyGraphUrl $allDependenciesClean $allUnicornConfigs | |
$description = "Mixed graph" | |
$legend = "Shows what you should do with your config files to fix dependency erros <br/> <b>Solid line</b> - missing dependency <br/> <b>Dotted line</b> - redundant dependency" | |
} | |
Default { | |
$url = "https://alan-null.github.io" | |
} | |
} | |
$result2 = Read-Variable -Parameters ` | |
@{ Name = "Info0"; Title = ""; Value = '<img src="' + $url + '""/>'; editor = "info" }, ` | |
@{ Name = "Info1"; Title = "Legend:"; Value = $legend; editor = "info" } ` | |
-Description $description -Title "Dependency Graph" -Width 1000 -Height 800 ` | |
-OkButtonName "Show different" -CancelButtonName "Exit" | |
if ($result2 -eq "cancel") { | |
Exit | |
} | |
} | |
} | |
end { | |
Write-Verbose "Cmdlet Show-ResultsDialog - End" | |
} | |
} | |
# initialize global variables | |
$start = Get-Date | |
$global_templateIdToItemDataMapping = @{} | |
$global_invalidTemplateIDs = @{} | |
$global_invalidTemplateIDs.Add("962b53c4-f93b-4df9-9821-415c867b8903", 1) | |
$global_invalidTemplateIDs.Add("f1828a2c-7e5d-4bbd-98ca-320474871548", 1) | |
[System.Collections.ArrayList]$dependencies = New-Object "System.Collections.ArrayList" | |
[System.Xml.XmlNode[]]$allConfigs = [Sitecore.Configuration.Factory]::GetConfigNodes("/sitecore/unicorn/configurations/configuration") | |
$allUnicornConfigs = $allConfigs | ForEach-Object { Get-UnicornConfiguration $_.Name } | |
$configsToProcess = Show-Dialog $allUnicornConfigs | |
$global_progress = 0.0 | |
$global_allObjects = $allUnicornConfigs | ForEach-Object { | |
Write-Progress -Activity "Initializing objects" ` | |
-Status "Creating comparable objects for $($_.Name)" ` | |
-PercentComplete ($global_progress) | |
$global_progress = $global_progress + 100.0 / ($allUnicornConfigs.Count) | |
Get-ComparableObjects $_ | |
} | |
$global_predicateIncludes = @{} | |
$global_progress = 0.0 | |
$allUnicornConfigs | ForEach-Object { $global_predicateIncludes.Add("$($_.Name)", $(Get-PredicateIncludes $_)) } | |
(Get-Date) - $start | |
# initialize result tables | |
$allDependencies = @{} | |
$allDependenciesClean = @{} | |
$allUnicornConfigs.Name | ForEach-Object { $allDependencies.Add("$($_)", $(New-Object "System.Collections.ArrayList")) } | |
$allUnicornConfigs.Name | ForEach-Object { $allDependenciesClean.Add("$($_)", $null) } | |
$start = Get-Date | |
$configsToProcess | ForEach-Object { | |
Write-Progress -Activity "Analysing dependencies for $($_.Name)" ` | |
-Status "Checking path/id dependencies" ` | |
-PercentComplete ($global_progress) | |
$global_progress = $global_progress + 100.0 / ($configsToProcess.Count) | |
Get-Depependencies $_.Name $allDependencies | |
} | |
(Get-Date) - $start | |
# print results | |
$allDependencies.Keys | % { | |
$allDependenciesClean[$_] = $allDependencies[$_] | Sort-Object | Get-Unique -AsString | |
} | |
if ((Show-Confirm -Title "Would you like to generate dependency report which can be then exported?") -eq "yes") { | |
$allDependenciesClean.Keys | ? { $allDependenciesClean[$_] -ne $null } | Show-ListView -InfoTitle "Desired unicorn dependency configuration" ` | |
-PageSize 25 ` | |
-Property ` | |
@{Label = 'Configuration'; Expression = { $_ } }, | |
@{Label = 'Dependencies'; Expression = { $allDependenciesClean[$_] -join ',' } } | |
} | |
Show-ResultsDialog $allDependenciesClean $allUnicornConfigs |
Logic has been updated . New method fixes two problems that were discovered:
414
error (Request-URI Too Large) - for solutions with multiple configurations, passing data via URL was not a good choice - yuml.me limits the length of incoming URLs;- CSP errors - loading images from other domains was blocked at some point by Sitecore - script will download them to temp folder now.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Splendid work!~ :)