From 69cdead4fc306f9697b4c173b24485a1190ed98b Mon Sep 17 00:00:00 2001 From: bogdan-damian-bgd <108331162+bogdan-damian-bgd@users.noreply.github.com> Date: Tue, 6 Dec 2022 18:16:20 +0100 Subject: [PATCH] Implement new software report generator for macOS (#6690) * Create environmentVariables.yml * Update environmentVariables.yml * Delete environmentVariables.yml * Add new SoftwareReport generator * Output generation fixes after review * Add json output print in pipeline * Modify json output print in pipeline * Removed redundant space character --- .../azure-pipelines/image-generation.yml | 6 +- .../provision/configuration/finalize-vm.sh | 2 +- .../software-report/SoftwareReport.Base.ps1 | 370 +++++++++++++++ .../SoftwareReport.Browsers.psm1 | 52 ++- .../SoftwareReport.Common.psm1 | 261 ++++++----- .../SoftwareReport.Generator.ps1 | 434 ++++++++---------- .../SoftwareReport.Toolcache.psm1 | 37 +- .../SoftwareReport.WebServers.psm1 | 9 +- .../SoftwareReport.Xamarin.psm1 | 4 +- .../software-report/SoftwareReport.Xcode.psm1 | 21 +- 10 files changed, 763 insertions(+), 433 deletions(-) create mode 100644 images/macos/software-report/SoftwareReport.Base.ps1 diff --git a/images.CI/macos/azure-pipelines/image-generation.yml b/images.CI/macos/azure-pipelines/image-generation.yml index 654832797..6ad177dff 100644 --- a/images.CI/macos/azure-pipelines/image-generation.yml +++ b/images.CI/macos/azure-pipelines/image-generation.yml @@ -107,7 +107,11 @@ jobs: - bash: | cat "$(Build.ArtifactStagingDirectory)/systeminfo.md" - displayName: Print software report + displayName: Print markdown software report + + - bash: | + cat "$(Build.ArtifactStagingDirectory)/systeminfo.json" + displayName: Print json software report - task: PublishBuildArtifacts@1 inputs: diff --git a/images/macos/provision/configuration/finalize-vm.sh b/images/macos/provision/configuration/finalize-vm.sh index c3d72b7a8..af3b4b6fd 100644 --- a/images/macos/provision/configuration/finalize-vm.sh +++ b/images/macos/provision/configuration/finalize-vm.sh @@ -20,7 +20,7 @@ if is_Monterey; then fi # Put documentation to $HOME root -cp $HOME/image-generation/output/software-report/systeminfo.txt $HOME/image-generation/output/software-report/systeminfo.md $HOME/ +cp $HOME/image-generation/output/software-report/systeminfo.* $HOME/ # Put build vm assets scripts to proper directory mkdir -p /usr/local/opt/$USER/scripts diff --git a/images/macos/software-report/SoftwareReport.Base.ps1 b/images/macos/software-report/SoftwareReport.Base.ps1 new file mode 100644 index 000000000..35475ada9 --- /dev/null +++ b/images/macos/software-report/SoftwareReport.Base.ps1 @@ -0,0 +1,370 @@ +class SoftwareReport { + [HeaderNode] $Root + + SoftwareReport([String] $Title) { + $this.Root = [HeaderNode]::new($Title) + } + + SoftwareReport([HeaderNode] $Root) { + $this.Root = $Root + } + + [String] ToJson() { + return $this.Root.ToJsonObject() | ConvertTo-Json -Depth 10 + } + + static [SoftwareReport] FromJson($jsonString) { + $jsonObj = $jsonString | ConvertFrom-Json + $rootNode = [SoftwareReport]::ParseNodeFromObject($jsonObj) + return [SoftwareReport]::new($rootNode) + } + + [String] ToMarkdown() { + return $this.Root.ToMarkdown(1).Trim() + } + + # This method is Nodes factory that simplifies parsing different types of notes + # Every node has own logic of parsing and this method just invokes "FromJsonObject" of correct node type + static [BaseNode] ParseNodeFromObject($jsonObj) { + if ($jsonObj.NodeType -eq [HeaderNode].Name) { + return [HeaderNode]::FromJsonObject($jsonObj) + } elseif ($jsonObj.NodeType -eq [ToolNode].Name) { + return [ToolNode]::FromJsonObject($jsonObj) + } elseif ($jsonObj.NodeType -eq [ToolVersionsNode].Name) { + return [ToolVersionsNode]::FromJsonObject($jsonObj) + } elseif ($jsonObj.NodeType -eq [TableNode].Name) { + return [TableNode]::FromJsonObject($jsonObj) + } elseif ($jsonObj.NodeType -eq [NoteNode].Name) { + return [NoteNode]::FromJsonObject($jsonObj) + } + + throw "Unknown node type in ParseNodeFromObject '$($jsonObj.NodeType)'" + } +} + +# class BaseNode doesn't really have any business logic or functionality +# We just create it to unite all types of Nodes and differ them from "object" type +class BaseNode {} + +# It is a node type to describe headers: "# Node.js" +# Header has Title and children nodes (HeaderNode is the single node type who has children) +class HeaderNode: BaseNode { + [String] $Title + [System.Collections.ArrayList] $Children + + HeaderNode([String] $Title) { + $this.Title = $Title + $this.Children = @() + } + + [void] AddNode([BaseNode] $node) { + if ($node.GetType() -eq [TableNode]) { + $existingTableNodesCount = $this.Children.Where({ $_.GetType() -eq [TableNode] }).Count + if ($existingTableNodesCount -gt 0) { + throw "Having multiple TableNode on the same header level is not supported" + } + } + + $this.Children.Add($node) + } + + [void] AddNodes([Array] $nodes) { + $nodes | ForEach-Object { $this.AddNode($_) } + } + + [HeaderNode] AddHeaderNode([String] $Title) { + $node = [HeaderNode]::new($Title) + $this.AddNode($node) + return $node + } + + [void] AddToolNode([String] $ToolName, [String] $Version) { + $this.AddNode([ToolNode]::new($ToolName, $Version)) + } + + [void] AddToolVersionsNode([String] $ToolName, [Array] $Version) { + $this.AddNode([ToolVersionsNode]::new($ToolName, $Version)) + } + + [void] AddTableNode([Array] $Table) { + $this.AddNode([TableNode]::FromObjectsArray($Table)) + } + + [void] AddNoteNode([String] $Content) { + $this.AddNode([NoteNode]::new($Content)) + } + + [String] ToMarkdown($level) { + $sb = [System.Text.StringBuilder]::new() + $sb.AppendLine() + $sb.AppendLine("$("#" * $level) $($this.Title)") + $this.Children | ForEach-Object { + $sb.AppendLine($_.ToMarkdown($level + 1)) + } + + return $sb.ToString().TrimEnd() + } + + [PSCustomObject] ToJsonObject() { + return [PSCustomObject]@{ + NodeType = $this.GetType().Name + Title = $this.Title + Children = $this.Children | ForEach-Object { $_.ToJsonObject() } + } + } + + static [HeaderNode] FromJsonObject($jsonObj) { + $node = [HeaderNode]::new($jsonObj.Title) + $jsonObj.Children | Where-Object { $_ } | ForEach-Object { $node.AddNode([SoftwareReport]::ParseNodeFromObject($_)) } + return $node + } + + [Boolean] IsSimilarTo([BaseNode] $OtherNode) { + if ($OtherNode.GetType() -ne [HeaderNode]) { + return $false + } + + return $this.Title -eq $OtherNode.Title + } + + [Boolean] IsIdenticalTo([BaseNode] $OtherNode) { + return $this.IsSimilarTo($OtherNode) + } +} + +# It is a node type to describe the single tool "Bash 5.1.16(1)-release" +class ToolNode: BaseNode { + [String] $ToolName + [String] $Version + + ToolNode([String] $ToolName, [String] $Version) { + $this.ToolName = $ToolName + $this.Version = $Version + } + + [String] ToMarkdown($level) { + return "- $($this.ToolName) $($this.Version)" + } + + [String] ToString() { + return "$($this.ToolName) $($this.Version)" + } + + [PSCustomObject] ToJsonObject() { + return [PSCustomObject]@{ + NodeType = $this.GetType().Name + ToolName = $this.ToolName + Version = $this.Version + } + } + + static [BaseNode] FromJsonObject($jsonObj) { + return [ToolNode]::new($jsonObj.ToolName, $jsonObj.Version) + } + + [Boolean] IsSimilarTo([BaseNode] $OtherNode) { + if ($OtherNode.GetType() -ne [ToolNode]) { + return $false + } + + # Don't compare by Version in SimilarTo method + # It is necessary to treat changing of tool version as "ChangedNodes" rather than "RemovedNodes" + "AddedNodes" + return $this.ToolName -eq $OtherNode.ToolName + } + + [Boolean] IsIdenticalTo([BaseNode] $OtherNode) { + return $this.IsSimilarTo($OtherNode) -and $this.Version -eq $OtherNode.Version + } +} + +# It is a node type to describe the single tool "Toolcache Node.js 14.17.6 16.2.0 18.2.3" +class ToolVersionsNode: BaseNode { + [String] $ToolName + [Array] $Versions + + ToolVersionsNode([String] $ToolName, [Array] $Versions) { + $this.ToolName = $ToolName + $this.Versions = $Versions + } + + [String] ToMarkdown($level) { + $sb = [System.Text.StringBuilder]::new() + $sb.AppendLine() + $sb.AppendLine("$("#" * $level) $($this.ToolName)") + $this.Versions | ForEach-Object { + $sb.AppendLine("- $_") + } + + return $sb.ToString().TrimEnd() + } + + [String] ToString() { + return "$($this.ToolName) $($this.Versions -join ', ')" + } + + [PSCustomObject] ToJsonObject() { + return [PSCustomObject]@{ + NodeType = $this.GetType().Name + ToolName = $this.ToolName + Versions = $this.Versions + } + } + + static [ToolVersionsNode] FromJsonObject($jsonObj) { + return [ToolVersionsNode]::new($jsonObj.ToolName, $jsonObj.Versions) + } + + [Boolean] IsSimilarTo([BaseNode] $OtherNode) { + if ($OtherNode.GetType() -ne [ToolVersionsNode]) { + return $false + } + + return $this.ToolName -eq $OtherNode.ToolName + } + + [Boolean] IsIdenticalTo([BaseNode] $OtherNode) { + if (-not $this.IsSimilarTo($OtherNode)) { + return $false + } + + return ($this.Versions -join " ") -eq ($OtherNode.Versions -join " ") + } +} + +# It is a node type to describe tables +class TableNode: BaseNode { + # It is easier to store the table as rendered lines because we will simplify finding differences in rows later + [String] $Headers + [System.Collections.ArrayList] $Rows + + TableNode($Headers, $Rows) { + $this.Headers = $Headers + $this.Rows = $Rows + } + + static [TableNode] FromObjectsArray([Array] $Table) { + # take column names from the first row in table because all rows that should have the same columns + [String] $tableHeaders = [TableNode]::ArrayToTableRow($Table[0].PSObject.Properties.Name) + [System.Collections.ArrayList] $tableRows = @() + + $Table | ForEach-Object { + $tableRows.Add([TableNode]::ArrayToTableRow($_.PSObject.Properties.Value)) + } + + return [TableNode]::new($tableHeaders, $tableRows) + } + + [String] ToMarkdown($level) { + $maxColumnWidths = $this.Headers.Split("|") | ForEach-Object { $_.Length } + $columnsCount = $maxColumnWidths.Count + + $this.Rows | ForEach-Object { + $columnWidths = $_.Split("|") | ForEach-Object { $_.Length } + for ($colIndex = 0; $colIndex -lt $columnsCount; $colIndex++) { + $maxColumnWidths[$colIndex] = [Math]::Max($maxColumnWidths[$colIndex], $columnWidths[$colIndex]) + } + } + + $delimeterLine = [String]::Join("|", @("-") * $columnsCount) + + $sb = [System.Text.StringBuilder]::new() + @($this.Headers) + @($delimeterLine) + $this.Rows | ForEach-Object { + $sb.Append("|") + $row = $_.Split("|") + for ($colIndex = 0; $colIndex -lt $columnsCount; $colIndex++) { + $padSymbol = $row[$colIndex] -eq "-" ? "-" : " " + $cellContent = $row[$colIndex].PadRight($maxColumnWidths[$colIndex], $padSymbol) + $sb.Append(" $($cellContent) |") + } + $sb.AppendLine() + } + + return $sb.ToString().TrimEnd() + } + + [PSCustomObject] ToJsonObject() { + return [PSCustomObject]@{ + NodeType = $this.GetType().Name + Headers = $this.Headers + Rows = $this.Rows + } + } + + static [TableNode] FromJsonObject($jsonObj) { + return [TableNode]::new($jsonObj.Headers, $jsonObj.Rows) + } + + [Boolean] IsSimilarTo([BaseNode] $OtherNode) { + if ($OtherNode.GetType() -ne [TableNode]) { + return $false + } + + # We don't support having multiple TableNode instances on the same header level so such check is fine + return $true + } + + [Boolean] IsIdenticalTo([BaseNode] $OtherNode) { + if (-not $this.IsSimilarTo($OtherNode)) { + return $false + } + + if ($this.Headers -ne $OtherNode.Headers) { + return $false + } + + if ($this.Rows.Count -ne $OtherNode.Rows.Count) { + return $false + } + + for ($rowIndex = 0; $rowIndex -lt $this.Rows.Count; $rowIndex++) { + if ($this.Rows[$rowIndex] -ne $OtherNode.Rows[$rowIndex]) { + return $false + } + } + + return $true + } + + hidden static [String] ArrayToTableRow([Array] $Values) { + return [String]::Join("|", $Values) + } +} + +class NoteNode: BaseNode { + [String] $Content + + NoteNode([String] $Content) { + $this.Content = $Content + } + + [String] ToMarkdown($level) { + return @( + '```', + $this.Content, + '```' + ) -join "`n" + } + + [PSCustomObject] ToJsonObject() { + return [PSCustomObject]@{ + NodeType = $this.GetType().Name + Content = $this.Content + } + } + + static [NoteNode] FromJsonObject($jsonObj) { + return [NoteNode]::new($jsonObj.Content) + } + + [Boolean] IsSimilarTo([BaseNode] $OtherNode) { + if ($OtherNode.GetType() -ne [NoteNode]) { + return $false + } + + return $this.Content -eq $OtherNode.Content + } + + [Boolean] IsIdenticalTo([BaseNode] $OtherNode) { + return $this.IsSimilarTo($OtherNode) + } +} \ No newline at end of file diff --git a/images/macos/software-report/SoftwareReport.Browsers.psm1 b/images/macos/software-report/SoftwareReport.Browsers.psm1 index 72589c4cc..2ee1fce63 100644 --- a/images/macos/software-report/SoftwareReport.Browsers.psm1 +++ b/images/macos/software-report/SoftwareReport.Browsers.psm1 @@ -1,64 +1,70 @@ -function Get-BrowserSection { - return New-MDList -Style Unordered -Lines @( - (Get-SafariVersion), - (Get-SafariDriverVersion), - (Get-ChromeVersion), - (Get-ChromeDriverVersion), - (Get-EdgeVersion), - (Get-EdgeDriverVersion), - (Get-FirefoxVersion), - (Get-GeckodriverVersion), - (Get-SeleniumVersion) +function Build-BrowserSection { + return @( + [ToolNode]::new("Safari", $(Get-SafariVersion)) + [ToolNode]::new("SafariDriver", $(Get-SafariDriverVersion)) + [ToolNode]::new("Google Chrome", $(Get-ChromeVersion)) + [ToolNode]::new("ChromeDriver", $(Get-ChromeDriverVersion)) + [ToolNode]::new("Microsoft Edge", $(Get-EdgeVersion)) + [ToolNode]::new("Microsoft Edge WebDriver", $(Get-EdgeDriverVersion)) + [ToolNode]::new("Mozilla Firefox", $(Get-FirefoxVersion)) + [ToolNode]::new("geckodriver", $(Get-GeckodriverVersion)) + [ToolNode]::new("Selenium server", $(Get-SeleniumVersion)) ) } function Get-SafariVersion { $version = Run-Command "defaults read /Applications/Safari.app/Contents/Info CFBundleShortVersionString" $build = Run-Command "defaults read /Applications/Safari.app/Contents/Info CFBundleVersion" - "Safari $version ($build)" + return "$version ($build)" } function Get-SafariDriverVersion { $version = Run-Command "safaridriver --version" | Take-Part -Part 3,4 - "SafariDriver $version" + return $version } function Get-ChromeVersion { $chromePath = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" - return Run-Command "'${chromePath}' --version" + $version = Run-Command "'${chromePath}' --version" + return ($version -replace ("^Google Chrome")).Trim() } function Get-ChromeDriverVersion { $rawOutput = Run-Command "chromedriver --version" $version = $rawOutput | Take-Part -Part 1 - return "ChromeDriver ${version}" + return $version } function Get-EdgeVersion { $edgePath = "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge" - return Run-Command "'${edgePath}' --version" + $version = Run-Command "'${edgePath}' --version" + return ($version -replace ("^Microsoft Edge")).Trim() } function Get-EdgeDriverVersion { - return Run-Command "msedgedriver --version" | Take-Part -Part 0,1,2,3 + return Run-Command "msedgedriver --version" | Take-Part -Part 3 } function Get-FirefoxVersion { $firefoxPath = "/Applications/Firefox.app/Contents/MacOS/firefox" - return Run-Command "'${firefoxPath}' --version" + $version = Run-Command "'${firefoxPath}' --version" + return ($version -replace "^Mozilla Firefox").Trim() } function Get-GeckodriverVersion { - return Run-Command "geckodriver --version" | Select-Object -First 1 + $version = Run-Command "geckodriver --version" | Select-Object -First 1 + return ($version -replace "^geckodriver").Trim() } function Get-SeleniumVersion { $seleniumVersion = (Get-ChildItem -Path "/usr/local/Cellar/selenium-server*/*").Name - return "Selenium server $seleniumVersion" + return $seleniumVersion } function Build-BrowserWebdriversEnvironmentTable { - return @( + $node = [HeaderNode]::new("Environment variables") + + $table = @( @{ "Name" = "CHROMEWEBDRIVER" "Value" = $env:CHROMEWEBDRIVER @@ -77,4 +83,8 @@ function Build-BrowserWebdriversEnvironmentTable { "Value" = $_.Value } } + + $node.AddTableNode($table) + + return $node } diff --git a/images/macos/software-report/SoftwareReport.Common.psm1 b/images/macos/software-report/SoftwareReport.Common.psm1 index b18206f6d..3a5c8a932 100644 --- a/images/macos/software-report/SoftwareReport.Common.psm1 +++ b/images/macos/software-report/SoftwareReport.Common.psm1 @@ -1,12 +1,12 @@ function Get-BashVersion { $version = bash -c 'echo ${BASH_VERSION}' - return "Bash $version" + return $version } function Get-DotnetVersionList { $sdkRawList = Run-Command "dotnet --list-sdks" $sdkVersionList = $sdkRawList | ForEach-Object { Take-Part $_ -Part 0 } - return ".NET SDK " + [string]::Join(" ", $sdkVersionList) + return [string]::Join(" ", $sdkVersionList) } function Get-GoVersion { @@ -15,101 +15,109 @@ function Get-GoVersion { $goOutput = $goOutput.Substring(2) } - return "Go $goOutput" + return $goOutput } function Get-RVersion { $rVersion = Run-Command "R --version | grep 'R version'" | Take-Part -Part 2 - return "R $rVersion" + return $rVersion } function Get-RustVersion { $rustVersion = Run-Command "rustc --version" | Take-Part -Part 1 - return "Rust $rustVersion" + return $rustVersion } function Get-RustfmtVersion { $version = Run-Command "rustfmt --version" | Take-Part -Part 1 - return "Rustfmt $version" + return $version } function Get-RustdocVersion { $version = Run-Command "rustdoc --version" | Take-Part -Part 1 - return "Rustdoc $version" + return $version } function Get-RustCargoVersion { $version = Run-Command "cargo --version" | Take-Part -Part 1 - return "Cargo $version" + return $version } function Get-RustClippyVersion { $version = Run-Command "cargo clippy --version" | Take-Part -Part 1 - return "Clippy $version" + return $version } function Get-Bindgen { $bindgenVersion = Run-Command "bindgen --version" | Take-Part -Part 1 - return "Bindgen $bindgenVersion" + return $bindgenVersion } function Get-Cbindgen { $cbindgenVersion = Run-Command "cbindgen --version" | Take-Part -Part 1 - return "Cbindgen $cbindgenVersion" + return $cbindgenVersion } function Get-Cargooutdated { $cargoOutdatedVersion = Run-Command "cargo outdated --version" | Take-Part -Part 1 - return "Cargo-outdated $cargoOutdatedVersion" + return $cargoOutdatedVersion } function Get-Cargoaudit { $cargoAuditVersion = Run-Command "cargo-audit --version" | Take-Part -Part 1 - return "Cargo-audit $cargoAuditVersion" + return $cargoAuditVersion } function Get-RustupVersion { $rustupVersion = Run-Command "rustup --version" | Select-Object -First 1 | Take-Part -Part 1 - return "Rustup ${rustupVersion}" + return $rustupVersion } function Get-VcpkgVersion { $vcpkgVersion = Run-Command "vcpkg version" | Select-Object -First 1 | Take-Part -Part 5 | Take-Part -Part 0 -Delimiter "-" $commitId = git -C "/usr/local/share/vcpkg" rev-parse --short HEAD - return "Vcpkg $vcpkgVersion (build from master \<$commitId>)" + return "$vcpkgVersion (build from master \<$commitId>)" } -function Get-GccVersion { +function Get-GccVersions { $versionList = Get-ToolsetValue -KeyPath gcc.versions $versionList | Foreach-Object { - $version = Run-Command "gcc-${_} --version" | Select-Object -First 1 - "$version - available by ``gcc-${_}`` alias" + $nameVersion = Run-Command "gcc-${_} --version" | Select-Object -First 1 + $version = ($nameVersion -replace "^gcc-${_}").Trim() -replace '\).*$', ')' + return [ToolNode]::new("GCC ${_}", "$version - available by ``gcc-${_}`` alias") } } -function Get-FortranVersion { +function Get-FortranVersions { $versionList = Get-ToolsetValue -KeyPath gcc.versions $versionList | Foreach-Object { - $version = Run-Command "gfortran-${_} --version" | Select-Object -First 1 - "$version - available by ``gfortran-${_}`` alias" + $nameVersion = Run-Command "gfortran-${_} --version" | Select-Object -First 1 + $version = ($nameVersion -replace "^GNU Fortran").Trim() -replace '\).*$', ')' + return [ToolNode]::new("GNU Fortran ${_}", "$version - available by ``gfortran-${_}`` alias") } } -function Get-ClangLLVMVersion { - $toolsetVersion = '$(brew --prefix llvm@{0})/bin/clang' -f (Get-ToolsetValue 'llvm.version') - $locationsList = @("$((Get-Command clang).Source)", $toolsetVersion) - $locationsList | Foreach-Object { - (Run-Command "${_} --version" | Out-String) -match "(?\d+\.\d+\.\d+)" | Out-Null - $version = $Matches.version - "Clang/LLVM $version " + $(if(${_} -Match "brew") {"is available on ```'${_}`'``"} else {"is default"}) - } +function Get-ClangLLVMVersions { + $clangVersionRegex = [Regex]::new("(?\d+\.\d+\.\d+)") + + $defaultClangOutput = Run-Command "clang --version" | Out-String + $defaultClangVersion = $clangVersionRegex.Match($defaultClangOutput).Groups['version'].Value + + $homebrewClangPath = '$(brew --prefix llvm@{0})/bin/clang' -f (Get-ToolsetValue 'llvm.version') + $homebrewClangOutput = Run-Command "$homebrewClangPath --version" | Out-String + $homebrewClangVersion = $clangVersionRegex.Match($homebrewClangOutput).Groups['version'].Value + + return @( + [ToolNode]::new("Clang/LLVM", $defaultClangVersion) + [ToolNode]::new("Clang/LLVM (Homebrew)", "$homebrewClangVersion - available on ```'$homebrewClangPath`'``") + ) } function Get-NVMVersion { $nvmPath = Join-Path $env:HOME ".nvm" "nvm.sh" $nvmInitCommand = ". ${nvmPath} > /dev/null 2>&1 || true" $nodejsVersion = Run-Command "${nvmInitCommand} && nvm --version" - return "NVM $nodejsVersion" + return $nodejsVersion } function Get-PipVersion { @@ -123,12 +131,12 @@ function Get-PipVersion { $versionPart1 = $commandOutput | Take-Part -Part 1 $versionPart2 = $commandOutput | Take-Part -Part 4 $versionPart3 = $commandOutput | Take-Part -Part 5 - return "Pip ${versionPart1} ${versionPart2} ${versionPart3}" + return "${versionPart1} ${versionPart2} ${versionPart3}" } function Get-PipxVersion { $pipxVersion = Run-Command "pipx --version" -SuppressStderr - return "Pipx $pipxVersion" + return $pipxVersion } function Get-NVMNodeVersionList { @@ -137,19 +145,27 @@ function Get-NVMNodeVersionList { $nodejsVersionsRaw = Run-Command "${nvmInitCommand} && nvm ls" $nodeVersions = $nodejsVersionsRaw | ForEach-Object { $_.TrimStart(" ").TrimEnd(" *") } | Where-Object { $_.StartsWith("v") } $result = [string]::Join(" ", $nodeVersions) - return "NVM - Cached node versions: $result" + return $result } function Build-OSInfoSection { + param ( + [string] $ImageName + ) + $fieldsToInclude = @("System Version:", "Kernel Version:") $rawSystemInfo = Invoke-Expression "system_profiler SPSoftwareDataType" $parsedSystemInfo = $rawSystemInfo | Where-Object { -not ($_ | Select-String -NotMatch $fieldsToInclude) } | ForEach-Object { $_.Trim() } - $output = "" $parsedSystemInfo[0] -match "System Version: macOS (?\d+\.\d+)" | Out-Null $version = $Matches.Version - $output += New-MDHeader "macOS $version info" -Level 1 - $output += New-MDList -Style Unordered -Lines $parsedSystemInfo -NoNewLine - return $output + $systemVersion = $parsedSystemInfo[0].Replace($fieldsToInclude[0],"").Trim() + $kernelVersion = $parsedSystemInfo[1].Replace($fieldsToInclude[1],"").Trim() + + $osInfoNode = [HeaderNode]::new("macOS $version info") + $osInfoNode.AddToolNode("System Version:", $systemVersion) + $osInfoNode.AddToolNode("Kernel Version:", $kernelVersion) + $osInfoNode.AddToolNode("Image Version:", $ImageName.Split('_')[1]) + return $osInfoNode } function Get-MSBuildVersion { @@ -157,404 +173,407 @@ function Get-MSBuildVersion { $result = Select-String -Path (Get-Command msbuild).Source -Pattern "msbuild" $result -match "(?\/\S*\.dll)" | Out-Null $msbuildPath = $Matches.path - return "MSBuild $msbuildVersion (from $msbuildPath)" + return "$msbuildVersion (from $msbuildPath)" } function Get-NodeVersion { $nodeVersion = Run-Command "node --version" - return "Node.js $nodeVersion" + return $nodeVersion } function Get-PerlVersion { $version = Run-Command "perl -e 'print substr(`$^V,1)'" - return "Perl $version" + return $version } function Get-PythonVersion { $pythonVersion = Run-Command "python --version" - return $pythonVersion + return ($pythonVersion -replace "^Python").Trim() } function Get-Python3Version { $python3Version = Run-Command "python3 --version" - return $python3Version + return ($python3Version -replace "^Python").Trim() } function Get-RubyVersion { $rubyVersion = Run-Command "ruby --version" | Take-Part -Part 1 - return "Ruby $rubyVersion" + return $rubyVersion } function Get-PHPVersion { $PHPVersion = Run-Command "php --version" | Select-Object -First 1 | Take-Part -Part 0,1 - return $PHPVersion + return ($PHPVersion -replace "^PHP").Trim() } function Get-JuliaVersion { $juliaVersion = Run-Command "julia --version" | Take-Part -Part 0,2 - return $juliaVersion + return ($juliaVersion -replace "^Julia").Trim() } function Get-BundlerVersion { $bundlerVersion = Run-Command "bundle --version" - return $bundlerVersion + return ($bundlerVersion -replace "^Bundler").Trim() } function Get-CarthageVersion { $carthageVersion = Run-Command "carthage version" -SuppressStderr - return "Carthage $carthageVersion" + return $carthageVersion } function Get-CocoaPodsVersion { $cocoaPodsVersion = Run-Command "pod --version" - return "CocoaPods $cocoaPodsVersion" + return $cocoaPodsVersion } function Get-HomebrewVersion { $homebrewVersion = Run-Command "brew --version" | Select-Object -First 1 - return $homebrewVersion + return ($homebrewVersion -replace "^Homebrew").Trim() } function Get-NPMVersion { $NPMVersion = Run-Command "npm --version" - return "NPM $NPMVersion" + return $NPMVersion } function Get-YarnVersion { $yarmVersion = Run-Command "yarn --version" - return "Yarn $yarmVersion" + return $yarmVersion } function Get-NuGetVersion { $nugetVersion = Run-Command "nuget help" | Select-Object -First 1 | Take-Part -Part 2 - return "NuGet $nugetVersion" + return $nugetVersion } function Get-CondaVersion { $condaVersion = Invoke-Expression "conda --version" - return "Mini$condaVersion" + return ($condaVersion -replace "^conda").Trim() } function Get-RubyGemsVersion { $rubyGemsVersion = Run-Command "gem --version" - return "RubyGems $rubyGemsVersion" + return $rubyGemsVersion } function Get-ComposerVersion { $composerVersion = Run-Command "composer --version" | Take-Part -Part 2 - return "Composer $composerVersion" + return $composerVersion } function Get-MavenVersion { $mavenVersion = Run-Command "mvn -version" | Select-Object -First 1 | Take-Part -Part 2 - return "Apache Maven $mavenVersion" + return $mavenVersion } #gradle output differs on the first launch – a welcome message, that we don't need is rendered. The solution is to take the last "Gradle" occurrence from the output function Get-GradleVersion { $gradleVersion = (Run-Command "gradle --version" | Select-String "Gradle")[-1] - return $gradleVersion + return ($gradleVersion.Line -replace "^Gradle").Trim() } function Get-ApacheAntVersion { $apacheAntVersion = Run-Command "ant -version" | Take-Part -Part 0,1,3 - return $apacheAntVersion + return ($apacheAntVersion -replace "^Apache Ant\(TM\)").Trim() } function Get-CurlVersion { $curlVersion = Run-Command "curl --version" | Select-Object -First 1 | Take-Part -Part 1 - return "Curl $curlVersion" + return $curlVersion } function Get-GitVersion { $gitVersion = Run-Command "git --version" | Take-Part -Part -1 - return "Git $gitVersion" + return $gitVersion } function Get-GitLFSVersion { $gitLFSVersion = Run-Command "git-lfs version" | Take-Part -Part 0 | Take-Part -Part 1 -Delimiter "/" - return "Git LFS: $gitLFSVersion" + return $gitLFSVersion } function Get-GitHubCLIVersion { $ghVersion = Run-Command "gh --version" | Select-String "gh version" | Select-Object -First 1 | Take-Part -Part 2 - return "GitHub CLI: $ghVersion" + return $ghVersion } function Get-HubVersion { $hubVersion = Run-Command "brew list --versions hub" | Take-Part -Part 1 - return "Hub CLI: $hubVersion" + return $hubVersion } function Get-WgetVersion { $wgetVersion = Run-Command "wget --version" | Select-String "GNU Wget" | Take-Part -Part 2 - return "GNU Wget $wgetVersion" + return $wgetVersion } function Get-SVNVersion { $svnVersion = Run-Command "svn --version --quiet" - return "Subversion (SVN) $svnVersion" + return $svnVersion } function Get-PackerVersion { # Packer 1.7.1 has a bug and outputs version to stderr instead of stdout https://github.com/hashicorp/packer/issues/10855 $result = Run-Command -Command "packer --version" $packerVersion = [regex]::matches($result, "(\d+.){2}\d+").Value - return "Packer $packerVersion" + return $packerVersion } function Get-OpenSSLVersion { $opensslVersion = Get-Item /usr/local/opt/openssl@1.1 | ForEach-Object {"{0} ``({1} -> {2})``" -f (Run-Command "openssl version"), $_.FullName, $_.Target} - return $opensslVersion + return ($opensslVersion -replace "^OpenSSL").Trim() } function Get-JqVersion { $jqVersion = Run-Command "jq --version" | Take-Part -Part 1 -Delimiter "-" - return "jq $jqVersion" + return $jqVersion } function Get-GPGVersion { $gpgVersion = Run-Command "gpg --version" | Select-String 'gpg (GnuPG)' -SimpleMatch - return $gpgVersion + return ($gpgVersion.Line -replace "^gpg \(GnuPG\)").Trim() } function Get-PostgresClientVersion { $postgresClientVersion = Run-Command "psql --version" - return $postgresClientVersion + return ($postgresClientVersion -replace "^psql \(PostgreSQL\)").Trim() } function Get-PostgresServerVersion { $postgresServerVersion = Run-Command "pg_config --version" - return $postgresServerVersion + return ($postgresServerVersion -replace "^PostgreSQL").Trim() } function Get-Aria2Version { $aria2Version = Run-Command "aria2c --version" | Select-Object -First 1 | Take-Part -Part 2 - return "aria2 $aria2Version" + return $aria2Version } function Get-AzcopyVersion { $azcopyVersion = Run-Command "azcopy --version" | Take-Part -Part 2 - return "azcopy $azcopyVersion" + return $azcopyVersion } function Get-ZstdVersion { $zstdVersion = Run-Command "zstd --version" | Take-Part -Part 1 -Delimiter "v" | Take-Part -Part 0 -Delimiter "," - return "zstd $zstdVersion" + return $zstdVersion } function Get-BazelVersion { $bazelVersion = Run-Command "bazel --version" | Take-Part -Part 0 -Delimiter "-" - return $bazelVersion + return ($bazelVersion -replace "^bazel").Trim() } function Get-BazeliskVersion { $bazeliskVersion = Run-Command "brew list bazelisk --versions" - return $bazeliskVersion + return ($bazeliskVersion -replace "^bazelisk").Trim() } function Get-HelmVersion { $helmVersion = Run-Command "helm version --short" - return "helm $helmVersion" + return $helmVersion } function Get-MongoVersion { $mongo = Run-Command "mongo --version" | Select-String "MongoDB shell version" | Take-Part -Part 3 - return "mongo $mongo" + return $mongo } function Get-MongodVersion { $mongod = Run-Command "mongod --version" | Select-String "db version " | Take-Part -Part 2 - return "mongod $mongod" + return $mongod } function Get-7zipVersion { $7zip = Run-Command "7z i" | Select-String "7-Zip" | Take-Part -Part 0,2 - return $7zip + return ($7zip -replace "^7-Zip").Trim() } function Get-GnuTarVersion { $gnuTar = Run-Command "gtar --version" | Select-String "tar" | Take-Part -Part 3 - return "GNU Tar $gnuTar - available by 'gtar' alias" + return "$gnuTar - available by 'gtar' alias" } function Get-BsdtarVersion { $bsdtar = Run-Command "tar --version" | Take-Part -Part 1 - return "bsdtar $bsdtar - available by 'tar' alias" + return "$bsdtar - available by 'tar' alias" } function Get-NewmanVersion { $newmanVersion = Run-Command "newman --version" - return "Newman $newmanVersion" + return $newmanVersion } function Get-VirtualBoxVersion { $virtualBox = Run-Command "vboxmanage -v" - return "VirtualBox $virtualBox" + return $virtualBox } function Get-VagrantVersion { $vagrant = Run-Command "vagrant -v" - return $vagrant + return ($vagrant -replace "^Vagrant").Trim() } function Get-ParallelVersion { $parallelVersion = Run-Command "parallel --version" | Select-String "GNU parallel" | Select-Object -First 1 - return $parallelVersion + return ($parallelVersion -replace "^GNU parallel").Trim() } function Get-FastlaneVersion { $fastlaneVersion = Run-Command "fastlane --version" | Select-String "^fastlane [0-9]" | Take-Part -Part 1 - return "Fastlane $fastlaneVersion" + return $fastlaneVersion } function Get-CmakeVersion { $cmakeVersion = Run-Command "cmake --version" | Select-Object -First 1 | Take-Part -Part 2 - return "Cmake $cmakeVersion" + return $cmakeVersion } function Get-AppCenterCLIVersion { $appcenterCLIVersion = Run-Command "appcenter --version" | Take-Part -Part 2 - return "App Center CLI $appcenterCLIVersion" + return $appcenterCLIVersion } function Get-AzureCLIVersion { $azureCLIVersion = (az version | ConvertFrom-Json).'azure-cli' - return "Azure CLI $azureCLIVersion" + return $azureCLIVersion } function Get-AzureDevopsVersion { $azdevopsVersion = (az version | ConvertFrom-Json).extensions.'azure-devops' - return "Azure CLI (azure-devops) $azdevopsVersion" + return $azdevopsVersion } function Get-AWSCLIVersion { $awsVersion = Run-Command "aws --version" | Take-Part -Part 0 | Take-Part -Delimiter "/" -Part 1 - return "AWS CLI $awsVersion" + return $awsVersion } function Get-AWSSAMCLIVersion { $awsSamVersion = Run-Command "sam --version" | Take-Part -Part 3 - return "AWS SAM CLI $awsSamVersion" + return $awsSamVersion } function Get-AWSSessionManagerCLIVersion { $awsSessionManagerVersion = Run-Command "session-manager-plugin --version" - return "AWS Session Manager CLI $awsSessionManagerVersion" + return $awsSessionManagerVersion } function Get-AliyunCLIVersion { $aliyunVersion = Run-Command "aliyun --version" | Select-String "Alibaba Cloud Command Line Interface Version " | Take-Part -Part 6 - return "Aliyun CLI $aliyunVersion" + return $aliyunVersion } function Get-GHCupVersion { $ghcUpVersion = (Run-Command "ghcup --version" | Take-Part -Part 5).Replace('v','') - return "GHCup $ghcUpVersion" + return $ghcUpVersion } function Get-GHCVersion { $ghcVersion = Run-Command "ghc --version" | Take-Part -Part 7 - return "GHC $ghcVersion" + return $ghcVersion } function Get-CabalVersion { $cabalVersion = Run-Command "cabal --version" | Take-Part -Part 3 - return "Cabal $cabalVersion" + return $cabalVersion } function Get-SwitchAudioOsxVersion { $switchAudioVersion = Get-BrewPackageVersion -CommandName "SwitchAudioSource" - return "Switchaudio-osx $switchAudioVersion" + return $switchAudioVersion } function Get-SoxVersion { $soxVersion = Get-BrewPackageVersion -CommandName "sox" - return "Sox $soxVersion" + return $soxVersion } function Get-StackVersion { $stackVersion = Run-Command "stack --version" | Take-Part -Part 1 | ForEach-Object {$_.replace(",","")} - return "Stack $stackVersion" + return $stackVersion } function Get-SwiftFormatVersion { $swiftFormatVersion = Run-Command "swiftformat --version" - return "SwiftFormat $swiftFormatVersion" + return $swiftFormatVersion } function Get-YamllintVersion { $yamllintVersion = Run-Command "yamllint --version" - return $yamllintVersion + return ($yamllintVersion -replace "^Yamllint").Trim() } function Get-SwiftLintVersion { $swiftlintVersion = Run-Command "swiftlint version" - return "SwiftLint $swiftlintVersion" + return $swiftlintVersion } function Get-PowershellVersion { $powershellVersion = Run-Command "powershell --version" - return $powershellVersion + return ($powershellVersion -replace "^PowerShell").Trim() } function Get-SwigVersion { $swigVersion = Run-Command "swig -version" | Select-Object -First 2 | Take-Part -Part 2 - return "Swig $swigVersion" + return $swigVersion } function Get-BicepVersion { $bicepVersion = Run-Command "bicep --version" | Take-Part -Part 3 - return "Bicep CLI $bicepVersion" + return $bicepVersion } function Get-KotlinVersion { $kotlinVersion = Run-Command "kotlin -version" | Take-Part -Part 2 - return "Kotlin $kotlinVersion" + return $kotlinVersion } function Get-SbtVersion { $sbtVersion = Run-Command "sbt -version" | Take-Part -Part 3 - return "Sbt $sbtVersion" + return $sbtVersion } function Get-JazzyVersion { $jazzyVersion = Run-Command "jazzy --version" | Take-Part -Part 2 - return "Jazzy $jazzyVersion" + return $jazzyVersion } function Get-ZlibVersion { $zlibVersion = brew info --json zlib | jq -r '.[].installed[].version' - return "Zlib $zlibVersion" + return $zlibVersion } function Get-LibXftVersion { $libXftVersion = brew info --json libxft | jq -r '.[].installed[].version' - return "libXft $libXftVersion" + return $libXftVersion } function Get-LibXextVersion { $libXextVersion = brew info --json libxext | jq -r '.[].installed[].version' - return "libXext $libXextVersion" + return $libXextVersion } function Get-TclTkVersion { $tcltkVersion = brew info --json tcl-tk | jq -r '.[].installed[].version' - return "Tcl/Tk $tcltkVersion" + return $tcltkVersion } function Get-YqVersion { $yqVersion = Run-Command "yq --version" - return "$yqVersion" + $yqVersion -match "\d{1,2}\.\d{1,2}\.\d{1,2}" | Out-Null + return ($Matches[0]) } function Get-ImageMagickVersion { $imagemagickVersion = Run-Command "magick --version" | Select-Object -First 1 | Take-Part -Part 1,2 - return "$imagemagickVersion" + return ($imagemagickVersion -replace "^ImageMagick").Trim() } function Build-PackageManagementEnvironmentTable { - return @( + $node = [HeaderNode]::new("Environment variables") + + $table = @( @{ "Name" = "CONDA" "Value" = $env:CONDA @@ -569,6 +588,10 @@ function Build-PackageManagementEnvironmentTable { "Value" = $_.Value } } + + $node.AddTableNode($table) + + return $node } function Build-MiscellaneousEnvironmentTable { @@ -605,10 +628,10 @@ function Get-CodeQLBundleVersion { $CodeQLVersionPath = Get-ChildItem $CodeQLVersionWildcard | Select-Object -First 1 -Expand FullName $CodeQLPath = Join-Path $CodeQLVersionPath -ChildPath "x64" | Join-Path -ChildPath "codeql" | Join-Path -ChildPath "codeql" $CodeQLVersion = & $CodeQLPath version --quiet - return "CodeQL Action Bundle $CodeQLVersion" + return $CodeQLVersion } function Get-ColimaVersion { $colimaVersion = Run-Command "colima version" | Select-String "colima version" | Take-Part -Part 2 - return "Colima $colimaVersion" + return $colimaVersion } diff --git a/images/macos/software-report/SoftwareReport.Generator.ps1 b/images/macos/software-report/SoftwareReport.Generator.ps1 index e58a9199b..b64b4df02 100644 --- a/images/macos/software-report/SoftwareReport.Generator.ps1 +++ b/images/macos/software-report/SoftwareReport.Generator.ps1 @@ -6,7 +6,7 @@ param ( $ErrorActionPreference = "Stop" -Import-Module MarkdownPS +. ("$PSScriptRoot/SoftwareReport.Base.ps1") Import-Module "$PSScriptRoot/SoftwareReport.Common.psm1" -DisableNameChecking Import-Module "$PSScriptRoot/SoftwareReport.Xcode.psm1" -DisableNameChecking Import-Module "$PSScriptRoot/SoftwareReport.Android.psm1" -DisableNameChecking @@ -22,311 +22,246 @@ Import-Module "$PSScriptRoot/../helpers/Xcode.Helpers.psm1" # Operating System info $os = Get-OSVersion -$markdown = "" - # OS info -$markdown += Build-OSInfoSection -$markdown += New-MDList -Style Unordered -Lines ("Image Version: {0}" -f $ImageName.Split('_')[1]) -# Software report -$markdown += New-MDHeader "Installed Software" -Level 2 -$markdown += New-MDHeader "Language and Runtime" -Level 3 -$languageAndRuntimeList = @( - (Get-BashVersion) - (Get-MSBuildVersion) - (Get-NodeVersion) - (Get-NVMVersion) - (Get-NVMNodeVersionList) - (Get-PerlVersion) - (Get-PythonVersion) - (Get-Python3Version) - (Get-RubyVersion) - (Get-DotnetVersionList) - (Get-GoVersion) - (Get-JuliaVersion) - (Get-KotlinVersion) - (Get-PHPVersion) - (Get-ClangLLVMVersion) - (Get-GccVersion) - (Get-FortranVersion) - (Get-RVersion) -) +$osInfo = Build-OSInfoSection $ImageName -# To sort GCC and Gfortran correctly, we need to use natural sort https://gist.github.com/markwragg/e2a9dc05f3464103d6998298fb575d4e#file-sort-natural-ps1 -$toNatural = { [regex]::Replace($_, '\d+', { $args[0].Value.PadLeft(20) }) } -$markdown += New-MDList -Style Unordered -Lines ($languageAndRuntimeList | Sort-Object $toNatural) +# Software report +$softwareReport = [SoftwareReport]::new($osInfo) +$installedSoftware = $softwareReport.Root.AddHeaderNode("Installed Software") + +# Language and Runtime +$languageAndRuntime = $installedSoftware.AddHeaderNode("Language and Runtime") +$languageAndRuntime.AddToolNode(".NET SDK", $(Get-DotnetVersionList)) +$languageAndRuntime.AddToolNode("Bash", $(Get-BashVersion)) +$languageAndRuntime.AddNodes($(Get-ClangLLVMVersions)) +$languageAndRuntime.AddNodes($(Get-GccVersions)) +$languageAndRuntime.AddNodes($(Get-FortranVersions)) +$languageAndRuntime.AddToolNode("Go", $(Get-GoVersion)) +$languageAndRuntime.AddToolNode("Julia", $(Get-JuliaVersion)) +$languageAndRuntime.AddToolNode("Kotlin", $(Get-KotlinVersion)) +$languageAndRuntime.AddToolNode("MSBuild", $(Get-MSBuildVersion)) +$languageAndRuntime.AddToolNode("Node.js", $(Get-NodeVersion)) +$languageAndRuntime.AddToolNode("NVM", $(Get-NVMVersion)) +$languageAndRuntime.AddToolNode("NVM - Cached node versions:", $(Get-NVMNodeVersionList)) +$languageAndRuntime.AddToolNode("Perl", $(Get-PerlVersion)) +$languageAndRuntime.AddToolNode("PHP", $(Get-PHPVersion)) +$languageAndRuntime.AddToolNode("Python", $(Get-PythonVersion)) +$languageAndRuntime.AddToolNode("Python3", $(Get-Python3Version)) +$languageAndRuntime.AddToolNode("R", $(Get-RVersion)) +$languageAndRuntime.AddToolNode("Ruby", $(Get-RubyVersion)) # Package Management -$markdown += New-MDHeader "Package Management" -Level 3 -$packageManagementList = @( - (Get-PipVersion -Version 2), - (Get-PipVersion -Version 3), - (Get-PipxVersion), - (Get-BundlerVersion), - (Get-CocoaPodsVersion), - (Get-CondaVersion), - (Get-HomebrewVersion), - (Get-NPMVersion), - (Get-YarnVersion), - (Get-NuGetVersion), - (Get-RubyGemsVersion), - (Get-ComposerVersion), - (Get-CarthageVersion), - (Get-VcpkgVersion) -) +$packageManagement = $installedSoftware.AddHeaderNode("Package Management") +$packageManagement.AddToolNode("Bundler", $(Get-BundlerVersion)) +$packageManagement.AddToolNode("Carthage", $(Get-CarthageVersion)) +$packageManagement.AddToolNode("CocoaPods", $(Get-CocoaPodsVersion)) +$packageManagement.AddToolNode("Composer", $(Get-ComposerVersion)) +$packageManagement.AddToolNode("Homebrew", $(Get-HomebrewVersion)) +$packageManagement.AddToolNode("Miniconda", $(Get-CondaVersion)) +$packageManagement.AddToolNode("NPM", $(Get-NPMVersion)) +$packageManagement.AddToolNode("NuGet", $(Get-NuGetVersion)) +$packageManagement.AddToolNode("Pip", $(Get-PipVersion -Version 2)) +$packageManagement.AddToolNode("Pip3", $(Get-PipVersion -Version 3)) +$packageManagement.AddToolNode("Pipx", $(Get-PipxVersion)) +$packageManagement.AddToolNode("RubyGems", $(Get-RubyGemsVersion)) +$packageManagement.AddToolNode("Vcpkg", $(Get-VcpkgVersion)) +$packageManagement.AddToolNode("Yarn", $(Get-YarnVersion)) -$markdown += New-MDList -Style Unordered -Lines ($packageManagementList | Sort-Object) -$markdown += New-MDHeader "Environment variables" -Level 4 -$markdown += Build-PackageManagementEnvironmentTable | New-MDTable -$markdown += New-MDNewLine +$packageManagement.AddNode($(Build-PackageManagementEnvironmentTable)) # Project Management -$markdown += New-MDHeader "Project Management" -Level 3 -$markdown += New-MDList -Style Unordered -Lines (@( - (Get-MavenVersion), - (Get-GradleVersion), - (Get-ApacheAntVersion), - (Get-SbtVersion) - ) | Sort-Object -) +$projectanagement = $installedSoftware.AddHeaderNode("Project Management") +$projectanagement.AddToolNode("Apache Ant", $(Get-ApacheAntVersion)) +$projectanagement.AddToolNode("Apache Maven", $(Get-MavenVersion)) +$projectanagement.AddToolNode("Gradle", $(Get-GradleVersion)) +$projectanagement.AddToolNode("Sbt", $(Get-SbtVersion)) # Utilities -$markdown += New-MDHeader "Utilities" -Level 3 -$utilitiesList = @( - (Get-CurlVersion), - (Get-GitVersion), - (Get-GitLFSVersion), - (Get-GitHubCLIVersion), - (Get-HubVersion), - (Get-WgetVersion), - (Get-SVNVersion), - (Get-PackerVersion), - (Get-OpenSSLVersion), - (Get-JqVersion), - (Get-PostgresClientVersion), - (Get-PostgresServerVersion), - (Get-Aria2Version), - (Get-AzcopyVersion), - (Get-ZstdVersion), - (Get-BazelVersion), - (Get-BazeliskVersion), - (Get-MongoVersion), - (Get-MongodVersion), - (Get-7zipVersion), - (Get-BsdtarVersion), - (Get-GnuTarVersion), - (Get-GPGVersion), - (Get-SwitchAudioOsxVersion), - (Get-SoxVersion), - (Get-YqVersion), - (Get-ImageMagickVersion) -) - -if ($os.IsLessThanMonterey) { - $utilitiesList += @( - (Get-HelmVersion) - ) -} - -if ($os.IsLessThanMonterey) { - $utilitiesList += @( - (Get-NewmanVersion) - ) -} - +$utilities = $installedSoftware.AddHeaderNode("Utilities") +$utilities.AddToolNode("7-Zip", $(Get-7zipVersion)) +$utilities.AddToolNode("aria2", $(Get-Aria2Version)) +$utilities.AddToolNode("azcopy", $(Get-AzcopyVersion)) +$utilities.AddToolNode("bazel", $(Get-BazelVersion)) +$utilities.AddToolNode("bazelisk", $(Get-BazeliskVersion)) +$utilities.AddToolNode("bsdtar", $(Get-BsdtarVersion)) +$utilities.AddToolNode("Curl", $(Get-CurlVersion)) +$utilities.AddToolNode("Git", $(Get-GitVersion)) +$utilities.AddToolNode("Git LFS", $(Get-GitLFSVersion)) +$utilities.AddToolNode("GitHub CLI", $(Get-GitHubCLIVersion)) if ($os.IsCatalina) { - $utilitiesList += @( - (Get-ParallelVersion) - ) + $utilities.AddToolNode("GNU parallel", $(Get-ParallelVersion)) } - +$utilities.AddToolNode("GNU Tar", $(Get-GnuTarVersion)) +$utilities.AddToolNode("GNU Wget", $(Get-WgetVersion)) +$utilities.AddToolNode("gpg (GnuPG)", $(Get-GPGVersion)) +if ($os.IsLessThanMonterey) { + $utilities.AddToolNode("helm", $(Get-HelmVersion)) +} +$utilities.AddToolNode("Hub CLI", $(Get-HubVersion)) +$utilities.AddToolNode("ImageMagick", $(Get-ImageMagickVersion)) +$utilities.AddToolNode("jq", $(Get-JqVersion)) +$utilities.AddToolNode("mongo", $(Get-MongoVersion)) +$utilities.AddToolNode("mongod", $(Get-MongodVersion)) +if ($os.IsLessThanMonterey) { + $utilities.AddToolNode("Newman", $(Get-NewmanVersion)) +} +$utilities.AddToolNode("OpenSSL", $(Get-OpenSSLVersion)) +$utilities.AddToolNode("Packer", $(Get-PackerVersion)) +$utilities.AddToolNode("PostgreSQL", $(Get-PostgresServerVersion)) +$utilities.AddToolNode("psql (PostgreSQL)", $(Get-PostgresClientVersion)) +$utilities.AddToolNode("Sox", $(Get-SoxVersion)) +$utilities.AddToolNode("Subversion (SVN)", $(Get-SVNVersion)) +$utilities.AddToolNode("Switchaudio-osx", $(Get-SwitchAudioOsxVersion)) if (-not $os.IsBigSur) { - $utilitiesList += @( - (Get-VagrantVersion), - (Get-VirtualBoxVersion) - ) + $utilities.AddToolNode("Vagrant", $(Get-VagrantVersion)) + $utilities.AddToolNode("VirtualBox", $(Get-VirtualBoxVersion)) } - -$markdown += New-MDList -Style Unordered -Lines ($utilitiesList | Sort-Object) +$utilities.AddToolNode("yq", $(Get-YqVersion)) +$utilities.AddToolNode("zstd", $(Get-ZstdVersion)) # Tools -$markdown += New-MDHeader "Tools" -Level 3 -$toolsList = @( - (Get-JazzyVersion), - (Get-FastlaneVersion), - (Get-CmakeVersion), - (Get-AppCenterCLIVersion), - (Get-AzureCLIVersion), - (Get-AzureDevopsVersion), - (Get-AWSCLIVersion), - (Get-AWSSAMCLIVersion), - (Get-AWSSessionManagerCLIVersion) -) - -if (-not $os.IsCatalina) { - $toolsList += @( - (Get-CodeQLBundleVersion) - ) -} - +$tools = $installedSoftware.AddHeaderNode("Tools") if ($os.IsLessThanMonterey) { - $toolsList += @( - (Get-AliyunCLIVersion) - ) + $tools.AddToolNode("Aliyun CLI", $(Get-AliyunCLIVersion)) } - -$toolsList += @( - (Get-XcodeCommandLineToolsVersion), - (Get-SwigVersion), - (Get-BicepVersion), - (Get-GHCupVersion), - (Get-GHCVersion), - (Get-CabalVersion), - (Get-StackVersion), - (Get-SwiftFormatVersion) -) - +$tools.AddToolNode("App Center CLI", $(Get-AppCenterCLIVersion)) +$tools.AddToolNode("AWS CLI", $(Get-AWSCLIVersion)) +$tools.AddToolNode("AWS SAM CLI", $(Get-AWSSAMCLIVersion)) +$tools.AddToolNode("AWS Session Manager CLI", $(Get-AWSSessionManagerCLIVersion)) +$tools.AddToolNode("Azure CLI (azure-devops)", $(Get-AzureDevopsVersion)) +$tools.AddToolNode("Azure CLI", $(Get-AzureCLIVersion)) +$tools.AddToolNode("Bicep CLI", $(Get-BicepVersion)) +$tools.AddToolNode("Cabal", $(Get-CabalVersion)) +$tools.AddToolNode("Cmake", $(Get-CmakeVersion)) if (-not $os.IsCatalina) { - $toolsList += @( - (Get-ColimaVersion) - ) + $tools.AddToolNode("CodeQL Action Bundle", $(Get-CodeQLBundleVersion)) } - -$markdown += New-MDList -Style Unordered -Lines ($toolsList | Sort-Object) +if (-not $os.IsCatalina) { + $tools.AddToolNode("Colima", $(Get-ColimaVersion)) +} +$tools.AddToolNode("Fastlane", $(Get-FastlaneVersion)) +$tools.AddToolNode("GHC", $(Get-GHCVersion)) +$tools.AddToolNode("GHCup", $(Get-GHCupVersion)) +$tools.AddToolNode("Jazzy", $(Get-JazzyVersion)) +$tools.AddToolNode("Stack", $(Get-StackVersion)) +$tools.AddToolNode("SwiftFormat", $(Get-SwiftFormatVersion)) +$tools.AddToolNode("Swig", $(Get-SwigVersion)) +$tools.AddToolNode("Xcode Command Line Tools", $(Get-XcodeCommandLineToolsVersion)) # Linters -$markdown += New-MDHeader "Linters" -Level 3 -$lintersList = @( - (Get-YamllintVersion), - (Get-SwiftLintVersion) -) +$linters = $installedSoftware.AddHeaderNode("Linters") +$linters.AddToolNode("Swift", $(Get-SwiftLintVersion)) +$linters.AddToolNode("Yamllint", $(Get-YamllintVersion)) -$markdown += New-MDList -Style Unordered -Lines ($lintersList | Sort-Object) +# Browsers +$browsers = $installedSoftware.AddHeaderNode("Browsers") +$browsers.AddNodes($(Build-BrowserSection)) +$browsers.AddNode($(Build-BrowserWebdriversEnvironmentTable)) -$markdown += New-MDHeader "Browsers" -Level 3 -$markdown += Get-BrowserSection -$markdown += New-MDHeader "Environment variables" -Level 4 -$markdown += Build-BrowserWebdriversEnvironmentTable | New-MDTable -$markdown += New-MDNewLine +# Java +$java = $installedSoftware.AddHeaderNode("Java") +$java.AddTableNode($(Get-JavaVersions)) -$markdown += New-MDHeader "Java" -Level 3 -$markdown += Get-JavaVersions | New-MDTable -$markdown += New-MDNewLine - -$markdown += New-MDHeader "GraalVM" -Level 3 -$markdown += Build-GraalVMTable | New-MDTable -$markdown += New-MDNewLine +# Graal +$graalvm = $installedSoftware.AddHeaderNode("GraalVM") +$graalvm.AddTableNode($(Build-GraalVMTable)) # Toolcache -$markdown += Build-ToolcacheSection -$markdown += New-MDNewLine +$toolcache = $installedSoftware.AddHeaderNode("Cached Tools") +$toolcache.AddNodes($(Build-ToolcacheSection)) -$markdown += New-MDHeader "Rust Tools" -Level 3 -$markdown += New-MDList -Style Unordered -Lines (@( - (Get-RustVersion), - (Get-RustupVersion), - (Get-RustdocVersion), - (Get-RustCargoVersion) - ) | Sort-Object -) +# Rust +$rust = $installedSoftware.AddHeaderNode("Rust Tools") +$rust.AddToolNode("Cargo", $(Get-RustCargoVersion)) +$rust.AddToolNode("Rust", $(Get-RustVersion)) +$rust.AddToolNode("Rustdoc", $(Get-RustdocVersion)) +$rust.AddToolNode("Rustup", $(Get-RustupVersion)) -$markdown += New-MDHeader "Packages" -Level 4 -$markdown += New-MDList -Style Unordered -Lines (@( - (Get-Bindgen), - (Get-Cbindgen), - (Get-Cargooutdated), - (Get-Cargoaudit), - (Get-RustfmtVersion), - (Get-RustClippyVersion) - ) | Sort-Object -) +$rustPackages = $rust.AddHeaderNode("Packages") +$rustPackages.AddToolNode("Bindgen", $(Get-Bindgen)) +$rustPackages.AddToolNode("Cargo-audit", $(Get-Cargoaudit)) +$rustPackages.AddToolNode("Cargo-outdated", $(Get-Cargooutdated)) +$rustPackages.AddToolNode("Cbindgen", $(Get-Cbindgen)) +$rustPackages.AddToolNode("Clippy", $(Get-RustClippyVersion)) +$rustPackages.AddToolNode("Rustfmt", $(Get-RustfmtVersion)) -$markdown += New-MDHeader "PowerShell Tools" -Level 3 -$markdown += New-MDList -Lines (Get-PowershellVersion) -Style Unordered +# PowerShell +$powerShell = $installedSoftware.AddHeaderNode("PowerShell Tools") +$powerShell.AddToolNode("PowerShell", $(Get-PowershellVersion)) -$markdown += New-MDHeader "PowerShell Modules" -Level 4 -$markdown += Get-PowerShellModules | New-MDTable -$markdown += New-MDNewLine +$powerShellModules = $powerShell.AddHeaderNode("PowerShell Modules") +$powerShellModules.AddTableNode($(Get-PowerShellModules)) # Web Servers -$markdown += Build-WebServersSection - +$webServers = $installedSoftware.AddHeaderNode("Web Servers") +$webServers.AddTableNode($(Build-WebServersSection)) # Xamarin section -$markdown += New-MDHeader "Xamarin" -Level 3 -$markdown += New-MDHeader "Visual Studio for Mac" -Level 4 -$markdown += Build-VSMacTable | New-MDTable -$markdown += New-MDNewLine +$xamarin = $installedSoftware.AddHeaderNode("Xamarin") +$vsForMac = $xamarin.AddHeaderNode("Visual Studio for Mac") +$vsForMac.AddTableNode($(Build-VSMacTable)) + if (-not $os.IsCatalina) { -$markdown += New-MDHeader "Notes:" -Level 5 -$reportVS = @' -``` + $note = + @' To use Visual Studio 2019 by default rename the app: mv "/Applications/Visual Studio.app" "/Applications/Visual Studio 2022.app" mv "/Applications/Visual Studio 2019.app" "/Applications/Visual Studio.app" -``` '@ -$markdown += New-MDParagraph -Lines $reportVS + $vsForMacNotes = $vsForMac.AddHeaderNode("Notes") + $vsForMacNotes.AddNoteNode($note) } -$markdown += New-MDHeader "Xamarin bundles" -Level 4 -$markdown += Build-XamarinTable | New-MDTable -$markdown += New-MDNewLine +$xamarinBundles = $xamarin.AddHeaderNode("Xamarin bundles") +$xamarinBundles.AddTableNode($(Build-XamarinTable)) -$markdown += New-MDHeader "Unit Test Framework" -Level 4 -$markdown += New-MDList -Lines @(Get-NUnitVersion) -Style Unordered +$unitTestFramework = $xamarin.AddHeaderNode("Unit Test Framework") +$unitTestFramework.AddToolNode("NUnit", $(Get-NUnitVersion)) +# Xcode section +$xcode = $installedSoftware.AddHeaderNode("Xcode") # First run doesn't provide full data about devices and runtimes Get-XcodeInfoList | Out-Null -# Xcode section + $xcodeInfo = Get-XcodeInfoList -$markdown += New-MDHeader "Xcode" -Level 3 -$markdown += Build-XcodeTable $xcodeInfo | New-MDTable -$markdown += New-MDNewLine +$xcode.AddTableNode($(Build-XcodeTable $xcodeInfo)) -$markdown += Build-XcodeSupportToolsSection +$xcodeTools = $xcode.AddHeaderNode("Xcode Support Tools") +$xcodeTools.AddNodes($(Build-XcodeSupportToolsSection)) -$markdown += New-MDHeader "Installed SDKs" -Level 4 -$markdown += Build-XcodeSDKTable $xcodeInfo | New-MDTable -$markdown += New-MDNewLine +$installedSdks = $xcode.AddHeaderNode("Installed SDKs") +$installedSdks.AddTableNode($(Build-XcodeSDKTable $xcodeInfo)) -$markdown += New-MDHeader "Installed Simulators" -Level 4 -$markdown += Build-XcodeSimulatorsTable $xcodeInfo | New-MDTable -$markdown += New-MDNewLine +$installedSimulators = $xcode.AddHeaderNode("Installed Simulators") +$installedSimulators.AddTableNode($(Build-XcodeSimulatorsTable $xcodeInfo)) # Android section -$markdown += New-MDHeader "Android" -Level 3 +$android = $installedSoftware.AddHeaderNode("Android") $androidTable = Build-AndroidTable if ($os.IsCatalina) { $androidTable += Get-IntelHaxmVersion } -$markdown += $androidTable | New-MDTable -$markdown += New-MDNewLine -$markdown += New-MDHeader "Environment variables" -Level 4 -$markdown += Build-AndroidEnvironmentTable | New-MDTable -$markdown += New-MDNewLine +$android.AddTableNode($androidTable) -$markdown += New-MDHeader "Miscellaneous" -Level 3 -$markdown += New-MDList -Style Unordered -Lines (@( - (Get-ZlibVersion), - (Get-LibXextVersion), - (Get-LibXftVersion), - (Get-TclTkVersion) - ) | Sort-Object -) +$androidEnv = $android.AddHeaderNode("Environment variables") +$androidEnv.AddTableNode($(Build-AndroidEnvironmentTable)) + +$miscellaneous = $installedSoftware.AddHeaderNode("Miscellaneous") +$miscellaneous.AddToolNode("libXext", $(Get-LibXextVersion)) +$miscellaneous.AddToolNode("libXft", $(Get-LibXftVersion)) +$miscellaneous.AddToolNode("Tcl/Tk", $(Get-TclTkVersion)) +$miscellaneous.AddToolNode("Zlib", $(Get-ZlibVersion)) if ($os.IsMonterey) { -$markdown += New-MDHeader "Environment variables" -Level 4 -$markdown += Build-MiscellaneousEnvironmentTable | New-MDTable -$markdown += New-MDNewLine + $miscellaneousEnv = $miscellaneous.AddHeaderNode("Environment variables") + $miscellaneousEnv.AddTableNode($(Build-MiscellaneousEnvironmentTable)) -$markdown += New-MDHeader "Notes:" -Level 5 -$misc = @' -``` + $notes = @' If you want to use Parallels Desktop you should download a package from URL stored in PARALLELS_DMG_URL environment variable. A system extension is allowed for this version. -``` '@ -$markdown += New-MDParagraph -Lines $misc + $miscellaneousEnvNotes = $miscellaneousEnv.AddHeaderNode("Notes") + $miscellaneousEnvNotes.AddNoteNode($notes) } # @@ -334,10 +269,9 @@ $markdown += New-MDParagraph -Lines $misc # $dateTime = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") $systemInfo = [string]::Join([System.Environment]::NewLine, @( - "Date: ${dateTime}", - "Image name: ${ImageName}" -)) - + "Date: ${dateTime}", + "Image name: ${ImageName}" + )) if (-not (Test-Path $OutputDirectory)) { New-Item -Path $OutputDirectory -ItemType Directory | Out-Null } @@ -346,4 +280,6 @@ if (-not (Test-Path $OutputDirectory)) { New-Item -Path $OutputDirectory -ItemTy # Write-Host $markdownExtended $systemInfo | Out-File -FilePath "${OutputDirectory}/systeminfo.txt" -Encoding UTF8NoBOM -$markdown | Out-File -FilePath "${OutputDirectory}/systeminfo.md" -Encoding UTF8NoBOM +$softwareReport.ToJson() | Out-File -FilePath "${OutputDirectory}/systeminfo.json" -Encoding UTF8NoBOM +$softwareReport.ToMarkdown() | Out-File -FilePath "${OutputDirectory}/systeminfo.md" -Encoding UTF8NoBOM + diff --git a/images/macos/software-report/SoftwareReport.Toolcache.psm1 b/images/macos/software-report/SoftwareReport.Toolcache.psm1 index 3b39b6fc7..4ab0b6fc4 100644 --- a/images/macos/software-report/SoftwareReport.Toolcache.psm1 +++ b/images/macos/software-report/SoftwareReport.Toolcache.psm1 @@ -35,37 +35,34 @@ function Get-ToolcacheGoTable { $Instance."Environment Variable" = "GOROOT_$($Version.major)_$($Version.minor)_X64" } - $Content = $ToolInstances | New-MDTable -Columns ([ordered]@{ - Version = "left"; - Architecture = "left"; - "Environment Variable" = "left" - }) + $Content = $ToolInstances | ForEach-Object { + return [PSCustomObject]@{ + Version = $_.Version + Architecture = $_.Architecture + "Environment Variable" = $_."Environment Variable" + } + } return $Content } function Build-ToolcacheSection { - $output = "" - $output += New-MDHeader "Cached Tools" -Level 3 - $output += New-MDHeader "Ruby" -Level 4 - $output += New-MDList -Lines (Get-ToolcacheRubyVersions) -Style Unordered - $output += New-MDHeader "Python" -Level 4 - $output += New-MDList -Lines (Get-ToolcachePythonVersions) -Style Unordered - $output += New-MDHeader "PyPy" -Level 4 - $output += New-MDList -Lines (Get-ToolcachePyPyVersions) -Style Unordered - $output += New-MDHeader "Node.js" -Level 4 - $output += New-MDList -Lines (Get-ToolcacheNodeVersions) -Style Unordered - $output += New-MDHeader "Go" -Level 4 - $output += Get-ToolcacheGoTable - - return $output + $goToolNode = [HeaderNode]::new("Go") + $goToolNode.AddTableNode($(Get-ToolcacheGoTable)) + return @( + [ToolVersionsNode]::new("Ruby", $(Get-ToolcacheRubyVersions)) + [ToolVersionsNode]::new("Python", $(Get-ToolcachePythonVersions)) + [ToolVersionsNode]::new("PyPy", $(Get-ToolcachePyPyVersions)) + [ToolVersionsNode]::new("Node.js", $(Get-ToolcacheNodeVersions)) + $goToolNode + ) } function Get-PowerShellModules { $modules = (Get-ToolsetValue powershellModules).name $psModules = Get-Module -Name $modules -ListAvailable | Sort-Object Name | Group-Object Name - $psModules | ForEach-Object { + return $psModules | ForEach-Object { $moduleName = $_.Name $moduleVersions = ($_.group.Version | Sort-Object -Unique) -join '
' diff --git a/images/macos/software-report/SoftwareReport.WebServers.psm1 b/images/macos/software-report/SoftwareReport.WebServers.psm1 index 041325538..795882682 100644 --- a/images/macos/software-report/SoftwareReport.WebServers.psm1 +++ b/images/macos/software-report/SoftwareReport.WebServers.psm1 @@ -29,13 +29,8 @@ function Get-NginxVersion { } function Build-WebServersSection { - $output = "" - $output += New-MDHeader "Web Servers" -Level 3 - $output += @( + return @( (Get-ApacheVersion), (Get-NginxVersion) - ) | Sort-Object Name | New-MDTable - - $output += New-MDNewLine - return $output + ) } diff --git a/images/macos/software-report/SoftwareReport.Xamarin.psm1 b/images/macos/software-report/SoftwareReport.Xamarin.psm1 index 09cac3209..1937ae73a 100644 --- a/images/macos/software-report/SoftwareReport.Xamarin.psm1 +++ b/images/macos/software-report/SoftwareReport.Xamarin.psm1 @@ -4,7 +4,7 @@ function Build-VSMacTable { $vsMacVersions = Get-ToolsetValue "xamarin.vsmac.versions" $defaultVSMacVersion = Get-ToolsetValue "xamarin.vsmac.default" - $vsMacVersions | ForEach-Object { + return $vsMacVersions | ForEach-Object { $isDefault = $_ -eq $defaultVSMacVersion $vsPath = "/Applications/Visual Studio $_.app" if ($isDefault) { @@ -25,7 +25,7 @@ function Build-VSMacTable { function Get-NUnitVersion { $version = Run-Command "nunit3-console --version" | Select-Object -First 1 | Take-Part -Part 3 - return "NUnit ${version}" + return $version } function Build-XamarinTable { diff --git a/images/macos/software-report/SoftwareReport.Xcode.psm1 b/images/macos/software-report/SoftwareReport.Xcode.psm1 index e6038fbd2..eff515319 100644 --- a/images/macos/software-report/SoftwareReport.Xcode.psm1 +++ b/images/macos/software-report/SoftwareReport.Xcode.psm1 @@ -78,7 +78,7 @@ function Get-XcodePlatformOrder { function Get-XcodeCommandLineToolsVersion { $xcodeCommandLineToolsVersion = Run-Command "pkgutil --pkg-info com.apple.pkg.CLTools_Executables" | Select -Index 1 | Take-Part -Part 1 - return "Xcode Command Line Tools $xcodeCommandLineToolsVersion" + return $xcodeCommandLineToolsVersion } function Build-XcodeTable { @@ -226,13 +226,13 @@ function Build-XcodeSimulatorsTable { } function Build-XcodeSupportToolsSection { + $toolNodes = @() + $xcpretty = Run-Command "xcpretty --version" $xcversion = Run-Command "xcversion --version" | Select-String "^[0-9]" - $toolList = @( - "xcpretty $xcpretty", - "xcversion $xcversion" - ) + $toolNodes += [ToolNode]::new("xcpretty", $xcpretty) + $toolNodes += [ToolNode]::new("xcversion", $xcversion) $nomadOutput = Run-Command "gem list nomad-cli" $nomadCLI = [regex]::matches($nomadOutput, "(\d+.){2}\d+").Value @@ -240,14 +240,9 @@ function Build-XcodeSupportToolsSection { $nomadShenzhen = [regex]::matches($nomadShenzhenOutput, "(\d+.){2}\d+").Value if ($os.IsLessThanMonterey) { - $toolList += @( - "Nomad CLI $nomadCLI", - "Nomad shenzhen CLI $nomadShenzhen" - ) + $toolNodes += [ToolNode]::new("Nomad CLI", $nomadCLI) + $toolNodes += [ToolNode]::new("Nomad shenzhen CLI", $nomadShenzhen) } - $output = "" - $output += New-MDHeader "Xcode Support Tools" -Level 4 - $output += New-MDList -Style Unordered -Lines $toolList - return $output + return $toolNodes }