MetaNullTechnologyReport.psm1

if (-not ("System.Xml.Linq.XDocument" -as [Type])) {
    Add-Type -Assembly System.Xml.Linq
}

if (-not ("System.Web.HttpUtility" -as [Type])) {
    Add-Type -Assembly System.Web
}

if (-not ("System.Management.Automation.PSCredential" -as [Type])) {
    Add-Type -Assembly System.Management.Automation
}

if (-not ("System.ServiceProcess.ServiceController" -as [Type])) {
    Add-Type -Assembly System.ServiceProcess
}

#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 &ldquo;up to date&rdquo; and 1 is &ldquo;not supported&rdquo;, then the ranking of the container is set to &ldquo;not supported&rdquo;</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 &ldquo;up to date&rdquo; and 1 is &ldquo;not supported&rdquo;, then the ranking of the product is set to &ldquo;not supported&rdquo;</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 documentaiton 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

    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 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')) -Query $PortfolioQuery -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'))
    }
}
}
class MetaNullTechnologyReport {
    static [hashtable]$Commands = @{}

    static [System.Management.Automation.FunctionInfo] GetModuleCommand([string]$Module, [string]$CommandName) {
        if( -not ([MetaNullTechnologyReport]::Commands.Keys -contains $Module )) {
            [MetaNullTechnologyReport]::Commands += @{ $Module = @{}}
        }
        if( -not ([MetaNullTechnologyReport]::Commands.$Module.$CommandName )) {
            $Command = Get-Command -Module $Module ($CommandName -ireplace '^([a-z]+-)',"`$1$((Get-Module $Module).Prefix)")
            if($Command) {
                [MetaNullTechnologyReport]::Commands.$Module.$CommandName = $Command
            }
        }
        return [MetaNullTechnologyReport]::Commands.$Module.$CommandName
    }

    static [System.Management.Automation.FunctionInfo] Utils([string]$CommandName) {
        return [MetaNullTechnologyReport]::GetModuleCommand('MetaNullUtils',$CommandName)
    }
    static [System.Management.Automation.FunctionInfo] Wiki([string]$CommandName) {
        return [MetaNullTechnologyReport]::GetModuleCommand('MetaNullWiki',$CommandName)
    }
    static [System.Management.Automation.FunctionInfo] Portfolio([string]$CommandName) {
        return [MetaNullTechnologyReport]::GetModuleCommand('MetaNullTechnologyReport',$CommandName)
    }
    static [System.Management.Automation.FunctionInfo] ConfluencePS([string]$CommandName) {
        return [MetaNullTechnologyReport]::GetModuleCommand('ConfluencePS',$CommandName)
    }

    static [string] $RegistryKeyPath = 'HKCU:\Software\MetaNullTechnologyReport'
    static [string] $CredentialKeyName = 'ConfluenceApiUri'
    static [string] $ConfluenceApiUri = 'https://eesc-cor.atlassian.net/wiki/rest/api'
    static [System.Management.Automation.PSCredential] $ConfluenceCredential = ([System.Management.Automation.PSCredential]::Empty)
    static [string] $PortfolioPageId = 34636515
    static [string] $TechnologyReportPageId = 157057188
    static [string[]] $ArtifactSet = @('domain','product','container','technology','key-data-entity')
    static [string[]] $TechnologyReportArtifactSubset = @('container','technology')

    static [void] InitializeCredential() {
        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
        }
    }
}
class TechnologyReportItem {
    [string] $Product

    [WikiContainer] $Container
    [WikiTechnology] $Technology
    [WikiVersionLink] $Version

    [WikiVersionPriority] GetPriority() {
        return $this.Technology.AssessVersion($this.Version.Version)
    }
    [bool] GetHasVersion() {
        return ($this.Version.Version  -ne '0.0.0.0' `
            -or $this.Technology.xVersionRecommended -ne '0.0.0.0' `
            -or $this.Technology.xVersionAccepted -ne '0.0.0.0' `
            -or $this.Technology.xVersionOutdated -ne '0.0.0.0' `
            -or $this.Technology.xVersionVulnerable -ne '0.0.0.0')
    }

    static [hashtable[]] $StaticScriptPropertyDefinition = @(
        @{
            MemberName = 'Priority'
            MemberType = 'ScriptProperty'
            Value = {
                $this.GetPriority()
            }
        }
        @{
            MemberName = 'HasVersion'
            MemberType = 'ScriptProperty'
            Value = {
                $this.GetHasVersion()
            }
        }
        @{
            MemberName = 'ProductName'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Product
            }
        }
        @{
            MemberName = 'ContainerName'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Container.PageTitle
            }
        }
        @{
            MemberName = 'TechnologyName'
            MemberType = 'ScriptProperty'
            Value = {
                $this.Technology.PageTitle
            }
        }
    )
    static TechnologyReportItem() {
        $TypeName = [TechnologyReportItem].Name
        foreach ($Definition in [TechnologyReportItem]::StaticScriptPropertyDefinition) {
            Update-TypeData -TypeName $TypeName @Definition
        }
    }

    TechnologyReportItem([string]$Product, [WikiContainer]$Container, [WikiTechnology]$Technology, [WikiVersionLink]$Version) {
        $this.Product = $Product
        $this.Container = $Container.Clone()
        $this.Technology = $Technology.Clone()
        $this.Version = $Version.Clone()
    }

    [TechnologyReportItem] Clone() {
        return $this.PSObject.Copy()
    }

    #[string] ToString() {
    # return "[$($this.Priority)][$($this.ProductName), $($this.ContainerName)] $($this.TechnologyName), $($this.Version.Version)"
    #}
}