Valo.ps1

<#
    .SYNOPSIS
        Sanitize the Language script webparts in the Genesis Language root XML configuration.
    .DESCRIPTION
        Sanitize the Language script webparts in the Genesis Language root XML configuration.
        The leftover elements are then purged from the file.
    .LINK
        Nexus Innovations : http://www.nexusinno.com
    --------------------------------------------------------------------------------------
    Module 'Nexus.PSToolkit'
    by: Nexus Innovations.
    --------------------------------------------------------------------------------------
#>

function global:Update-ValoMultilingualRoot {
    Param(
        [Parameter(Mandatory = $true)]
        [ValidateScript( {Test-Path $_})]
        [string]$LanguagesConfigFilePath,

        [Parameter(Mandatory = $true)]
        [ValidateScript( {Test-Path $_})]
        [ValidatePattern("genesis-languageroot\.[\D]+\.xml")]
        [string]$GenesisLanguageRootFilePath,

        [Parameter(Mandatory = $true)]
        [ValidatePattern("https\:\/\/[\D\d]*\.sharepoint\.com|de")]
        [string]$O365Tenant
    )

    # Load the Multilingual XML configuration file
    [xml]$languagesConfigXml = Get-Content $LanguagesConfigFilePath

    # Load the Genesis Language root file to be sanitized
    [xml]$xml = Get-Content $GenesisLanguageRootFilePath

    # Obtain the ScriptWebParts tag to modify
    $webParts = $xml.Tenant.SiteCollection.Pages.Page.ScriptWebParts.ScriptWebPart | Where-Object {$_.Zone -like "WebPartZoneWide*"}

    # Replacing the original ScriptWebPArt CDATA section by updating the Node InnerText string value
    $webPartPattern = '<language-selection url="{0}" title="{1}"/>'
    $webPartIndex = 0

    $languagesConfigXml.Languages.Language | ForEach-Object {
        $siteCollectionUrl = "$O365Tenant/sites$($_.SiteCollectionRelativeUrl)"
        $newTagInfo = [string]::Format($webPartPattern, $siteCollectionUrl, $_.Title)
        $updated = $xml.CreateCDataSection($newTagInfo)
        $webParts[$webPartIndex].'#cdata-section' = $updated.Value
        $webPartIndex++
    }

    # Select all ScriptWebPart nodes within the file that haven't been modified. The idea is to sanitize the xml file and keep the minimum nodes
    # to ensure having only the minimum webparts deployed and configured within the root landing page in a Valo multilingual scenario.
    $parentNode = $xml | Select-Xml -XPath  '//*[local-name() = ''ScriptWebParts'']' 
    $childsToRemove = $parentNode.Node.ChildNodes | Where-Object {$_.Zone -like "WebpartZoneWide*" -and $_.InnerText -like '*url=""*'}
    $childsToRemove | ForEach-Object {
        $parentNode.Node.RemoveChild($_)
    }

    $xml.Save($GenesisLanguageRootFilePath)
}

<#
    .SYNOPSIS
        Activate the Language script in the Genesis Language ressources xml configuration.
    .DESCRIPTION
        Uncommenting xml elements used by the multilingual mode.
    .LINK
        Nexus Innovations : http://www.nexusinno.com
    --------------------------------------------------------------------------------------
    Module 'Nexus.PSToolkit'
    by: Nexus Innovations.
    --------------------------------------------------------------------------------------
#>

function global:Enable-ScriptForMultilingualSiteCollection  {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateScript( {Test-Path $_})]
        [string]$GenesisRessourceFilePath
    )

    $regEx = "(<!--(.*((.*)\W*((?i)valo-riot-language-webparts.js(.*)(?-i))\W)(.*))-->)"
    $scriptName = "valo-riot-language-webparts.js"

    #Obtains the xml file.
    $xml = (Get-Content -Path $GenesisRessourceFilePath)
    
    #Look if the xml file contains the call to valo-riot-language-webparts.js script.
    $xml | Where-Object {$_ -match $scriptName}

    if ($Matches.ContainsValue($scriptName)) {

        #Uncomment each call refering to the script.
        $xml -replace $regEx,'$2' | Set-Content -Path $GenesisRessourceFilePath
        Write-host "The $GenesisRessourceFilePath have been properly modified !" -ForegroundColor Green
    }
    else {
        Write-Host " The analysed file does not contains the script name : $scriptName " -ForegroundColor Red
    }
}

<#
    .SYNOPSIS
        Adds the language reusable content to all the sites listed in the configuration file.
    .DESCRIPTION
        Adds the language reusable content to all the sites listed in the configuration file.
        Must already be logged to a Sharepoint account.
 
        Returns all the added list items.
    .LINK
        Nexus Innovations : http://www.nexusinno.com
    --------------------------------------------------------------------------------------
    Module 'Nexus.PSToolkit'
    by: Nexus Innovations.
    --------------------------------------------------------------------------------------
