Quicklinks.ps1



function global:Set-ValoQuicklinksWebpart {
    [CmdletBinding()]
    Param (
        [ValidateScript({Test-Path $_})]
        [Parameter(Mandatory=$true)]
        [string]$QuicklinksFilePath,

        [ValidateScript({Test-Path $_})]
        [Parameter(Mandatory=$true)]
        [string]$PnPTemplatePath,

        [ValidateScript({
            try{
                [System.Guid]::Parse($_) | Out-Null
                $true
            } catch {
                $false
            }
        })]
        [string]$WebpartControlId = "438cd742-4c76-4d42-bd2d-e7b312ebaed2"
    )

    try{
        Write-Verbose "[$($MyInvocation.MyCommand)] Entering function scope"

        Write-Verbose "Converting raw json string to manipulable object from $QuicklinksFilePath"
        $quicklinksJson = Get-Content $QuicklinksFilePath -Raw | `
                                Format-QuicklinksData | `
                                ConvertFrom-Json

        Write-Verbose "Updating PnPTemplate Webpart definition for --> $WebpartControlId"
        [xml]$xml = Get-Content $PnPTemplatePath
        $xmlNamespace = @{ pnp = 'http://schemas.dev.office.com/PnP/2018/05/ProvisioningSchema'; };
        $xpath = "//pnp:CanvasControl"
        
        $webPart = $xml | `
            Select-Xml -XPath $xpath -Namespace $xmlNamespace | `
            Where-Object { $_.Node.ControlId -eq $WebpartControlId }
        
        Write-Verbose "Updating the webpart's JsonControlData properties"
        $webPartControlData = $webPart.Node.JsonControlData | ConvertFrom-Json
        $webPartControlData.properties = $quicklinksJson.properties

        # Needs to be explicitly converted to a string
        $webPart.Node.JsonControlData = ($webPartControlData | ConvertTo-Json).ToString()

        # The XML Save method requires an absolute path or nothing will happen
        Write-Verbose "Overwriting current PnPTemplate $PnPTemplatePath"
        $absolutePath = Resolve-Path $PnPTemplatePath
        $xml.Save($absolutePath.Path)
    } catch {
        Write-Error $_.Exception.Message
    } finally {
        Write-Verbose "[$($MyInvocation.MyCommand)] Exiting function scope"
    }
}

function global:Resolve-QuicklinksResources {
    Param(
        [ValidateScript({Test-Path $_ -PathType Leaf})]
        [Parameter(Mandatory=$true)]
        [string]$QuicklinksFilePath,

        [ValidateScript({Test-Path $_ -PathType Container})]
        [Parameter(Mandatory=$true)]
        [string]$ResourceFolder
    )

    try {
        Write-Verbose "[$($MyInvocation.MyCommand)] Entering function scope"

        $config = Get-Content $QuicklinksFilePath -Raw | ConvertFrom-Json

        $resourceFiles = Get-ChildItem -Recurse $ResourceFolder
        $resourceFiles | ForEach-Object {
            $locale = $_.BaseName
            $resxPath = $_.FullName

            Write-Verbose "Resx file found, trying to match to $locale configuration"

            $nodes = @()
            $config.Resources | ForEach-Object {
                $label = $_.labels | Where-Object {$_.locale -eq $locale}
                if($null -ne $label) {
                    $nodes += @{Title= $_.Title; Value=$label.value}
                }
            }

            if($nodes.Count -gt 0) {
                Write-Verbose "Resx File was matched to $locale configuration"
                [xml]$resxDocument = Get-Content $resxPath
                $resxDocument | Add-QuicklinkResourceNode -Nodes $nodes
                $resxDocument.Save($resxPath)
            }
        }
    } catch {
        Write-Error $_.Exception.Message
    } finally {
        Write-Verbose "[$($MyInvocation.MyCommand)] Exiting function scope"
    }
}

function script:Format-QuicklinksData{
    Param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [string]$RawJson
    )
    Write-Verbose "[$($MyInvocation.MyCommand)]Entering function scope"

    Write-Verbose "Removing undesired characters (extra spaces and carriage return)"
    return $RawJson.Replace("`r`n", [string]::Empty).Replace(" ", [string]::Empty)
}

function script:Add-QuicklinkResourceNode {
    Param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [xml]$ResxDocument,

        [Parameter(Mandatory=$true)]
        [PSObject[]]$Nodes
    )
    Write-Verbose "[$($MyInvocation.MyCommand)]Entering function scope"

    try {
        $Nodes | ForEach-Object {
            
            Write-Verbose "Creating the attributes for the nodes $()"
            $spaceAttribute = $ResxDocument.CreateAttribute("xml:space")
            $dataNameAttribute = $ResxDocument.CreateAttribute("name")
            $spaceAttribute.Value = "preserve"
            $dataNameAttribute.Value = $_.Title

            Write-Verbose "Creating the nodes to be added to the document"
            $dataNode = $ResxDocument.CreateElement("data")
            $valueNode = $ResxDocument.CreateElement("value")
            $dataNode.Attributes.Append($dataNameAttribute)
            $dataNode.Attributes.Append($spaceAttribute)
            $valueNode.InnerText = $_.Value
            
            Write-Verbose "Data node [$($_.Title)] appended to the document Root node"
            $dataNode.AppendChild($valueNode)
            $ResxDocument.root.AppendChild($dataNode)
        }
    } catch {
        Write-Error $_.Exception.Message
    } finally {
        Write-Verbose "[$($MyInvocation.MyCommand)]Exiting function scope"
    }
}