|
############################################## |
|
############## Script Info ################### |
|
############################################## |
|
## Created By : Dan Cook 2017 ######## |
|
############################################## |
|
<# |
|
|
|
Version Info : |
|
0.1 – 25/05/2017 – Creates a graphvis diagram and accompanying DOT code file |
|
for an SCCM application dependency tree, by querying the |
|
primary server WMI |
|
|
|
0.1.1 – TODO – Make the console output tidier. Use the Write-Color function to add to the console output |
|
0.2 – TODO – Script needs to handle more dependencies better, not all the dependencies are "Automatically Installed". Some are either / OR etc… |
|
|
|
#> |
|
|
|
#region Variables |
|
|
|
$query = "QueryString" # Query used as initial search string |
|
|
|
$server = "ServerName" # SCCM Primary Site Server name (may need to be FQDN) |
|
$sitecode = "PS1" # Site code |
|
|
|
$basepath = "C:\Path\" # Path for output |
|
|
|
## Output graphvis type |
|
## (png, svg, pdf) tested so far |
|
$graph_filetype = "png" |
|
|
|
#endregion |
|
|
|
function Write-Color([String[]]$Text, [ConsoleColor[]]$Color = "White", [int]$StartTab = 0, [int] $LinesBefore = 0,[int] $LinesAfter = 0) { |
|
$DefaultColor = $Color[0] |
|
if ($LinesBefore -ne 0) { for ($i = 0; $i -lt $LinesBefore; $i++) { Write-Host "`n" -NoNewline } } # Add empty line before |
|
if ($StartTab -ne 0) { for ($i = 0; $i -lt $StartTab; $i++) { Write-Host "`t" -NoNewLine } } # Add TABS before text |
|
if ($Color.Count -ge $Text.Count) { |
|
for ($i = 0; $i -lt $Text.Length; $i++) { Write-Host $Text[$i] -ForegroundColor $Color[$i] -NoNewLine } |
|
} else { |
|
for ($i = 0; $i -lt $Color.Length ; $i++) { Write-Host $Text[$i] -ForegroundColor $Color[$i] -NoNewLine } |
|
for ($i = $Color.Length; $i -lt $Text.Length; $i++) { Write-Host $Text[$i] -ForegroundColor $DefaultColor -NoNewLine } |
|
} |
|
Write-Host |
|
if ($LinesAfter -ne 0) { for ($i = 0; $i -lt $LinesAfter; $i++) { Write-Host "`n" } } # Add empty line after |
|
} |
|
|
|
function IsNull($objectToCheck) { |
|
if ($objectToCheck -eq $null) { |
|
return $true |
|
} |
|
|
|
if ($objectToCheck -is [String] -and $objectToCheck -eq [String]::Empty) { |
|
return $true |
|
} |
|
|
|
if ($objectToCheck -is [DBNull] -or $objectToCheck -is [System.Management.Automation.Language.NullString]) { |
|
return $true |
|
} |
|
|
|
return $false |
|
} |
|
|
|
# Empty Hash table to cache CIID and AppName |
|
$CI_NameTable = @{} |
|
|
|
# Empty array to cache SMS_AppDependenceRelation WMI Class objects |
|
$AppDependenceTable = New-Object System.Collections.Generic.List[pscustomobject] |
|
|
|
# Colours to use for the different levels |
|
$levelColors = @{ |
|
1 = "deepskyblue2" |
|
2 = "brown3" |
|
3 = "goldenrod" |
|
4 = "deeppink3" |
|
5 = "coral3" |
|
} |
|
|
|
# First couple of lines of DOT output syntax |
|
$output = @" |
|
digraph Dependencies { |
|
rankdir = LR |
|
node [shape=box] |
|
"@ |
|
|
|
function Resolve-ApplicationName($appCIID) |
|
{ |
|
# Check if we have already resolved this appCIID |
|
$appname = $null |
|
$appname = $CI_NameTable.Item($appCIID) |
|
if ($appname -eq $null) |
|
{ |
|
# Name not resolved get name from WMI |
|
gwmi -ComputerName $server -Namespace "root\sms\site_$sitecode" -Class SMS_ApplicationLatest -Filter "CI_ID = '$appCIID'" | select LocalizedDisplayName | %{ |
|
$CI_NameTable.Add($appCIID, $_.LocalizedDisplayName) |
|
return $_.LocalizedDisplayName |
|
} |
|
} else { |
|
# Name already resolved |
|
return $appname |
|
} |
|
} |
|
|
|
function Get-Dependency($appCIID, $parentName, $depth) |
|
{ |
|
$apprelation = $null |
|
## Check if relation is already resolved and in cache |
|
$apprelation = $AppDependenceTable | ?{$_.FromApplicationCIID -eq $appCIID} |
|
|
|
## Generate Indent based on depth |
|
$indent = "" |
|
$i=0 |
|
for ($i=1;$i -le $depth;$i++) |
|
{ |
|
$indent = "$indent " |
|
} |
|
|
|
if ($apprelation -eq $null) |
|
{ |
|
## Relations not found in cache – need to query server WMI |
|
$apprelation = gwmi -ComputerName $server -Namespace "root\sms\site_$sitecode" -class SMS_AppDependenceRelation -Filter "FromApplicationCIID='$appCIID'" |
|
$apprelation | Add-Member -MemberType NoteProperty -Name WhereFrom -Value WMI |
|
} else { |
|
#$apprelation | Add-Member -MemberType NoteProperty -Name WhereFrom -Value Cache |
|
return |
|
} |
|
|
|
$apprelation | %{ |
|
$ToApplicationCIID = $_.ToApplicationCIID |
|
$ToApplicationName = Resolve-ApplicationName $ToApplicationCIID |
|
|
|
$colourName = $null |
|
$colourName = $levelColors.Item($depth) |
|
if ($colourName -eq $null) |
|
{ |
|
# deep node – no colour match, use gray |
|
$colourName = "gray34" |
|
} |
|
$nextdepth = $depth+1 |
|
|
|
## Write Output |
|
write-host "$indent -> $($ToApplicationName) (Depth : $depth; Query : $($_.WhereFrom); Parent : $parentName)" |
|
|
|
## Adding to global output |
|
$global:output += "`n`"$parentName`" -> `"$ToApplicationName`"[color=`"$colourName`", label=`"$depth`"]" |
|
|
|
## Cache relation data |
|
$AppDependenceTable.Add([pscustomobject]@{FromApplicationCIID=$_.FromApplicationCIID;ToApplicationCIID=$_.ToApplicationCIID}) |
|
|
|
## Recurse! |
|
Get-Dependency $ToApplicationCIID $ToApplicationName $nextdepth |
|
} |
|
|
|
} |
|
|
|
# Query WMI for the names of the main app to choose from |
|
$appnames = gwmi -ComputerName $server -Namespace "root\sms\site_$sitecode" -query "select LocalizedDisplayName, LocalizedDescription, CI_ID, CI_UniqueID, CIVersion, CIType_ID from SMS_ApplicationLatest where LocalizedDisplayName like '%$query%'" | select-object -Property LocalizedDisplayName, LocalizedDescription, CI_ID, CI_UniqueID, CIVersion, CIType_ID |
|
|
|
## Show Window of found applications from the query |
|
$chosenapp = $appnames | Out-GridView -PassThru -Title "Choose Application" |
|
|
|
## Quit Script if no app chosen |
|
if (IsNull($chosenapp)) { |
|
"No App Chosen" |
|
exit |
|
} |
|
|
|
"Loading Dependency Tree for $($chosenapp.LocalizedDisplayName)…" |
|
|
|
## Main Recursing Function |
|
get-dependency $chosenapp.CI_ID $chosenapp.LocalizedDisplayName 1 |
|
|
|
$output += @" |
|
|
|
} |
|
"@ |
|
|
|
## Generate code filename from the app name |
|
$code_filename = $chosenapp.LocalizedDisplayName.replace(' ','_') |
|
$code_filepath = "$basepath\$code_filename.gv" |
|
|
|
## Generate graph filename from the app name |
|
$graph_filename = $chosenapp.LocalizedDisplayName.replace(' ','_') |
|
$graph_filepath = "$basepath\$graph_filename.$graph_filetype" |
|
|
|
## Save output as file |
|
$output | out-file $code_filepath |
|
|
|
## Make Output PNG |
|
$output | & 'dot.exe' -T $graph_filetype -o $graph_filepath |
|
ii $graph_filepath |