MetaNullWiki.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 } #Requires -Module ConfluencePS #Requires -Module MetaNullUtils # Module Constants Set-Variable CWikiNamespaces -option Constant -value @('ac','ri') Function Clear-Placeholder { <# .Synopsis Remove any Wiki placeholders (template values, that are not displayed in view mode) from a piece of html .PARAMETER InputHtml The HTML input .EXAMPLE $Body | Select-Placeholder #> [CmdletBinding(SupportsShouldProcess=$false)] [OutputType([string])] param ( [Parameter(Mandatory, Position=0, ValueFromPipeline)] [Alias('Html','String')] [string]$InputHtml ) Process { $InputHtml -replace '<(ac:placeholder\b).*?>.*?</\1>' | Write-Output } } Function ConvertFrom-WikiArtifact { <# .SYNOPSIS Convert a WikiArtifact object into the best matching type of artifact .Description Convert a WikiArtifact object into the best matching type of artifact. The function checks the presence of specific Page Properties OR specific Labels .PARAMETER InputArtifact The input object. #> [CmdletBinding()] [OutputType( [WikiArtifact], [WikiDomain], [WikiProduct], [WikiContainer], [WikiTechnology], [WikiKeyDataEntity] ) ] param ( [Parameter(ValueFromPipeline, Position=0)] [AllowEmptyString()] [Alias('Artifact','Product','Container','Technology','KeyDataEntity','Object')] [WikiArtifact]$InputArtifact ) Process { if($InputArtifact.Properties.Id -contains 'info' -or $InputArtifact.Labels -contains 'product') { [WikiProduct]::new($InputArtifact) | Write-Output } elseif($InputArtifact.Properties.Id -contains 'container' -or $InputArtifact.Labels -contains 'container') { [WikiContainer]::new($InputArtifact) | Write-Output } elseif($InputArtifact.Properties.Id -contains 'technology' -or $InputArtifact.Labels -contains 'technology') { [WikiTechnology]::new($InputArtifact) | Write-Output } elseif($InputArtifact.Properties.Id -contains 'domain' -or $InputArtifact.Labels -contains 'domain') { [WikiDomain]::new($InputArtifact) | Write-Output } elseif($InputArtifact.Properties.Id -contains [string]::Empty -or $InputArtifact.Labels -contains 'key-data-entity') { [WikiKeyDataEntity]::new($InputArtifact) | Write-Output } elseif($InputArtifact.Properties.Length -gt 0) { $InputArtifact | Write-Output } else { # Nothing to return, Input Object was probably not a valid artifact } } } Function ConvertTo-XDocument { <# .SYNOPSIS Convert a piece of html into a XDocument .Description Convert a piece of (well formed) html into a XDocument .PARAMETER InputString The input string. It must contain a valid piece of XHTML #> [CmdletBinding()] [OutputType([System.Xml.Linq.XDocument])] param ( [Parameter(ValueFromPipeline, Position=0)] [AllowEmptyString()] [Alias('String','Html')] [String]$InputString ) Process { ConvertTo-UtilsXDocument -InputString $InputString -Namespaces $CWikiNamespaces | Write-Output } } Function Select-HyperLink { <# .Synopsis Select the title and uri of html hyperlinks .PARAMETER InputHtml The HTML input .EXAMPLE # Get all hyperlinks in a html page $Body | Select-Hyperlink #> [CmdletBinding()] [OutputType([string[]])] param ( [Parameter(Mandatory, Position=0, ValueFromPipeline)] [Alias('Html','String')] [string]$InputHtml ) Process { $InputHtml | ConvertTo-XDocument | Select-Xpath -XPath '//a[@href]' | Foreach-Object { $Href = $_ | Select-XPath -XPath './@href' | Select-Object -ExpandProperty Node | Select-Object -ExpandProperty Value $Node = $_ | Select-XPath -XPath '.' | Select-Object -ExpandProperty Node $Body = $Node | Select-Object -ExpandProperty InnerXml $Text = $Node | Select-Object -ExpandProperty InnerText [PSCustomObject]@{ Href = $Href Text = $Text Body = $Body } | Write-Output } } } Function Select-LinkCard { <# .Synopsis Select the "titles" of all wiki link cards in a blob of (html) text .PARAMETER InputHtml The HTML input .EXAMPLE # Get the titles from all link cards $Body | Select-LinkCard #> [CmdletBinding()] [OutputType([string[]])] param ( [Parameter(Mandatory, Position=0, ValueFromPipeline)] [Alias('Html','String')] [string]$InputHtml ) Begin { $xpathSelectors = @( "//ac:link/ri:page/@ri:content-title" ) $xpathExpression = "($($xpathSelectors -join ' | '))" } Process { $InputHtml | ConvertTo-XDocument | Select-Xpath -XPath $xpathExpression | Select-Object -ExpandProperty Node | Select-Object -ExpandProperty Value | Write-Output } } Function Select-Macro { <# .Synopsis Select the content of all excerpt macros in a wiki page (html) .EXAMPLE # Get the body from all exerpt macros $Body | Select-Excerpt #> [CmdletBinding()] [OutputType([MetaNullWiki.Macro[]])] param ( # Hashtable to represent [Parameter(Mandatory, Position=0, ValueFromPipeline)] [Alias('Html','String')] [string]$InputHtml ) Process { $Macros = $InputHtml | ConvertTo-XDocument | Select-Xpath -XPath '//ac:structured-macro' $Macros | ForEach-Object { $Object = [MetaNullWiki.Macro]::new() $Macro = $_ $Object.Name = $Macro | Select-Xpath -XPath '@ac:name[position()=1]' | Select-Object -ExpandProperty Node | Select-Object -ExpandProperty Value $Object.Parameters = @{} $Macro | Select-Xpath -XPath 'ac:parameter' | Select-Object -ExpandProperty Node | Select-Object name,InnerText | Foreach-Object { $Object.Parameters.($_.name) = $_.InnerText } $Object.Body = $Macro | Select-Xpath -XPath 'ac:rich-text-body' | Select-Object -ExpandProperty Node | Select-Object -ExpandProperty InnerXml $Object.Attributes = @{} $Macro | Select-Xpath -XPath '@*' | Select-Object -ExpandProperty Node | Select-Object Name,Value | Foreach-Object { $Object.Attributes.($_.Name) = $_.Value } $Object | Write-Output } } } Function Select-Placeholder { <# .Synopsis Select the content of all Wiki placeholders (template values, that are not displayed in view mode) .PARAMETER InputHtml The HTML input .EXAMPLE $Body | Select-Placeholder #> [CmdletBinding()] [OutputType([string[]])] param ( [Parameter(Mandatory, Position=0, ValueFromPipeline)] [Alias('Html','String')] [string]$InputHtml ) Begin { $xpathExpression = "//ac:placeholder" } Process { $InputHtml | ConvertTo-XDocument | Select-Xpath -XPath $xpathExpression | Select-Object -ExpandProperty Node | Select-Object -ExpandProperty InnerXml | Write-Output } } Function Select-Text { <# .Synopsis Select the content of all text nodes in a blob of (html) text .PARAMETER InputHtml The HTML input .EXAMPLE # Extract the piexes of text from (x|ht)ml '<strong>Meta<u>Null</u></strong><p>was here</p>' | Select-Text #> [CmdletBinding()] [OutputType([string[]])] param ( [Parameter(Mandatory, Position=0, ValueFromPipeline)] [Alias('Html','String')] [string]$InputHtml ) Begin { $xpathSelectors = @( "/xml//*[local-name()=name()]" "//ac:link/ac:link-body" "//ac:structured-macro[@ac:name='status']/ac:parameter[@ac:name='title']" ) $xpathExpression = "($($xpathSelectors -join ' | '))/text()[normalize-space()]" } Process { $InputHtml | ConvertTo-XDocument | Select-Xpath -XPath $xpathExpression | Select-Object -ExpandProperty Node | Select-Object -ExpandProperty InnerText | Write-Output } } Function Select-VersionLinkCard { <# .Synopsis Select "Versions", representred by a wiki link card and a free text version number in (html) text .PARAMETER InputHtml The HTML input .EXAMPLE # Get the titles from all link cards $Body | Select-VersionLinkCard #> [CmdletBinding()] [OutputType([WikiVersionLink[]])] param ( [Parameter(Mandatory, Position=0, ValueFromPipeline)] [Alias('Html','String')] [string]$InputHtml ) Process { [WikiVersionLink]::CreateFromHtml($InputHtml) } } Function Select-Xpath { <# .SYNOPSIS Use XPath on a blob of (xhtml) text .Description Use XPath on a blob of (xhtml) text. .PARAMETER InputDocument The input XML onwhich to run xpath .PARAMETER Xpath The xpath query string .PARAMETER Namespaces A list of additional NS to declare .EXAMPLE # Get URL from all hyperlinks in a page $Html | ConvertTo-XDocument | Select-Xpath -XPath '//a/@href/text()' #> [CmdletBinding(DefaultParameterSetName)] [OutputType([object[]])] param ( [Parameter(Mandatory, Position=0)] [string]$XPath, [Parameter(ValueFromPipeline,Mandatory,Position=1)] [Alias('Document','Xml','Node')] [object]$InputDocument ) Process { $InputDocument | Select-UtilsXpath -Namespaces $CWikiNamespaces -XPath $Xpath | Write-Output } } class WikiArtifact { [object]$Page [WikiPageProperties[]]$Properties [string[]]$Labels [string[]]$LinkCards WikiArtifact() { $this.InitFromHashtable(@{}) } WikiArtifact([object]$Page) { $this.InitFromHashtable(@{Page = $Page}) } WikiArtifact([object]$Page,[string[]]$Labels) { $this.InitFromHashtable(@{Page = $Page; Labels = $Labels}) } WikiArtifact([object]$Page,[string[]]$Labels,[WikiPageProperties[]]$Properties) { $this.InitFromHashtable(@{Page = $Page; Labels = $Labels; Properties = $Properties}) } WikiArtifact([WikiArtifact]$Artifact) { $this.InitFromHashtable(@{Page = $Artifact.Page; Labels = $Artifact.Labels; Properties = $Artifact.Properties}) } [void] SetPage([object]$Page) { if($null -ne $Page) { $this.Page = $Page.PSObject.Copy() } } [void] SetProperties([WikiPageProperties[]]$Properties) { if($null -ne $Properties) { $this.Properties = $Properties.Clone() } else { $this.Properties = @() } } [void] SetLabels([string[]]$Labels) { if($null -ne $Labels) { $this.Labels = $Labels.Clone() } else { $this.Labels = @() } } [void] SetLinkCards([string[]]$LinkCards) { if($null -ne $LinkCards) { $this.LinkCards = $LinkCards.Clone() } else { $this.LinkCards = @() } } [void] InitFromHashtable([hashtable]$properties) { foreach ($Property in $Properties.Keys) { switch($Property) { 'Page' { $this.SetPage($Properties.Page) if($properties.Keys -notcontains 'Properties') { # Load 'properties' from the page, unless if they are explicitly provided $this.LoadPageProperties() $this.LoadLinkCards() } } 'Properties' { $this.SetProperties($Properties.Properties) } 'Labels' { $this.SetLabels($Properties.Labels) } 'LinkCards' { $this.SetLinkCards($Properties.LinkCards) } default { [System.ArgumentException]::New("No such property: $($Property).") } } } } [WikiArtifact] Clone() { return $this.PSObject.Copy() } [void] LoadPageProperties() { if($this.Page) { $this.Properties = [WikiPageProperties]::CreateFromMacros([WikiMacro]::CreateFromHtml($this.Page.Body)) } else { $this.Properties = [WikiPageProperties[]]@() } } [void] LoadLinkCards() { if($this.Page) { $this.LinkCards = $this.Page.Body | Select-LinkCard | Select-Object -Unique | Sort-Object } else { $this.LinkCards = [string[]]@() } } [WikiPageProperties[]] GetPageProperties([string]$Id) { return $this.Properties | Where-Object { $_.Id -eq $Id } } [string] ToString() { return "[$($this.Page.ID)] [$($this.GetType().Name -replace '^Wiki')] $($this.Page.Title)" } } class WikiContainer : WikiArtifact { WikiContainer() : base() {} WikiContainer([object]$Page) : base($Page) {} WikiContainer([object]$Page,[string[]]$Labels) : base($Page,$Labels) {} WikiContainer([object]$Page,[string[]]$Labels,[WikiPageProperties[]]$Properties) : base($Page,$Labels,$Properties) {} WikiContainer([WikiArtifact]$Artifact) : base($Artifact) {} WikiContainer([WikiContainer]$Container) : base($Container.Page,$Container.Labels,$Container.Properties) {} } class WikiDomain : WikiArtifact { WikiDomain() : base() {} WikiDomain([object]$Page) : base($Page) {} WikiDomain([object]$Page,[string[]]$Labels) : base($Page,$Labels) {} WikiDomain([object]$Page,[string[]]$Labels,[WikiPageProperties[]]$Properties) : base($Page,$Labels,$Properties) {} WikiDomain([WikiArtifact]$Artifact) : base($Artifact) {} WikiDomain([WikiDomain]$Container) : base($Container.Page,$Container.Labels,$Container.Properties) {} } class WikiKeyDataEntity : WikiArtifact { WikiKeyDataEntity() : base() {} WikiKeyDataEntity([object]$Page) : base($Page) {} WikiKeyDataEntity([object]$Page,[string[]]$Labels) : base($Page,$Labels) {} WikiKeyDataEntity([object]$Page,[string[]]$Labels,[WikiPageProperties[]]$Properties) : base($Page,$Labels,$Properties) {} WikiKeyDataEntity([WikiArtifact]$Artifact) : base($Artifact) {} WikiKeyDataEntity([WikiKeyDataEntity]$Container) : base($Container.Page,$Container.Labels,$Container.Properties) {} } class WikiMacro { [string]$Name [hashtable]$Parameters [hashtable]$Attributes [string]$Body WikiMacro() { $this.InitFromHashtable(@{}) } WikiMacro([string]$Name,[hashtable]$Parameters,[hashtable]$Attributes,[string]$Body) { $this.InitFromHashtable(@{Name = $Name; Parameters = $Parameters; Attributes = $Attributes; Body = $Body }) } WikiMacro([string]$Name,[hashtable]$Parameters,[hashtable]$Attributes) { $this.InitFromHashtable(@{Name = $Name; Parameters = $Parameters; Attributes = $Attributes }) } WikiMacro([string]$Name,[hashtable]$Parameters,[string]$Body) { $this.InitFromHashtable(@{Name = $Name; Parameters = $Parameters; Body = $Body }) } WikiMacro([string]$Name, [string]$Body) { $this.InitFromHashtable(@{Name = $Name; Body = $Body }) } WikiMacro([string]$Name,[hashtable]$Parameters) { $this.InitFromHashtable(@{Name = $Name; Parameters = $Parameters}) } [void] SetName([string]$Name) { $this.Name = $Name } [void] SetParameters([hashtable]$Parameters) { $this.Parameters = $Parameters.Clone() } [void] SetAttributes([hashtable]$Attributes) { $this.Attributes = $Attributes.Clone() } [void] SetBody([string]$Body) { $this.Body = $Body } [void] InitFromHashtable([hashtable]$properties) { foreach ($Property in $Properties.Keys) { if($Properties.$Property -is [hashtable]) { $this.$Property = $Properties.$Property.Clone() } else { $this.$Property = $Properties.$Property } } } [WikiMacro] Clone() { return $this.PSObject.Copy() } static [WikiMacro[]] CreateFromHtml([string]$Html) { return [WikiMacro]::CreateFromXDocument(($Html | ConvertTo-XDocument)) } static [WikiMacro[]] CreateFromXDocument([object]$Document) { $Output = @() $Document | Select-Xpath -XPath '//ac:structured-macro' | Foreach-Object { $Name = $_ | Select-Xpath -XPath './@ac:name' | Select-Object -ExpandProperty Node -First 1 | Select-Object -ExpandProperty '#text' $Attributes = @{} $_ | Select-Xpath -XPath './@*' | Foreach-Object { $Attributes += @{ $_.Node.Name = $_.Node.Value } } $Parameters = @{} $_ | Select-Xpath -XPath './ac:parameter' | Foreach-Object { $Parameters += @{ $_.Node.Name = $_.Node.InnerXml } } $Body = $_ | Select-Xpath -XPath './ac:rich-text-body' | Select-Object -ExpandProperty Node -First 1 | Select-Object -ExpandProperty InnerXml $Output += ([WikiMacro]::new($Name,$Parameters,$Attributes,$Body)) } return [WikiMacro[]]($Output) } [string] ToString() { return "[Macro] Name:$($this.Name), Parameters:{$(($this.Parameters.Keys -join ', '))}, Body:$($this.Body)" } } class WikiPageProperties { [string]$Id [hashtable]$Properties [WikiMacro]$Macro WikiPageProperties() { $this.InitFromHashtable(@{}) } WikiPageProperties([hashtable]$Properties) { $this.InitFromHashtable(@{Properties = $Properties }) } WikiPageProperties([hashtable]$Properties, [string] $Id) { $this.InitFromHashtable(@{Properties = $Properties; Id = $Id }) } WikiPageProperties([string]$Id, [hashtable]$Properties, [WikiMacro]$Macro) { $this.InitFromHashtable(@{Properties = $Properties; Id = $Id; Macro = $Macro }) } [void] SetId([string]$Id) { $this.Id = $Id } [void] SetProperties([hashtable]$Properties) { $this.Properties = $Properties.Clone() } [void] SetMacro([string]$Macro) { $this.Macro = $Macro.Clone() } [void] InitFromHashtable([hashtable]$properties) { foreach ($Property in $Properties.Keys) { switch($Property) { 'Macro' { $this.$Property = $Properties.$Property.Clone() } 'Properties' { $this.$Property = $Properties.$Property.Clone() } 'Id' { $this.$Property = $Properties.$Property } default { [System.ArgumentException]::New("No such property: $($Property).") } } } } [WikiPageProperties] Clone() { return $this.PSObject.Copy() } static [WikiPageProperties[]] CreateFromHtml([string]$Html) { return [WikiPageProperties]::CreateFromXDocument(($Html | ConvertTo-XDocument)) } static [WikiPageProperties[]] CreateFromXDocument([object]$Document) { return [WikiPageProperties]::CreateFromMacros([WikiMacro]::CreateFromXDocument($Document)) } static [WikiPageProperties[]] CreateFromMacros([WikiMacro[]]$Macros) { $Output = @() $Macros | Where-Object { $_.Name -eq 'details' } | Foreach-Object { $Macro = $_.Clone() $Id = $_.Parameters.id $Properties = @{} $_.Body | ConvertTo-XDocument | Select-Xpath '//tr' | Foreach-Object { $Key = ($_ | Select-Xpath '(./td|./th)[position()=1]').Node.InnerText # $Value = ($_ | Select-Xpath '(./td|./th)[position()=2]/p').Node.InnerXml $Value = ($_ | Select-Xpath '(./td|./th)[position()=2]/p' | Remove-Placeholder | Where-Object {$_}) $Properties += @{ $Key = $Value } } $Output += [WikiPageProperties]::new($Id,$Properties,$Macro) } return [WikiPageProperties[]]($Output) } [string] ToString() { return "[PageProperties] Id:$($this.Id), Properties:{$(($this.Properties.Keys -join ', '))}" } } class WikiProduct : WikiArtifact { WikiProduct() : base() {} WikiProduct([object]$Page) : base($Page) {} WikiProduct([object]$Page,[string[]]$Labels) : base($Page,$Labels) {} WikiProduct([object]$Page,[string[]]$Labels,[WikiPageProperties[]]$Properties) : base($Page,$Labels,$Properties) {} WikiProduct([WikiArtifact]$Artifact) : base($Artifact) {} WikiProduct([WikiProduct]$Container) : base($Container.Page,$Container.Labels,$Container.Properties) {} } class WikiTechnology : WikiArtifact { WikiTechnology() : base() {} WikiTechnology([object]$Page) : base($Page) {} WikiTechnology([object]$Page,[string[]]$Labels) : base($Page,$Labels) {} WikiTechnology([object]$Page,[string[]]$Labels,[WikiPageProperties[]]$Properties) : base($Page,$Labels,$Properties) {} WikiTechnology([WikiArtifact]$Artifact) : base($Artifact) {} WikiTechnology([WikiTechnology]$Technology) : base($Technology.Page,$Technology.Labels,$Technology.Properties) {} [WikiVersionPriority] AssessVersion([version]$Version) { if($null -eq $version -or $version -eq '0.0.0.0') { return [WikiVersionPriority]::Unknown } if($null -ne $_.VulnerableVersion -and $_.VulnerableVersion -ne '0.0.0.0' -and $Version -le $_.VulnerableVersion) { # Smaller than Vulnerable = Vulnerable return [WikiVersionPriority]::Vulnerable } elseif($null -ne $_.OutdatedVersion -and $_.OutdatedVersion -ne '0.0.0.0' -and $Version -le $_.OutdatedVersion) { # Smaller than Outdated = Outdated return [WikiVersionPriority]::NotSupported } if($null -ne $_.RecommendedVersion -and $_.RecommendedVersion -ne '0.0.0.0' -and $Version -ge $_.RecommendedVersion) { # Greater than Recommended = Recommended return [WikiVersionPriority]::UpToDate } elseif($null -ne $_.AcceptedVersion -and $_.AcceptedVersion -ne '0.0.0.0' -and $Version -ge $_.AcceptedVersion) { # Greater than Accepted = Accepted return [WikiVersionPriority]::Supported } # In any other case = Outdated return [WikiVersionPriority]::NotSupported } } class WikiVersionLink { [string]$Title [version]$Version [string]$VersionString [string]$Body WikiVersionLink() { $this.InitFromHashtable(@{}) } WikiVersionLink([string]$Title,[version]$Version) { $this.InitFromHashtable(@{Title = $Title; Version = $Version}) } [void] SetTitle([string]$Title) { $this.Title = $Title } [void] SetVersion([version]$Version) { $this.Version = $Version.Clone() } [void] InitFromHashtable([hashtable]$properties) { foreach ($Property in $Properties.Keys) { if($Properties.$Property -is [hashtable]) { $this.$Property = $Properties.$Property.Clone() } else { $this.$Property = $Properties.$Property } } } [WikiVersionLink] Clone() { return $this.PSObject.Copy() } static [WikiVersionLink[]] CreateFromHtml([string]$Html) { return [WikiVersionLink]::CreateFromXDocument(($Html | ConvertTo-XDocument)) } static [WikiVersionLink] CreateFromCurrentNode([object]$Node) { $Output = [WikiVersionLink]::empty $Node | Select-Xpath -XPath 'self::ac:link' | Foreach-Object { $Title = $_ | Select-XPath './ri:page/@ri:content-title' | Select-Object -ExpandProperty Node | Select-Object -ExpandProperty Value $VersionString = $_ | Select-XPath '(./following-sibling::text()[1]|./following-sibling::code/text())' | Select-Object -ExpandProperty Node | Select-Object -ExpandProperty Value if($VersionString) { $VersionString = $VersionString.Trim() } $Version = ConvertTo-UtilsVersion -InputString $VersionString <##> $Body = $_ | Select-Object -ExpandProperty Node | Select-Object -ExpandProperty OuterXml $VersionLink = [WikiVersionLink]::new() $VersionLink.Title = $Title $VersionLink.Version = $Version <##> $VersionLink.VersionString = $VersionString <##> $VersionLink.Body = $Body $Output = $VersionLink # $Output += ([WikiVersionLink]::new($Title,$Version)) } return [WikiVersionLink]($Output) } static [WikiVersionLink[]] CreateFromXDocument([object]$Document) { $Output = @() $Document | Select-Xpath -XPath '//ac:link' | Foreach-Object { $Output += [WikiVersionLink]::CreateFromCurrentNode($_) } return $Output } [string] ToString() { return "[VersionLink] Link:$($this.Title), Version:$($this.Version), VersionString:$($this.VersionString)" } } enum WikiVersionPriority { UpToDate Supported NotSupported Vulnerable Unknown } |