MetaNullTechnologyReport.psm1
#Requires -Module ConfluencePS #Requires -Module MetaNullUtils #Requires -Module MetaNullWiki # Module Constants # Registry Hive for the module Set-Variable CRegistryHive -option Constant -value ([string]"HKCU:\SOFTWARE\TechnologyReport") # Time, in minutes, for how long cached data is considered valid Set-Variable CCredentialCacheDuration -option Constant -value ([int]43200) Function Init-Statics { <# .SYNOPSIS Initializes Static Variables .EXAMPLE Init-Statics #> [CmdletBinding()] [OutputType([void])] param() Process { if(-not([MetaNullTechnologyReport]::ConfluenceCredential) -or ([MetaNullTechnologyReport]::ConfluenceCredential -eq [PSCredential]::empty)) { [MetaNullTechnologyReport]::ConfluenceCredential = &([MetaNullTechnologyReport]::Utils('Get-CachedCredential')) -Name [MetaNullTechnologyReport]::CredentialKeyName } if(-not([MetaNullTechnologyReport]::ConfluenceCredential) -or ([MetaNullTechnologyReport]::ConfluenceCredential -eq [PSCredential]::empty)) { [MetaNullTechnologyReport]::ConfluenceCredential = Get-Credential -Message 'Please provide your Atlassian Credentials' } if(-not([MetaNullTechnologyReport]::ConfluenceCredential) -or ([MetaNullTechnologyReport]::ConfluenceCredential -eq [PSCredential]::empty)) { throw 'Credentials required' } else { if( &([MetaNullTechnologyReport]::Utils('Get-CachedCredential')) -Name [MetaNullTechnologyReport]::CredentialKeyName ) { &([MetaNullTechnologyReport]::Utils('Remove-CachedCredential')) -Name [MetaNullTechnologyReport]::CredentialKeyName } &([MetaNullTechnologyReport]::Utils('Set-CachedCredential')) -Name [MetaNullTechnologyReport]::CredentialKeyName -InputCredential ([MetaNullTechnologyReport]::ConfluenceCredential) | Out-Null } } } Function ConvertFrom-Portfolio { <# .SYNOPSIS Convert a portfolio (an array of [WikiArtifact] objects) into a TechnologyReport .EXAMPLE Import-Portfolio | ConvertFrom-Portfolio | Format-Screen .PARAMETER Artifacts The portfolio of Artifacts #> [CmdletBinding()] [OutputType([TechnologyReportItem[]])] param( [Parameter(Mandatory,ValueFromPipeline)] [Alias('Portfolio')] [WikiArtifact[]] $Artifacts ) Begin { # Bufferize the input, we need all artifacts before starting to create the report [WikiContainer[]] $Containers = @() [WikiTechnology[]] $Technologies = @() } Process { # Bufferize the input, we need all artifacts before starting to create the report $Artifacts | Foreach-Object { $Artifact = $_ switch( $_.GetType().Name ) { ([WikiContainer].Name) { $Containers += $Artifact.Clone() } ([WikiTechnology].Name) { $Technologies += $Artifact.Clone() } } } } End { $Containers | Foreach-Object { $Container = $_ 'xVersionLinkFramework','xVersionLinkLibrary' | Foreach-Object { $xField = $_ $Container.$xField | Foreach-Object { $VersionLinkCard = $_ $Technologies | Where-Object { $_.PageTitle -eq $VersionLinkCard.Title } | Foreach-Object { $Technology = $_ $Container.xLinkProduct | Foreach-Object { $ProductName = $_ [TechnologyReportItem]::new($ProductName, $Container, $Technology, $VersionLinkCard) } } } } } } } Function Format-Html { <# .SYNOPSIS Format a TechnologyReport into HTML, in view of saving to Confluence .EXAMPLE $Body = (Import-Portfolio | ConvertFrom-Portfolio | Format-Html) -join '' Set-ConfluencePage -ID 123456 -Body $Body .PARAMETER TechnologyReport The TechnologyReport #> [CmdletBinding()] [OutputType([string[]])] param( [Parameter(Mandatory,ValueFromPipeline)] [TechnologyReportItem[]] $TechnologyReport, [switch] $FilterUnknown, [switch] $FilterAccepted ) Begin { # Bufferize the input, we need all artifacts before starting to create the report [TechnologyReportItem[]] $ReportBuffer = @() Function TdElement { param( [Parameter(ValueFromPipeline,Position=0,ParameterSetName='String')] [string] $Content, [Parameter(Mandatory,ValueFromPipeline,Position=0,ParameterSetName='Version')] [AllowNull()] [Version] $Version, [int] $Priority, [int] $RowSpan, [switch] $DontEscape, [switch] $Strong ) Process { switch([WikiVersionPriority]$Priority) { ([WikiVersionPriority]::UpToDate) { $Color = '#b3d4ff' } ([WikiVersionPriority]::Supported) { $Color = '#abf5d1' } ([WikiVersionPriority]::NotSupported) { $Color = '#ffbdad' } ([WikiVersionPriority]::Vulnerable) { $Color = '#ff8f73' } ([WikiVersionPriority]::Unknown) { $Color = '#c0b6f2' } default { $Color = $null } } $Html = '<td' if($PSBoundParameters.ContainsKey('Priority')) { $html += " data-highlight-colour=""$($Color)""" } if($RowSpan -gt 1) { $html += " rowspan=""$($RowSpan)""" } $Html += '>' if($Strong) { $html += '<strong>' } if($Version) { if($null -ne $Version -and $Version -ne '0.0.0.0') { $Content = $Version } } if($Content) { if($DontEscape) { $html += $Content } else { $html += $Content | &([MetaNullTechnologyReport]::Utils('ConvertTo-HtmlEncoded')) } } if($Strong) { $html += '</strong>' } $Html += '</td>' return $Html } } Function AcLinkElement { param( [Parameter(ValueFromPipeline,Position=0)] [string] $PageTitle ) Process { $Html = '<ac:link ac:card-appearance="inline">' $Html += "<ri:page ri:space-key=""ARC"" ri:content-title=""$($PageTitle | &([MetaNullTechnologyReport]::Utils('ConvertTo-HtmlEncoded')))"" />" $Html += "<ac:link-body>$($PageTitle | &([MetaNullTechnologyReport]::Utils('ConvertTo-HtmlEncoded')))</ac:link-body>" $Html += '</ac:link>' return $Html } } } Process { # Buffering, as we need the whole report before doing the grouping $TechnologyReport | Where-Object { (-not $FilterUnknown) -or ($_.Priority -notin (4)) } | Where-Object { (-not $FilterAccepted) -or ($_.Priority -notin (0,1)) } | Foreach-Object { $ReportBuffer += $_.Clone() } } End { # Organize the report data by Product and Priority $GrouppedReport = @{} $ReportBuffer | Sort-Object ProductName | Group-Object ProductName | Foreach-Object { $GrouppedReport.($_.Name) = $_.Group | Sort-Object Priority -Descending | Group-Object ContainerName } # Print the report to HTML @( '<ac:layout>' '<ac:layout-section ac:type="two_left_sidebar" ac:breakout-mode="default">' '<ac:layout-cell><h5>Presentation</h5><p>The report presents <em>Technology versions</em> grouped by <em>Product </em>and <em>Container</em>.<br /></p><p><strong>Technology</strong> is colored according to the version installed (see the legend here after).</p><p><strong>Container</strong> is colored according to the <em>worst</em> of its <strong>technologies</strong> (e.g. if the Container has 5 dependencies, out of which, 4 are “up to date” and 1 is “not supported”, then the ranking of the container is set to “not supported”</p><p><strong>Product</strong> is colored according to the <em>worst</em> of its containers (e.g. if the Product has 5 dependencies, out of which, 4 are “up to date” and 1 is “not supported”, then the ranking of the product is set to “not supported”</p><p><strong>Installed</strong> shows the version of the technology currently installed (check the container page for more details)</p><p><strong>Required</strong> shows the minimum required version of the technology (check the technology page for more details)</p><p><strong>Technology Status</strong> is an indicator showing the maturity of the technology (check the technology page for more details)</p></ac:layout-cell>' '<ac:layout-cell><h5>Legend</h5><table data-table-width="760" data-layout="default" ac:local-id="b8dbf444-c85b-48d5-88df-cdc7e5b0fdea"><colgroup><col style="width: 201.0px;" /><col style="width: 337.0px;" /><col style="width: 375.0px;" /></colgroup><tbody><tr><th><p><strong>Color/Priority</strong></p></th><th><p><strong>Meaning</strong></p></th><th><p><strong>Priority/Action</strong></p></th></tr><tr><td data-highlight-colour="#998dd9"><p>Unknown</p></td><td><p>The version number was not specified</p></td><td><p><strong>HIGH </strong>: please update the documentation of the container</p></td></tr><tr><td data-highlight-colour="#ff8f73"><p>Vulnerable</p></td><td><p>Version has known security issues</p></td><td><p><strong>HIGHEST</strong>: the technology must be upgraded as soon as possible (the only exception is if a rewrite/replacement of the container is foreseen in less than 6 month).</p></td></tr><tr><td data-highlight-colour="#ffbdad"><p>Not Supported</p></td><td><p>Version is not supported anymore</p></td><td><p><strong>HIGH</strong>: the technology must be upgraded at the earliest convenience of the product owner.</p></td></tr><tr><td data-highlight-colour="#abf5d1"><p>Supported</p></td><td><p>Version is supported</p></td><td><p><strong>No action</strong>: the technology is supported</p></td></tr><tr><td data-highlight-colour="#4c9aff"><p>Up to date</p></td><td><p>Version is up to the recommendations</p></td><td><p><strong>No action</strong>: the technology is supported</p></td></tr></tbody></table></ac:layout-cell>' '</ac:layout-section>' '<ac:layout-section ac:type="fixed-width" ac:breakout-mode="default"><ac:layout-cell><h1>Versions report</h1></ac:layout-cell></ac:layout-section>' '<ac:layout-section ac:type="fixed-width" ac:breakout-mode="default">' '<ac:layout-cell>' '<table data-table-width="1800" data-layout="default">' '<colgroup>' '<col style="width: 300.0px;" />' '<col style="width: 150.0px;" />' '<col style="width: 300.0px;" />' '<col style="width: 150.0px;" />' '<col style="width: 300.0px;" />' '<col style="width: 150.0px;" />' '<col style="width: 150.0px;" />' '<col style="width: 100.0px;" />' '<col style="width: 100.0px;" />' '<col style="width: 100.0px;" />' '</colgroup>' '<tbody>' '<tr>' '<th>Product</th>' '<th>Priority</th>' '<th>Container</th>' '<th>Priority</th>' '<th>Technology</th>' '<th>Priority</th>' '<th>Installed</th>' '<th>Required</th>' '<th>Recommended</th>' '<th>Technology Status</th>' '</tr>' ) | Write-Output foreach( $ProductGroupEnumerator in $GrouppedReport.GetEnumerator() ) { $ContainerK = 0 $ProductName = ($ProductGroupEnumerator.Name | Select-Object -First 1) $ProductTechnologyCount = $ProductGroupEnumerator.Value.Group | Measure-Object | Select-Object -ExpandProperty Count $ProductTechnologyPriority = $ProductGroupEnumerator.Value.Group | Foreach-Object { $_.Priority } | Measure-Object -Maximum | Select-Object -ExpandProperty Maximum $ProductGroupEnumerator.Value | Foreach-Object { $TechnologyK = 0 $ContainerName = $_.Name $ContainerTechnologyCount = $_.Group | Measure-Object | Select-Object -ExpandProperty Count $ContainerTechnologyPriority = $_.Group | Foreach-Object { $_.Priority } | Measure-Object -Maximum | Select-Object -ExpandProperty Maximum $_.Group | ForEach-Object { $TechnologyName = $_.TechnologyName $TrElement = '<tr>' if(($ContainerK++) -eq 0) { $TrElement += AcLinkElement -PageTitle $ProductName | TdElement -DontEscape -RowSpan $ProductTechnologyCount $TrElement += [WikiVersionPriority].GetEnumName(([int]$ProductTechnologyPriority)) | TdElement -DontEscape -RowSpan $ProductTechnologyCount -Priority ([WikiVersionPriority]$ProductTechnologyPriority) } if(($TechnologyK++) -eq 0) { $TrElement += AcLinkElement -PageTitle $ContainerName | TdElement -DontEscape -RowSpan $ContainerTechnologyCount $TrElement += [WikiVersionPriority].GetEnumName(([int]$ContainerTechnologyPriority)) | TdElement -DontEscape -RowSpan $ContainerTechnologyCount -Priority ([WikiVersionPriority]$ContainerTechnologyPriority) } $TrElement += AcLinkElement -PageTitle $TechnologyName | TdElement -DontEscape $TrElement += $_.Priority | TdElement -Priority ([WikiVersionPriority]$_.Priority) $TrElement += TdElement -Version ($_.Version.Version) -Strong -Priority ($_.Priority) $TrElement += TdElement -Version ($_.Technology.xVersionAccepted) $TrElement += TdElement -Version ($_.Technology.xVersionRecommended) $TrElement += $_.Technology.xTechnologyStatus | TdElement $TrElement += '</tr>' $TrElement | Write-Output } Write-Output '' } Write-Output '' } @( '</tbody>' '</table>' '</ac:layout-cell>' '</ac:layout-section>' '</ac:layout>' ) | Write-Output } } Function Format-Screen { <# .SYNOPSIS Format a TechnologyReport for screen display .EXAMPLE $Body = Import-Portfolio | ConvertFrom-Portfolio | Format-Screen .PARAMETER TechnologyReport The TechnologyReport #> [CmdletBinding()] [OutputType([string[]])] param( [Parameter(Mandatory,ValueFromPipeline)] [TechnologyReportItem[]] $TechnologyReport, [switch] $FilterUnknown, [switch] $FilterAccepted ) Begin { # Bufferize the input, we need all artifacts before starting to create the report [TechnologyReportItem[]] $ReportBuffer = @() $Columns = @{ Product = 40 Container = 40 Technology = 40 Priority = 16 Version = 16 Accepted = 16 Recommended = 16 Status = 16 } Function Out-Column { param( [Parameter(ValueFromPipeline,Position=0,ParameterSetName='String')] [string] $Content, [Parameter(Mandatory,ValueFromPipeline,Position=0,ParameterSetName='Version')] [AllowNull()] [Version] $Version, [Parameter(Mandatory,Position=1)] [ValidateRange(1,128)] [int] $Width, [Parameter(Position=2)] [int] $Priority, [switch] $Strong ) Process { if($PSBoundParameters.ContainsKey('Priority')) { switch([WikiVersionPriority]$Priority) { ([WikiVersionPriority]::UpToDate) { $BackgroundColor = 'Green'; $ForegroundColor = 'White' } ([WikiVersionPriority]::Supported) { $BackgroundColor = 'Blue'; $ForegroundColor = 'White' } ([WikiVersionPriority]::NotSupported) { $BackgroundColor = 'Yellow'; $ForegroundColor = 'Black' } ([WikiVersionPriority]::Vulnerable) { $BackgroundColor = 'Red'; $ForegroundColor = 'White' } ([WikiVersionPriority]::Unknown) { $BackgroundColor = 'White'; $ForegroundColor = 'Magenta' } default { $ForegroundColor = ((get-host).UI.RawUI.ForegroundColor) $BackgroundColor = ((get-host).UI.RawUI.BackgroundColor) } } } else { $ForegroundColor = ((get-host).UI.RawUI.ForegroundColor) $BackgroundColor = ((get-host).UI.RawUI.BackgroundColor) } if($Strong) { $ForegroundColor = ((get-host).UI.RawUI.BackgroundColor) $BackgroundColor = ((get-host).UI.RawUI.ForegroundColor) } if($Version) { if($null -ne $Version -and $Version -ne '0.0.0.0') { $Content = $Version } } $Content | &([MetaNullTechnologyReport]::Utils('Format-FixedWidthString')) -Length $Width | Write-Host -ForegroundColor $ForegroundColor -BackgroundColor $BackgroundColor -NoNewline } } } Process { # Buffering, as we need the whole report before doing the grouping $TechnologyReport | Where-Object { (-not $FilterUnknown) -or ($_.Priority -notin (4)) } | Where-Object { (-not $FilterAccepted) -or ($_.Priority -notin (0,1)) } | Foreach-Object { $ReportBuffer += $_.Clone() } } End { # Organize the report data by Product and Priority $GrouppedReport = @{} $ReportBuffer | Sort-Object ProductName | Group-Object ProductName | Foreach-Object { $GrouppedReport.($_.Name) = $_.Group | Sort-Object Priority -Descending | Group-Object ContainerName } # Print the report to screen 'Product' | Out-Column -Strong -Width ($Columns.Product + $Columns.Priority) 'Container' | Out-Column -Strong -Width ($Columns.Container + $Columns.Priority) 'Technology' | Out-Column -Strong -Width ($Columns.Technology + $Columns.Priority) 'Installed' | Out-Column -Strong -Width $Columns.Version 'Accepted' | Out-Column -Strong -Width $Columns.Accepted 'Recommended' | Out-Column -Strong -Width $Columns.Recommended 'Status' | Out-Column -Strong -Width $Columns.Status '' | Write-Host 'Name' | Out-Column -Strong -Width ($Columns.Product) 'Priority' | Out-Column -Strong -Width ($Columns.Priority) 'Name' | Out-Column -Strong -Width ($Columns.Container) 'Priority' | Out-Column -Strong -Width ($Columns.Priority) 'Name' | Out-Column -Strong -Width ($Columns.Technology) 'Priority' | Out-Column -Strong -Width ($Columns.Priority) '' | Out-Column -Strong -Width $Columns.Version '' | Out-Column -Strong -Width $Columns.Accepted '' | Out-Column -Strong -Width $Columns.Recommended '' | Out-Column -Strong -Width $Columns.Status '' | Write-Host foreach( $ProductGroupEnumerator in $GrouppedReport.GetEnumerator() ) { $ContainerK = 0 $ProductName = ($ProductGroupEnumerator.Name | Select-Object -First 1) #$ProductTechnologyCount = $ProductGroupEnumerator.Value.Group | Measure-Object | Select-Object -ExpandProperty Count $ProductTechnologyPriority = $ProductGroupEnumerator.Value.Group | Foreach-Object { $_.Priority } | Measure-Object -Maximum | Select-Object -ExpandProperty Maximum $ProductGroupEnumerator.Value | Foreach-Object { $TechnologyK = 0 $ContainerName = $_.Name #$ContainerTechnologyCount = $_.Group | Measure-Object | Select-Object -ExpandProperty Count $ContainerTechnologyPriority = $_.Group | Foreach-Object { $_.Priority } | Measure-Object -Maximum | Select-Object -ExpandProperty Maximum $_.Group | ForEach-Object { $TechnologyName = $_.TechnologyName if(($ContainerK++) -eq 0) { $ProductName -replace '\s+\(product\)$' | Out-Column -Width $Columns.Product [WikiVersionPriority].GetEnumName(([int]$ProductTechnologyPriority)) | Out-Column -Width $Columns.Priority } else { '' | Out-Column -Width ($Columns.Product + $Columns.Priority) } if(($TechnologyK++) -eq 0) { $ContainerName -replace '\s+\(container\)$' | Out-Column -Width $Columns.Container [WikiVersionPriority].GetEnumName(([int]$ContainerTechnologyPriority)) | Out-Column -Width $Columns.Priority } else { '' | Out-Column -Width ($Columns.Container + $Columns.Priority) } $TechnologyName -replace '\s+\(technology\)$' | Out-Column -Width $Columns.Technology $_.Priority | Out-Column -Priority ([WikiVersionPriority]$_.Priority) -Width $Columns.Priority Out-Column -Version ($_.Version.Version) -Strong -Priority ($_.Priority) -Width $Columns.Version Out-Column -Version ($_.Technology.xVersionAccepted) -Width $Columns.Accepted Out-Column -Version ($_.Technology.xVersionRecommended) -Width $Columns.Recommended $_.Technology.xTechnologyStatus | Out-Column -Width $Columns.Status '' | Write-Host } #'' | Write-Host } #'' | Write-Host } } } Function Get-WikiPage { <# .SYNOPSIS Get confluence page(s) by CQL query or by ID .EXAMPLE $Page = Get-WikiPage -query 'parent = 123456' .PARAMETER ID Confluence Page ID .PARAMETER Query Confluence CQL query .PARAMETER WithLabels If set, also retrieve the Page Labels #> [CmdletBinding()] [OutputType([WikiArtifact[]])] param( [Parameter(ParameterSetName='Id')] [Alias('PageID')] [string]$Id, [Parameter(ParameterSetName='Query')] [string]$Query, [switch] $WithLabels ) Begin { [MetaNullTechnologyReport]::InitializeCredential() } Process { $P = $PSBoundParameters.PSObject.Copy() $P.Remove('WithLabels') &([MetaNullTechnologyReport]::ConfluencePS('Get-Page')) -ApiUri ([MetaNullTechnologyReport]::ConfluenceApiUri) -Credential ([MetaNullTechnologyReport]::ConfluenceCredential) @P | Foreach-Object { if($WithLabels) { [WikiArtifact]::new($_,(&([MetaNullTechnologyReport]::ConfluencePS('Get-Label')) -ApiUri ([MetaNullTechnologyReport]::ConfluenceApiUri) -Credential ([MetaNullTechnologyReport]::ConfluenceCredential) -Id ($_.ID) | Select-Object -ExpandProperty Labels | Select-Object -ExpandProperty Name )) } else { [WikiArtifact]::new($_) } } } } Function Import-Portfolio { <# .SYNOPSIS Import the Portfolio of Artifacts from Confluence .EXAMPLE $Portfolio = Import-Portfolio -ArtifactTypes 'domain','products' .PARAMETER ArtifactTypes A list of artifact types to retreive (from: domain,product,container,technology,key-data-entity) .PARAMETER WithLabels If set, also retrieve the Page Labels #> [CmdletBinding()] [OutputType([WikiArtifact[]])] param( [Parameter(Mandatory=$false,Position=0)] [string[]] $ArtifactTypes = ([MetaNullTechnologyReport]::TechnologyReportArtifactSubset), [switch] $WithLabels ) Begin { Init-Statics } Process { $PortfolioQuery = "ancestor = $([MetaNullTechnologyReport]::PortfolioPageId)" if($ArtifactTypes) { $PortfolioQuery = "$($PortfolioQuery) and label in ('$(($ArtifactTypes) -join "', '")')" } "CQL query: $($PortfolioQuery)" | Write-Verbose # Load the Portfolio &([MetaNullTechnologyReport]::ConfluencePS('Get-Page')) -Query $PortfolioQuery -ApiUri ([MetaNullTechnologyReport]::ConfluenceApiUri) -Credential ([MetaNullTechnologyReport]::ConfluenceCredential) | Foreach-Object { if( $WithLabels ) { $Labels = &([MetaNullTechnologyReport]::ConfluencePS('Get-Label')) -Id $_.ID -ApiUri ([MetaNullTechnologyReport]::ConfluenceApiUri) -Credential ([MetaNullTechnologyReport]::ConfluenceCredential) | Select-Object -ExpandProperty Labels | Select-Object -ExpandProperty Name [WikiArtifact]::new($_, $Labels) | Write-Output } else { [WikiArtifact]::new($_) | Write-Output } } | Foreach-Object { $_ | &([MetaNullTechnologyReport]::Wiki('ConvertFrom-Artifact')) } } } |