#>

function global:Add-ValoLanguageReusableContent {
    Param (
        [Parameter(Mandatory=$true)]
        [ValidateScript({Test-Path $_})]
        [string]$ConfigFilePath,

        [Parameter(Mandatory=$true)]
        [ValidatePattern("https\:\/\/[\D\d]*\.sharepoint\.com|de")]
        [string]$O365Tenant,

        [Parameter(Mandatory=$true)]
        [pscredential]$Credentials
    )
    $config = Get-Content $ConfigFilePath -Raw | ConvertFrom-Json
    $listItems = @()
    ForEach ($rc in $config.ReusableContents) {
        $site = Get-PnPTenantSite | Where-Object {$_.Url.EndsWith($rc.RelativeUrl)}
        Connect-PnPOnline -Url $site.Url -Credentials $Credentials

        $reusableContent = @()
        for ($i = 0; $i -lt $config.UrlTemplates.Count; $i++){
            $reusableContent += ,[string]::Format($config.UrlTemplates[$i], $rc.Labels[$i], $O365Tenant)
        }
        $listItems += Add-PnPListItem -ContentType "Item" -List "Reusable Content" -Values @{"Title" = "Languages"; 
                                                                               "AutomaticUpdate" = $true; 
                                                                               "ReusableHtml" = ($reusableContent -join ",")}
    }

    return $listItems
}

<#
    .SYNOPSIS
        Creates and properly copy a valid Valo deployment folder with integrated customisation
    .DESCRIPTION
        This method acted as a controller to coordinate all methods to create a Valo Intranet
        from the information contained in client repository.
    .LINK
        Nexus Innovations : http://www.nexusinno.com
#>

function global:Initialize-DeploymentFolder {
    Param (
        [Parameter(Mandatory=$true)]
        [hashtable]$ClientConfig,

        [string]$DeploymentFolderName = [string]::Format("Valo-Deployment-{0}", (Get-Date).ToShortDateString().Replace('/','-'))
    )
    
    if(-not (Test-Path($DeploymentFolderName))) {
        Write-Verbose "Creating the deployment folder -> $DeploymentFolderName"
        $deploymentFolder = New-Item -Path $DeploymentFolderName -ItemType Directory

        # Login to Azure and select the subscription containing
        Login-AzureRmAccount
        Select-AzureRmSubscription -SubscriptionId $ClientConfig.AzureSubscriptionId

        # Fetch Valo starter pack from the Blob Storage
        Write-Verbose "Connection established to Azure, downloading the Valo partner pack"
        $valoFolderPath = Get-ValoStarterPackFromAzureStorage `
                                    -ValoVersion $ClientConfig.ValoVersion `
                                    -ResourceGroupName $ClientConfig.AzureResourceGroup `
                                    -StorageAccountName $ClientConfig.AzureStorageAccount `
                                    -DestinationPath $deploymentFolder.FullName

        # Unzip the Valo partner pack and content import
        $valoPartnerPackFolderPath = Expand-ValoPartnerPack `
                                        -ValoFolderPath $valoFolderPath[1].ToString() `
                                        -DestinationPath $deploymentFolder.FullName `
                                        -ClientConfig $clientConfig
    }

    # Copy all client specific files to the Extracted valo folder
    Get-ChildItem -Directory -Exclude $deploymentFolder.Name | ForEach-Object {
        Write-Verbose "Copying $($_.FullName) to $valoPartnerPackFolderPath"
        Copy-Item $_.FullName -Recurse -Destination $valoPartnerPackFolderPath -Force
    }

    Write-Verbose "Deployment folder is ready!"
    return $valoPartnerPackFolderPath
}

<#
    .SYNOPSIS
        This method is used to read the client information contains in his configuration file.
    .DESCRIPTION
        This method return a hash table with all useful informations from the client xml file.
    .LINK
        Nexus Innovations : http://www.nexusinno.com
    --------------------------------------------------------------------------------------
    Module 'Nexus.PSToolkit'
    by: Nexus Innovations.
    --------------------------------------------------------------------------------------
#>

function global:Read-ClientConfigurationProject {
    Param (
        [Parameter(Mandatory=$true)]
        [ValidateScript({Test-Path $_})]
        [string]$FilePath
    )

    [xml]$xml = (Get-Content($FilePath))
    $clientConfig = @{ ValoProjectName = $xml.Project.Name ;
                       ValoVersion = $xml.Project.Valo.Version ;
                       ValoEnvironment = $xml.Project.Valo.Environment;
                       ValoLanguages = $xml.Project.Valo.Languages;
                       AzureSubscriptionID = $xml.Project.Azure.SubscriptionID;
                       AzureResourceGroup = $xml.Project.Azure.ResourceGroupName;
                       AzureStorageAccount = $xml.Project.Azure.StorageAccountName;
                    }

    $clientConfig.Keys | ForEach-Object {
        if ([string]::IsNullOrEmpty($clientConfig.Item($_))) {
            throw [string]::Format("The {0} element is empty!", $_)
        }
    }

    return $clientConfig
}

<#
    .SYNOPSIS
        This method return the file path of where the folder has been downloaded.
    .DESCRIPTION
        From the Azure storage account and the resource group name and the valo version,
        this method retrieve the original partner pack from the blob storage and download it.
    .LINK
        Nexus Innovations : http://www.nexusinno.com
    --------------------------------------------------------------------------------------
    Module 'Nexus.PSToolkit'
    by: Nexus Innovations.
    --------------------------------------------------------------------------------------
#>

function script:Get-ValoStarterPackFromAzureStorage {
    Param(
        [Parameter(Mandatory=$true)]
        [string]$ValoVersion,

        [Parameter(Mandatory=$true)]
        [string]$ResourceGroupName,

        [Parameter(Mandatory=$true)]
        [string]$StorageAccountName,

        [Parameter(Mandatory=$false)]
        [ValidateScript({Test-Path $_})]
        [string]$DestinationPath = (Get-Location)
    )

    # Obtain storage account context
    $storageKeys = Get-AzureRmStorageAccountKey -ResourceGroupName $ResourceGroupName -Name $StorageAccountName
    $storageContext = New-AzureStorageContext -StorageAccountName $StorageAccountName -StorageAccountKey $storageKeys[0].Value

    # Obtain reference to blob partner pack based on the desired version of Valo
    $container = Get-AzureStorageContainer -Context $storageContext
    $blob = Get-AzureStorageBlob -Container $container.Name -Context $storageContext | Where-Object {$_.Name -like "*$ValoVersion*"}

    if($blob -eq $null) {
        throw "Valo version not found!"
    }

    # Download the partner pack localy
    Get-AzureStorageBlobContent -Blob $blob.Name -Container $container.Name -Context $storageContext -Destination $DestinationPath

    $valoFilePath = Join-Path $DestinationPath $blob.Name
    return $valoFilePath
}


<#
    .SYNOPSIS
        This method create, by unziping, the valo partner pack and return is folder path.
    .DESCRIPTION
        From the downloaded zip valo package and the client configuration,
        this method generate a folder ready to launch the genesis valo generator.
    .LINK
        Nexus Innovations : http://www.nexusinno.com
    --------------------------------------------------------------------------------------
    Module 'Nexus.PSToolkit'
    by: Nexus Innovations.
    --------------------------------------------------------------------------------------
#>

function script:Expand-ValoPartnerPack {
    Param (
        [Parameter(Mandatory=$true)]
        [ValidateScript({Test-Path $_})]
        [string]$ValoFolderPath,

        [Parameter(Mandatory=$true)]
        [hashtable]$ClientConfig,

        [ValidateScript({Test-Path $_})]
        [string]$DestinationPath = (Get-Location)
    )

    try {
        # Unzip the valo package
        Expand-Archive -Path $ValoFolderPath -DestinationPath $DestinationPath -Force

        # Move inside the unzipped Valo folder
        Push-Location -Path $ValoFolderPath.TrimEnd('.zip')

        # Retrieve the Installation packages path
        $partnerPackName = [string]::Format("PartnerPack-release{0}-{1}.zip", $ClientConfig.ValoEnvironment, $ClientConfig.ValoVersion)
        $partnerPackPath = (Get-ChildItem -Recurse -Filter $partnerPackName).FullName

        if (-not ([string]::IsNullOrEmpty($partnerPackPath))) {
            Set-Location -Path $DestinationPath

            # Unzip the PartnerPack
            Expand-Archive -Path $partnerPackPath -DestinationPath $DestinationPath -Force

            # Unzip Content Importation folder inside the PartnerPack
            $contentValoPath = (Get-ChildItem -Recurse -Filter "Example-Content-Valo-demo.zip").FullName
            $destination = (Get-ChildItem | Where-Object { $_.Name -match $clientConfig.ValoEnvironment}).FullName
            Expand-Archive -Path $contentValoPath -DestinationPath $destination -Force

            return $destination
        } else {
            throw " An error occurred, the valo environment or the valo version is wrong "
        }
    } finally {
        Pop-Location
    }
}