Kva.psm1

#########################################################################################
#
# Copyright (c) Microsoft Corporation. All rights reserved.
#
# Kva Module
#
#########################################################################################

#requires -runasadministrator
using module .\Common.psm1

#region Module Constants

$moduleName       = "Kva"
$moduleVersion    = "1.0.27"

#endregion

#region Download catalog constants

# Defaults until KVA has it's own catalog
$catalogName = "aks-hci-stable-catalogs-ext"
$ringName    = "stable"
$productName = "kva"

$productInfoMapName      = "cloudop-product-information"
$productInfoMapNamespace = "cloudop-system"

#endregion

#region Script Constants

$defaultContainerRegistryServer = "ecpacr.azurecr.io"
$defaultContainerRegistryU      = "1516df5a-f1cc-4a6a-856c-03d127b02d05"
$defaultContainerRegistryP      = "92684690-48b5-4dce-856d-ef4cccb54f22"

$logFilePath = $($env:ProgramData + "\kva\kva.log")
$kubeconfigMgmtFile = "kubeconfig-mgmt"
$script:defaultTokenExpiryDays = 90
$script:kvaVersionMar    = "1.0.9.10413"
$kvaBinaries = @(
    $global:kvactlBinary,
    $global:kubectlBinary
)

$kvaBinariesMap = @{
    $global:kvactlBinary = $global:kvaCtlFullPath;
    $global:kubectlBinary = $global:kubeCtlFullPath;
}

if (!$global:config) {
    $global:config = @{}
}

$nodePoolYamlTemplate = @"
azurestackhcinodepool:
  hardwareprofile:
    vmsize: {2}
  osprofile:
    ostype: {3}
    ssh:
      publickeys:
      - keydata: {4}
name: {0}
replicas: {1}
"@


# azure
$global:azureCloud = "AzureCloud"
$global:azureChinaCloud = "AzureChinaCloud"
$global:azureUSGovernment = "AzureUSGovernment"
$global:azureGermanCloud = "AzureGermanCloud"
$global:azurePPE = "AzurePPE"

$global:graphEndpointResourceIdAzureCloud = "https://graph.windows.net/"
$global:graphEndpointResourceIdAzurePPE = "https://graph.ppe.windows.net/"
$global:graphEndpointResourceIdAzureChinaCloud = "https://graph.chinacloudapi.cn/"
$global:graphEndpointResourceIdAzureUSGovernment = "https://graph.windows.net/"
$global:graphEndpointResourceIdAzureGermancloud = "https://graph.cloudapi.de/"

#endregion

#region
# Install Event Log
New-ModuleEventLog -moduleName $moduleName
#endregion

Import-LocalizedData -BindingVariable "GenericLocMessage" -FileName commonLocalizationMessages
Import-LocalizedData -BindingVariable "KvaLocMessage" -FileName KvaLocalizationMessages

#region Private Function

function Initialize-KvaConfiguration
{
    <#
    .DESCRIPTION
        Initialize Kva Configuration.
        Wipes off any existing cached configuration
    #>

    if ($global:config.ContainsKey($moduleName)) {
        $global:config.Remove($moduleName)
    }
    $global:config += @{
        $moduleName = @{
            "cloudAgentAuthorizerPort" = 0
            "cloudAgentPort"           = 0
            "cloudLocation"           = ""
            "controlplaneVmSize"      = [VmSize]::Default
            "dnsservers"              = ""
            "gateway"                 = ""
            "group"                   = ""
            "imageDir"                = ""
            "insecure"                = $false
            "installationPackageDir"  = ""
            "installState"            = [InstallState]::NotInstalled
            "ipaddressprefix"         = ""
            "k8snodeippoolstart"      = ""
            "k8snodeippoolend"        = ""
            "kubeconfig"              = ""
            "kvaconfig"               = ""
            "kvaK8sVersion"           = ""
            "kvaName"                 = ""
            "kvaPodCidr"              = ""
            "macPoolEnd"              = ""
            "macpoolname"             = ""
            "macPoolStart"            = ""
            "manifestCache"           = ""
            "moduleVersion"           = $moduleVersion
            "proxyServerCertFile"     = ""
            "proxyServerHTTP"         = ""
            "proxyServerHTTPS"        = ""
            "proxyServerNoProxy"      = ""
            "proxyServerPassword"     = ""
            "proxyServerUsername"     = ""
            "skipUpdates"             = $false
            "stagingShare"            = ""
            "tokenExpiryDays"         = 0
            "containerRegistryServer" = ""
            "containerRegistryUser"   = ""
            "containerRegistryPass"   = ""
            "useStagingShare"         = $false
            "version"                 = ""
            "vlanid"                  = 0
            "vnetName"                = ""
            "vswitchName"             = ""
            "vnetvippoolend"          = ""
            "vnetvippoolstart"        = ""
            "workingDir"              = ""
            "catalog"                 = ""
            "ring"                    = ""
            "identity"                = ""
            "operatorTokenValidity"   = 0
            "addonTokenValidity"      = 0
        };
    }
}

#endregion

#region global config

Initialize-KvaConfiguration
#endregion

#region Exported Functions

function Install-Kva
{
    <#
    .DESCRIPTION
        Uses KVACTL to deploy a management cluster.
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    [CmdletBinding()]
    param (
        [String]$activity = $MyInvocation.MyCommand.Name
    )

    Initialize-KvaEnvironment -createConfigIfNotPresent -activity $activity

    $curState =  Get-ConfigurationValue -module $moduleName -type ([Type][InstallState]) -name "installState"
    if ($null -ne $curState) {
        switch ($curState) {
            ([InstallState]::Installed) {
                Write-Status -moduleName $moduleName $($KvaLocMessage.kva_installed)
                Write-SubStatus -moduleName $moduleName $($KvaLocMessage.kva_reinstall_uninstall)
                return
            }
            ([InstallState]::Installing) {
                Write-Status -moduleName $moduleName $($KvaLocMessage.kva_installing)
                return
                break
            }
            ([InstallState]::NotInstalled) {
                # Fresh install
                break
            }
            Default {
                # Cleanup partial installs from previous attempts
                Uninstall-Kva -activity $activity
            }
        }
    }

    try
    {
        Install-KvaInternal -activity $activity
    }
    catch [Exception]
    {
        Write-ModuleEventLog -moduleName $moduleName -entryType Error -eventId 100 -message "$activity - $_"
        throw $_
    }


    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
}

function Restart-Kva
{
    <#
    .DESCRIPTION
        Cleans up an existing KVA deployment and reinstalls everything. This isn't equivalent to
        executing 'Uninstall-Kva' followed by 'Install-Kva' as Restart-Kva will preserve existing
        configuration settings and any downloaded images.
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    [CmdletBinding()]
    param (
        [String]$activity = $MyInvocation.MyCommand.Name
    )

    Initialize-KvaEnvironment -activity $activity

    # Skip the config cleanup to reinstall
    Uninstall-Kva -SkipConfigCleanup:$True -activity $activity

    Install-KvaInternal -activity $activity

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
}

function Uninstall-Kva
{
    <#
    .DESCRIPTION
        Removes a KVA deployment.
 
    .PARAMETER SkipConfigCleanup
        skips removal of the configurations after uninstall.
        After uninstall, you have to Set-KvaConfig to install again.
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    [CmdletBinding()]
    param (
        [Switch]$SkipConfigCleanup,
        [String]$activity = $MyInvocation.MyCommand.Name
    )

    try
    {
        Initialize-KvaEnvironment -activity $activity
    }
    catch [Exception]
    {
        Write-ModuleEventLog -moduleName $moduleName -entryType Warning -eventId 2 -message "$activity - $_"
    }

    Set-KvaConfigValue -name "installState" -value ([InstallState]::Uninstalling)

    try
    {
        # Do not use Get-KvaConfigYaml, which can generate one, if not already there.
        if (Test-Path $kvaCtlFullPath)
        {
            $yamlFile = $global:config[$modulename]["kvaconfig"]
            if ($yamlFile -and (Test-Path $yamlFile))
            {
                $kvaRegistration = Get-KvaRegistration

                try 
                {
                    Test-KvaAzureConnection

                    # If Test-KvaAzureConnection worked, do the cleanup for the connectedcluster azure resource
                    Invoke-KvaCtlWithAzureContext -arguments "delete --configfile ""$yamlFile""" -showOutputAsProgress -activity $activity
                }
                catch
                {
                    Write-Status -moduleName $moduleName -Verbose -msg $($KvaLocMessage.kva_leaked_arc_connected_clusters)
                    Invoke-KvaCtl -arguments "delete --configfile ""$yamlFile""" -showOutput -activity $activity
                }
            }
        }
    }
    catch [Exception]
    {
        Write-ModuleEventLog -moduleName $moduleName -entryType Error -eventId 100 -message "$activity - $_"
    }
    try
    {
        $kubeconfig = $global:config[$moduleName]["kubeconfig"]
        if ($kubeconfig -and (Test-Path $kubeconfig))
        {
            Remove-Item $kubeconfig -ErrorAction Ignore
        }

        $kubernetesVersion = $global:config[$modulename]["kvaK8sVersion"]
        if ($kubernetesVersion)
        {
            $imageGalleryName = Get-KubernetesGalleryImageName -imagetype "Linux" -k8sVersion $kubernetesVersion

            try {
                Remove-MocGalleryImage -name $imageGalleryName -location $global:config[$modulename]["cloudLocation"] | Out-Null
            } catch {
                if (-not ($_.Exception.Message -like "*connection closed*")) {
                    Write-SubStatus -moduleName $moduleName  $("Warning: " + $_.Exception.Message)
                }
            }
        }

        # 1. Update the binaries
        if (Test-MultiNodeDeployment)
        {
            Get-ClusterNode -ErrorAction Continue | ForEach-Object {
                Uninstall-KvaBinaries -nodeName $_.Name
            }
        }
        else
        {
            Uninstall-KvaBinaries -nodeName ($env:computername)
        }
        # 2. Remove KVA Identity
        try {
            $clusterName = $($global:config[$modulename]["kvaName"])
            Remove-MocIdentity -name $clusterName  | Out-Null
        } catch {
            if (-not ($_.Exception.Message -like "*connection closed*")) {
                Write-SubStatus -moduleName $moduleName  $("Warning: " + $_.Exception.Message)
            }
        }
        # Clean CloudConfig
        Remove-Item -Path $global:kvaMetadataDirectory -Force -Recurse -ErrorAction Ignore
    }
    catch [Exception]
    {
        Write-ModuleEventLog -moduleName $moduleName -entryType Error -eventId 100 -message "$activity - $_"
    }
    Set-KvaConfigValue -name "installState" -value ([InstallState]::NotInstalled)
    if (!$SkipConfigCleanup.IsPresent)
    {
        Reset-Configuration -moduleName $moduleName
    }
    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
}

function New-KvaAutoScalerProfile
{
    <#
    .SYNOPSIS
        Creates a new AutoScalerProfile.
 
    .DESCRIPTION
        Creates a new AutoScalerProfile.
 
    .PARAMETER Name
        Name of the AutoScalerProfile
 
    .PARAMETER AutoScalerProfileConfig
        Hashtable containing AutoScalerProfile config keys and their values
 
    .PARAMETER activity
        Activity name to use when updating progress
 
    .INPUTS
        An AutoScalerProfile config is comprised of the following keys. Note: keys that are not provided will fall back to its default value
            - min-node-count
            - max-node-count
            - max-nodes-total
            - scale-down-enabled
            - scan-interval
            - scale-down-delay-after-add
            - scale-down-delay-after-delete
            - scale-down-delay-after-failure
            - scale-down-unneeded-time
            - scale-down-unready-time
            - scale-down-utilization-threshold
            - max-graceful-termination-sec
            - balance-similar-node-groups
            - expander
            - skip-nodes-with-local-storage
            - skip-nodes-with-system-pods
            - max-empty-bulk-delete
            - new-pod-scale-up-delay
            - max-total-unready-percentage
            - max-node-provision-time
            - ok-total-unready-count
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String] $Name,

        [Parameter(Mandatory=$true)]
        [ValidateScript({Test-ValidAutoScalerProfileConfig -AutoScalerProfileConfig $_ })]
        [hashtable] $AutoScalerProfileConfig,

        [Parameter()]
        [String] $activity
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $Name"
    }

    Initialize-KvaEnvironment -activity $activity

    $kubeconfig = Get-KvaCredential -activity $activity

    $yamlFile = New-AutoScalerProfileConfigYaml -Name $Name -AutoScalerProfileConfig $AutoScalerProfileConfig

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_creating_autoscalerprofile, $Name))

    $cmdArgs = "autoscalerprofile create --profileconfig=""$yamlFile"" --kubeconfig=""$kubeconfig"""
    Invoke-KvaCtl -arguments $cmdArgs -showOutput -activity $activity

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
}

function New-AutoScalerProfileConfigYaml
{
    <#
    .DESCRIPTION
        Creates an AutoScalerProfile KVA YAML string for the settings field.
 
    .PARAMETER Name
        The AutoScalerProfile name
 
    .PARAMETER AutoScalerProfileConfig
        An AutoScalerProfile config
    #>

    param (
        [String] $Name,
        [hashtable] $AutoScalerProfileConfig
    )

    $yaml = @"
name: $Name
settings:
"@


    foreach($k in $AutoScalerProfileConfig.Keys)
    {
        $key = $global:autoScalerProfileConfigToKvaYamlKeys[$k]
        $val = $AutoScalerProfileConfig[$k]
        $yaml += "`r`n $($key): $val"
    }

    $yamlFile = $($global:config[$modulename]["installationPackageDir"]+"\"+$global:yamlDirectoryName+"\$Name-autoscalerprofile.yaml")
    Set-Content -Path $yamlFile -Value $yaml -ErrorVariable err
    if ($null -ne $err -and $err.count -gt 0)
    {
        throw $err
    }

    return $yamlFile
}

function Get-KvaAutoScalerProfile
{
    <#
    .SYNOPSIS
        Returns the available AutoScalerProfile(s).
 
    .DESCRIPTION
        Returns the available AutoScalerProfile(s).
 
    .PARAMETER Name
        Name of the AutoScalerProfile
 
    .PARAMETER activity
        Activity name to use when updating progress
 
    .OUTPUTS
        If Name is provided, then the AutoScalerProfile object will be returned
        If Name is not provided, then a list of all AutoScalerProfile objects will be returned
    #>


    param (
        [Parameter()]
        [String] $Name,

        [Parameter()]
        [String] $activity
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $Name"
    }

    Initialize-KvaEnvironment -activity $activity

    $kubeconfig = Get-KvaCredential -activity $activity

    $cmdArgs=""
    if (![string]::IsNullOrEmpty($name))
    {
        $cmdArgs = "autoscalerprofile get --name=$Name --kubeconfig=""$kubeconfig"""
    }
    else
    {
        $cmdArgs = "autoscalerprofile list --kubeconfig=""$kubeconfig"""
    }

    Write-StatusWithProgress -activity $activity -status $($KvaLocMessage.kva_getting_autoscalerprofile_details) -moduleName $moduleName

    $autoScalerProfiles = Invoke-KvaCtl -arguments $cmdArgs -activity $activity | ConvertFrom-Json

    $result = @()
    foreach($asp in $autoScalerProfiles) {

        $inUse = $false
        $clusters = @()
        if ($asp | Get-Member status)
        {
            $inUse = $asp.status.inuse
            $clusters = $asp.status.clustersusingprofile
        }

        $props = [ordered]@{
        'Name' = $($asp.name);
        'InUse' = $($inUse);
        'Clusters' = $(($clusters));
        'min-node-count' = $($asp.settings.minnodecount);
        'max-node-count' = $($asp.settings.maxnodecount);
        'max-nodes-total' = $(if ($asp.settings | Get-Member maxnodestotal) { $asp.settings.maxnodestotal } else { 0 });
        'scale-down-enabled' = $($asp.settings.scaledownenabled);
        'scan-interval' = $($asp.settings.scaninterval);
        'scale-down-delay-after-add' = $($asp.settings.scaledowndelayafteradd);
        'scale-down-delay-after-delete' = $($asp.settings.scaledowndelayafterdelete);
        'scale-down-delay-after-failure' = $($asp.settings.scaledowndelayafterfailure);
        'scale-down-unneeded-time' = $($asp.settings.scaledownunneededtime);
        'scale-down-unready-time' = $($asp.settings.scaledownunreadytime);
        'scale-down-utilization-threshold' = $($asp.settings.scaledownutilizationthreshold);
        'max-graceful-termination-sec' = $($asp.settings.maxgracefulterminationsec);
        'balance-similar-node-groups' = $(if ($asp.settings | Get-Member balancesimilarnodegroups) { $asp.settings.balancesimilarnodegroups } else { $false });
        'expander' = $($asp.settings.expander);
        'skip-nodes-with-local-storage' = $($asp.settings.skipnodeswithlocalstorage);
        'skip-nodes-with-system-pods' = $($asp.settings.skipnodeswithsystempods);
        'max-empty-bulk-delete' = $($asp.settings.maxemptybulkdelete);
        'new-pod-scale-up-delay' = $($asp.settings.newpodscaleupdelay);
        'max-total-unready-percentage' = $($asp.settings.maxtotalunreadypercentage);
        'max-node-provision-time' = $($asp.settings.maxnodeprovisiontime);
        'ok-total-unready-count' = $($asp.settings.oktotalunreadycount);
        }
        $result += New-Object -TypeName PsObject -Property $props
    }

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)

    return $result
}

function Set-KvaAutoScalerProfile
{
    <#
    .SYNOPSIS
        Updates an existing AutoScalerProfile.
 
    .DESCRIPTION
        Updates an existing AutoScalerProfile.
 
    .PARAMETER Name
        Name of the AutoScalerProfile
 
    .PARAMETER AutoScalerProfileConfig
        Hashtable containing AutoScalerProfile config keys and their values
 
    .PARAMETER activity
        Activity name to use when updating progress
 
    .INPUTS
        An AutoScalerProfile config is comprised of the following keys. Note: keys' values in the profile will not change if not specified in the argument to this cmdlet.
            - min-node-count
            - max-node-count
            - max-nodes-total
            - scale-down-enabled
            - scan-interval
            - scale-down-delay-after-add
            - scale-down-delay-after-delete
            - scale-down-delay-after-failure
            - scale-down-unneeded-time
            - scale-down-unready-time
            - scale-down-utilization-threshold
            - max-graceful-termination-sec
            - balance-similar-node-groups
            - expander
            - skip-nodes-with-local-storage
            - skip-nodes-with-system-pods
            - max-empty-bulk-delete
            - new-pod-scale-up-delay
            - max-total-unready-percentage
            - max-node-provision-time
            - ok-total-unready-count
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String] $Name,

        [Parameter(Mandatory=$true)]
        [ValidateScript({Test-ValidAutoScalerProfileConfig -AutoScalerProfileConfig $_ })]
        [hashtable] $AutoScalerProfileConfig,

        [Parameter()]
        [String] $activity
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $Name"
    }

    Initialize-KvaEnvironment -activity $activity

    $kubeconfig = Get-KvaCredential -activity $activity

    $asp = Get-KvaAutoScalerProfile -Name $Name -ErrorAction Ignore
    if ($null -eq $asp)
    {
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_autoscalerprofile_does_not_exist, $Name))
    }

    $yamlFile = New-AutoScalerProfileConfigYaml -Name $Name -AutoScalerProfileConfig $AutoScalerProfileConfig

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_updating_autoscalerprofile, $Name))

    $cmdArgs = "autoscalerprofile update --name=$Name --profileconfig=""$yamlFile"" --kubeconfig=""$kubeconfig"""
    Invoke-KvaCtl -arguments $cmdArgs -showOutput -activity $activity

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
}

function Remove-KvaAutoScalerProfile
{
    <#
    .SYNOPSIS
        Removes the AutoScalerProfile.
 
    .DESCRIPTION
        Removes the AutoScalerProfile.
 
    .PARAMETER Name
        Name of the AutoScalerProfile
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String] $Name,

        [Parameter()]
        [String] $activity
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $Name"
    }

    Initialize-KvaEnvironment -activity $activity

    $kubeconfig = Get-KvaCredential -activity $activity

    $asp = Get-KvaAutoScalerProfile -Name $Name -ErrorAction Ignore
    if ($null -eq $asp)
    {
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_autoscalerprofile_does_not_exist, $Name))
    }

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_deleting_autoscalerprofile, $Name))

    $cmdArgs = "autoscalerprofile delete --name=$Name --kubeconfig=""$kubeconfig"""
    Invoke-KvaCtl -arguments $cmdArgs -showOutput -activity $activity

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
}

function New-KvaCluster
{
    <#
    .DESCRIPTION
        Adds a worker cluster to the deployment.
 
    .PARAMETER Name
        Name of the cluster
 
    .PARAMETER kubernetesVersion
        Version of kubernetes to deploy
 
    .PARAMETER controlPlaneNodeCount
        The number of control plane (master) nodes
 
    .PARAMETER linuxNodeCount
        The number of Linux worker nodes
 
    .PARAMETER windowsNodeCount
        The number of Windows worker nodes
 
    .PARAMETER controlplaneVmSize
        The VM size to use for control plane nodes
 
    .PARAMETER loadBalancerVmSize
        The VM size to use for the cluster load balancer
 
    .PARAMETER linuxNodeVmSize
        The VM size to use for Linux worker nodes
 
    .PARAMETER windowsNodeVmSize
        The VM size to use for Windows worker nodes
 
    .PARAMETER nodePoolName
        The name of the node pool
 
    .PARAMETER nodeCount
        The number of worker nodes in the node pool
 
    .PARAMETER nodeMaxPodCount
        The maximum number of pods that can run on a worker node
 
    .PARAMETER taints
        A list of taints to put on each worker node
 
    .PARAMETER nodeVmSize
        The VM size to use for the worker nodes in the node pool
 
    .PARAMETER osType
        The OS type for the worker nodes in the node pool
 
    .PARAMETER enableAutoScaler
        Enable the horizontal Nodepool AutoScaler for this cluster
 
    .PARAMETER autoScalerProfileName
        The name of the AutoScalerProfile to use
 
    .PARAMETER enableADAuth
        Whether the call should or not setup Kubernetes for AD Auth
 
    .PARAMETER vnet
        The virtual network to use for the cluster. If not specified, the virtual network
        of the management cluster will be used
 
    .PARAMETER activity
        Activity name to use when writing progress
 
    .PARAMETER primaryNetworkPlugin
        Network plugin (CNI) definition. Simple string values can be passed to this parameter such as "flannel", or "calico". Defaults to "calico".
 
    .PARAMETER clusterStorageContainer
        Storage container that is associated to the Cluster.
 
    .PARAMETER loadBalancerSettings
         Load Balancer Settings.
    #>


    [CmdletBinding(PositionalBinding=$False, DefaultParameterSetName = 'twonodepools')]
    param (
        [Parameter(Mandatory=$true)]
        [ValidateScript({Test-ValidClusterName -Name $_ })]
        [String] $Name,

        [Parameter()]
        [String] $kubernetesVersion = $global:defaultTargetK8Version,

        [Parameter()]
        [ValidateSet(1,3,5)]
        [int] $controlPlaneNodeCount = 1,

        [Parameter(ParameterSetName = 'twonodepools')]
        [ValidateRange(0,250)]
        [int] $linuxNodeCount = 1,

        [Parameter(ParameterSetName = 'twonodepools')]
        [ValidateRange(0,250)]
        [int] $windowsNodeCount = 0,

        [Parameter()]
        [String] $controlplaneVmSize = $global:defaultControlPlaneVmSize,

        [Parameter()]
        [String] $loadBalancerVmSize = $global:defaultLoadBalancerVmSize,

        [Parameter(ParameterSetName = 'twonodepools')]
        [String] $linuxNodeVmSize = $global:defaultWorkerVmSize,

        [Parameter(ParameterSetName = 'twonodepools')]
        [String] $windowsNodeVmSize = $global:defaultWorkerVmSize,

        [Parameter(ParameterSetName = 'onenodepool')]
        [ValidateScript({Test-ValidNodePoolName -Name $_ })]
        [String] $nodePoolName = $global:defaultNodePoolName,

        [Parameter(ParameterSetName = 'onenodepool')]
        [int] $nodeCount = $global:defaultWorkerNodeCount,

        [Parameter(ParameterSetName = 'onenodepool')]
        [int] $nodeMaxPodCount = 0,

        [Parameter(ParameterSetName = 'onenodepool')]
        [String[]] $taints,

        [Parameter(ParameterSetName = 'onenodepool')]
        [VmSize] $nodeVmSize = $global:defaultWorkerVmSize,

        [Parameter(ParameterSetName = 'onenodepool')]
        [ValidateSet("Windows", "Linux")]
        [OsType] $osType = $global:defaultWorkerNodeOS,

        [Parameter()]
        [Switch] $enableAutoScaler,

        [Parameter()]
        [String] $autoScalerProfileName,

        [Parameter()]
        [Switch] $enableADAuth,

        [Parameter()]
        [String] $activity,

        [Parameter()]
        [ValidateScript({return $true})]#Note: ValidateScript automatically constructs the NetworkPlugin object, therefore validates the parameter
        [NetworkPlugin] $primaryNetworkPlugin = [NetworkPlugin]::new(),

        [Parameter()]
        [VirtualNetwork]$vnet,

        [Parameter()]
        [string]$clusterStorageContainer = $global:cloudStorageContainer,

        [Parameter(mandatory=$true)]
        [LoadBalancerSettings]$loadBalancerSettings

    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $Name"
    }

    Initialize-KvaEnvironment -activity $activity

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($GenericLocMessage.comm_validating_cluster_configuration)

    $capiCluster = Invoke-Kubectl -arguments $("get akshciclusters/$Name") -ignoreError
    if ($null -ne $capiCluster)
    {
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_cluster_exists, $Name))
    }

    $group = $("$global:cloudGroupPrefix-$Name")

    if ($PSCmdlet.ParameterSetName -ieq "twonodepools")
    {
        New-KvaClusterInternal `
            -Name $Name -group $group -kubernetesVersion $kubernetesVersion `
            -controlPlaneReplicas $controlPlaneNodeCount -controlplaneVmSize $controlplaneVmSize `
            -loadBalancerVmSize $loadBalancerVmSize `
            -linuxWorkerReplicas $linuxNodeCount -linuxNodeVmSize $linuxNodeVmSize `
            -windowsWorkerReplicas $windowsNodeCount -windowsNodeVmSize $windowsNodeVmSize `
            -enableAutoScaler:$enableAutoScaler.IsPresent -autoScalerProfileName $autoScalerProfileName `
            -enableADAuth:$enableADAuth.IsPresent `
            -primaryNetworkPlugin $primaryNetworkPlugin -vnet $vnet `
            -activity $activity -loadBalancerSettings $loadBalancerSettings
    }
    elseif ($PSCmdlet.ParameterSetName -ieq "onenodepool")
    {
        New-KvaClusterInternal `
            -Name $Name -group $group -kubernetesVersion $kubernetesVersion `
            -controlPlaneReplicas $controlPlaneNodeCount -controlplaneVmSize $controlplaneVmSize `
            -loadBalancerVmSize $loadBalancerVmSize `
            -nodePoolName $nodePoolName -nodeCount $nodeCount -nodeMaxPodCount $nodeMaxPodCount -taints $taints `
            -nodeVmSize $nodeVmSize -osType $osType `
            -enableAutoScaler:$enableAutoScaler.IsPresent -autoScalerProfileName $autoScalerProfileName `
            -enableADAuth:$enableADAuth.IsPresent `
            -primaryNetworkPlugin $primaryNetworkPlugin -vnet $vnet `
            -activity $activity -loadBalancerSettings $loadBalancerSettings
    }

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
}

function New-KvaClusterInternal
{
    <#
    .DESCRIPTION
        Internal function to call KVACTL to create a capicluster.
 
    .PARAMETER Name
        Name of the cluster
 
    .PARAMETER group
        Cloudagent group
 
    .PARAMETER kubernetesVersion
        Version of kubernetes to deploy
 
    .PARAMETER controlPlaneReplicas
        The number of control plane (master) replicas
 
    .PARAMETER linuxWorkerReplicas
        The number of Linux worker replicas
 
    .PARAMETER windowsWorkerReplicas
        The number of Windows worker replicas
 
    .PARAMETER controlplaneVmSize
        The VM size to use for control plane nodes
 
    .PARAMETER loadBalancerVmSize
        The VM size to use for the cluster load balancer
 
    .PARAMETER linuxNodeVmSize
        The VM size to use for Linux worker nodes
 
    .PARAMETER windowsNodeVmSize
        The VM size to use for Windows worker nodes
 
    .PARAMETER nodePoolName
        The name of the node pool
 
    .PARAMETER nodeCount
        The number of worker nodes in the node pool
 
    .PARAMETER nodeMaxPodCount
        The maximum number of pods that can run on a worker node
 
    .PARAMETER taints
        A list of taints to put on each worker node
 
    .PARAMETER nodeVmSize
        The VM size to use for the worker nodes in the node pool
 
    .PARAMETER osType
        The OS type for the worker nodes in the node pool
 
    .PARAMETER enableAutoScaler
        Enable the horizontal Nodepool AutoScaler for this cluster
 
    .PARAMETER autoScalerProfileName
        The name of the AutoScalerProfile to use
 
    .PARAMETER enableADAuth
        Whether the call should or not setup Kubernetes for AD Auth
 
    .PARAMETER vnet
        The virtual network to use for the cluster. If not specified, the virtual network
        of the management cluster will be used
 
    .PARAMETER activity
        Activity name to use when writing progress
 
    .PARAMETER primaryNetworkPlugin
        Network plugin (CNI) definition. Simple string values can be passed to this parameter such as "flannel", or "calico". Defaults to "calico".
 
    .PARAMETER clusterStorageContainer
        Storage container that is associated to the Cluster.
 
   .PARAMETER loadBalancerSettings
        Load Balancer settings
    #>


    [CmdletBinding(PositionalBinding=$False, DefaultParameterSetName = 'twonodepools')]
    param (
        [Parameter(Mandatory=$true)]
        [ValidateScript({Test-ValidClusterName -Name $_ })]
        [String]$Name,

        [Parameter(Mandatory=$true)]
        [String]$group,

        [Parameter(Mandatory=$true)]
        [String]$kubernetesVersion,

        [Parameter(Mandatory=$true)]
        [int] $controlPlaneReplicas,

        [Parameter(Mandatory=$true, ParameterSetName = 'twonodepools')]
        [int] $linuxWorkerReplicas,

        [Parameter(Mandatory=$true, ParameterSetName = 'twonodepools')]
        [int] $windowsWorkerReplicas,

        [Parameter(Mandatory=$true)]
        [VmSize] $controlplaneVmSize,

        [Parameter(Mandatory=$true)]
        [VmSize] $loadBalancerVmSize,

        [Parameter(Mandatory=$true, ParameterSetName = 'twonodepools')]
        [VmSize] $linuxNodeVmSize,

        [Parameter(Mandatory=$true, ParameterSetName = 'twonodepools')]
        [VmSize] $windowsNodeVmSize,

        [Parameter(Mandatory=$true, ParameterSetName = 'onenodepool')]
        [ValidateScript({Test-ValidNodePoolName -Name $_ })]
        [String] $nodePoolName,

        [Parameter(Mandatory=$true, ParameterSetName = 'onenodepool')]
        [int] $nodeCount,

        [Parameter(ParameterSetName = 'onenodepool')]
        [int] $nodeMaxPodCount,

        [Parameter(ParameterSetName = 'onenodepool')]
        [String[]] $taints,

        [Parameter(Mandatory=$true, ParameterSetName = 'onenodepool')]
        [VmSize] $nodeVmSize,

        [Parameter(Mandatory=$true, ParameterSetName = 'onenodepool')]
        [OsType] $osType,

        [Parameter()]
        [Switch]$enableADAuth,

        [Parameter()]
        [Switch] $enableAutoScaler,

        [Parameter()]
        [String] $autoScalerProfileName,

        [Parameter()]
        [String] $activity = $MyInvocation.MyCommand.Name,

        [Parameter()]
        [NetworkPlugin] $primaryNetworkPlugin = [NetworkPlugin]::new(),

        [Parameter()]
        [VirtualNetwork]$vnet,

        [Parameter()]
        [String]$clusterStorageContainer = $global:cloudStorageContainer,

        [Parameter()]
        [LoadBalancerSettings]$loadBalancerSettings
    )

    Add-GalleryImage -imageType Linux -k8sVersion $kubernetesVersion -activity $activity
    if ($PSCmdlet.ParameterSetName -ieq "twonodepools")
    {
        if ($windowsWorkerReplicas -gt 0)
        {
            Add-GalleryImage -imageType Windows -k8sVersion $kubernetesVersion -activity $activity
        }
    }

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_creating_workload_cluster, $Name))

    $publicKey = Get-Content -Path (Get-SshPublicKey)
    $publicKey = $publicKey.Split(" ")
    $publicKey = $($publicKey[0]+" "+$publicKey[1])

    if (-not $vnet) {
        $vnet = Get-VNetConfiguration -module $moduleName
    }

    if (-not $($loadBalancerSettings.VmSize) -or $($loadBalancerSettings.VmSize) -eq 0) {
          $loadBalancerVmSize = $global:defaultLoadBalancerVmSize
    }

    $yamlDir = ($global:config[$modulename]["installationPackageDir"]+"\"+$global:yamlDirectoryName)

    $yaml = @"
name: $Name
version: $kubernetesVersion
controlplane:
  nodepool:
    replicas: $controlPlaneReplicas
    azurestackhcinodepool:
      hardwareprofile:
        vmsize: $controlplaneVmSize
      osprofile:
        ostype: Linux
        ssh:
          publickeys:
          - keydata: $publicKey
  servicecidr: $($global:workloadServiceCidr)
  podcidr: $($global:workloadPodCidr)
azurestackhcicluster:
  storageconfiguration:
    storagecontainer: $clusterStorageContainer
    dynamic: true
  containernetworkconfiguration:
    primaryconfiguration: $($primaryNetworkPlugin.Name)
  loadbalancer:
    vmsize: $loadBalancerVmSize
    type: $($global:loadBalancerTypeStr[$($loadBalancerSettings.LoadBalancerSku)])
    replicas: $($loadBalancerSettings.loadBalancerCount)
  servicesloadbalancer:
    vmsize: $loadBalancerVmSize
    type: $($global:loadBalancerTypeStr[$($loadBalancerSettings.ServicesLoadBalancerSku)])
    replicas: $($loadBalancerSettings.loadBalancerCount)
  location: $($global:config[$modulename]["cloudLocation"])
  group: $group
  virtualnetwork:
    name: "$($vnet.Name)"
"@


$yaml += "
additionalfeatures:
- featurename: secrets-encryption
- featurename: rotate-certificates"


    # TODO add switch to kvactl
    if ($enableADAuth.IsPresent)
    {
        $yaml += "
- featurename: ad-auth-webhook"

    }

    $yamlFile = $($yamlDir+"\$Name.yaml")
    Set-Content -Path $yamlFile -Value $yaml -ErrorVariable err
    if ($null -ne $err -and $err.count -gt 0)
    {
        throw $err
    }

    $kubeconfig = Get-KvaCredential -activity $activity

    $aspNameParams = ""
    if ($enableAutoScaler.IsPresent)
    {
        $aspNameParams = "--enableautoscaler"
        if ($autoScalerProfileName -ne "")
        {
            $aspNameParams += " --autoscalerprofilename $autoScalerProfileName"
        }
    }

    Invoke-KvaCtl -arguments "cluster create --clusterconfig ""$yamlFile"" $aspNameParams --kubeconfig ""$kubeconfig""" -showOutput -activity $activity

    if ($PSCmdlet.ParameterSetName -ieq "twonodepools")
    {
        $defaultLinuxNodepoolName = GetDefaultLinuxNodepoolName -clusterName $Name
        $defaultWindowsNodepoolName = GetDefaultWindowsNodepoolName -clusterName $Name

        $linuxNodePoolYaml = $nodePoolYamlTemplate -f $defaultLinuxNodepoolName, $linuxWorkerReplicas, $linuxNodeVmSize, "Linux", $publicKey

        $linuxNodePoolYamlFile = $($yamlDir+"\$defaultLinuxNodepoolName.yaml")
        Set-Content -Path $linuxNodePoolYamlFile -Value $linuxNodePoolYaml -ErrorVariable err
        if ($null -ne $err -and $err.count -gt 0)
        {
            throw $err
        }

        Invoke-KvaCtl -arguments "cluster nodepool create --clustername $Name --nodepoolconfig ""$linuxNodePoolYamlFile"" --kubeconfig ""$kubeconfig""" -showOutput -activity $activity

        $windowsNodePoolYaml = $nodePoolYamlTemplate -f $defaultWindowsNodepoolName, $windowsWorkerReplicas, $windowsNodeVmSize, "Windows", $publicKey

        $windowsNodePoolYamlFile = $($yamlDir+"\$defaultWindowsNodepoolName.yaml")
        Set-Content -Path $windowsNodePoolYamlFile -Value $windowsNodePoolYaml -ErrorVariable err
        if ($null -ne $err -and $err.count -gt 0)
        {
            throw $err
        }

        Invoke-KvaCtl -arguments "cluster nodepool create --clustername $Name --nodepoolconfig ""$windowsNodePoolYamlFile"" --kubeconfig ""$kubeconfig""" -showOutput -activity $activity
    }
    elseif ($PSCmdlet.ParameterSetName -ieq "onenodepool")
    {
        New-KvaClusterNodePool `
            -ClusterName $Name -Name $nodePoolName `
            -MaxPodCount $nodeMaxPodCount -Taints $taints -Count $nodeCount `
            -OSType $osType -VMSize $nodeVmSize
    }
}

function Get-Kva
{
    <#
    .DESCRIPTION
        Get the Kva management cluster
 
    .PARAMETER activity
        Activity name to use when writing progress
    #>


    [CmdletBinding()]
    param (
        [Parameter()]
        [String] $activity = $MyInvocation.MyCommand.Name
    )

    Initialize-KvaEnvironment -activity $activity

    # Check the status of Installation
    $curState =  Get-ConfigurationValue -module $moduleName -type ([Type][InstallState]) -name "installState"
    if ($null -ne $curState) {
        switch ($curState) {
            ([InstallState]::Installing) {
                Write-Status -moduleName $moduleName $($KvaLocMessage.kva_installing)
                return
                break
            }
            ([InstallState]::NotInstalled) {
                throw $($KvaLocMessage.kva_not_installed)
            }
        }
    }

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($KvaLocMessage.kva_retrieving_config_file)
    $yamlFile = $global:config[$modulename]["kvaconfig"]
    if (-not (Test-Path $yamlFile))
    {
        # For some reason, this file doest exist - Generate one
        $yamlFile = Get-KvaConfigYaml
    }

    $kubeconfig = Get-KvaCredential -activity $activity
    if (!(Test-Path $kubeconfig))
    {
        # Retrieve the kubeconfig
        Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($KvaLocMessage.kva_retrieving_credentials)
        Invoke-KvaCtl -arguments "retrieve --configfile ""$yamlFile"" --outfile ""$kubeconfig"" " -showOutput -activity $activity
    }

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($KvaLocMessage.kva_retrieving_deployment_status)

    $capiCluster = $null
    $kvaStatus = $null

    try{
        $kvaStatus = Invoke-KvaCtl -arguments "status --configfile ""$yamlFile"" --kubeconfig ""$kubeconfig""" -activity $activity | ConvertFrom-Json
    }
    catch {}

    if ($kvaStatus)
    {
        # Verify that the appliance is deployed and reachable before requesting cluster information
        if (($kvaStatus.phase -ine "NotDeployed") -and
            ($kvaStatus.phase -ine "WaitingForAPIServer") -and
            ($kvaStatus.phase -ine "Failed"))
        {
            Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($KvaLocMessage.kva_retrieving_management_cluster_details)
            $capiCluster = Get-KvaCluster -Name $global:config[$moduleName]["kvaName"] -activity $activity
        }
    }

    $status = [PSCustomObject]@{
        CapiCluster = $capiCluster
        KvaStatus = $kvaStatus
    }

    return $status
}

function Get-KvaCluster
{
    <#
    .DESCRIPTION
        Validates the requested cluster name and ensures that the cluster exists.
        Returns the cluster object.
 
    .PARAMETER Name
        Name of the cluster
 
    .PARAMETER activity
        Activity name to use when writing progress
    #>


    [CmdletBinding()]
    param (
        [Parameter()]
        [String]$Name,

        [Parameter()]
        [String]$activity
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $Name"
    }

    Write-StatusWithProgress -activity $activity -status $($KvaLocMessage.kva_retrieving_cluster) -moduleName $moduleName
    return Get-KvaClusterInternal -Name $Name
}

function Get-KvaClusterInternal
{
    <#
    .DESCRIPTION
        Validates the requested cluster name and ensures that the cluster exists.
        Returns the cluster object.
 
    .PARAMETER Name
        Name of the cluster
    #>


    param (
        [Parameter()]
        [String]$Name,

        [Parameter()]
        [String] $activity
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $Name"
    }

    $kubeconfig = Get-KvaCredential -activity $activity

    $migrationNeeded = $false
    try
    {
        $clusters = Get-KvaCapiCluster -Name $Name
        foreach($cluster in $clusters)
        {
            if ($null -eq $cluster.kubernetesversion)
            {
                $migrationNeeded = $true
                throw
            }
        }
    }
    catch
    {
        # Migration code needed for upgrades from before Sept release due to kubernetesversion being missing from Cluster object.
        # Can be removed alongside Get-CapiCluster once August is no longer supported.
        if (($_.Exception.Message -like "*unknown flag: *") -or ($migrationNeeded))
        {
            if (![string]::IsNullOrEmpty($name))
            {
                $clusters = @()
                $clusters += Get-CapiCluster -Name $Name
            }
            else
            {
                $clusters = Get-CapiClusters
            }

            $result = @()
            foreach($cluster in $clusters)
            {
                $props = [ordered]@{
                    'ProvisioningState' = $($cluster.status.phase);
                    'KubernetesVersion' = $($cluster.spec.clusterConfiguration.kubernetesVersion);
                    'PackageVersion' = $($cluster.spec.packageVersion);
                    'NodePools' = $( $($cluster.nodepools.items) | %{ $_.metadata.name } );
                    'WindowsNodeCount' = $($cluster.windowsWorkerReplicas);
                    'LinuxNodeCount' = $($cluster.linuxWorkerReplicas);
                    'ControlPlaneNodeCount' = $($cluster.spec.controlPlaneConfiguration.replicas);
                    'Name' = $($cluster.metadata.name);
                    'IsManagement' = $($cluster.spec.management);
                }
                $result += New-Object -TypeName PsObject -Property $props
            }
            return $result
        }

        # Filter Not Found exception
        if (![string]::IsNullOrEmpty($name) -and ($_.Exception.Message -like "*akshciclusters.msft.microsoft ""$name"" not found*"))
        {
            throw $([System.Exception]::new([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_no_cluster_found , $Name), $_.Exception))
        }
        throw $_
    }

    $result = @()
    foreach($cluster in $clusters) {
        $clusterName = $cluster.name
        $cmdArgs = "cluster status --clustername $clusterName --kubeconfig ""$kubeconfig"""
        $status = Invoke-KvaCtl -arguments $cmdArgs -activity $activity | ConvertFrom-Json

        # For ease, calculate default linux and windows nodepool replica counts and append them as a member of the returned object
        $linuxWorkerReplicas = 0
        $windowsWorkerReplicas = 0

        $nodePools = $cluster.nodepools

        if ($null -ne $nodePools)
        {
            $linuxNodePools = ($nodePools | Where-Object { $_.azurestackhcinodepool.osprofile.ostype -eq "Linux" })
            foreach ($np in $linuxNodePools)
            {
                $linuxWorkerReplicas += $(if ($np | Get-Member replicas) { $np.replicas } else { 0 })
            }

            $windowsNodePools = ($nodePools | Where-Object { $_.azurestackhcinodepool.osprofile.ostype -eq "Windows" })
            foreach ($np in $windowsNodePools)
            {
                $windowsWorkerReplicas += $(if ($np | Get-Member replicas) { $np.replicas } else { 0 })
            }
        }

        $statusProps = [ordered]@{
            'ProvisioningState' = $($status.phase);
            'Details' = $($status.details);
        }

        if ($null -ne $status.lasterror)
        {
            $statusProps.Insert($statusProps.Count, 'Error', $status.lasterror)

            if ($null -ne $status.reason)
            {
                $statusProps.Insert($statusProps.Count, 'Reason', $status.reason)
            }
        }

        $props = [ordered]@{
            'Status' = $($statusProps);
            'ProvisioningState' = $($status.phase);
            'KubernetesVersion' = $($cluster.kubernetesversion);
            'PackageVersion' = $($cluster.version);
            'NodePools' = $( $($cluster.nodepools) | %{ $_.name } );
            'WindowsNodeCount' = $($windowsWorkerReplicas);
            'LinuxNodeCount' = $($linuxWorkerReplicas);
            'ControlPlaneNodeCount' = $($cluster.controlplane.nodepool.replicas);
            'ControlPlaneVmSize' = $([Enum]::Parse([VmSize], $($cluster.controlplane.nodepool.azurestackhcinodepool.hardwareprofile.vmsize), $true));
            'AutoScalerEnabled' = $(if ($status | Get-Member autoscalerenabled) { $status.autoscalerenabled } else { $false });
            'AutoScalerProfile' = $(if ($status | Get-Member autoscalerprofilename) { $status.autoscalerprofilename } else { "" });
            'Name' = $($clusterName);
        }

        $result += New-Object -TypeName PsObject -Property $props
    }

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)

    return $result

}

function Get-KvaCapiCluster
{
    <#
    .DESCRIPTION
        Validates the requested cluster name and ensures that the cluster exists.
        Returns the cluster object.
 
    .PARAMETER Name
        Name of the cluster
    #>


    param (
        [Parameter()]
        [String]$Name,

        [Parameter()]
        [String] $activity

    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $Name"
    }

    Initialize-KvaEnvironment -activity $activity

    $kubeconfig = Get-KvaCredential -activity $activity

    $msg=""
    $cmdArgs=""
    if (![string]::IsNullOrEmpty($name))
    {
        Test-ValidClusterName -Name $Name | Out-Null

        $msg = "Retrieving cluster $name..."
        $cmdArgs = "cluster get --clustername=$name --kubeconfig=""$kubeconfig"""
    }
    else
    {
        $msg = "Retrieving AksHciClusters..."
        $cmdArgs = "cluster list --kubeconfig=""$kubeconfig"""
    }

    Write-StatusWithProgress -activity $activity -status $msg -moduleName $moduleName

    $clusters = Invoke-KvaCtl -arguments $cmdArgs -activity $activity | ConvertFrom-Json

    return $clusters
}

function Get-TargetClusterKubernetesVersions
{
    <#
    .DESCRIPTION
        Get the Kubernetes Versions used for the target clusters
 
    .PARAMETER Version
        Version
    #>


    $tmp = Get-TargetClusterKubernetesReferences
    return $tmp.Keys
}

function Get-TargetClusterKubernetesReferences
{
    <#
    .DESCRIPTION
        Get the Kubernetes Versions used for the target clusters
    #>


    $k8sversionsInUse = @{}
    Get-KvaClusterInternal | ForEach-Object {
        $tmp = $_
        if (!($tmp -contains 'IsManagement') -or ($null -eq $tmp.IsManagement))
        {
            $k8sversion = $tmp.KubernetesVersion
            if (!$k8sversionsInUse.ContainsKey($k8sversion))
            {
                $k8sversionsInUse += @{$k8sversion = @();}
            }
            # Add references to the target cluster name
            $k8sversionsInUse[$k8sversion] += ($tmp.Name)
        }
    }
    return $k8sversionsInUse
}

function Get-KvaClusterUpgrades
{
    <#
    .DESCRIPTION
        Gets the upgrades available for a kva cluster.
 
    .PARAMETER Name
        Name of the cluster.
 
    .PARAMETER activity
        Activity name to use when updating progress.
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String] $Name,

        [Parameter()]
        [String] $activity
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $Name"
    }

    Initialize-KvaEnvironment -activity $activity

    $kubeconfig = Get-KvaCredential -activity $activity
    if (-not (Test-Path $kubeconfig))
    {
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_missing_kubeconfig, $kubeconfig))
    }

    $cmdArgs = "cluster get-upgrades --clustername $Name --kubeconfig ""$kubeconfig"""
    $upgrades = Invoke-KvaCtl -arguments $cmdArgs -activity $activity | ConvertFrom-Json

    return $upgrades
}

function Wait-ForClusterUpgrade
{
    <#
    .DESCRIPTION
        Waits for all nodes of a cluster to be running the specified kubernetes version.
 
    .PARAMETER kubeconfigFile
        Path to a kubeconfig file for the cluster
 
    .PARAMETER expectedVersion
        The version that we expect all nodes of the cluster to be running
 
    .PARAMETER sleepDuration
        Duration to sleep for between attempts
    #>


    param (
        [String] $kubeconfigFile,
        [String] $expectedVersion,
        [int] $sleepDuration=60
    )

    Write-Status $($KvaLocMessage.kva_monitoring_cluster_upgrade) -moduleName $moduleName

    while ($true)
    {
        $results = @()
        $allNodesUpgraded = $true

        $nodes = (Invoke-Kubectl -ignoreError -kubeconfig $kubeconfigFile -arguments "get nodes --request-timeout=1m -o name") 2>$null
        if (-not ($nodes))
        {
            Write-SubStatus $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_unable_to_retrive_cluster_node_information, $sleepDuration)) -moduleName $moduleName
            Start-Sleep $sleepDuration
            continue
        }

        foreach ($node in $nodes)
        {
            $nodeJson = (Invoke-Kubectl -ignoreError -kubeconfig $kubeconfigFile -arguments $("get $node --request-timeout=1m -o json") | ConvertFrom-Json) 2>$null
            if ($nodeJson)
            {
                $result = New-Object -TypeName PsObject -Property @{Name = $($nodeJson.metadata.name); Version = $($nodeJson.status.nodeInfo.kubeletVersion); Upgraded = $true}

                if ($nodeJson.status.nodeInfo.kubeletVersion -ine $expectedVersion)
                {
                    $result.upgraded = $false
                    $allNodesUpgraded = $false
                }

                $results += $result
            }
            else
            {
                $allNodesUpgraded = $false
            }
        }

        Write-SubStatus $($KvaLocMessage.kva_current_status_of_cluster_nodes) -moduleName $moduleName
        $results | Format-Table *

        if ($allNodesUpgraded)
        {
            break
        }

        Write-SubStatus $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_cluster_nodes_still_upgrading, $sleepDuration)) -moduleName $moduleName
        Start-Sleep $sleepDuration
    }

    Write-SubStatus $($KvaLocMessage.kva_cluster_nodes_running_expected_version) -moduleName $moduleName
}

function Update-KvaCluster
{
    <#
    .DESCRIPTION
        Updates the kubernetes version of a cluster by performing an upgrade.
 
    .PARAMETER Name
        Name of the cluster.
 
    .PARAMETER nextVersion
        desired kubernetes version.
 
    .PARAMETER operatingSystem
        If specified, akshcicluster will only attempt an operating system upgrade.
 
    .PARAMETER activity
        Activity name to use when writing progress
    #>


    [CmdletBinding(PositionalBinding=$False, SupportsShouldProcess, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory=$true)]
        [String]$Name,

        [Parameter()]
        [String]$nextVersion,

        [Parameter()]
        [Switch]$operatingSystem,

        [String]$activity
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $Name"
    }

    Initialize-KvaEnvironment -activity $activity

    $cluster = Get-KvaCluster -Name $Name -activity $activity

    $kubeconfig = Get-KvaCredential -activity $activity
    if (-not (Test-Path $kubeconfig))
    {
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_missing_kubeconfig, $kubeconfig))
    }

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($KvaLocMessage.kva_determining_upgrade_path)

    $cmdArgs = "cluster upgrade --clustername $($cluster.Name) --plan --kubeconfig ""$kubeconfig"""
    if (($null -ne $nextVersion) -and ($nextVersion -ne ""))
    {
        $cmdArgs += " --kubernetes-version $nextVersion"
    }
    if ($operatingSystem.IsPresent)
    {
        $cmdArgs += " --operating-system"
    }

    $upgradePlan = Invoke-KvaCtl -arguments $cmdArgs -activity $activity
    $upgradePlan = $upgradePlan -join "`n"

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $upgradePlan
    Write-Status -moduleName $moduleName -Verbose -msg "`n$upgradePlan`n"

    if ($upgradePlan -like "*no upgrade found*")
    {
        return
    }

    if (-not $PSCmdlet.ShouldProcess($Name, "Update the managed Kubernetes cluster"))
        {
            return
        }

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($KvaLocMessage.kva_provisioning_image_gallery)
    Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_cluster_on_kubernetes_version, $cluster.KubernetesVersion))

    # We may need to re-add the same image, to pick up just OS update
    Add-GalleryImage -imageType Linux -k8sVersion $cluster.KubernetesVersion -activity $activity

    if ($cluster.WindowsNodeCount -gt 0)
    {
        Add-GalleryImage -imageType Windows -k8sVersion $cluster.KubernetesVersion -activity $activity
    }

    if ($nextVersion)
    {
        Add-GalleryImage -imageType Linux -k8sVersion $nextVersion -activity $activity

        if ($cluster.WindowsNodeCount -gt 0)
        {
            Add-GalleryImage -imageType Windows -k8sVersion $nextVersion -activity $activity
        }
    }

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($KvaLocMessage.kva_cluster_upgrade)
    $cmdArgs = "cluster upgrade --clustername $($cluster.Name) --kubeconfig ""$kubeconfig"""
    if (($null -ne $nextVersion) -and ($nextVersion -ne ""))
    {
        $cmdArgs += " --kubernetes-version $nextVersion"
    }
    if ($operatingSystem.IsPresent)
    {
        $cmdArgs += " --operating-system"
    }

    Invoke-KvaCtl -arguments $cmdArgs -showOutput -activity $activity

    try 
    {
        $rando = Get-Random
        $targetClusterKubeconfig = $($env:USERPROFILE+"\.kube\$Name-kubeconfig-$rando")
        Get-KvaClusterCredential -Name $cluster.Name -outputLocation $targetClusterKubeconfig -activity $activity 
        
        if ($nextVersion)
        {
            Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($KvaLocMessage.kva_waiting_for_upgrade_to_complete)
            Wait-ForClusterUpgrade -expectedVersion $nextVersion -kubeconfigFile $targetClusterKubeconfig
        }
    }
    finally
    {
        Remove-Item -Path $targetClusterKubeconfig -Force -ErrorAction Ignore
    }

}

function Set-KvaClusterNodeCount
{
    <#
    .DESCRIPTION
        Sets the configuration of a cluster. Currently used to scale the cluster
        control plane or to scale the worker nodes.
 
    .PARAMETER Name
        Name of the cluster
 
    .PARAMETER controlPlaneNodeCount
        The number of control plane nodes to scale to
 
    .PARAMETER controlPlaneVMSize
        The VM size of control plane nodes to scale to
 
    .PARAMETER linuxNodeCount
        The number of Linux worker nodes to scale to
 
    .PARAMETER windowsNodeCount
        The number of Windows worker nodes to scale to
 
    .PARAMETER activity
        Activity name to use when writing progress
    #>


    [CmdletBinding()]
    param (
        [Parameter()]
        [String] $Name,

        [Parameter(ParameterSetName='controlplane')]
        [ValidateSet(1,3,5)]
        [int] $controlPlaneNodeCount,

        [Parameter(ParameterSetName='controlplane')]
        [VmSize] $controlPlaneVMSize,

        [Parameter(Mandatory=$true, ParameterSetName='worker')]
        [ValidateRange(0,250)]
        [int] $linuxNodeCount,

        [Parameter(Mandatory=$true, ParameterSetName='worker')]
        [ValidateRange(0,250)]
        [int] $windowsNodeCount,

        [Parameter()]
        [String] $activity
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $Name"
    }

    Initialize-KvaEnvironment -activity $activity

    $cluster = Get-KvaCluster -Name $Name -activity $activity

    $controlPlane = $cluster.ControlPlaneNodeCount
    $linuxWorker = $cluster.LinuxNodeCount
    $windowsWorker = $cluster.WindowsNodeCount

    $kubeconfig = Get-KvaCredential -activity $activity

    if ($PSCmdlet.ParameterSetName -ieq "controlplane")
    {
        if ($PSBoundParameters.ContainsKey("controlplanenodecount") -and ($cluster.ControlPlaneNodeCount -gt 1) -and ($controlPlaneNodeCount -lt 3) -and ($controlPlaneNodeCount -ne -1))
        {
            throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_cluster_ha_min_node, $cluster.Name))
        }
        $controlPlane = $controlPlaneNodeCount

        $cmdControlPlaneArgs = "cluster scale --clustername=$Name --kubeconfig ""$kubeconfig"""
        if ($PSBoundParameters.ContainsKey("controlplanevmsize")) {
            $cmdControlPlaneArgs = $cmdControlPlaneArgs + " --controlplanevmsize=$controlPlaneVMSize"
        }
        if ($PSBoundParameters.ContainsKey("controlplanenodecount")) {
            $cmdControlPlaneArgs = $cmdControlPlaneArgs + " --controlplane=$controlPlane"
        }

        Invoke-KvaCtl -arguments $cmdControlPlaneArgs -showOutput -activity $activity
    }
    elseif ($PSCmdlet.ParameterSetName -ieq "worker")
    {
        $defaultLinuxNodepoolName = GetDefaultLinuxNodepoolName -clusterName $Name
        $defaultWindowsNodepoolName = GetDefaultWindowsNodepoolName -clusterName $Name

        $linuxNodePool = Get-KvaClusterNodePool -ClusterName $Name -Name $defaultLinuxNodepoolName
        $windowsNodePool = Get-KvaClusterNodePool -ClusterName $Name -Name $defaultWindowsNodepoolName

        if ($null -ne $windowsNodePool)
        {
            if ($windowsNodeCount -gt 0)
            {
                Add-GalleryImage -imageType Windows -k8sVersion $cluster.KubernetesVersion -activity $activity
            }

            $windowsWorker = $windowsNodeCount

            $cmdWindowsWorkerArgs = "cluster nodepool scale --clustername=$Name --nodepoolname=$defaultWindowsNodepoolName --replicas=$windowsWorker --kubeconfig ""$kubeconfig"""

            Invoke-KvaCtl -arguments $cmdWindowsWorkerArgs -showOutput -activity $activity
        }

        if ($null -ne $linuxNodePool)
        {
            $linuxWorker = $linuxNodeCount

            $cmdLinuxWorkerArgs = "cluster nodepool scale --clustername=$Name --nodepoolname=$defaultLinuxNodepoolName --replicas=$linuxWorker --kubeconfig ""$kubeconfig"""

            Invoke-KvaCtl -arguments $cmdLinuxWorkerArgs -showOutput -activity $activity
        }
    }

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
}

function Set-KvaClusterAutoScaler
{
    <#
    .DESCRIPTION
        Sets the cluster's AutoScaler configuration: either enable/disable the AutoScaler
        or change the AutoScalerProfile used to configure it.
 
    .PARAMETER Name
        Name of the cluster
 
    .PARAMETER Enable
        If true, enable the cluster autoscaler. Disable it if it's false.
 
    .PARAMETER ProfileName
        The name of the AutoScalerProfile used to configure the autoscaler.
 
    .INPUTS
        To enable/disable the autoscaler, the -Enable flag must be provided with an optional profile name. If the profile name is not provided, then the default profile will be used.
        To change the AutoScalerProfile, the -ProfileName flag must be provided and the -Enable flag must not be provided
 
    .EXAMPLE
        Set-KvaClusterAutoScaler -Name samplecluster -Enable $true -ProfileName sampleprofile
 
    .EXAMPLE
        Set-KvaClusterAutoScaler -Name samplecluster -ProfileName sampleprofile2
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [String] $Name,

        [Parameter(Mandatory=$true, ParameterSetName='toggleautoscaler')]
        [Boolean] $Enable,

        [Parameter(ParameterSetName='toggleautoscaler')]
        [Parameter(Mandatory=$true, ParameterSetName='changeautoscalerprofile')]
        [String] $ProfileName,

        [Parameter()]
        [String] $activity
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $Name"
    }

    Initialize-KvaEnvironment -activity $activity

    $kubeconfig = Get-KvaCredential -activity $activity

    $msg = ""
    $cmdArgs = ""
    $getProfCmdArgs = "autoscalerprofile get --name=$ProfileName --kubeconfig=""$kubeconfig"""

    switch ($PSCmdlet.ParameterSetName) {
        ("toggleautoscaler") {
            if ($Enable)
            {
                $autoScalerProfileArg = ""
                if (![string]::IsNullOrEmpty($ProfileName))
                {
                    $asp = Invoke-KvaCtl -arguments $getProfCmdArgs -activity $activity -ignoreError
                    if ($null -eq $asp)
                    {
                        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_autoscalerprofile_does_not_exist, $ProfileName))
                    }

                    $autoScalerProfileArg = "--profilename=$ProfileName"
                }
                $cmdArgs = "cluster autoscaler enable --clustername=$Name $autoScalerProfileArg --kubeconfig ""$kubeconfig"""
                $msg = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_enabling_cluster_autoscaler, $Name, $ProfileName))
            }
            else
            {
                $cmdArgs = "cluster autoscaler disable --clustername=$Name --kubeconfig ""$kubeconfig"""
                $msg = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_disabling_cluster_autoscaler, $Name))
            }
            break
        }
        ("changeautoscalerprofile") {
            $asp = Invoke-KvaCtl -arguments $getProfCmdArgs -activity $activity -ignoreError
            if ($null -eq $asp)
            {
                throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_autoscalerprofile_does_not_exist, $ProfileName))
            }

            $cmdArgs = "cluster autoscaler setprofile --clustername=$Name --profilename=$ProfileName --kubeconfig=""$kubeconfig"""
            $msg = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_changing_cluster_autoscalerprofile, $Name, $ProfileName))
            break
        }
    }

    Write-StatusWithProgress -activity $activity -status $msg -moduleName $moduleName

    Invoke-KvaCtl -arguments $cmdArgs -showOutput -activity $activity

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
}

function New-KvaClusterNodePool
{
    <#
    .SYNOPSIS
        Creates a new nodepool in a cluster.
 
    .DESCRIPTION
        Creates a new nodepool in a cluster.
 
    .PARAMETER ClusterName
        Name of the cluster
 
    .PARAMETER Name
        Name of the nodepool
 
    .PARAMETER Count
        The number of worker nodes in the nodepool
 
    .PARAMETER OSType
        OS type of the node pool. Defaults to Linux
 
    .PARAMETER VMSize
        The VM size to use for the worker nodes. Defaults to Standard_K8S3_v1
 
    .PARAMETER MaxPodCount
        The maximum number of pods that can run on a worker node
 
    .PARAMETER Taints
        A list of taints to put on each worker node
 
    .PARAMETER DisableAutoScaler
        Prevent the horizontal nodepool AutoScaler from managing this nodepool.
        If AutoScaler is enabled on the cluster, all new nodepools will be managed
        by it. This flag can be used to override that for the new nodepool.
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [ValidateScript({Test-ValidClusterName -Name $_ })]
        [String] $ClusterName,

        [Parameter(Mandatory=$true)]
        [ValidateScript({Test-ValidNodePoolName -Name $_ })]
        [String] $Name,

        [Parameter()]
        [int] $Count = $global:defaultWorkerNodeCount,

        [Parameter()]
        [VmSize] $VMSize = $global:defaultWorkerVmSize,

        [Parameter()]
        [OsType] $OSType = $global:defaultWorkerNodeOS,

        [Parameter()]
        [int] $MaxPodCount = 0,

        [Parameter()]
        [String[]] $Taints,

        [Parameter()]
        [Switch] $DisableAutoScaler,

        [Parameter()]
        [String] $activity
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $Name"
    }

    Initialize-KvaEnvironment -activity $activity
    $kubeconfig = Get-KvaCredential -activity $activity

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($KvaLocMessage.kva_getting_cluster_details)
    $cluster = Get-KvaCluster -Name $ClusterName  -activity $activity
    $kubernetesVersion = $cluster.KubernetesVersion

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($KvaLocMessage.kva_check_if_image_in_gallery)
    Add-GalleryImage -imageType $OSType -k8sVersion $kubernetesVersion -activity $activity

    $publicKey = Get-Content -Path (Get-SshPublicKey)
    $publicKey = $publicKey.Split(" ")
    $publicKey = $($publicKey[0]+" "+$publicKey[1])

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($KvaLocMessage.kva_creating_np_yaml)

    $yaml = $nodePoolYamlTemplate -f $Name, $Count, $VMSize, $OSType, $publicKey
    if ($MaxPodCount -gt 0)
    {
        $yaml += "`r`nmaxpodcount: $MaxPodCount"
    }
    if ($Taints.Count -gt 0)
    {
        $taintsYaml = "taints:"
        foreach ($t in $Taints) {
            $taintsYaml += "`r`n - ""$t"""
        }
        $yaml += "`r`n$taintsYaml"
    }

    $yamlFile = $($global:config[$modulename]["installationPackageDir"]+"\"+$global:yamlDirectoryName+"\$ClusterName-$Name.yaml")
    Set-Content -Path $yamlFile -Value $yaml -ErrorVariable err
    if ($null -ne $err -and $err.count -gt 0)
    {
        throw $err
    }

    $cmdArgs = "cluster nodepool create --clustername $ClusterName --nodepoolconfig ""$yamlFile"" --kubeconfig ""$kubeconfig"""
    if ($DisableAutoScaler.IsPresent)
    {
        $cmdArgs += " --disableautoscaler"
    }

    $msg = "Creating node pool $Name in cluster $ClusterName..."
    Write-StatusWithProgress -activity $activity -status $msg -moduleName $moduleName

    Invoke-KvaCtl -arguments $cmdArgs -showOutput -activity $activity

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
}

function Get-KvaClusterNodePool
{
    <#
    .SYNOPSIS
        Returns the nodepool(s) of a cluster.
 
    .DESCRIPTION
        Returns the nodepool(s) of a cluster.
 
    .PARAMETER ClusterName
        Name of the cluster
 
    .PARAMETER Name
        Name of the nodepool
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    param (
        [Parameter(Mandatory=$true)]
        [ValidateScript({Test-ValidClusterName -Name $_ })]
        [String] $ClusterName,

        [Parameter()]
        [String] $Name,

        [Parameter()]
        [String] $activity
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $Name"
    }

    Initialize-KvaEnvironment -activity $activity
    $kubeconfig = Get-KvaCredential -activity $activity

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($KvaLocMessage.kva_gathering_cluster_information)
    $cluster = Get-KvaCluster -Name $ClusterName -activity $activity
    $k8sVersion = $cluster[0].PackageVersion

    $msg=""
    $cmdArgs=""
    if (![string]::IsNullOrEmpty($name))
    {
        $msg = "Retrieving nodepool $Name from cluster $ClusterName..."
        $cmdArgs = "cluster nodepool get --clustername=$ClusterName --nodepoolname=$Name --kubeconfig=""$kubeconfig"""
    }
    else
    {
        $msg = "Retrieving cluster $ClusterName's nodepools..."
        $cmdArgs = "cluster nodepool list --clustername=$ClusterName --kubeconfig=""$kubeconfig"""
    }

    Write-StatusWithProgress -activity $activity -status $msg -moduleName $moduleName

    $nodepools = Invoke-KvaCtl -arguments $cmdArgs -activity $activity | ConvertFrom-Json

    $result = @()
    foreach($np in $nodepools) {
        $vmSize = [VmSize]::Default
        $vmSize = [Enum]::Parse([VmSize], $($np.azurestackhcinodepool.hardwareprofile.vmsize), $true)

        $statusProps = [ordered]@{
            'Phase' = $($np.status.phase);
            'Details' = $($np.status.details);
        }

        if ($null -ne $np.status.lasterror)
        {
            $statusProps.Insert($o.Count, 'Error', $np.status.lasterror)

            if ($null -ne $np.status.reason)
            {
                $statusProps.Insert($o.Count, 'Reason', $np.status.reason)
            }
        }

        $props = [ordered]@{
            'Status' = $($statusProps);
            'ClusterName' = $($ClusterName);
            'NodePoolName' = $($np.name);
            'Version' = $($k8sVersion);
            'OsType' = $($np.azurestackhcinodepool.osprofile.ostype);
            'NodeCount' = $(if ($np | Get-Member replicas) { $np.replicas } else { 0 });
            'VmSize' = $($vmSize);
            'Phase' = $($np.status.phase);
            'AutoScalerEnabled' = $(if ($np.status | Get-Member autoscalerenabled) { $np.status.autoscalerenabled } else { $false });
        }

        $n = New-Object -TypeName PsObject -Property $props
        if ($np | Get-Member maxPodCount)
        {
            $n | Add-Member -NotePropertyName 'MaxPodCount' -NotePropertyValue $np.maxPodCount
        }
        if ($np | Get-Member taints)
        {
            $n | add-member -notepropertyname 'Taints' -notepropertyvalue $np.taints
        }
        $result += $n
    }

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)

    return $result
}

function Set-KvaClusterNodePool
{
    <#
    .SYNOPSIS
        Scale a cluster's nodepool.
 
    .DESCRIPTION
        Scale a cluster's nodepool.
 
    .PARAMETER ClusterName
        Name of the cluster
 
    .PARAMETER Name
        Name of the nodepool
 
    .PARAMETER Count
        Node count to scale to
 
    .PARAMETER VMSize
        VM size of the node
 
    .PARAMETER AutoScaler
        If set to true, allow the cluster AutoScaler to horizontally
        scale this nodepool if it is enabled on the cluster. Else,
        if set to false, autoscaling will be disabled on this nodepool.
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    param (
        [Parameter(Mandatory=$true)]
        [ValidateScript({Test-ValidClusterName -Name $_ })]
        [String] $ClusterName,

        [Parameter(Mandatory=$true)]
        [ValidateScript({Test-ValidNodePoolName -Name $_ })]
        [String] $Name,

        [Parameter(ParameterSetName='scale')]
        [int] $Count,

        [Parameter(ParameterSetName='scale')]
        [VmSize] $VMSize,

        [Parameter(Mandatory=$true, ParameterSetName='autoscaler')]
        [Boolean] $AutoScaler,

        [Parameter()]
        [String] $activity
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $Name"
    }

    Initialize-KvaEnvironment -activity $activity

    $kubeconfig = Get-KvaCredential -activity $activity

    $cmdArgs = ""
    $msg = ""

    switch ($PSCmdlet.ParameterSetName) {
        ("scale") {
            Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($KvaLocMessage.kva_gathering_cluster_information)
            $cluster = Get-KvaCluster -Name $ClusterName -activity $activity
            $k8sVersion = $cluster.KubernetesVersion

            Write-StatusWithProgress -activity $activity -status $($KvaLocMessage.kva_getting_nodepool_details) -moduleName $moduleName
            $nodepool = Get-KvaClusterNodePool -ClusterName $ClusterName -Name $Name

            Write-StatusWithProgress -activity $activity -status $($KvaLocMessage.kva_check_if_image_in_gallery) -moduleName $moduleName
            Add-GalleryImage -imageType $nodepool.OsType -k8sVersion $k8sVersion -activity $activity

            $msg = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_scaling_nodepool, $Name, $ClusterName, $Count))

            $cmdArgs = "cluster nodepool scale --clustername=$ClusterName --nodepoolname=$Name --kubeconfig=""$kubeconfig"""
            if ($PSBoundParameters.ContainsKey("vmsize")) {
                $cmdArgs = $cmdArgs + " --vmsize=$VMSize"
            }
            if ($PSBoundParameters.ContainsKey("count")) {
                $cmdArgs = $cmdArgs + " --replicas=$Count"
            }
            break
        }
        ("autoscaler") {
            if ($AutoScaler)
            {
                $msg = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_enabling_nodepool_autoscaler, $Name, $ClusterName))

                $cmdArgs = "cluster nodepool autoscaler enable --clustername=$ClusterName --nodepoolname=$Name --kubeconfig=""$kubeconfig"""
            }
            else
            {
                $msg = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_disabling_nodepool_autoscaler, $Name, $ClusterName))

                $cmdArgs = "cluster nodepool autoscaler disable --clustername=$ClusterName --nodepoolname=$Name --kubeconfig=""$kubeconfig"""
            }
            break
        }
    }

    Write-StatusWithProgress -activity $activity -status $msg -moduleName $moduleName
    Invoke-KvaCtl -arguments $cmdArgs -showOutput -activity $activity

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
}

function Remove-KvaClusterNodePool
{
    <#
    .SYNOPSIS
        Delete a nodepool in a managed Kubernetes cluster.
 
    .DESCRIPTION
        Delete a nodepool in a managed Kubernetes cluster.
 
    .PARAMETER ClusterName
        Name of the cluster
 
    .PARAMETER Name
        Name of the nodepool
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    [CmdletBinding(PositionalBinding=$False, SupportsShouldProcess, ConfirmImpact = 'High')]
    param (
        [Parameter(Mandatory=$true)]
        [ValidateScript({Test-ValidClusterName -Name $_ })]
        [String] $ClusterName,

        [Parameter(Mandatory=$true)]
        [ValidateScript({Test-ValidNodePoolName -Name $_ })]
        [String] $Name,

        [Parameter()]
        [String] $activity
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $Name"
    }

    if ($PSCmdlet.ShouldProcess($Name, "Delete the node pool in the managed Kubernetes cluster"))
    {
        Initialize-KvaEnvironment -activity $activity

        $kubeconfig = Get-KvaCredential -activity $activity
        
        $msg = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_deleting_nodepool, $Name, $ClusterName))
        Write-StatusWithProgress -activity $activity -status $msg -moduleName $moduleName

        $cmdArgs = "cluster nodepool delete --clustername=$ClusterName --nodepoolname=$Name --kubeconfig=""$kubeconfig"""
        Invoke-KvaCtl -arguments $cmdArgs -showOutput -activity $activity

        Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)

        return $result
    }
}

function Remove-KvaCluster
{
    <#
    .DESCRIPTION
        Removes a cluster from the deployment.
 
    .PARAMETER Name
        Name of the cluster
 
    .PARAMETER activity
        Activity name to use when writing progress
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [ValidateScript({Test-ValidClusterName -Name $_ })]
        [String]$Name,

        [Parameter()]
        [String]$activity
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $Name"
    }

    Initialize-KvaEnvironment -activity $activity

    Get-KvaCluster -Name $Name -activity $activity | Out-Null

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($GenericLocMessage.kva_removing_cluster)
    Write-SubStatus -moduleName $moduleName $($KvaLocMessage.kva_cluster_removal_in_progress)

    $kubeconfig = Get-KvaCredential -activity $activity
    Invoke-KvaCtl -arguments "cluster delete --clustername $Name --kubeconfig ""$kubeconfig""" -showOutput -activity $activity

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
}

function Update-Kva
{
    <#
    .DESCRIPTION
        Update a the kva
 
    .PARAMETER version
        Optional version to udate to
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    [CmdletBinding()]
    param (
        [String]$version,
        [String]$activity = $MyInvocation.MyCommand.Name
    )

    Initialize-KvaEnvironment -activity $activity

    $curState = Get-ConfigurationValue -module $moduleName -type ([Type][InstallState]) -name "installState"
    if ($null -ne $curState) {
        switch ($curState) {
            ([InstallState]::Updating) {
                throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_update_in_progress, $moduleName))
                return
            }
        }
    }

    $currentVersion = Get-KvaVersion
    $currentKvaK8sVersion = $global:config[$moduleName]["kvaK8sVersion"]

    Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_current_version, $currentVersion))

    # If no version is specified, try to move to the latest
    if (!$version) {
        # If no version is specified, use the latest
        $release = Get-LatestRelease -moduleName $moduleName
        $version = $release.Version
        Set-KvaConfigValue -name "version" -value $version
        $release = Get-ProductRelease -Version $version -module $moduleName
    } else {
        if ($version -eq $currentVersion) {
            Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_already_in_expected_version, $version))
            return
        }
        $release = Get-ProductRelease -Version $version -module $moduleName
        Set-KvaConfigValue -name "version" -value $version
    }

    $workingDir = $global:config[$moduleName]["workingDir"]

    Write-StatusWithProgress -activity $activity -module $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_updating_to_version , $version))

    try {
        # Set the new K8s version
        Set-KvaConfigValue -name "kvaK8sVersion" -value ("v" +$release.CustomData.ManagementNodeImageK8sVersion)
        Set-KvaConfigValue -name "installationPackageDir" -value $([io.Path]::Combine($workingDir, $version))
        Set-KvaConfigValue -name "kubeconfig" -value $([io.Path]::Combine($workingDir, $version, $kubeconfigMgmtFile))
        Get-KvaConfigYaml | Out-Null

        Update-KvaInternal
    } catch {
        Set-KvaConfigValue -name "kvaK8sVersion" -value $currentKvaK8sVersion
        Set-KvaConfigValue -name "version" -value $currentVersion
        Set-KvaConfigValue -name "installationPackageDir" -value $([io.Path]::Combine($workingDir, $currentVersion))
        Set-KvaConfigValue -name "kubeconfig" -value $([io.Path]::Combine($workingDir, $currentVersion, $kubeconfigMgmtFile))
        Get-KvaConfigYaml | Out-Null

        Write-Warning $_.Exception.Message -ErrorAction Continue
        # Revert
        Write-StatusWithProgress -activity $activity -module $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_reverting_to_version , $currentVersion))
        Update-KvaInternal
        throw
    }
}

function Update-KvaInternal
{
    <#
    .DESCRIPTION
        Update KVA
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    param (
        [String] $activity = $MyInvocation.MyCommand.Name
    )

    Write-StatusWithProgress -activity $activity -module $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_updating_to, $(Get-KvaVersion)))

    # 1. Invalidate all gallery images
    # Gallery images are downloaded and managed by KVA. So it is okay to reset it completely for now.
    # If this assumption changes, change the logic here to just remove what KVA brings in
    Reset-GalleryImage

    New-Item -ItemType Directory -Force -Path $([io.Path]::Combine($global:config[$modulename]["installationPackageDir"], $global:yamlDirectoryName)) | Out-Null
    # 2. Update the binaries
    Get-KvaRelease -version $(Get-KvaVersion) -activity $activity

    if (Test-MultiNodeDeployment)
    {
        Get-ClusterNode -ErrorAction Stop | ForEach-Object {
            Install-KvaBinaries -nodeName $_.Name
        }
    }
    else
    {
        Install-KvaBinaries -nodeName ($env:computername)
    }

    # 3. Upgrade the management appliance
    $cluster = Get-KvaClusterInternal -Name $global:config[$moduleName]["kvaName"]

    Write-Status -moduleName $moduleName $($GenericLocMessage.kva_provisioning_image_gallery)
    Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_cluster_on_kubernetes_version, $cluster.KubernetesVersion))

    $nextVersion = $global:config[$moduleName]["kvaK8sVersion"]
    Add-GalleryImage -imageType Linux -k8sVersion $nextVersion -activity $activity

    $kubeconfig = Get-KvaCredential -activity $activity
    $version = Get-KvaVersion
    if (-not $global:config[$modulename]["insecure"])
    {
            Write-Status -moduleName $moduleName $($KvaLocMessage.kva_updating_identity)
            $clusterName = $($global:config[$modulename]["kvaName"])
            if (([version]$version) -eq ([version]$script:kvaVersionMar)) {
                Set-KvaConfigValue -name "tokenExpiryDays" -value $script:defaultTokenExpiryDays
                Set-KvaConfigValue -name "operatorTokenValidity" -value $script:defaultTokenExpiryDays
                Update-MocIdentity -name $clusterName -validityDays $global:config[$moduleName]["tokenExpiryDays"] -location $global:config[$modulename]["cloudLocation"] | Out-Null
            }
            $kvaIdentity = Invoke-MocIdentityRotate -name $clusterName -encode
            Set-KvaConfigValue -name "identity" -value $kvaIdentity
    }

    $updateConfig = Get-KvaUpdateConfigYaml
    $deploymentManifestLocation = $([io.Path]::Combine($global:config[$modulename]["installationPackageDir"], $global:cloudOperatorYaml))
    $privateKey = Get-SshPrivateKey

    # Fix for October upgrade
    Update-ConfigMapPauseImages

    Invoke-KvaCtl -arguments "upgrade --configfile ""$updateConfig"" --kubeconfig ""$kubeconfig"" --cloudop ""$deploymentManifestLocation"" --sshprivatekey ""$privateKey"" " -showOutput -activity $activity
}

function Set-KvaProxySetting
{
    param (
        [Parameter()]
        [ValidateNotNull()]
        [ProxySettings] $proxySettings
    )

    $yamlFile = $($global:config[$modulename]["installationPackageDir"]+"\yaml\proxy-update.yaml")
    $yaml = @"
networking:
  proxy:
    noproxy: "$($proxySettings.NoProxy)"
"@


    Set-Content -Path $yamlFile -Value $yaml
    if ($null -ne $err -and $err.count -gt 0)
    {
        throw $err
    }
    $kubeconfig = Get-KvaCredential -activity $activity
    Set-ProxyConfiguration -proxySettings $proxySettings -moduleName $moduleName
    Invoke-KvaCtl -arguments "set --configfile ""$yamlFile"" --kubeconfig ""$kubeconfig"""  -showOutput
}

function Add-KvaGalleryImage
{
    <#
    .DESCRIPTION
        Downloads an image and adds it to the cloud gallery.
 
    .PARAMETER kubernetesVersion
        Optional, override the default version of kubernetes that the image will use.
 
    .PARAMETER imageType
        Type of the image: Linux or Windows
 
    .PARAMETER activity
        Activity name to use when writing progress
    #>


    [CmdletBinding()]
    param (
        [Parameter()]
        [String] $kubernetesVersion,

        [Parameter()]
        [ValidateSet("Windows", "Linux")]
        [String] $imageType = "Linux",

        [Parameter()]
        [String] $activity = $MyInvocation.MyCommand.Name
    )

    Initialize-KvaEnvironment -activity $activity

    if (-not $kubernetesVersion)
    {
        $kubernetesVersion = $global:config[$modulename]["kvaK8sVersion"]
    }

    Add-GalleryImage -imagetype $imageType -k8sVersion $kubernetesVersion -activity $activity

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
}

function Get-KvaVersion {
    <#
    .DESCRIPTION
        Get the current KVA version
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    [CmdletBinding()]
    param (
        [String]$activity = $MyInvocation.MyCommand.Name
    )

    Initialize-KvaEnvironment -activity $activity

    return $global:config[$modulename]["version"]
}

function Get-KvaConfig
{
    <#
    .DESCRIPTION
        Loads and returns the current KVA configuration.
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    [CmdletBinding()]
    param (
        [String]$activity = $MyInvocation.MyCommand.Name
    )

    Import-KvaConfig -activity $activity

    Write-Status -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_getting_configuration, $moduleName))
    $global:config[$modulename]["installState"] = Get-ConfigurationValue -module $moduleName -type ([Type][InstallState]) -name "installState"
    $global:config[$modulename]["controlplaneVmSize"] = Get-ConfigurationValue -module $moduleName -type ([Type][VmSize]) -name "controlplaneVmSize"
    return $global:config[$modulename]
}

function Import-KvaConfig
{
    <#
    .DESCRIPTION
        Loads a configuration from persisted storage. If no configuration is present
        then a default configuration can be optionally generated and persisted.
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    [CmdletBinding()]
    param (
        [Switch] $createIfNotPresent,
        [String] $activity = $MyInvocation.MyCommand.Name
    )

    Write-StatusWithProgress -activity $activity -module $moduleName -status $($GenericLocMessage.comm_importing_configuration)

    if  (Test-Configuration -moduleName $moduleName)
    {
        Import-Configuration -moduleName $moduleName
    }
    else
    {
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_cannot_deploy, $moduleName))
    }

    Write-StatusWithProgress -activity $activity -module $moduleName -status $($GenericLocMessage.comm_importing_configuration_completed)
}

function Set-KvaRegistration
{
    <#
    .DESCRIPTION
        Configures KVA by persisting the specified parameters to the registry.
        Any parameter which is not explictly provided by the user will be defaulted.
 
    .PARAMETER azureResourceGroup
        azureResourceGroup is the name of the azure resource group to place arc resources.
 
    .PARAMETER azureLocation
        azureLocation is the name of the azure location where the resource group lives.
    #>


    [CmdletBinding()]
    param (
        [String] $azureResourceGroup,
        [String] $azureLocation
    )

    $kvaRegistration = Get-KvaRegistration
    if ([string]::IsNullOrWhiteSpace($kvaRegistration.azureResourceGroup))
    {
        Set-KvaConfigValue -name "azureResourceGroup" -value $azureResourceGroup
        Set-KvaConfigValue -name "azureLocation" -value $azureLocation
    }
}

function Get-KvaRegistration
{
    <#
    .DESCRIPTION
        Configures KVA by persisting the specified parameters to the registry.
        Any parameter which is not explictly provided by the user will be defaulted.
    #>


    $obj = New-Object -TypeName psobject
    $obj | Add-Member -MemberType NoteProperty -Name azureResourceGroup -Value (Get-KvaConfigValue -name "azureResourceGroup")
    $obj | Add-Member -MemberType NoteProperty -Name azureLocation -Value (Get-KvaConfigValue -name "azureLocation")

    return $obj
}

function Set-KvaConfig
{
    <#
    .DESCRIPTION
        Configures KVA by persisting the specified parameters to the registry.
        Any parameter which is not explictly provided by the user will be defaulted.
    #>


    [CmdletBinding()]
    param (
        [string] $activity = $MyInvocation.MyCommand.Name,
        [String] $kvaName = (New-Guid).Guid,
        [String] $workingDir = $global:defaultWorkingDir,
        [String] $imageDir,
        [String] $version,
        [String] $stagingShare = $global:defaultStagingShare,
        [String] $cloudLocation = $global:defaultCloudLocation,
        [Parameter(Mandatory=$true)]
        [VirtualNetwork] $vnet,
        [VmSize] $controlplaneVmSize = $global:defaultMgmtControlPlaneVmSize,
        [String] $kvaPodCIDR = $global:defaultPodCidr,
        [Switch] $kvaSkipWaitForBootstrap,
        [ProxySettings] $proxySettings = $null,
        [Switch] $skipUpdates,
        [Switch] $skipHostLimitChecks,
        [Switch] $insecure,
        [String] $macPoolStart,
        [String] $macPoolEnd,
        [switch] $useStagingShare,
        [ContainerRegistry] $containerRegistry = $null,
        [String] $catalog = $script:catalogName,
        [String] $ring = $script:ringName,
        [int] $cloudAgentPort = $global:defaultCloudAgentPort,
        [int] $cloudAgentAuthorizerPort = $global:defaultCloudAuthorizerPort,
        [String] $deploymentId = [Guid]::NewGuid().ToString(),
        [int] $tokenExpiryDays = $script:defaultTokenExpiryDays,
        [parameter(DontShow)]
        [int] $operatorTokenValidity = $global:operatorTokenValidity,
        [parameter(DontShow)]
        [int] $addonTokenValidity = $global:addonTokenValidity,
        [parameter(DontShow)]
        [Switch] $enablePreview
    )

    # Import the existing config, if any
    try {
        Import-KvaConfig -activity $activity
    } catch {}
    $currentState = Get-ConfigurationValue -module $moduleName -type ([Type][InstallState]) -name "installState"
    if ($currentState) {
        switch ($currentState) {
            ([InstallState]::NotInstalled) {
                # Fresh install
                break
            }
            Default {
                Write-Status -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_current_state, $currentState))
                throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_config_state, $moduleName, $currentState))
            }
        }
    }

    if ($enablePreview.IsPresent)
    {
        $catalog = "aks-hci-stable-catalogs-ext"
        $ring = "earlyaccesspreview"
    }

    Confirm-Configuration -useStagingShare:$useStagingShare.IsPresent -stagingShare $stagingShare

    Set-ProxyConfiguration -proxySettings $proxySettings -moduleName $moduleName
    Set-ContainerRegistryConfiguration -containerRegistry $containerRegistry

    # If okay to proceed, overwrite
    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_creating_configuration, $moduleName))

    Set-KvaConfigValue -name "workingDir" -value $workingDir
    Set-KvaConfigValue -name "manifestCache" -value ([io.Path]::Combine($workingDir, "$catalog.json"))
    New-Item -ItemType Directory -Force -Path $workingDir | Out-Null

    if (!$imageDir)
    {
        $imageDir = [io.Path]::Combine($workingDir, $global:imageDirectoryName)
        New-Item -ItemType Directory -Force -Path $workingDir | Out-Null
    }
    Set-KvaConfigValue -name "imageDir" -value $imageDir
    Set-KvaConfigValue -name "stagingShare" -value $stagingShare
    Set-KvaConfigValue -name "group" -value "clustergroup"
    Set-KvaConfigValue -name "cloudLocation" -value $cloudLocation
    Set-KvaConfigValue -name "moduleVersion" -value $moduleVersion
    Set-KvaConfigValue -name "catalog" -value $catalog
    Set-KvaConfigValue -name "ring" -value $ring
    Set-KvaConfigValue -name "deploymentId" -value $deploymentId
    Set-KvaConfigValue -name "operatorTokenValidity" -value $operatorTokenValidity
    Set-KvaConfigValue -name "addonTokenValidity" -value $addonTokenValidity

    Set-VNetConfiguration -module $moduleName -vnet $vnet

    Set-KvaConfigValue -name "controlplaneVmSize" -value $controlplaneVmSize
    Set-KvaConfigValue -name "skipUpdates" -value $skipUpdates.IsPresent
    Set-KvaConfigValue -name "insecure" -value $insecure.IsPresent
    Set-KvaConfigValue -name "useStagingShare" -value $useStagingShare.IsPresent
    Set-KvaConfigValue -name "macPoolStart" -value $macPoolStart
    Set-KvaConfigValue -name "macPoolEnd" -value $macPoolEnd
    Set-KvaConfigValue -name "kvaName" -value $kvaName
    Set-KvaConfigValue -name "kvaPodCidr" -value $kvaPodCIDR
    Set-KvaConfigValue -name "kvaSkipWaitForBootstrap" -value $kvaSkipWaitForBootstrap.IsPresent
    Set-KvaConfigValue -name "cloudAgentPort" -value $cloudAgentPort
    Set-KvaConfigValue -name "cloudAgentAuthorizerPort" -value $cloudAgentAuthorizerPort
    Set-KvaConfigValue -name "tokenExpiryDays" -value $tokenExpiryDays

    if (-not $version)
    {
        # If no version is specified, use the latest from the product catalog
        $release = Get-LatestRelease -moduleName $moduleName
        $version = $release.Version
        Set-KvaConfigValue -name "version" -value $version
    }
    else
    {
        Get-LatestCatalog -moduleName $moduleName | Out-Null # This clears the cache
        $release = Get-ProductRelease -Version $version -module $moduleName
        Set-KvaConfigValue -name "version" -value $version
    }

    $installationPackageDir = ([io.Path]::Combine($workingDir, $version))
    Set-KvaConfigValue -name "installationPackageDir" -value $installationPackageDir
    New-Item -ItemType Directory -Force -Path $installationPackageDir | Out-Null

    if (-not $release.CustomData.ManagementNodeImageK8sVersion)
    {
        throw $($KvaLocMessage.kva_k8s_version_unknown)
    }

    Set-KvaConfigValue -name "kvaK8sVersion" -value ("v" +($release.CustomData.ManagementNodeImageK8sVersion))
    Set-KvaConfigValue -name "kubeconfig" -value $([io.Path]::Combine($workingDir, $version, $kubeconfigMgmtFile))

    # Check if this is rehydration of an already running deployment
    if ((Test-Path $global:kvaCtlFullPath))
    {
        try {
            Get-Kva | Out-Null
            Set-KvaConfigValue -name "installState" -value ([InstallState]::Installed)
            Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_existing_configuration_loaded, $moduleName))
            return
        } catch {
            Write-Verbose -Message $_
        }
    }

    Set-KvaConfigValue -name "installState" -value ([InstallState]::NotInstalled)
    Save-ConfigurationDirectory -moduleName $moduleName  -WorkingDir $workingDir
    Save-Configuration -moduleName $moduleName
    Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_new_configuration_saved, $moduleName))
}

function Confirm-Configuration
{
    <#
    .DESCRIPTION
        Validate if the configuration can be used for the deployment
 
    .PARAMETER useStagingShare
        Requests a staging share to be used for downloading binaries and images (for private testing)
 
    .PARAMETER stagingShare
        The staging share endpoint to use when useStagingShare is requested
    #>


    param (
        [Switch] $useStagingShare,
        [String] $stagingShare
    )

    if ($useStagingShare.IsPresent -and [string]::IsNullOrWhiteSpace($stagingShare))
    {
        throw $($KvaLocMessage.generic_staging_share_unspecified)
    }
}

function Set-KvaConfigValue {
     <#
    .DESCRIPTION
        Persists a configuration value to the registry
 
    .PARAMETER name
        Name of the configuration value
 
    .PARAMETER value
        Value to be persisted
    #>


    param (
        [String] $name,
        [Object] $value
    )

    Set-ConfigurationValue -name $name -value $value -module $moduleName
}

function Get-KvaConfigValue
{
    <#
   .DESCRIPTION
       Persists a configuration value to the registry
 
   .PARAMETER name
       Name of the configuration value
   #>


   param (
       [String] $name
   )

   return Get-ConfigurationValue -name $name -module $moduleName
}

function Get-KvaConfigYaml
{
    <#
    .DESCRIPTION
        Sets Configuration for the management appliance.
 
    .PARAMETER kubernetesVersion
        Version of kubernetes to deploy
 
    .PARAMETER group
        The name of the group in which the vault resides
    #>


    param (
        [String]$kubernetesVersion = $global:config[$modulename]["kvaK8sVersion"],
        [String]$group = $global:config[$modulename]["group"]
    )

    $installationPackageDir = $global:config[$moduleName]["installationPackageDir"]
    $yamlFile = $($installationPackageDir+"\yaml\appliance.yaml")
    if (-not (Test-Path $yamlFile))
    {
        New-Item -ItemType File -Force -Path $yamlFile | Out-Null
    }

    # ACL the yaml so that it is only readable by administrator
    Set-SecurePermissionFile -Path $yamlFile

    $publicKey = Get-Content -Path (Get-SshPublicKey)
    $publicKey = $publicKey.Split(" ")
    $publicKey = $($publicKey[0]+" "+$publicKey[1])

    $containerRegistryUser = $global:config[$moduleName]["containerRegistryUser"]
    $containerRegistryPass = $global:config[$moduleName]["containerRegistryPass"]
    if(-not [String]::IsNullOrEmpty($containerRegistryPass))
    {
        $securePass = $containerRegistryPass | ConvertTo-SecureString -Key $global:credentialKey
        $credential = New-Object System.Management.Automation.PSCredential -ArgumentList $containerRegistryUser, $securePass
        $containerRegistryPass = $credential.GetNetworkCredential().Password
    }

    $deploymentManifestLocation = $([io.Path]::Combine($global:config[$modulename]["installationPackageDir"], $global:cloudOperatorYaml))
    $cloudFqdn = Get-CloudFqdn
    $clusterName = $($global:config[$modulename]["kvaName"])
    $cloudPort = $global:config[$moduleName]["cloudAgentPort"].ToString()
    $cloudAuthPort = $global:config[$moduleName]["cloudAgentAuthorizerPort"].ToString()
    $kvaIdentity = $global:config[$moduleName]["identity"]
    $vnet = Get-VNetConfiguration -module $moduleName
    $operatorTokenValidity = $global:config[$moduleName]["operatorTokenValidity"]
    $addonTokenValidity = $global:config[$moduleName]["addonTokenValidity"]
    
    # Decide whether to use the macpool or not
    $useMacPool = (-not [string]::IsNullOrWhiteSpace( $global:config[$modulename]["macPoolStart"]) -and -not [string]::IsNullOrWhiteSpace($global:config[$modulename]["macPoolEnd"]))
    $macPoolName = ""
    if ($useMacPool)
    {
        $macPoolName = $vnet.MacPoolName
    }

    $dnsserver = ""
    if(-Not [string]::IsNullOrEmpty($vnet.IpAddressPrefix))
    {
        foreach ($dns in $vnet.DnsServers)
        {
            if(-Not [string]::IsNullOrEmpty($dns))
            {
                $dnsserver += "`n - " +  $dns
            }
        }
    }

    $kvaRegistration = Get-KvaRegistration
    if (($kvaRegistration.azureResourceGroup -ne "") -and
    ($kvaRegistration.azureLocation -ne ""))
    {
        $azContext = Get-AzContext
        $subscriptionid = $azContext.Subscription.Id
        $tenantid = $azContext.Tenant.Id
        $resourcegroup = $global:config[$moduleName]["azureResourceGroup"]
        $location = $global:config[$moduleName]["azureLocation"]
    }

    Write-Status -moduleName $moduleName $($KvaLocMessage.kva_creating_configuration)
    $waitForBootstrap = -not (Get-ConfigurationValue -module $moduleName -Name "kvaSkipWaitForBootstrap" -type ([Type][System.Boolean]))
    $proxySettings = Get-ProxyConfiguration -moduleName $moduleName
    $configMaps = Get-KvaConfigMaps
    $kvaProductInfo = Get-KvaProductInfo
    $isHyperThreadingEnabled = Get-MocHyperThreadingEnabled

    $yaml = @"
clustername: $clusterName
kubernetesversion: $kubernetesVersion
sshauthorizedkey: $publicKey
lowprivilegekubeconfig: false
encryptsecrets: true
waitforbootstrapcompletion: $waitForBootstrap
containerregistry:
  name: $($global:config[$moduleName]["containerRegistryServer"])
  username: $containerRegistryUser
  password: $containerRegistryPass
networking:
  controlplanecidr: $($global:mgmtControlPlaneCidr)
  clustercidr: $($global:mgmtClusterCidr)
  podcidr: $($global:config[$moduleName]["kvaPodCidr"])
  proxy:
    http: "$($proxySettings.HTTP)"
    https: "$($proxySettings.HTTPS)"
    noproxy: "$($proxySettings.NoProxy)"
    certfilename: "$($proxySettings.CertName)"
deploymentmanifest:
  cloudoperatormanifestpath: $deploymentManifestLocation
  cni:
    type: calico
applianceagents:
  onboardingagent:
    subscriptionid: $subscriptionid
    tenantid: $tenantid
    resourcegroup: $resourcegroup
    location: $location
    infrastructure: azure_stack_hci
  billingagent:
    hyperthreading: $isHyperThreadingEnabled
azurestackhciprovider:
  cloudagent:
    address: $cloudFqdn
    port: $cloudPort
    authenticationport: $cloudAuthPort
    loginconfig: $kvaIdentity
    insecure: $($global:config[$moduleName]["insecure"])
  appliancevm:
    imagename: ""
    vmsize: $(Get-ConfigurationValue -module $moduleName -name "controlplaneVmSize" -type ([Type][VmSize]))
  loadbalancer:
    imagename: ""
  location: $($global:config[$modulename]["cloudLocation"])
  group: $group
  storagecontainer: $($global:cloudStorageContainer)
  virtualnetwork:
    name: "$($vnet.Name)"
    vswitchname: "$($vnet.VswitchName)"
    type: "Transparent"
    macpoolname: $macPoolName
    vlanid: $($vnet.VlanID)
    ipaddressprefix: $($vnet.IpAddressPrefix)
    gateway: $($vnet.Gateway)
    dnsservers: $dnsserver
    vippoolstart: $($vnet.VipPoolStart)
    vippoolend: $($vnet.VipPoolEnd)
    k8snodeippoolstart: $($vnet.K8snodeIPPoolStart)
    k8snodeippoolend: $($vnet.K8snodeIPPoolEnd)
  tokenvalidities:
    addontokenvalidity: $addonTokenValidity
    operatortokenvalidity: $operatorTokenValidity
"@


    if ($proxySettings.CertName)
    {
        $yaml += @"
`ncertificates:
- filename: "$($proxySettings.CertName)"
  contentb64: "$($proxySettings.CertContent)"
"@

    }

    if ($configMaps)
    {
        $yaml += "`n$configMaps"
    }

    if ($kvaProductInfo)
    {
        $yaml += "`n$kvaProductInfo"
    }

    Set-Content -Path $yamlFile -Value $yaml
    if ($null -ne $err -and $err.count -gt 0)
    {
        throw $err
    }

    Set-KvaConfigValue -name "kvaconfig" -value $yamlFile
    return $yamlFile
}

function Get-KvaUpdateConfigYaml
{
    <#
    .DESCRIPTION
        Returns a configuration file for appliance upgrade. This configuration is merged with the existing
        config stored within the appliance. It is mainly used to provide updated version information and
        config maps.
    #>


    $installationPackageDir = $global:config[$moduleName]["installationPackageDir"]
    $yamlFile = $($installationPackageDir+"\yaml\appliance-update.yaml")
    if (-not (Test-Path $yamlFile))
    {
        New-Item -ItemType File -Force -Path $yamlFile | Out-Null
    }

    $operatorTokenValidity = $global:config[$moduleName]["operatorTokenValidity"]
    $kvaIdentity = $global:config[$moduleName]["identity"]
    $kubernetesVersion = $global:config[$modulename]["kvaK8sVersion"]
    
    Write-Status -moduleName $moduleName $($KvaLocMessage.kva_creating_update_configuration)
    $yaml = @"
kubernetesversion: $kubernetesVersion
azurestackhciprovider:
  cloudagent:
    loginconfig: $kvaIdentity
  tokenvalidities:
    operatortokenvalidity: $operatorTokenValidity
"@


    $configMaps = Get-KvaConfigMaps
    if ($configMaps)
    {
        $yaml +=  "`n$configMaps"
    }

    $kvaProductInfo = Get-KvaProductInfo
    if ($kvaProductInfo)
    {
        $yaml +=  "`n$kvaProductInfo"
    }

    Set-Content -Path $yamlFile -Value $yaml
    if ($null -ne $err -and $err.count -gt 0)
    {
        throw $err
    }

    return $yamlFile
}

function Repair-KvaCerts
{
    <#
    .DESCRIPTION
        Attempts to repair failed TLS to cloudagent
 
    .PARAMETER Name
        Name of the cluster to fix certs on
 
    .PARAMETER sshPrivateKeyFile
        Kubeconfig for the cluster the node belongs to
 
    .PARAMETER force
        Force repair(without checks)
 
    .PARAMETER patchLoadBalancer
        PatchLoadBalancerCerts
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>

    [CmdletBinding(DefaultParameterSetName = 'Default')]
    param (
        [Parameter(ParameterSetName='target',Mandatory=$true)]
        [String] $Name,

        [Parameter()]
        [String] $sshPrivateKeyFile,

        [Parameter()]
        [Switch] $force,

        [Parameter(ParameterSetName='target')]
        [Switch] $patchLoadBalancer,

        [Parameter()]
        [String] $activity
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $Name"
    }
    Write-Status -moduleName $moduleName $($KvaLocMessage.kva_repair)
    Initialize-KvaEnvironment -activity $activity
    
    $version = Get-KvaVersion
    $kubeconfig = Get-KvaCredential -activity $activity
    $privateKey = Get-SshPrivateKey
    if ($PSCmdlet.ParameterSetName -ieq "target")
    {
        $group = $("$global:cloudGroupPrefix-$Name")
        If ($null -eq $Name) {
            throw $($KvaLocMessage.kva_clustername_missing)
        }
        $tags = "Group=$group"
        if ($patchLoadBalancer.IsPresent) {
            $tags += ",PatchLoadBalancer=true"
        }
        Invoke-KvaCtl -arguments "cluster repair --clustername $Name --kubeconfig ""$kubeconfig"" --sshprivatekey ""$privateKey"" --tags ""$tags"" --force=$force" -showOutput -activity $activity
        return
    }

    $clusterName = $($global:config[$modulename]["kvaName"])
    $group = $global:config[$moduleName]["group"]
    $installationPackageDir = $global:config[$moduleName]["installationPackageDir"]
    $repairConfig = $($installationPackageDir+"\yaml\appliance-repair.yaml")
    if (-not (Test-Path $repairConfig))
    {
        New-Item -ItemType File -Force -Path $repairConfig | Out-Null
    }
    if (-not $global:config[$modulename]["insecure"])
    {
        Write-Status -moduleName $moduleName $($KvaLocMessage.kva_updating_identity)
        $clusterName = $($global:config[$modulename]["kvaName"])
        $kvaIdentity = Invoke-MocIdentityRotate -name $clusterName -encode
        Set-KvaConfigValue -name "identity" -value $kvaIdentity
    }
    $kvaIdentity = $global:config[$moduleName]["identity"]
    $cloudPort = $global:config[$moduleName]["cloudAgentPort"].ToString()
    $cloudAuthPort = $global:config[$moduleName]["cloudAgentAuthorizerPort"].ToString()
    $group = $global:config[$modulename]["group"]
    Write-Status -moduleName $moduleName $($KvaLocMessage.kva_creating_repair_configuration)
    $yaml = @"
clustername: $clusterName
azurestackhciprovider:
  cloudagent:
    loginconfig: $kvaIdentity
    port: $cloudPort
    authenticationport: $cloudAuthPort
    insecure: $($global:config[$moduleName]["insecure"])
  location: $($global:config[$modulename]["cloudLocation"])
  group: $group
  storagecontainer: $($global:cloudStorageContainer)
"@


    Set-Content -Path $repairConfig -Value $yaml
    if ($null -ne $err -and $err.count -gt 0)
    {
        throw $err
    }

    Invoke-KvaCtl -arguments "repair --configfile ""$repairConfig"" --kubeconfig ""$kubeconfig"" --sshprivatekey ""$privateKey"" --tags ""Group=$group"" --force=$force" -showOutput -activity $activity
}


function Get-KvaLogs
{
    <#
    .DESCRIPTION
        Collects all the logs from the deployment
 
    .PARAMETER Path
        Path to store the logs
 
    .PARAMETER activity
        Activity name to use when writing progress
    #>


    [CmdletBinding()]
    param (
        [Parameter()]
        [String]$path,

        [Parameter()]
        [String] $activity = $MyInvocation.MyCommand.Name
    )

    $logDir = [io.Path]::Combine($path, $moduleName)
    New-Item -ItemType Directory -Force -Path $logDir | Out-Null

    Initialize-KvaEnvironment -activity $activity

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($GenericLocMessage.comm_collecting_configuration)
    $global:config[$moduleName] > $logDir"\KvaConfig.txt"

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($GenericLocMessage.comm_collecting_module_info)
    Get-Command -Module Kva | Sort-Object -Property Source > $($logDir+"\moduleinfo.txt")

    if (Test-Path -Path $script:logFilePath)
    {
        Copy-Item -Path $script:logFilePath -Destination $logDir
    }

    if (Test-Path -Path $global:config[$moduleName]["kubeconfig"])
    {
        Write-SubStatus -moduleName $moduleName $($GenericLocMessage.comm_collecting_kubernetes_logs)

        try
        {
            $clusters = Invoke-Kubectl -arguments $("get akshciclusters -o json") | ConvertFrom-Json

            foreach ($cluster in $clusters.items)
            {
                $clusterName = $cluster.metadata.name
                Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_collecting_logs_for_cluster, $clusterName))

                if ($clusterName -ine $global:config[$moduleName]["kvaName"])
                {
                    $rando = Get-Random
                    $kubeconfigFileLocation = $($env:USERPROFILE+"\.kube\$Name-kubeconfig-$rando")
                    Get-KvaClusterCredential -Name $clusterName -outputLocation $kubeconfigFileLocation
                }
                else
                {
                    $kubeconfigFileLocation = Get-KvaCredential
                }

                Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_collecting_cluster_logs, $clusterName))
                try{
                    Invoke-Kubectl -kubeconfig $kubeconfigFileLocation -arguments "get all,pvc,pv,sc -A -o yaml" > "${logDir}\worker_objects_$clusterName.json"
                    $clusterLogDirectory = [io.Path]::Combine($logDir, "clusterlogs_$clusterName")
                    Invoke-Kubectl -kubeconfig $kubeconfigFileLocation -arguments $("cluster-info dump -A --output-directory=`"$clusterLogDirectory`"") | Out-Null
                }catch{}

                if ($clusterName -ine $global:config[$moduleName]["kvaName"])
                {
                    Remove-Item -Path $kubeconfigFileLocation -Force -ErrorAction Ignore
                }
            }
        }
        catch [Exception]
        {
            Write-Status -moduleName $moduleName  -msg $($GenericLocMessage.generic_exception)
            Write-SubStatus -moduleName $moduleName  -msg $_.Exception.Message.ToString()
        }
    }
    else
    {
        Write-SubStatus -moduleName $moduleName $($GenericLocMessage.comm_skipping_log_collection)
    }

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
}

function Get-KvaEventLog
{
    <#
    .DESCRIPTION
        Gets all the event logs from Kva Module
    #>


    Get-WinEvent -ProviderName $moduleName -ErrorAction Ignore
}

function Repair-KvaCluster
{
    <#
    .DESCRIPTION
        Attempts to repair failed TLS on a cluster by reprovisioning control plane certs
 
    .PARAMETER Name
        Name of the node to reprovision certs on
 
    .PARAMETER sshPrivateKeyFile
        Kubeconfig for the cluster the node belongs to
 
    .PARAMETER fixCertificates
        If specified, will attempt to reprovision control plane certs to fix TLS errors
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [String] $Name,

        [Parameter()]
        [String] $sshPrivateKeyFile,

        [Parameter()]
        [Switch] $fixCertificates,

        [Parameter()]
        [String] $activity
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $Name"
    }

    Initialize-KvaEnvironment -activity $activity

    if ($fixCertificates.IsPresent) {
        $kvaConfig = Get-KvaConfigYaml
        Invoke-KvaCtl -arguments "cluster addons certs repair --cluster-name $Name --sshprivatekey ""$sshPrivateKeyFile"" --configfile ""$kvaConfig"" " -showOutput -activity $activity
    }
}

function Set-ContainerRegistryConfiguration
{
    <#
    .DESCRIPTION
        Sets the container registry configuration
 
    .PARAMETER containerRegistry
        Container registry settings
    #>


    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')]
    param (
        [Parameter()]
        [ContainerRegistry] $containerRegistry
    )

    $server = ""
    $user = ""
    $pass = ""

    if ($containerRegistry)
    {
        $server = $containerRegistry.Server
        if ($containerRegistry.Credential.Username)
        {
            $user = $containerRegistry.credential.UserName
        }
        if ($containerRegistry.Credential.Password)
        {
            $pass = $containerRegistry.credential.Password | ConvertFrom-SecureString -Key $global:credentialKey
        }
    }
    else
    {
        $server = $script:defaultContainerRegistryServer
        $user = $script:defaultContainerRegistryU

        $ss = ConvertTo-SecureString $script:defaultContainerRegistryP -AsPlainText -Force
        $pass = $ss | ConvertFrom-SecureString -Key $global:credentialKey
    }

    Set-ConfigurationValue -name "containerRegistryServer" -value $server -module $moduleName
    Set-ConfigurationValue -name "containerRegistryUser" -value $user -module $moduleName
    Set-ConfigurationValue -name "containerRegistryPass" -value $pass -module $moduleName
}

#endregion

#region Billing
function Sync-KvaBilling
{
    <#
    .DESCRIPTION
        Helper function to sync billing.
 
    .PARAMETER activity
        Activity name to use when writing progress
    #>


    [CmdletBinding()]
    param (
        [Parameter()]
        [String] $activity = $MyInvocation.MyCommand.Name
    )

    Initialize-KvaEnvironment -activity $activity

    $kubeconfig = Get-KvaCredential -activity $activity

    try
    {
        $syncResult = Invoke-KvaCtl -arguments "cluster addons billing sync --kubeconfig ""$kubeconfig""" -activity $activity
    }
    catch [Exception]
    {
        Write-ModuleEventLog -moduleName $moduleName -entryType Error -eventId 100 -message "$activity - $_"
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_sync_billing_failed, $_))
    }

    return $syncResult
}

function Get-KvaBillingRecords
{
    <#
    .DESCRIPTION
        Helper function to list billing records.
 
    .PARAMETER outputformat
        Output format of result
 
    .PARAMETER activity
        Activity name to use when writing progress
    #>

    
    [CmdletBinding()]
    param (
        [Parameter()]
        [String] $outputformat = "json",

        [Parameter()]
        [String] $activity = $MyInvocation.MyCommand.Name
    )

    Initialize-KvaEnvironment -activity $activity

    $kubeconfig = Get-KvaCredential -activity $activity

    try
    {
        $recordsResult = Invoke-KvaCtl -arguments "cluster addons billing get-records --kubeconfig ""$kubeconfig"" --outputformat=$outputformat" -activity $activity
    }
    catch [Exception]
    {
        Write-ModuleEventLog -moduleName $moduleName -entryType Error -eventId 100 -message "$activity - $_"
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_get_records_failed, $_))
    }

    return $recordsResult
}

function Get-KvaBillingStatus
{
    <#
    .DESCRIPTION
        Helper function to get billing status.
 
    .PARAMETER outputformat
        Output format of result
 
    .PARAMETER activity
        Activity name to use when writing progress
    #>

    
    [CmdletBinding()]
    param (
        [Parameter()]
        [String] $outputformat = "json",

        [Parameter()]
        [String] $activity = $MyInvocation.MyCommand.Name
    )

    Initialize-KvaEnvironment -activity $activity

    $yamlFile = $global:config[$moduleName]["kubeconfig"]

    try
    {
        $statusResult = Invoke-KvaCtl -arguments "cluster addons billing get-status --kubeconfig ""$yamlFile"" --outputformat=$outputformat" -activity $activity
    }
    catch [Exception]
    {
        Write-ModuleEventLog -moduleName $moduleName -entryType Error -eventId 100 -message "$activity - $_"
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_billing_failed, $_))
    }

    return $statusResult
}
#endregion

#region Installation and Provisioning functions

function Install-KvaInternal
{
    <#
    .DESCRIPTION
        The main deployment method for KVA. This function is responsible for provisioning files,
        deploying the agents.
 
    .PARAMETER activity
        Activity name to use when writing progress
    #>


    param (
        [String] $activity = $MyInvocation.MyCommand.Name
    )

    Set-KvaConfigValue -name "installState" -value ([InstallState]::Installing)
    try
    {
        Get-KvaRelease -version $(Get-KvaVersion) -activity $activity

        if (Test-MultiNodeDeployment)
        {
            Get-ClusterNode -ErrorAction Stop | ForEach-Object {
                Install-KvaBinaries -nodeName $_.Name
            }
        }
        else
        {
            Install-KvaBinaries -nodeName ($env:computername)
        }

        if (-not $global:config[$modulename]["insecure"])
        {
            Write-Status -moduleName $moduleName $($KvaLocMessage.kva_creating_identity)
            $clusterName = $($global:config[$modulename]["kvaName"])
            $kvaIdentity = New-MocIdentity -name $clusterName -validityDays $global:config[$moduleName]["tokenExpiryDays"] -fqdn $cloudFqdn -location $global:config[$modulename]["cloudLocation"] -port $cloudPort -authport $cloudAuthPort -encode
            Set-KvaConfigValue -name "identity" -value $kvaIdentity
            New-MocRoleAssignmentWhenAvailable -identityName $clusterName -roleName "IdentityContributor" | Out-Null
            New-MocRoleAssignmentWhenAvailable -identityName $clusterName -roleName "RoleContributor" | Out-Null
            New-MocRoleAssignmentWhenAvailable -identityName $clusterName -roleName "CertificateReader" | Out-Null
            New-MocRoleAssignmentWhenAvailable -identityName $clusterName -roleName "VipPoolReader" -location $global:config[$modulename]["cloudLocation"] | Out-Null
            New-MocRoleAssignmentWhenAvailable -identityName $clusterName -roleName "GroupContributor" -location $global:config[$modulename]["cloudLocation"] | Out-Null
            New-MocRoleAssignmentWhenAvailable -identityName $clusterName -roleName "KeyVaultContributor" -location $global:config[$modulename]["cloudLocation"] | Out-Null
            New-MocRoleAssignmentWhenAvailable -identityName $clusterName -roleName "VirtualNetworkContributor" -location $global:config[$modulename]["cloudLocation"] | Out-Null
            New-MocRoleAssignmentWhenAvailable -identityName $clusterName -roleName "LBContributor" -location $global:config[$modulename]["cloudLocation"] | Out-Null
            New-MocRoleAssignmentWhenAvailable -identityName $clusterName -roleName "NetworkInterfaceContributor" -location $global:config[$modulename]["cloudLocation"] | Out-Null
            New-MocRoleAssignmentWhenAvailable -identityName $clusterName -roleName "VMContributor" -location $global:config[$modulename]["cloudLocation"] | Out-Null
            New-MocRoleAssignmentWhenAvailable -identityName $clusterName -roleName "SecretContributor" -location $global:config[$modulename]["cloudLocation"] | Out-Null
        }
        
        $yamlFile = Get-KvaConfigYaml -kubernetesVersion $global:config[$modulename]["kvaK8sVersion"] -group $global:config[$modulename]["group"]
        $kubeconfig = $global:config[$modulename]["kubeconfig"]

        Invoke-KvaCtlWithAzureContext -arguments "create --configfile ""$yamlFile"" --outfile ""$kubeconfig"" " -showOutputAsProgress -activity $activity
    }
    catch
    {
        Set-KvaConfigValue -name "installState" -value ([InstallState]::InstallFailed)
        throw $_
    }

    Set-SecurePermissionFile -Path $kubeconfig
    Set-KvaConfigValue -name "installState" -value ([InstallState]::Installed)
    Write-Status -moduleName $moduleName $($KvaLocMessage.kva_installation_complete)
}

function Get-KvaRelease
{
    <#
    .DESCRIPTION
        Download a KVA release (e.g. package, images, etc)
 
    .PARAMETER version
        Release version
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String] $version,

        [Parameter()]
        [String] $activity = $MyInvocation.MyCommand.Name
    )

    Write-StatusWithProgress -activity $activity -module $moduleName -status $($KvaLocMessage.kva_discovering_release)
    $productRelease = Get-ProductRelease -version $version -moduleName $moduleName

    if (-not $productRelease.CustomData.ManagementNodeImageK8sVersion)
    {
        throw $($KvaLocMessage.kva_k8s_version_unknown)
    }

    $k8sVersion = $productRelease.CustomData.ManagementNodeImageK8sVersion
    Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_required_kubernetes_version, $k8sVersion))

    Add-GalleryImage -imageType "Linux" -k8sVersion $k8sVersion -activity $activity
    Write-SubStatus -moduleName $moduleName $($KvaLocMessage.kva_management_cluster_provisioned)

    Get-ReleaseContent -name $script:productName -version $version -activity $activity

    if (-not ($global:config[$modulename]["useStagingShare"]))
    {
        Test-AuthenticodeBinaries -workingDir $global:config[$moduleName]["installationPackageDir"] -binaries $script:kvaBinaries
    }
}

function Get-KvaConfigMaps
{
    <#
    .DESCRIPTION
        Retrieve KVA config maps
    #>


    Write-Status -moduleName $moduleName $($KvaLocMessage.kva_get_configmaps)

    if ([string]::IsNullOrWhiteSpace($global:config[$modulename]["version"]))
    {
        throw $($KvaLocMessage.kva_unable_to_fetch_version)        
    }
       
    $productRelease = Get-ProductRelease -version $global:config[$modulename]["version"] -moduleName $moduleName

    $yaml = @"
configmaps:
  - name: $script:productInfoMapName
    namespace: $script:productInfoMapNamespace
    data:
      offer: "$($productRelease.ProductName)"
      version: "$($global:config[$modulename]["version"])"
      catalog: "$($global:config[$modulename]["catalog"])"
      audience: "$($global:config[$modulename]["ring"])"
      deploymentid: "$($global:config[$modulename]["deploymentId"])"
"@


    return $yaml
}

function Get-KvaProductInfo
{
    <#
    .DESCRIPTION
        Retrieve KVA product details
    #>


    Write-Status -moduleName $moduleName $($KvaLocMessage.kva_get_product_details)

    if ([string]::IsNullOrWhiteSpace($global:config[$modulename]["version"]))
    {
        throw $($KvaLocMessage.kva_unable_to_fetch_version)
    }

    $productRelease = Get-ProductRelease -version $global:config[$modulename]["version"] -moduleName $moduleName

    $yaml = @"
product:
      name: "$($productRelease.ProductName)"
      version: "$($global:config[$modulename]["version"])"
      catalog: "$($global:config[$modulename]["catalog"])"
      audience: "$($global:config[$modulename]["ring"])"
      deploymentid: "$($global:config[$modulename]["deploymentId"])"
"@


    return $yaml
}

function Get-ReleaseContent
{
    <#
    .DESCRIPTION
        Download all required files and packages for the specified release
 
    .PARAMETER name
        Release name
 
    .PARAMETER version
        Release version
 
    .PARAMETER destination
        Destination directory for the content
 
    .PARAMETER activity
        Activity name to use when writing progress
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String] $name,

        [Parameter(Mandatory=$true)]
        [String] $version,

        [Parameter()]
        [string] $destination = $global:config[$moduleName]["installationPackageDir"],

        [Parameter()]
        [String] $activity = $MyInvocation.MyCommand.Name

    )

    $destination = $destination -replace "\/", "\"

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_discovering_release_content, $name))
    $productRelease = Get-ProductRelease -version $version -moduleName $moduleName

    # find requested release
    foreach($releaseStream in $productRelease.ProductStreamRefs)
    {
        foreach($subProductRelease in $releaseStream.ProductReleases)
        {
            if ($subProductRelease.ProductName -ieq $name)
            {
                $versionManifestPath = [io.Path]::Combine($destination, $("$name-release.json"))

                Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_downloading_release_content_to, $name, $destination))

                $downloadParams = Get-ReleaseDownloadParameters -name $subProductRelease.ProductStream -version $subProductRelease.Version -destination $destination -parts 3 -moduleName $moduleName
                $releaseInfo = Get-DownloadSdkRelease @downloadParams

                if (-not ($global:config[$moduleName]["useStagingShare"]))
                {
                    if ($releaseInfo.Files.Count -ne 1)
                    {
                        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_wrong_release_files_count, $name, $releaseInfo.Files.Count))
                    }

                    $packagename = $releaseInfo.Files[0] -replace "\/", "\"

                    # Temporary until cross-platform signing is available
                    $auth = Get-AuthenticodeSignature -filepath $packagename
                    if (($global:expectedAuthResponse.status -ne $auth.status) -or ($auth.SignatureType -ne $global:expectedAuthResponse.SignatureType))
                    {
                        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_authenticode_failed, $global:expectedAuthResponse.status, $global:expectedAuthResponse.SignatureType, $auth.status, $auth.SignatureType))
                    }

                    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_expanding_package, $name, $packagename, $destination))
                    $expandoutput = expand.exe -r $packagename $destination -f:*
                    Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_expand_result, $expandoutput))
                }

                $versionJson = $subProductRelease | ConvertTo-Json -depth 100
                set-content -path $versionManifestPath -value $versionJson -encoding UTF8

                return
            }
        }
    }

    throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_no_release_content, $name, $version))
}

function Initialize-KvaEnvironment
{
    <#
    .DESCRIPTION
        Executes steps to prepare the environment for day 0 operations.
 
    .PARAMETER createConfigIfNotPresent
        Whether the call should create a new deployment configuration if one is not already present.
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    param (
        [Switch]$createConfigIfNotPresent,
        [String]$activity = $MyInvocation.MyCommand.Name
    )

    Get-MocConfig -activity $activity | Out-Null

    Write-StatusWithProgress -activity $activity -module $moduleName -status $($GenericLocMessage.comm_discovering_configuration)
    Import-KvaConfig -createIfNotPresent:($createConfigIfNotPresent.IsPresent) -activity $activity

    Write-StatusWithProgress -activity $activity -module $moduleName -status $($GenericLocMessage.comm_applying_configuration)
    Initialize-Environment -checkForUpdates:$false -moduleName $script:moduleName
}

function Install-KvaBinaries
{
    <#
    .DESCRIPTION
        Copies KVA binaries to a node
 
    .PARAMETER nodeName
        The node to execute on.
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$nodeName
    )
    Install-Binaries -module $moduleName -nodeName $nodeName -binariesMap $kvaBinariesMap
}

function Uninstall-KvaBinaries
{
    <#
    .DESCRIPTION
        Copies KVA binaries to a node
 
    .PARAMETER nodeName
        The node to execute on.
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$nodeName
    )

    Uninstall-Binaries -module $moduleName -nodeName $nodeName -binariesMap $kvaBinariesMap

    # Cleanup log files associated with kva binary
    Invoke-Command -ComputerName $nodeName -ScriptBlock {
        $tmpFile = $args[0]
        if (Test-Path -Path $tmpFile)
        {
            Remove-Item -Path $tmpFile -Force -ErrorAction Ignore
        }
    } -ArgumentList $script:logFilePath
}

#endregion

#region Verification Functions

function Test-KvaConfiguration
{
    <#
    .DESCRIPTION
        A basic sanity test to make sure that a node is ready to deploy kubernetes.
 
    .PARAMETER nodeName
        The node to execute on.
    #>


    param (
        [String]$nodeName
    )

    Test-KvaInstallation -nodeName $nodeName
}

function Test-KvaInstallation
{
    <#
    .DESCRIPTION
        Sanity checks an installation to make sure that all expected binaries are present.
 
    .PARAMETER nodeName
        The node to execute on.
    #>


    param (
        [String]$nodeName
    )

    Write-SubStatus -moduleName $moduleName $($GenericLocMessage.generic_testing_expected_binaries)
    Test-Binary -nodeName $nodeName -binaryName $global:kvaCtlFullPath
    Test-Binary -nodeName $nodeName -binaryName $global:kubeCtlFullPath
}

function Get-KvaLatestVersion
{
    <#
    .DESCRIPTION
        Get the current KVA release version
 
    .PARAMETER activity
        Activity name to use when writing progress
    #>


    param (
        [Parameter()]
        [String] $activity = $MyInvocation.MyCommand.Name
    )

    Write-StatusWithProgress -activity $activity -module $moduleName -status $($GenericLocMessage.comm_discovering_latest_version)
    $catalog = Get-LatestCatalog -moduleName $moduleName
    $productRelease = $catalog.ProductStreamRefs[0].ProductReleases[0]

    # find latest kva release
    foreach($subProductStream in $productRelease.ProductStreamRefs)
    {
        foreach($subProductRelease in $subProductStream.ProductReleases)
        {
            if ($subProductRelease.ProductName -ieq $script:productName)
            {
                return $subProductRelease.Version
            }
        }
    }

    throw $($KvaLocMessage.kva_unknown_kva_version)
}

function Reset-GalleryImage
{
    <#
    .DESCRIPTION
        Resets all gallery image
    #>


    Write-Status -moduleName $moduleName $($GenericLocMessage.comm_resetting_galleryimage)
    # 1. Invalidate all gallery images
    # Gallery images are downloaded and managed by KVA. So it is okay to reset it completely for now.
    # If this assumption changes, change the logic here to just remove what KVA brings in
    Reset-MocGalleryImage -location $global:config[$modulename]["cloudLocation"]

    # 2. Cleanup all vhd files
    $imagepath = $global:config[$moduleName]["imageDir"]
    Get-ChildItem -Path $imagepath -Filter *.vhdx | ForEach-Object {
        $tmpFile = $_.FullName
        Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_removing_image, $tmpFile))
        Remove-Item -Path $tmpFile -Force -ErrorAction Ignore
    }

    # 3. Cleanup all json manifest cache files
    Get-ChildItem -Path $imagepath -Filter *.json | ForEach-Object {
        $tmpFile = $_.FullName
        Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_removing_image_cache, $tmpFile))
        Remove-Item -Path $tmpFile -Force -ErrorAction Ignore
    }
}

function Add-GalleryImage
{
    <#
    .DESCRIPTION
        Checks the gallery to see if the requested image is present. If it is missing, this function downloads
        the missing image and adds it to the gallery.
 
    .PARAMETER imageName
        Name of the gallery image to provision
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    param (
        # [Parameter(Mandatory=$true)]
        # [String] $imageName,

        [Parameter(Mandatory=$true)]
        [ValidateSet("Windows", "Linux")]
        [String] $imageType,

        [Parameter(Mandatory=$true)]
        [String] $k8sVersion,

        [Parameter()]
        [String] $activity = $MyInvocation.MyCommand.Name
    )

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($GenericLocMessage.comm_provisioning_galleryimage)

    $imageName = Get-KubernetesGalleryImageName -imagetype $imageType -k8sVersion $k8sVersion

    Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_requested_image, $imageName))
    Write-SubStatus -moduleName $moduleName $($KvaLocMessage.kva_checking_existing_gallery_image)
    $galleryImage = $null
    $mocLocation = $global:config[$modulename]["cloudLocation"]

    try {
        $galleryImage = Get-MocGalleryImage -name $imageName -location $mocLocation
    } catch {}

    if ($null -ine $galleryImage)
    {
        Write-SubStatus -moduleName $moduleName $($GenericLocMessage.comm_image_already_present_in_gallery)
        return
    }

    Write-StatusWithProgress -activity $activity -module $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_obtain_download_information_for_image, $imageName))
    $imageRelease = Get-ImageReleaseManifest -imageVersion $(Get-KvaVersion) -operatingSystem $imageType -k8sVersion $k8sVersion

    Write-StatusWithProgress -activity $activity -module $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_downloading_image, $imageName))
    $result = Get-ImageRelease -imageRelease $imageRelease -imageDir $global:config[$moduleName]["imageDir"]

    Write-StatusWithProgress -activity $activity -module $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_adding_image_to_cloud_gallery, $imageName))
    New-MocGalleryImage -name $imageName -location $mocLocation -imagePath $result -container $global:cloudStorageContainer  | Out-Null
}

function Invoke-KvaCtl
{
    <#
    .DESCRIPTION
        Executes a KVACTL command.
 
    .PARAMETER arguments
        Arguments to pass to the command.
 
    .PARAMETER ignoreError
        Optionally, ignore errors from the command (don't throw).
 
    .PARAMETER showOutput
        Optionally, show live output from the executing command.
 
    .PARAMETER activity
        Activity name to use when updating progress.
    #>


    param (
        [string] $arguments,
        [switch] $ignoreError,
        [switch] $showOutput,
        [string] $activity = $MyInvocation.MyCommand.Name
    )

    return Invoke-CommandLine -command $kvaCtlFullPath -arguments $("$arguments") -ignoreError:$ignoreError.IsPresent -showOutputAsProgress:$showOutput.IsPresent -progressActivity $activity -moduleName $moduleName
}

function Invoke-KvaCtlWithAzureContext
{
    <#
    .DESCRIPTION
        Executes a command and optionally ignores errors.
 
    .PARAMETER arguments
        Arguments to pass to the command.
 
    .PARAMETER ignoreError
        Optionally, ignore errors from the command (don't throw).
 
    .PARAMETER showOutput
        Optionally, show live output from the executing command.
 
    .PARAMETER showOutputAsProgress
        Optionally, show output from the executing command as progress bar updates.
 
    .PARAMETER credential
        credential is a PSCredential holding a user's Service Principal.
 
    .PARAMETER activity
        The activity name to display when showOutputAsProgress was requested.
    #>


    [CmdletBinding()]
    param (
        [Parameter()]
        [String]$command,

        [Parameter()]
        [String]$arguments,

        [Parameter()]
        [Switch]$ignoreError,

        [Parameter()]
        [Switch]$showOutput,

        [Parameter()]
        [Switch]$showOutputAsProgress,

        [Parameter()]
        [PSCredential] $credential,

        [Parameter()]
        [string] $activity = $MyInvocation.MyCommand.Name
    )

    $obj = New-Object -TypeName psobject

    if ($null -ne $credential)
    {
        $obj | Add-Member -MemberType NoteProperty -Name clientId -Value $credential.GetNetworkCredential().UserName
        $obj | Add-Member -MemberType NoteProperty -Name clientSecret -Value $credential.GetNetworkCredential().Password
    }
    else
    {
        $armAccessToken = Get-AzAccessToken
        $graphAccessToken = Get-GraphAccessToken

        $obj | Add-Member -MemberType NoteProperty -Name armAccessToken -Value $armAccessToken.Token
        $obj | Add-Member -MemberType NoteProperty -Name graphAccessToken -Value $graphAccessToken.Token

        $azContext = Get-AzContext
        # If account type is ServicePrincipal, clientSecret has to be pulled in from AzContext.
        if ($azContext.Account.Type -eq "ServicePrincipal")
        {
            $obj | Add-Member -MemberType NoteProperty -Name clientId -Value $azContext.Account.Id
            if ($azContext.Account.ExtendedProperties.ServicePrincipalSecret)
            {
                $obj | Add-Member -MemberType NoteProperty -Name clientSecret -Value $azContext.Account.ExtendedProperties.ServicePrincipalSecret
            }
            else
            {
                # Try to load the credential from config
                $azContextConfig = [io.Path]::Combine($env:USERPROFILE, ".Azure", "AzureRmContext.json")
                if (!(Test-Path $azContextConfig))
                {
                    throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_unable_to_get_secrets))
                }
                $tmp = Get-Content  $azContextConfig | ConvertFrom-Json
                # Assumption is that Value would have array count more than 0, if it exists
                $tmpsecret = $tmp.Contexts.psobject.Members.Value[0].Account.ExtendedProperties.ServicePrincipalSecret
                if ($tmpsecret)
                {
                    $obj | Add-Member -MemberType NoteProperty -Name clientSecret -Value $tmpsecret
                }
                else
                {
                    # this would be empty.
                    # Adding a fail fast code here until we fix the logic to read the creds properly
                    throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_unable_to_get_secrets))
                }
            }
        }
        # Else,
        # it is a user context, and the armAccessToken/graphAccessToken should be good to generate new service principle from
        # within the appliance
        # TODO : Are there other types of account ?
    }

    $arguments += " --azure-creds-stdin"

    try
    {
        if ($showOutputAsProgress.IsPresent)
        {
            $result = ($obj | ConvertTo-Json | & $kvaCtlFullPath $arguments.Split(" ") | ForEach-Object { $status = $_ -replace "`t"," - "; Write-StatusWithProgress -activity $activity -moduleName $moduleName -Status $status }) 2>&1
        }
        elseif ($showOutput.IsPresent)
        {
            $result = ($obj | ConvertTo-Json | & $kvaCtlFullPath $arguments.Split(" ") | Out-Default) 2>&1
        }
        else
        {
            $result = ($obj | ConvertTo-Json | & $kvaCtlFullPath $arguments.Split(" ") 2>&1)
        }
    }
    catch
    {
        if ($ignoreError.IsPresent)
        {
            return
        }
        throw
    }

    $out = $result | Where-Object {$_.gettype().Name -ine "ErrorRecord"}  # On a non-zero exit code, this may contain the error
    #$outString = ($out | Out-String).ToLowerInvariant()

    if ($LASTEXITCODE)
    {
        if ($ignoreError.IsPresent)
        {
            return
        }
        $err = $result | Where-Object {$_.gettype().Name -eq "ErrorRecord"} | %{ "$_" }
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_non_zero_params , $command, $arguments, $LASTEXITCODE, (Out-String -InputObject $err)))
    }
    return $out
}

function Get-KvaClusterCredential
{
    <#
    .DESCRIPTION
        Obtains the credentials for a cluster as a kubeconfig and outputs it to the requested
        location (by default the location is the current users .kube directory).
 
    .PARAMETER Name
        Name of the cluster to obtain the credential/kubeconfig for.
 
    .PARAMETER outputLocation
        Location to output the credential/kubeconfig file to.
 
    .PARAMETER adAuth
        To get the Active Directory SSO version of the kubeconfig.
 
    .PARAMETER activity
        Activity name to use when writing progress
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string] $Name,

        [Parameter()]
        [string] $outputLocation = $($env:USERPROFILE+"\.kube\config"),

        [Parameter(Mandatory=$false)]
        [Switch] $adAuth,

        [Parameter()]
        [String]$activity
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $Name"
    }

    Initialize-KvaEnvironment -activity $activity

    #TODO Fix this from the kvasdk layer
    if ($Name -eq $global:config[$moduleName]["kvaName"])
    {
        throw $($KvaLocMessage.kva_invalid_cluster)
    }

    $kubeconfig = Get-KvaCredential -activity $activity
    if (-not (Test-Path $kubeconfig))
    {
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_missing_kubeconfig, $kubeconfig))
    }

    # TODO move directory check to kva
    #
    # The following line will only return true if the passed in location
    # is a valid directory. If the path doesn't exist or if it points a
    # file it will return false.
    #
    # In the case where the user passes in an invalid path, we will fail at a later point as
    # its hard to tell whether they are passing valid location to a file to be written or an
    # path that doesn't exist.
    if (Test-Path -Path $outputLocation -PathType Container)
    {
        $outputLocation = [io.Path]::Combine($outputLocation, "kubeconfig-$Name")
    }

    # Ensure that the cluster exists
    $cluster = Get-KvaCluster -Name $Name -activity $activity    
    if ($adAuth.IsPresent)
    {
        $exePath = [io.Path]::Combine($global:installDirectory, "kubectl-adsso.exe")
        if (-not [System.IO.File]::Exists($exePath))
        {
            Get-ClientCredPluginRelease -version $(Get-KvaVersion) -destinationDir $global:installDirectory -activity $activity
        }

        Invoke-KvaCtl -arguments "cluster retrieve --clustername $Name --kubeconfig ""$kubeconfig"" --outfile ""$outputLocation"" --adauth" -showOutput -activity $activity
        Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_ad_kubeconfig_written_to, $outputLocation))
    }
    else
    {
        Invoke-KvaCtl -arguments "cluster retrieve --clustername $Name --kubeconfig ""$kubeconfig"" --outfile ""$outputLocation""" -showOutput -activity $activity
        Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_kubeconfig_will_be_written_to, $outputLocation))
    }

    Set-SecurePermissionFile -Path $outputLocation
}

function Get-KvaCredential
{
    <#
    .DESCRIPTION
        Obtains the credentials for kva as a kubeconfig and outputs it to the default
        location).
 
    .PARAMETER activity
        Activity name to use when writing progress
    #>


    [CmdletBinding()]
    param (
        [Parameter()]
        [String]$activity
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name)"
    }

    $kubeconfigPath = $global:config[$moduleName]["kubeconfig"]
    if (Test-Path $kubeconfigPath)
    {
        return $kubeconfigPath
    }

    $yamlFile = Get-KvaConfigYaml
    Invoke-KvaCtl -arguments "retrieve --configfile ""$yamlFile"" --outfile ""$kubeconfigPath"" " -showOutput -activity $activity

    Set-SecurePermissionFile -Path $kubeconfigPath

    return $kubeconfigPath
}

function Get-ClientCredPluginRelease
{
    <#
    .DESCRIPTION
        Download the client cred plugin release content
 
    .PARAMETER Version
        Version
 
    .PARAMETER destinationDir
        Destination directory
 
    .PARAMETER activity
        Activity name to use when writing progress
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String] $version,

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

        [Parameter()]
        [String] $activity = $MyInvocation.MyCommand.Name
    )

    Get-ReleaseContent -name "client-cred-plugin" -version $version -activity $activity

    $srcPath = [io.Path]::Combine($global:config[$moduleName]["installationPackageDir"], "kubectl-adsso.exe")
    Copy-Item $srcPath -Destination $destinationDir
}

function Wait-ForKubernetesSecret
{
    <#
    .DESCRIPTION
        Polls the vault for a secret to become available and returns the secret value.
 
    .PARAMETER name
        Name of the secret
 
    .PARAMETER sleepDuration
        Duration to sleep for between attempts to retrieve the secret
    #>


    param (
        [String]$name,
        [int]$sleepDuration=20
    )

    Write-SubStatus $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_polling_cluster, $name)) -moduleName $moduleName

    while($true)
    {
        try {
            $clusterSecret = Invoke-Kubectl -arguments $("get secrets/$name")
        } catch {}

        if ($null -ne $clusterSecret)
        {
            break
        }
        Sleep $sleepDuration
    }

    return $clusterSecret
}

function Set-KvaGMSAWebhook
{
    <#
    .DESCRIPTION
        Internal helper function that installs gMSA webhook for a cluster.
 
    .PARAMETER Name
        Cluster Name
 
    .PARAMETER activity
        Activity name to use when writing progress
    #>


    [CmdletBinding(PositionalBinding=$False)]
    param (
        [Parameter(Mandatory=$true)]
        [String] $Name,

        [Parameter()]
        [String]$activity
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $Name"
    }

    Initialize-KvaEnvironment -activity $activity

    # ensures cluster exists
    Get-KvaCluster -Name $Name -activity $activity | Out-Null

    try
    {
        # Schedules a gmsa webhook deployment onto the cluster master node
        $yaml = @"
apiVersion: msft.microsoft/v1
kind: AddOn
metadata:
  name: gmsa-webhook-$Name
  labels:
    msft.microsoft/capicluster-name: $Name
spec:
  configuration:
    supportedAddOnName: gmsa-webhook
    targetNamespace: kube-system
    templateType: yaml
"@

        $yamlFile = $($global:config[$moduleName]["installationPackageDir"]+"\"+$global:yamlDirectoryName+"\$Name-gmsa-webhook.yaml")
        Set-Content -Path $yamlFile -Value $yaml -ErrorVariable err
        if ($null -ne $err -and $err.count -gt 0)
        {
            throw $err
        }
        Invoke-Kubectl -arguments $("apply -f ""$yamlFile"" ")

        $rando = Get-Random
        $targetClusterKubeconfig = $($env:USERPROFILE+"\.kube\$Name-kubeconfig-$rando")
        Get-KvaClusterCredential -Name $Name -outputLocation $targetClusterKubeconfig

        # wait for gmsa-webhook to be ready
        $sleepDuration = 5
        $namespace = 'kube-system'
        $selector = 'app=gmsa-webhook'
        Write-SubStatus $($KvaLocMessage.kva_waiting_for_gmsa_webhook) -moduleName $moduleName

        while($true)
        {
            $result = (Invoke-Kubectl -ignoreError -kubeconfig $targetClusterKubeconfig -arguments $("wait --for=condition=Ready --timeout=5m -n $namespace pod -l $selector"))
            if ($null -ne $result)
            {
                break
            }
            Start-Sleep $sleepDuration
        }

        #Write-SubStatus $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_pod_is_ready, $podFriendlyName)) -moduleName $moduleName
    }
    finally
    {
        Remove-Item -Path $targetClusterKubeconfig
    }
}

function Reset-KvaGMSAWebhook
{
    <#
    .DESCRIPTION
        Internal helper function that uninstalls gMSA webhook for a cluster.
 
    .PARAMETER Name
        Cluster Name
 
    .PARAMETER activity
        Activity name to use when writing progress
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [String] $Name,

        [Parameter()]
        [String]$activity
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $Name"
    }

    Initialize-KvaEnvironment -activity $activity

    # ensures cluster exists
    Get-KvaCluster -Name $Name -activity $activity | Out-Null

    if (-not (Invoke-Kubectl -arguments $("get addon gmsa-webhook-$Name") -ignoreError)) {
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_gmsa_addon_not_installed, $Name))
    }

    try
    {
        $yaml = @"
apiVersion: msft.microsoft/v1
kind: AddOn
metadata:
  name: gmsa-webhook-$Name
  labels:
    msft.microsoft/capicluster-name: $Name
spec:
  configuration:
    supportedAddOnName: gmsa-webhook
    targetNamespace: kube-system
    templateType: yaml
"@


        $yamlFile = $($global:config[$moduleName]["installationPackageDir"]+"\"+$global:yamlDirectoryName+"\$Name-gmsa-webhook.yaml")
        Set-Content -Path $yamlFile -Value $yaml -ErrorVariable err
        if ($null -ne $err -and $err.count -gt 0)
        {
            throw $err
        }

        Invoke-Kubectl -arguments $("delete -f ""$yamlFile"" ")
    }
    finally
    {
        Remove-Item $yamlFile
    }
}

function Set-KvaGMSACredentialSpec
{
<#
    .DESCRIPTION
        Helper function to install setup for gmsa deployments on a cluster.
 
    .PARAMETER Name
        Cluster Name
 
    .PARAMETER credSpecFilePath
        File Path of the JSON cred spec file
 
    .PARAMETER credSpecName
        Name of the k8s credential spec object the user would like to designate
 
    .PARAMETER secretName
        Name of the Kubernetes secret object storing the Active Directory user credentials and gMSA domain
 
    .PARAMETER secretNamespace
        Namespace where the Kubernetes secret object resides in
 
    .PARAMETER serviceAccount
        Name of the K8s service account assigned to read the k8s gMSA credspec object
 
    .PARAMETER clusterRoleName
        Name of the K8s clusterrole assigned to use the k8s gMSA credspec object
 
    .PARAMETER overwrite
        Overwrites existing gMSA setup
 
    .PARAMETER activity
        Activity name to use when writing progress
    #>


    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '', Justification='Not a plaintext password')]
    [CmdletBinding(PositionalBinding=$False, SupportsShouldProcess)]
    param (
        [Parameter(Mandatory=$true)]
        [String]$Name,

        [Parameter(Mandatory=$true)]
        [String]$credSpecFilePath,

        [Parameter(Mandatory=$true)]
        [String]$credSpecName,

        [Parameter(Mandatory=$true)]
        [String]$secretName,

        [Parameter()]
        [String]$secretNamespace = "default",

        [Parameter()]
        [String]$serviceAccount = "default",

        [Parameter(Mandatory=$true)]
        [String]$clusterRoleName,

        [Parameter()]
        [switch]$overwrite,

        [Parameter()]
        [String]$activity
    )


    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $Name"
    }

    Initialize-KvaEnvironment -activity $activity

    try
    {
        $rando = Get-Random

        $targetClusterKubeconfig = $($env:USERPROFILE+"\.kube\$Name-kubeconfig-$rando")
        Get-KvaClusterCredential -Name $Name -outputLocation $targetClusterKubeconfig

        # Converting credSpecName, secretName, serviceAccount, and clusterRoleName to lowercase to conform with RFC1123
        $credSpecName = $credSpecName.ToLower()
        $secretName = $secretName.ToLower()
        $serviceAccount = $serviceAccount.ToLower()
        $clusterRoleName = $clusterRoleName.ToLower()
        $roleBindingName = 'allow-' + $serviceAccount + '-svc-account-read-on-' + $credSpecName

        # check if namespace is created
        if (-not (Invoke-Kubectl -kubeconfig $targetClusterKubeconfig -arguments $("get namespace $secretNamespace") -ignoreError))
        {
            if ($PSCmdlet.ShouldProcess("kubectl get namespace $secretNamespace", $secretNamespace, "namespace check"))
            {
                throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_namespace_not_created, $secretNamespace, $secretNamespace))
            }
        }

        # check if a gmsa webhook pod is running
        $webhookPodRunning = (Invoke-Kubectl -kubeconfig $targetClusterKubeconfig -arguments $("get pods -n kube-system") -ignoreError) | Select-String -Pattern 'gmsa-webhook.*1/1.*Running'
        if (-not $webhookPodRunning)
        {
            throw $($KvaLocMessage.kva_gmsa_not_installed)
        }

        # check if serviceaccount exists
        if (-not (Invoke-Kubectl -kubeconfig $targetClusterKubeconfig -arguments $("get serviceaccount $serviceAccount -n $secretNamespace") -ignoreError))
        {
            if ($PSCmdlet.ShouldProcess("kubectl get serviceaccount $serviceAccount -n $secretNamespace", "$serviceAccount in namespace $secretNamespace", "service account check"))
            {
                throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_namespace_not_found, $serviceAccount, $secretNamespace, $serviceAccount, $secretNamespace))
            }
        }

        # check if any of the resources already exist
        # if overwrite, delete them
        # if not overwrite, throw error
        if ($overwrite.IsPresent)
        {
            if (Invoke-Kubectl -kubeconfig $targetClusterKubeconfig -arguments $("get GMSACredentialSpec $credSpecName") -ignoreError)
            {
                Invoke-Kubectl -kubeconfig $targetClusterKubeconfig -arguments $("delete GMSACredentialSpec $credSpecName")
            }
            if (Invoke-Kubectl -kubeconfig $targetClusterKubeconfig -arguments $("get clusterRole $clusterRoleName") -ignoreError)
            {
                Invoke-Kubectl -kubeconfig $targetClusterKubeconfig -arguments $("delete clusterRole $clusterRoleName")
            }
            if (Invoke-Kubectl -kubeconfig $targetClusterKubeconfig -arguments $("get roleBinding $roleBindingName -n $secretNamespace") -ignoreError)
            {
                Invoke-Kubectl -kubeconfig $targetClusterKubeconfig -arguments $("delete roleBinding $roleBindingName -n $secretNamespace")
            }
        }
        else
        {
            if (Invoke-Kubectl -kubeconfig $targetClusterKubeconfig -arguments $("get GMSACredentialSpec $credSpecName") -ignoreError)
            {
                throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_namespace_exists, $kva_namespace_not_found))
            }
            if (Invoke-Kubectl -kubeconfig $targetClusterKubeconfig -arguments $("get clusterRole $clusterRoleName") -ignoreError)
            {
                throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_cluster_role_exists, $clusterRoleName))
            }
            if (Invoke-Kubectl -kubeconfig $targetClusterKubeconfig -arguments $("get roleBinding $roleBindingName -n $secretNamespace") -ignoreError)
            {
                throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_name_exists_in_namespace, $roleBindingName, $secretNamespace))
            }
        }

        # check if credspec file exists
        If (-not (Test-Path -Path $credSpecFilePath)) {

            throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_file_path_exists, $credSpecFilePath))
        }

        # catch errors when get-content or convertfrom-json
        $credSpecJsonRaw = Get-Content $credSpecFilePath -raw | ConvertFrom-Json
        $adServiceAccount = $credSpecJsonRaw.DomainJoinConfig.MachineAccountName
        $domainName = $credSpecJsonRaw.DomainJoinConfig.DNSName
        $netbios = $credSpecJsonRaw.DomainJoinConfig.NetBiosName
        $gmsaSid = $credSpecJsonRaw.DomainJoinConfig.Sid
        $gmsaGuid = $credSpecJsonRaw.DomainJoinConfig.Guid

        if (-not ($adServiceAccount -and $domainName -and $netbios -and $gmsaSid -and $gmsaGuid))
        {
            throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_file_path_exists, $credSpecFilePath))
        }

        # create credspec yaml
        $credSpecYaml = @"
apiVersion: windows.k8s.io/v1alpha1
kind: GMSACredentialSpec
metadata:
  name: $credSpecName
credspec:
  ActiveDirectoryConfig:
    GroupManagedServiceAccounts:
    - Name: $adServiceAccount
      Scope: $netbios
    - Name: $adServiceAccount
      Scope: $domainName
    HostAccountConfig:
      PortableCcgVersion: "1"
      PluginGUID: "{FF4E3124-6831-44A0-8C34-06EC66E148C6}"
      PluginInput: SecretName=$($secretName);SecretNamespace=$($secretNamespace)
  CmsPlugins:
  - ActiveDirectory
  DomainJoinConfig:
    DnsName: $domainName
    DnsTreeName: $domainName
    Guid: $gmsaGuid
    MachineAccountName: $adServiceAccount
    NetBiosName: $netbios
    Sid: $gmsaSid
"@

        # apply and remove credspec yaml
        if ($PSCmdlet.ShouldProcess("$credspecYaml", $credSpecName, "credspec creation"))
        {
            $yamlFile = $($global:config[$modulename]["installationPackageDir"]+"\$credSpecName-$rando.yaml")
            Set-Content -Path $yamlFile -Value $credSpecYaml -ErrorVariable err
            if ($null -ne $err -and $err.count -gt 0)
            {
                Remove-Item $yamlFile
                throw $err
            }

            Invoke-Kubectl -kubeconfig $targetClusterKubeconfig -arguments $("apply -f ""$yamlFile"" ")
            Remove-Item $yamlFile
        }

        # create cluster role yaml
        $clusterRoleYaml = @"
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: $clusterRoleName
rules:
- apiGroups: ["windows.k8s.io"]
  resources: ["gmsacredentialspecs"]
  verbs: ["use"]
  resourceNames: [$($credSpecName)]
"@

        # apply and remove cluster role yaml
        if ($PSCmdlet.ShouldProcess("$clusterRoleYaml", $clusterRoleName, "clusterrole creation"))
        {
            $yamlFile = $($global:config[$modulename]["installationPackageDir"]+"\$clusterRoleName-clusterrole-$rando.yaml")
            Set-Content -Path $yamlFile -Value $clusterRoleYaml -ErrorVariable err
            if ($null -ne $err -and $err.count -gt 0)
            {
                Remove-Item $yamlFile
                throw $err
            }

            Invoke-Kubectl -kubeconfig $targetClusterKubeconfig -arguments $("apply -f ""$yamlFile"" ")
            Remove-Item $yamlFile
        }

        # create rolebinding yaml
        $roleBindingYaml = @"
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: allow-$($serviceAccount)-svc-account-read-on-$($credSpecName)
  namespace: $($secretNamespace)
subjects:
- kind: ServiceAccount
  name: $($serviceAccount)
  namespace: $($secretNamespace)
roleRef:
  kind: ClusterRole
  name: $clusterRoleName
  apiGroup: rbac.authorization.k8s.io
"@

        # apply and remove rolebinding yaml
        if ($PSCmdlet.ShouldProcess("$roleBindingYaml", "allow-$($serviceAccount)-svc-account-read-on-$($credSpecName)", "clusterrolebinding creation"))
        {
            # apply yaml file and delete
            $yamlFile = $($global:config[$modulename]["installationPackageDir"]+"\$clusterRoleName-rolebinding-$rando.yaml")
            Set-Content -Path $yamlFile -Value $roleBindingYaml -ErrorVariable err
            if ($null -ne $err -and $err.count -gt 0)
            {
                Remove-Item $yamlFile
                throw $err
            }

            Invoke-Kubectl -kubeconfig $targetClusterKubeconfig -arguments $("apply -f ""$yamlFile"" ")
            Remove-Item $yamlFile
        }
    }
    finally
    {
        Remove-Item -Path $targetClusterKubeconfig -ErrorAction Ignore
    }
}

function Reset-KvaGMSACredentialSpec
{
    <#
    .DESCRIPTION
        Helper function to uninstall setup for gmsa deployments on a cluster.
 
    .PARAMETER Name
        Cluster Name
 
    .PARAMETER credSpecName
        Name of the k8s credential spec object the user would like to designate
 
    .PARAMETER serviceAccount
        K8s service account assigned to read the k8s gMSA credspec object
 
    .PARAMETER clusterRoleName
        Name of the K8s clusterrole assigned to use the k8s gMSA credspec object
 
    .PARAMETER secretNamespace
        Namespace where the Kubernetes secret object resides in
 
    .PARAMETER activity
        Activity name to use when writing progress
    #>


    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '', Justification='Not a plaintext password')]
    [CmdletBinding(PositionalBinding=$False, SupportsShouldProcess)]
    param (
        [Parameter(Mandatory=$true)]
        [String]$Name,

        [Parameter(Mandatory=$true)]
        [String]$credSpecName,

        [Parameter()]
        [String]$serviceAccount = "default",

        [Parameter(Mandatory=$true)]
        [String]$clusterRoleName,

        [Parameter()]
        [String]$secretNamespace = "default",

        [Parameter()]
        [String]$activity
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $Name"
    }

    Initialize-KvaEnvironment -activity $activity

    try
    {
        $noDeleteCount = 0
        $rando = Get-Random

        $targetClusterKubeconfig = $($env:USERPROFILE+"\.kube\$Name-kubeconfig-$rando")
        Get-KvaClusterCredential -Name $Name -outputLocation $targetClusterKubeconfig

        # check if namespace is created
        if (-not (Invoke-Kubectl -kubeconfig $targetClusterKubeconfig -arguments $("get namespace $secretNamespace") -ignoreError))
        {
            if ($PSCmdlet.ShouldProcess("kubectl get namespace $secretNamespace", $secretNamespace, "namespace check"))
            {
                throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_namespace_not_created, $secretNamespace, $secretNamespace))
            }
        }

        # delete GMSACredentialSpec object if exists
        if (Invoke-Kubectl -kubeconfig $targetClusterKubeconfig -arguments $("get GMSACredentialSpec $credSpecName") -ignoreError)
        {
            if ($PSCmdlet.ShouldProcess("kubectl delete GMSACredentialSpec $credSpecName", $credSpecName, "credspec deletion"))
            {
                Invoke-Kubectl -kubeconfig $targetClusterKubeconfig -arguments $("delete GMSACredentialSpec $credSpecName")
            }
        }
        else
        {
            $noDeleteCount += 1
            Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_gmsacredentialspec_does_not_exist, $credSpecName))
        }

        # delete clusterrole object if exists
        if (Invoke-Kubectl -kubeconfig $targetClusterKubeconfig -arguments $("get clusterRole $clusterRoleName") -ignoreError)
        {
            if ($PSCmdlet.ShouldProcess("kubectl delete $clusterRoleName", $clusterRoleName, "clusterrole deletion"))
            {
                Invoke-Kubectl -kubeconfig $targetClusterKubeconfig -arguments $("delete clusterRole $clusterRoleName")
            }
        }
        else
        {
            $noDeleteCount += 1
            Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_clusterrole_does_not_exist, $clusterRoleName))
        }

        # delete rolebinding object if exists
        $roleBindingName = 'allow-' + $serviceAccount + '-svc-account-read-on-' + $credSpecName
        if (Invoke-Kubectl -kubeconfig $targetClusterKubeconfig -arguments $("get roleBinding $roleBindingName -n $secretNamespace") -ignoreError)
        {
            if ($PSCmdlet.ShouldProcess("kubectl delete $roleBindingName -n $secretNamespace", "allow-$($serviceAccount)-svc-account-read-on-$($credSpecName)", "clusterrolebinding deletion"))
            {
                Invoke-Kubectl -kubeconfig $targetClusterKubeconfig -arguments $("delete roleBinding $roleBindingName -n $secretNamespace")
            }
        }
        else
        {
            $noDeleteCount += 1
            Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_rolebinding_does_not_exist, $roleBindingName, $secretNamespace))
        }

        # give feedback on no objects were deleted
        if ($noDeleteCount -eq 3)
        {
            Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_gmsacredentialspec_clusterrole_rolebinding_does_not_exist, $credSpecName, $clusterRole, $rolebindingName))
        }
    }
    finally
    {
        Remove-Item -Path $targetClusterKubeconfig -ErrorAction Ignore
    }
}
#endregion

#region Image functions

function Get-ImageReleaseManifest
{
    <#
    .DESCRIPTION
        Discovers the requested image and returns the release manifest for it
 
    .PARAMETER imageVersion
        Image release version to target
 
    .PARAMETER operatingSystem
        Image operating system to target
 
    .PARAMETER k8sVersion
        Kubernetes version to target
    #>


    param(
        [parameter(Mandatory=$true)]
        [String] $imageVersion,

        [parameter(Mandatory=$true)]
        [String] $operatingSystem,

        [Parameter(Mandatory=$true)]
        [String] $k8sVersion
    )

    $k8sVersion = $k8sVersion.TrimStart("v")

    $productRelease = Get-ProductRelease -version $imageVersion -moduleName $moduleName
    foreach($releaseStream in $productRelease.ProductStreamRefs)
    {
        foreach($subProductRelease in $releaseStream.ProductReleases)
        {
            $vhdInfo = Get-ImageReleaseVhdInfo -release $subProductRelease
            if (-not $vhdInfo)
            {
                continue
            }

            if ($vhdInfo.CustomData.BaseOSImage.OperatingSystem -ine $operatingSystem)
            {
                continue
            }

            foreach($pkg in $vhdInfo.CustomData.K8SPackages)
            {
                if ($pkg.Version -ieq $k8sVersion)
                {
                    return $subProductRelease
                }
            }
        }
    }

    throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_image_not_found, $imageVersion, $operatingSystem, $k8sVersion))
}

function Get-ImageReleaseVhdInfo
{
    <#
    .DESCRIPTION
        Discovers and returns vhd information for the specified image release
 
    .PARAMETER imageRelease
        Image release manifest
    #>


    param(
        [parameter(Mandatory=$True)]
        [PSCustomObject] $release
    )

    foreach ($fileRelease in $release.ProductFiles)
    {
        if ($fileRelease.CustomData.Type -ieq "vhd")
        {
            return $fileRelease
        }
    }

    return $null
}

function Get-ImageRelease
{
    <#
    .DESCRIPTION
        Download the specified image release.
 
    .PARAMETER imageRelease
        Image release manifest
 
    .PARAMETER imageDir
        Directory for local image store.
    #>


    param(
        [parameter(Mandatory=$True)]
        [PSCustomObject] $imageRelease,

        [parameter(Mandatory=$True)]
        [String] $imageDir
    )

    $downloadpath = "$imageDir\$([System.IO.Path]::GetRandomFileName().Split('.')[0])"
    New-Item -ItemType Directory -Force -Confirm:$false -Path $downloadpath | Out-Null

    try
    {
        $vhdInfo = Get-ImageReleaseVhdInfo -release $imageRelease
        $k8sVersion = $vhdInfo.CustomData.K8SPackages[0].Version
        $imageGalleryName = Get-KubernetesGalleryImageName -imagetype $vhdInfo.CustomData.BaseOSImage.OperatingSystem -k8sVersion $k8sVersion

        $imageVersionCurrent = $imageRelease.Version
        $imageVersionManifestPath = "$ImageDir\$imageGalleryName.json"
        $destinationpath = $("$ImageDir\$imageGalleryName.vhdx" -replace "\/", "\")
        if (Test-Path $imageVersionManifestPath)
        {
            $OldImageData = get-content $imageVersionManifestPath | ConvertFrom-Json
            $OldImageVersion = $OldImageData.Version
            Write-SubStatus -moduleName $moduleName "Existing image $imageVersionManifestPath has version $OldImageVersion . Requested version is $imageVersionCurrent"

            if ($imageVersionCurrent -ieq $OldImageVersion)
            {
                if (Test-Path -Path $destinationpath)
                {
                    Write-SubStatus -moduleName $moduleName $($KvaLocMessage.kva_existing_image_upto_date)
                    return $destinationpath
                }

                Write-SubStatus -moduleName $moduleName $($KvaLocMessage.kva_existing_image_not_present)
            }
        }

        Write-Status -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_download_image_with_version, $($imageRelease.ProductStream), $imageVersionCurrent, $downloadPath))

        $downloadParams = Get-ReleaseDownloadParameters -name $imageRelease.ProductStream -version $imageVersionCurrent -destination $downloadPath -parts 10 -moduleName $moduleName
        $releaseInfo = Get-DownloadSdkRelease @downloadParams

        if ($global:config[$moduleName]["useStagingShare"])
        {
            $imageFile = $releaseInfo.Files[0] -replace "\/", "\"
        }
        else
        {
            $imageFile = Expand-SfsImage -files $releaseInfo.Files -destination $downloadPath -workingDirectory $downloadPath
        }

        $imageJson = $imageRelease | ConvertTo-Json -depth 100
        Set-Content -path $imageversionManifestPath -value $imageJson -encoding UTF8 -Confirm:$false

        if (test-path $destinationpath)
        {
            Remove-Item $destinationpath -Force -Confirm:$false
        }

        Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_moving_image, $imageFile, $destinationpath))
        Move-Item -Path $imageFile -Destination $destinationpath -Confirm:$false
    }
    finally
    {
        Remove-Item -Force $downloadpath -Recurse -Confirm:$false
    }

    return $destinationpath
}

function Expand-SfsImage
{
    <#
    .DESCRIPTION
        Expand and verify a SFS image download using authenticode.
        NOTE: This is temporary until cross-platform signing is available in Download SDK.
 
    .PARAMETER files
        The downloaded image files (expected to be a image file zip and a companion cab).
 
    .PARAMETER destination
        Destination for the expanded image file.
 
    .PARAMETER workingDirectory
        Working directory to use for zip and cab file expansion.
    #>


    param(
        [Parameter(Mandatory=$True)]
        [String[]] $files,

        [Parameter(Mandatory=$True)]
        [String] $destination,

        [Parameter(Mandatory=$True)]
        [String] $workingDirectory
    )

    Write-Status -moduleName $moduleName $($GenericLocMessage.comm_verifying_image_companion_file_download)

    [String[]]$cabfile = $files | Where-Object { $_ -match "\.cab$"}
    if (($null -eq $cabfile) -or ($cabfile.count -ne 1))
    {
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_wrong_cab_file_count, $cabfile.count))
    }

    Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_verify_authenticode_signature, $cabFile))
    $auth = Get-AuthenticodeSignature -filepath $cabfile
    if (($global:expectedAuthResponse.status -ne $auth.status) -or ($global:expectedAuthResponse.SignatureType -ne $auth.SignatureType))
    {
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_authenticode_failed, "KVA image companion file", $global:expectedAuthResponse.status, $global:expectedAuthResponse.SignatureType, $auth.status, $auth.SignatureType))
    }

    Write-Status -moduleName $moduleName $($GenericLocMessage.comm_expanding_image_companion_file)
    $expandDir = "$WorkingDirectory\expand_" + [System.IO.Path]::GetRandomFileName().Split('.')[0]
    New-Item -ItemType Directory -Force -Confirm:$false -Path $expandDir | Out-Null
    $expandoutput = expand.exe $cabfile $expandDir
    Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_expand_output, $expandoutput))
    $manifest = Get-ChildItem $expandDir | select-object -first 1
    $manifestcontents = get-content $manifest.fullname | convertfrom-json

    $packageAlgo = $manifestcontents.PackageVerification.VerificationDescriptor.Algorithm
    $packageHash = $manifestcontents.PackageVerification.VerificationDescriptor.FileHash
    $packageName = $manifestcontents.PackageVerification.VerificationDescriptor.Filename

    Write-Status -moduleName $moduleName $($GenericLocMessage.comm_verifying_image_file_download)
    Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_verification_request, $packageName, $packageAlgo, $packageHash))

    [string[]]$imagezip = $files | Where-Object { $_ -match "$packageName$"}
    if ($null -eq $imagezip)
    {
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_unable_to_locate_image_file, $packageName))
    }

    Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_calculating_hash_for_archive, $packageAlgo, $imagezip[0]))
    $hash = Get-Base64Hash -file $imagezip[0] -algorithm $packageAlgo
    if ($packageHash -ne $hash)
    {
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_unexpected_hash, $packageHash, $($imagezip[0]), $hash))
    }

    Write-Status -moduleName $moduleName $($GenericLocMessage.comm_expanding_image_file_archive)
    $contentsDir = "$expandDir\contents"
    New-Item -ItemType Directory -Force -Confirm:$false -Path $contentsdir | Out-Null
    Expand-Archive -path $imagezip[0] -destinationpath $contentsDir -Confirm:$false | Out-Null
    Remove-Item $imagezip[0] -Confirm:$false

    [System.IO.FileInfo[]]$workimage = Get-ChildItem -r $contentsDir | Where-Object { (-not $_.psiscontainer) -and ($_.name -match "\.vhdx$")}
    if ($workimage.count -ne 1)
    {
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_invalid_file_count_expansion, $($workimage.count)))
    }
    $content0 = $manifestcontents.ContentVerification[0].VerificationDescriptor

    Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_calculating_hash_for_file, $packageAlgo, $workimage[0].fullname))

    $hash = Get-Base64Hash -file $workimage[0].fullname -algorithm $content0.algorithm
    if ($content0.FileHash -ne $hash)
    {
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_unexpected_hash, $($content0.FileHash), $($workimage[0].fullname), $hash))
    }

    $image = $("$destination\" + $workimage[0].name)

    Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_moving_image_file, $image))
    Move-item -Path $workimage[0].fullname -Destination $image -Confirm:$false

    return $image
}

function Get-Base64Hash
{
    <#
    .DESCRIPTION
        Obtain the base64 byte hash of the specified file. Used to verify SFS binaries
 
    .PARAMETER file
        File to generate the base64 hash for
 
    .PARAMETER algorithm
        Hashing algorithm to use
    #>

    param (
        [Parameter(Mandatory=$True)]
        [string] $file,

        [Parameter(Mandatory=$True)]
        [string] $algorithm
    )

    Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_generating_base64_hash, $file, $algorithm))
    $diskhash_hex = Get-FileHash -algo $algorithm -path $file

    [byte[]] $diskhash_bin = for ([int]$i = 0; $i -lt $diskhash_hex.hash.length; $i += 2) {
        [byte]::parse($diskhash_hex.hash.substring($i, 2), [System.Globalization.NumberStyles]::HexNumber)
    }

    return [convert]::ToBase64String($diskhash_bin)
}

function Get-GraphAccessToken
{
    <#
    .DESCRIPTION
        Gets the Graph Access Token
    #>

        $azContext = Get-AzContext

        $graphTokenItemResource = $global:graphEndpointResourceIdAzureCloud

        if($azContext.Environment -eq $global:azureChinaCloud)
        {
            $graphTokenItemResource = $global:graphEndpointResourceIdAzureChinaCloud
        }
        elseif($azContext.Environment -eq $global:azureUSGovernment)
        {
            $graphTokenItemResource = $global:graphEndpointResourceIdAzureUSGovernment
        }
        elseif($azContext.Environment -eq $global:azureGermanCloud)
        {
            $graphTokenItemResource = $global:graphEndpointResourceIdAzureGermancloud
        }
        elseif($azContext.Environment -eq $global:azurePPE)
        {
            $graphTokenItemResource = $global:graphEndpointResourceIdAzurePPE
        }

        return Get-AzAccessToken -ResourceUrl $graphTokenItemResource
}

function New-KvaArcConnection
{
    <#
    .DESCRIPTION
        Helper function to add the arc onboarding agent addon on a cluster.
 
    .PARAMETER Name
        cluster Name
 
    .PARAMETER tenantId
       tenant id for azure
 
    .PARAMETER subscriptionId
        subscription id for azure
 
    .PARAMETER resourceGroup
        azure resource group for connected cluster
 
    .PARAMETER credential
        credential for azure service principal
 
    .PARAMETER location
        azure location
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    [CmdletBinding(PositionalBinding=$False, DefaultParametersetName='None')]
    param (
        [Parameter(Mandatory=$true)]
        [String] $Name,

        [Parameter(Mandatory=$true, ParameterSetName='azureoveride')]
        [String] $tenantId,

        [Parameter(Mandatory=$true, ParameterSetName='azureoveride')]
        [String] $subscriptionId,

        [Parameter(Mandatory=$true, ParameterSetName='azureoveride')]
        [String] $resourceGroup,

        [Parameter(Mandatory=$true, ParameterSetName='azureoveride')]
        [PSCredential] $credential,

        [Parameter(Mandatory=$true, ParameterSetName='azureoveride')]
        [String] $location,

        [Parameter()]
        [String] $activity = $MyInvocation.MyCommand.Name
    )

    Initialize-KvaEnvironment -activity $activity

    # because of the parameter set we know that subid can represent the set.
    if ([string]::IsNullOrWhiteSpace($subscriptionId))
    {
        $azContext = Get-AzContext
        $subscriptionid = $azContext.Subscription.Id
        $tenantid = $azContext.Tenant.Id
        $resourcegroup = $global:config[$moduleName]["azureResourceGroup"]
        $location = $global:config[$moduleName]["azureLocation"]
    }

    # Ensure that the cluster exists
    Get-KvaCluster -Name $Name | Out-Null

    $kubeconfig = Get-KvaCredential -activity $activity
    $arguments = "cluster addons arc install"
    $arguments += " --cluster-name $Name"
    $arguments += " --kubeconfig ""$kubeconfig"""
    $arguments += " --subscription-id $subscriptionId"
    $arguments += " --tenant-id $tenantId"
    $arguments += " --resource-group $resourceGroup"
    $arguments += " --location $location"


    Invoke-KvaCtlWithAzureContext -arguments $arguments  -showOutputAsProgress -activity $activity -credential $credential

    Write-SubStatus -moduleName $moduleName $($KvaLocMessage.kva_arc_onboarding_agent_installed_to_cluster)
    Write-SubStatus -moduleName $moduleName $($KvaLocMessage.kva_watch_progress_for_arc_agents)

    Write-StatusWithProgress -activity $activity -status $($GenericLocMessage.generic_done) -completed -moduleName $moduleName
}

function Remove-KvaArcConnection
{
   <#
    .DESCRIPTION
        Helper function to add the arc onboarding agent addon on a cluster.
 
    .PARAMETER Name
        cluster Name
 
    .PARAMETER tenantId
       tenant id for azure
 
    .PARAMETER subscriptionId
        subscription id for azure
 
    .PARAMETER resourceGroup
        azure resource group for connected cluster
 
    .PARAMETER credential
        credential for azure service principal
 
    .PARAMETER location
        azure location
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    [CmdletBinding(PositionalBinding=$False, DefaultParametersetName='None')]
    param (
        [Parameter(Mandatory=$true)]
        [String] $Name,

        [Parameter(Mandatory=$true, ParameterSetName='azureoveride')]
        [String] $tenantId,

        [Parameter(Mandatory=$true, ParameterSetName='azureoveride')]
        [String] $subscriptionId,

        [Parameter(Mandatory=$true, ParameterSetName='azureoveride')]
        [String] $resourceGroup,

        [Parameter(Mandatory=$true, ParameterSetName='azureoveride')]
        [PSCredential] $credential,

        [Parameter(Mandatory=$true, ParameterSetName='azureoveride')]
        [String] $location,

        [Parameter()]
        [String] $activity = $MyInvocation.MyCommand.Name
    )

    Initialize-KvaEnvironment -activity $activity

    # because of the parameter set we know that subid can represent the set.
    if ([string]::IsNullOrWhiteSpace($subscriptionId))
    {
        $azContext = Get-AzContext
        $subscriptionid = $azContext.Subscription.Id
        $tenantid = $azContext.Tenant.Id
        $resourcegroup = $global:config[$moduleName]["azureResourceGroup"]
        $location = $global:config[$moduleName]["azureLocation"]
    }

    # Ensure that the cluster exists
    Get-KvaCluster -Name $Name | Out-Null

    $kubeconfig = Get-KvaCredential -activity $activity

    $arguments = "cluster addons arc uninstall"
    $arguments += " --cluster-name $Name"
    $arguments += " --kubeconfig ""$kubeconfig"""
    $arguments += " --subscription-id $subscriptionId"
    $arguments += " --tenant-id $tenantId"
    $arguments += " --resource-group $resourceGroup"
    $arguments += " --location $location"

    Invoke-KvaCtlWithAzureContext -arguments $arguments  -showOutputAsProgress -activity $activity -credential $credential

    Write-SubStatus -moduleName $moduleName $($KvaLocMessage.kva_onboarding_agent_uninstalled)

    Write-StatusWithProgress -activity $activity -status $($GenericLocMessage.generic_done) -completed -moduleName $moduleName
}

function Set-KvaCsiSmb
{
    <#
    .DESCRIPTION
        Installs csi smb plugin to an AKS-HCI cluster.
 
    .PARAMETER ClusterName
        Cluster Name
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String] $ClusterName
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $ClusterName"
    }

    Initialize-KvaEnvironment -activity $activity

    Get-KvaCluster -Name $ClusterName -activity $activity | Out-Null

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($KvaLocMessage.kva_installing_csi_smb_to_cluster)

    $kubeconfig = Get-KvaCredential -activity $activity

    Invoke-KvaCtl -arguments "cluster addons csismb install --cluster-name $ClusterName --kubeconfig ""$kubeconfig""" -showOutput -activity $activity

    Write-SubStatus -moduleName $moduleName $($KvaLocMessage.kva_csi_smb_installation_complete)

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)

}


function Set-KvaCsiNfs
{
    <#
    .DESCRIPTION
        Installs csi nfs plugin to an AKS-HCI cluster.
 
    .PARAMETER ClusterName
        Cluster Name
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String] $ClusterName
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $ClusterName"
    }

    Initialize-KvaEnvironment -activity $activity

    Get-KvaCluster -Name $ClusterName -activity $activity | Out-Null

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($KvaLocMessage.kva_installing_csi_nfs_to_cluster)

    $kubeconfig = Get-KvaCredential -activity $activity

    Invoke-KvaCtl -arguments "cluster addons csinfs install --cluster-name $ClusterName --kubeconfig ""$kubeconfig""" -showOutput -activity $activity

    Write-SubStatus -moduleName $moduleName $($KvaLocMessage.kva_csi_nfs_installation_complete)

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
}

function Reset-KvaCsiSmb
{
    <#
    .DESCRIPTION
        Uninstalls csi smb plugin from an AKS-HCI cluster.
 
    .PARAMETER ClusterName
        Cluster Name
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String] $ClusterName
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $ClusterName"
    }

    Initialize-KvaEnvironment -activity $activity

    Get-KvaCluster -Name $ClusterName -activity $activity | Out-Null

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($KvaLocMessage.kva_uninstalling_csi_smb_to_cluster)

    $kubeconfig = Get-KvaCredential -activity $activity
    Invoke-KvaCtl -arguments "cluster addons csismb uninstall --cluster-name $ClusterName --kubeconfig ""$kubeconfig""" -showOutput -activity $activity

    Write-SubStatus -moduleName $moduleName $($KvaLocMessage.kva_csi_smb_uninstallation_complete)

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
}


function Reset-KvaCsiNfs
{
    <#
    .DESCRIPTION
        Uninstalls csi nfs plugin from an AKS-HCI cluster.
 
    .PARAMETER ClusterName
        Cluster Name
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String] $ClusterName
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $ClusterName"
    }

    Initialize-KvaEnvironment -activity $activity

    Get-KvaCluster -Name $ClusterName -activity $activity | Out-Null

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($KvaLocMessage.kva_uninstalling_csi_nfs_to_cluster)

    $kubeconfig = Get-KvaCredential -activity $activity
    Invoke-KvaCtl -arguments "cluster addons csinfs uninstall --cluster-name $ClusterName --kubeconfig ""$kubeconfig""" -showOutput -activity $activity

    Write-SubStatus -moduleName $moduleName $($KvaLocMessage.kva_csi_nfs_uninstallation_complete)

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
}

function Set-KvaCsiSmb
{
    <#
    .DESCRIPTION
        Installs csi smb plugin to an AKS-HCI cluster.
 
    .PARAMETER ClusterName
        Cluster Name
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String] $ClusterName
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $ClusterName"
    }

    Initialize-KvaEnvironment -activity $activity

    Get-KvaCluster -Name $ClusterName -activity $activity | Out-Null

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($KvaLocMessage.kva_installing_csi_smb_to_cluster)

    $kubeconfig = Get-KvaCredential -activity $activity
    Invoke-KvaCtl -arguments "cluster addons csismb install --cluster-name $ClusterName --kubeconfig ""$kubeconfig""" -showOutput -activity $activity

    Write-SubStatus -moduleName $moduleName $($KvaLocMessage.kva_csi_smb_installation_complete)

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)

}


function Set-KvaCsiNfs
{
    <#
    .DESCRIPTION
        Installs csi nfs plugin to an AKS-HCI cluster.
 
    .PARAMETER ClusterName
        Cluster Name
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String] $ClusterName
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $ClusterName"
    }

    Initialize-KvaEnvironment -activity $activity

    Get-KvaCluster -Name $ClusterName -activity $activity | Out-Null

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($KvaLocMessage.kva_installing_csi_nfs_to_cluster)

    $kubeconfig = Get-KvaCredential -activity $activity
    Invoke-KvaCtl -arguments "cluster addons csinfs install --cluster-name $ClusterName --kubeconfig ""$kubeconfig""" -showOutput -activity $activity

    Write-SubStatus -moduleName $moduleName $($KvaLocMessage.kva_csi_nfs_installation_complete)

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
}

function Reset-KvaCsiSmb
{
    <#
    .DESCRIPTION
        Uninstalls csi smb plugin from an AKS-HCI cluster.
 
    .PARAMETER ClusterName
        Cluster Name
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String] $ClusterName
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $ClusterName"
    }

    Initialize-KvaEnvironment -activity $activity

    Get-KvaCluster -Name $ClusterName -activity $activity | Out-Null

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($KvaLocMessage.kva_uninstalling_csi_smb_to_cluster)

    $kubeconfig = Get-KvaCredential -activity $activity

    Invoke-KvaCtl -arguments "cluster addons csismb uninstall --cluster-name $ClusterName --kubeconfig ""$kubeconfig""" -showOutput -activity $activity

    Write-SubStatus -moduleName $moduleName $($KvaLocMessage.kva_csi_smb_uninstallation_complete)

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
}


function Reset-KvaCsiNfs
{
    <#
    .DESCRIPTION
        Uninstalls csi nfs plugin from an AKS-HCI cluster.
 
    .PARAMETER ClusterName
        Cluster Name
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String] $ClusterName
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $ClusterName"
    }

    Initialize-KvaEnvironment -activity $activity

    Get-KvaCluster -Name $ClusterName -activity $activity | Out-Null

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($KvaLocMessage.kva_uninstalling_csi_nfs_to_cluster)

    $kubeconfig = Get-KvaCredential -activity $activity
    Invoke-KvaCtl -arguments "cluster addons csinfs uninstall --cluster-name $ClusterName --kubeconfig ""$kubeconfig""" -showOutput -activity $activity

    Write-SubStatus -moduleName $moduleName $($KvaLocMessage.kva_csi_nfs_uninstallation_complete)

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
}
function Set-KvaHciMonitoring
{
    <#
    .DESCRIPTION
        Installs monitoring infrastructure on AKS-HCI cluster.
 
    .PARAMETER Name
        Cluster Name
 
    .PARAMETER storageSizeGB
        Amount of storage for Prometheus in GB
 
    .PARAMETER retentionTimeHours
        metrics retention time in hours. (min 2 hours, max 876000 hours(100 years))
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String] $Name,

        [Parameter(Mandatory=$true)]
        [int] $storageSizeGB,

        [Parameter(Mandatory=$true)]
        [int] $retentionTimeHours,

        [Parameter()]
        [String] $activity
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $Name"
    }
    
    $hundredYearsInHours = 876000

    if (($retentionTimeHours -lt 2) -or ($retentionTimeHours -gt $hundredYearsInHours))
    {
        throw $($KvaLocMessage.kva_invalid_retention_time)
    }
    if ($storageSizeGB -lt 1)
    {
        throw $($KvaLocMessage.kva_zero_storage_size)
    }

    Initialize-KvaEnvironment -activity $activity

    $kvaCluster=Get-KvaCluster -Name $Name -activity $activity

    if($kvaCluster.LinuxNodeCount -lt 1)
    {
        throw $($KvaLocMessage.kva_linux_node_required)
    }

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($KvaLocMessage.kva_installing_monitoring)

    $kubeconfig = Get-KvaCredential -activity $activity

    ## check if akshci-monitoring is already installed.
    $installedAddons = Invoke-KvaCtl -arguments "cluster addons list --cluster-name $Name --kubeconfig ""$kubeconfig"" --outputformat json" -activity $activity | ConvertFrom-Json
    foreach ($addon in $installedAddons)
    {
        if ($addon.name -eq "akshci-monitoring")
        {
            #vishal
            return "AksHci Monitoring is already installed"
        }
    }
    Invoke-KvaCtl -arguments "cluster addons akshci-monitoring install --cluster-name $Name --kubeconfig ""$kubeconfig"" --storageSizeGB $storageSizeGB --retentionTimeHours $retentionTimeHours" -showOutput -activity $activity

    Write-SubStatus -moduleName $moduleName $($KvaLocMessage.kva_monitoring_installation_complete)

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
}
function Reset-KvaHciMonitoring
{
    <#
    .DESCRIPTION
        Uninstalls monitoring infrastructure from AKS-HCI cluster.
 
    .PARAMETER Name
        Cluster Name
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [String] $Name,

        [Parameter()]
        [String] $activity
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $Name"
    }

    Initialize-KvaEnvironment -activity $activity

    Get-KvaCluster -Name $Name -activity $activity | Out-Null

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($KvaLocMessage.kva_uninstalling_monitoring)

    $kubeconfig = Get-KvaCredential -activity $activity
    ## check if akshci-monitoring is already installed.
    $installedAddons = Invoke-KvaCtl -arguments "cluster addons list --cluster-name $Name --kubeconfig ""$kubeconfig"" --outputformat json" -activity $activity | ConvertFrom-Json
    $akshciMonitoringInstalled = $false
    foreach ($addon in $installedAddons)
    {
        if ($addon.name -eq "akshci-monitoring")
        {
            $akshciMonitoringInstalled = $true
            break
        }
    }
    if($akshciMonitoringInstalled -eq $false)
    {
        throw $($KvaLocMessage.kva_akshci_monitoring_not_installed)
    }
    Invoke-KvaCtl -arguments "cluster addons akshci-monitoring uninstall --cluster-name $Name --kubeconfig ""$kubeconfig""" -showOutput -activity $activity

    Write-SubStatus -moduleName $moduleName $($KvaLocMessage.kva_monitoring_uninstallation_complete)

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
}
function Test-KvaAzureConnection
{
     <#
    .DESCRIPTION
       Tests the connection to Azure.
    #>


    $azContext = Get-AzContext

    if([string]::IsNullOrEmpty($azContext.Subscription.Id))
    {
        throw $($KvaLocMessage.kva_azure_connection_failed)
    }
     
    try 
    {
        Get-AzSubscription -SubscriptionId $azContext.Subscription.Id -ErrorAction Stop | Out-Null
    }
    catch [Exception]
    {
        throw $($KvaLocMessage.kva_azure_connection_failed)
    }


    $aksHciRegistration = Get-AksHciRegistration
    if ([string]::IsNullOrWhiteSpace($aksHciRegistration.azureResourceGroup))
    {
        throw $($KvaLocMessage.kva_azure_resource_group_not_found)
    }

}

#endregion

#region Cluster Networking commands
function New-KvaClusterNetwork
{
    <#
    .DESCRIPTION
        Create network settings to be used for the target clusters.
 
    .PARAMETER name
        The name of the vnet
 
    .PARAMETER vswitchName
        The name of the vswitch
 
    .PARAMETER vlanID
        The VLAN ID for the vnet
 
    .PARAMETER ipaddressprefix
        The address prefix to use for static IP assignment
 
    .PARAMETER gateway
        The gateway to use when using static IP
 
    .PARAMETER dnsservers
        The dnsservers to use when using static IP
 
    .PARAMETER vippoolstart
        The starting ip address to use for the vip pool.
        The vip pool addresses will be used by the k8s API server and k8s services'
 
    .PARAMETER vippoolend
        The ending ip address to use for the vip pool.
        The vip pool addresses will be used by the k8s API server and k8s services
 
    .PARAMETER k8snodeippoolstart
        The starting ip address to use for VM's in the cluster.
 
    .PARAMETER k8snodeippoolend
        The ending ip address to use for VM's in the cluster.
 
    .OUTPUTS
        VirtualNetwork object
 
    .NOTES
        The cmdlet will throw an exception if the mgmt cluster is not up.
 
    .EXAMPLE
        $clusterVNetDHCP = New-AksHciClusterNetwork -name e1 -vswitchName External -vippoolstart 172.16.0.0 -vippoolend 172.16.0.240
 
    .EXAMPLE
        $clusterVNetStatic = New-AksHciClusterNetwork -name e1 -vswitchName External -ipaddressprefix 172.16.0.0/24 -gateway 172.16.0.1 -dnsservers 4.4.4.4, 8.8.8.8 -vippoolstart 172.16.0.0 -vippoolend 172.16.0.240
    #>


    param (
        [Parameter(Mandatory=$true)]
        [string] $name,

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

        [Parameter(Mandatory=$false)]
        [int] $vlanID = $global:defaultVlanID,

        [Parameter(Mandatory=$false)]
        [String] $ipaddressprefix,

        [Parameter(Mandatory=$false)]
        [String] $gateway,

        [Parameter(Mandatory=$false)]
        [String[]] $dnsservers,

        [Parameter(Mandatory=$true)]
        [String] $vippoolstart,

        [Parameter(Mandatory=$true)]
        [String] $vippoolend,

        [Parameter(Mandatory=$false)]
        [String] $k8snodeippoolstart,

        [Parameter(Mandatory=$false)]
        [String] $k8snodeippoolend,

        [Parameter()]
        [String] $activity
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $name"
    }

    Initialize-KvaEnvironment -activity $activity

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($KvaLocMessage.kva_validating_network_configuration)

    $vnet = New-VirtualNetwork -name $name -vswitchName $vswitchName -vlanID $vlanID -ipaddressprefix $ipaddressprefix -gateway $gateway -dnsservers $dnsservers -vippoolstart $vippoolstart -vippoolend $vippoolend -k8snodeippoolstart $k8snodeippoolstart -k8snodeippoolend $k8snodeippoolend

    $dnsserver = ""
    if(-Not [string]::IsNullOrEmpty($vnet.IpAddressPrefix))
    {
        foreach ($dns in $vnet.DnsServers)
        {
            if(-Not [string]::IsNullOrEmpty($dns))
            {
                $dnsserver += "`n - " +  $dns
            }
        }
    }

    $yaml = @"
virtualnetwork:
  name: "$($vnet.Name)"
  vswitchname: "$($vnet.VswitchName)"
  type: "Transparent"
  vlanid: $($vnet.VlanID)
  ipaddressprefix: $($vnet.IpAddressPrefix)
  gateway: $($vnet.Gateway)
  dnsservers: $dnsserver
  vippoolstart: $($vnet.VipPoolStart)
  vippoolend: $($vnet.VipPoolEnd)
  k8snodeippoolstart: $($vnet.K8snodeIPPoolStart)
  k8snodeippoolend: $($vnet.K8snodeIPPoolEnd)
"@


    $yamlFile = $($global:config[$modulename]["installationPackageDir"]+"\"+$global:yamlDirectoryName+"\$name.yaml")
    Set-Content -Path $yamlFile -Value $yaml -ErrorVariable err
    if ($null -ne $err -and $err.count -gt 0)
    {
        throw $err
    }
    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($KvaLocMessage.kva_creating_network)
    $kubeconfig = Get-KvaCredential -activity $activity
    Invoke-KvaCtl -arguments "network create --networkconfig ""$yamlFile"" --kubeconfig ""$kubeconfig""" -showOutput -activity $activity

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
    return $vnet
}

function Remove-KvaClusterNetwork
{
    <#
    .DESCRIPTION
        Remove a virtual network object for a target cluster
 
    .PARAMETER name
        The name of the vnet
 
    .NOTES
        The cmdlet will throw an exception if the network is still being used.
        The cmdlet will throw an exception if the mgmt cluster is not up.
 
    .EXAMPLE
        Remove-AksHciClusterNetwork -name e1
    #>


    param (
        [Parameter(Mandatory=$true)]
        [string] $name,

        [Parameter()]
        [String] $activity
    )


    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $name"
    }

    Initialize-KvaEnvironment -activity $activity

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($KvaLocMessage.kva_removing_network)
    Write-SubStatus -moduleName $moduleName $($KvaLocMessage.kva_network_removal_in_progress)

    $kubeconfig = Get-KvaCredential -activity $activity
    Invoke-KvaCtl -arguments "network delete --networkname $name --kubeconfig ""$kubeconfig""" -showOutput -activity $activity

    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
}

function Get-KvaClusterNetwork
{
    <#
    .DESCRIPTION
        Gets the VirtualNetwork object for a target cluster given either the vnet name or the cluster name. If no parameter is given, all vnet's are returned.
 
    .PARAMETER name
        The name of the vnet
 
    .PARAMETER clusterName
        The name of the cluster (NOTE: This is P2 -- but we really want to add this functionality for Ben)
 
    .OUTPUTS
        If name is specified, the VirtualNetwork object will be returned.
        If clusterName is specified, the VirtualNetwork object that the cluster is using will be returned.
        If no parameters are specified all VirtualNetwork objects will be returned.
 
    .NOTES
        The cmdlet will throw an exception if the mgmt cluster is not up.
 
    .EXAMPLE
        $clusterVNet = Get-AksHciClusterNetwork -name e1
 
    .EXAMPLE
        $clusterVNet = Get-AksHciClusterNetwork -clusterName myTargetCluster
 
    .EXAMPLE
        $allClusterVNets = Get-AksHciClusterNetwork
    #>


    param (
        [Parameter(Mandatory=$false)]
        [string] $name,

        [Parameter(Mandatory=$false)]
        [string] $clusterName,

        [Parameter()]
        [String] $activity
    )

    if (-not $activity)
    {
        $activity = "$($MyInvocation.MyCommand.Name) - $name"
    }

    Initialize-KvaEnvironment -activity $activity

    Write-StatusWithProgress -activity $activity -status $($KvaLocMessage.kva_retrieving_network) -moduleName $moduleName

    Write-Status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_retrieving_configuration_for_network, $name)) -moduleName $global:KvaModule
    $retrievedVnet = Get-KvaClusterNetworkInternal -name $name -clusterName $clusterName -activity $activity
    Write-SubStatus $($KvaLocMessage.kva_successfully_retrieved_network) -moduleName $global:KvaModule
    return $retrievedVnet
}

function Get-KvaClusterNetworkInternal
{
    <#
    .DESCRIPTION
        This function is similar to Get-KvaNetworkCluster excpet it does not update the powershell
        status. It is intended to be called by other cmdlets that do update the status
 
    .PARAMETER name
        The name of the vnet
 
    .PARAMETER clusterName
        The name of the cluster (NOTE: This is P2 -- but we really want to add this functionality for Ben)
 
    .OUTPUTS
        If name is specified, the VirtualNetwork object will be returned.
        If clusterName is specified, the VirtualNetwork object that the cluster is using will be returned.
        If no parameters are specified all VirtualNetwork objects will be returned.
 
    .NOTES
        The cmdlet will throw an exception if the mgmt cluster is not up.
 
    .EXAMPLE
        $clusterVNet = Get-AksHciClusterNetwork -name e1
 
    .EXAMPLE
        $clusterVNet = Get-AksHciClusterNetwork -clusterName myTargetCluster
 
    .EXAMPLE
        $allClusterVNets = Get-AksHciClusterNetwork
    #>


    param (
        [Parameter(Mandatory=$false)]
        [string] $name,

        [Parameter(Mandatory=$false)]
        [string] $clusterName,

        [Parameter()]
        [String] $activity
    )

    $kubeconfig = Get-KvaCredential -activity $activity
    $queryParam = ""
    if ($name) {
        $queryParam = "--networkname $name"
    } elseif ($clusterName) {
        $queryParam = "--clustername $clusterName"
    }

    $kvaVnets = Invoke-KvaCtl -arguments "network get $queryParam --kubeconfig ""$kubeconfig"" --outputformat json" -activity $activity | ConvertFrom-Json
    $vnet = @()
    foreach ($kvaVnet in $kvaVnets) {
       $vnet += [VirtualNetwork]::new($kvaVnet.virtualnetwork.name, $kvaVnet.virtualnetwork.vswitchname, $kvaVnet.virtualnetwork.ipaddressprefix, $kvaVnet.virtualnetwork.gateway, $kvaVnet.virtualnetwork.dnsservers, $kvaVnet.virtualnetwork.macpoolname, $kvaVnet.virtualnetwork.vlanid, $kvaVnet.virtualnetwork.vippoolstart, $kvaVnet.virtualnetwork.vippoolend, $kvaVnet.virtualnetwork.k8snodeippoolstart, $kvaVnet.virtualnetwork.k8snodeippoolend)
    }
    return $vnet
}

function Enable-KvaPreview
{
    <#
    .SYNOPSIS
        Enable AKSHCI catalog and ring configuration to expose early access preview builds.
 
    .DESCRIPTION
        Enable AKSHCI catalog and ring configuration to expose early access preview builds.
 
    .PARAMETER activity
        Activity name to use when updating progress
 
    .PARAMETER catalog
        Release catalog for AKS HCI. Reserved for internal use. We do not recommend using this parameter.
 
    .PARAMETER ring
        Audience (aka ring) type of each catalog. Reserved for internal use. We do not recommend using this parameter.
    #>


    [CmdletBinding()]
    param (
        [parameter(DontShow)]
        [String] $activity = $MyInvocation.MyCommand.Name,

        [parameter()]
        [String] $catalog = "aks-hci-stable-catalogs-ext",

        [parameter()]
        [String] $ring = "earlyaccesspreview"

    )


    #Set KvaConfig for preview
    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_enabling_preview, $moduleName))
    Set-KvaConfigValue -name "catalog" -value $catalog
    Set-KvaConfigValue -name "ring" -value $ring
    Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_configuration_for_module_update, $moduleName))
    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)

}

function Disable-KvaPreview
{

    <#
    .SYNOPSIS
        Disable AKSHCI catalog and ring configuration which exposes early access preview builds and revert to a stable build.
 
    .DESCRIPTION
        Disable AKSHCI catalog and ring configuration which exposes early access preview builds and revert to a stable build.
 
    .PARAMETER activity
        Activity name to use when updating progress.
 
    .PARAMETER catalog
        Release catalog for AKS HCI. Reserved for internal use. We do not recommend using this parameter.
 
    .PARAMETER ring
        Audience (aka ring) type of each catalog. Reserved for internal use. We do not recommend using this parameter.
    #>


    [CmdletBinding()]
    param (
        [parameter(DontShow)]
        [String] $activity = $MyInvocation.MyCommand.Name,

        [parameter()]
        [String] $catalog = "aks-hci-stable-catalogs-ext",

        [parameter()]
        [String] $ring = "stable"
    )

    #Set KvaConfig
    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_disabling_preview, $moduleName))
    Set-KvaConfigValue -name "catalog" -value $catalog
    Set-KvaConfigValue -name "ring" -value $ring
    Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $KvaLocMessage.kva_configuration_for_module_update, $moduleName))
    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)

}

function  Update-ConfigMapPauseImages
{
    <#
    .SYNOPSIS
        Update-Kva workaround for Github Issues https://github.com/Azure/aks-hci/issues/147 and https://github.com/Azure/aks-hci/issues/148
 
    .DESCRIPTION
        Patches the KVA ConfigMaps, which hold the pause version metadata, to the correct pause version. This will prevent users from hitting Github Issues
        https://github.com/Azure/aks-hci/issues/147 and https://github.com/Azure/aks-hci/issues/148
     
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    [CmdletBinding()]
    param (
        [String]$activity = $MyInvocation.MyCommand.Name
    )

    try
    {
        $deploymentManifestLocation = $([io.Path]::Combine($global:config[$modulename]["installationPackageDir"], $global:cloudOperatorYaml))

        $file = Get-Content -Path $deploymentManifestLocation -Raw
        $separator = "---"
        $objects = $file -split $separator, 0, "multiline"
    
        $newFile = ""
        foreach ($object in $objects)
        {
            if (($object.Contains("kubernetes: v1.21.2") -or $object.Contains("kubernetes: v1.21.1")) -and $object.Contains('mariner: "2021-10-01"'))
            {
                $object = $object -Replace ' pause: "3.2"', ' pause: "3.4.1"' 
            }
    
            $newFile += $object + "---"
        }
    
        $newFile | Set-Content -Path $deploymentManifestLocation    
    }
    catch [Exception]
    {
        Write-ModuleEventLog -moduleName $moduleName -entryType Warning -eventId 2 -message "$activity - $_"
    }
}

#endregion

# SIG # Begin signature block
# MIInugYJKoZIhvcNAQcCoIInqzCCJ6cCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCC+y34traXELp7F
# 3ERx01L6a4SRo4U5I9AiwlMUoZ4YxqCCDYEwggX/MIID56ADAgECAhMzAAACUosz
# qviV8znbAAAAAAJSMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMjU5WhcNMjIwOTAxMTgzMjU5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDQ5M+Ps/X7BNuv5B/0I6uoDwj0NJOo1KrVQqO7ggRXccklyTrWL4xMShjIou2I
# sbYnF67wXzVAq5Om4oe+LfzSDOzjcb6ms00gBo0OQaqwQ1BijyJ7NvDf80I1fW9O
# L76Kt0Wpc2zrGhzcHdb7upPrvxvSNNUvxK3sgw7YTt31410vpEp8yfBEl/hd8ZzA
# v47DCgJ5j1zm295s1RVZHNp6MoiQFVOECm4AwK2l28i+YER1JO4IplTH44uvzX9o
# RnJHaMvWzZEpozPy4jNO2DDqbcNs4zh7AWMhE1PWFVA+CHI/En5nASvCvLmuR/t8
# q4bc8XR8QIZJQSp+2U6m2ldNAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUNZJaEUGL2Guwt7ZOAu4efEYXedEw
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDY3NTk3MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAFkk3
# uSxkTEBh1NtAl7BivIEsAWdgX1qZ+EdZMYbQKasY6IhSLXRMxF1B3OKdR9K/kccp
# kvNcGl8D7YyYS4mhCUMBR+VLrg3f8PUj38A9V5aiY2/Jok7WZFOAmjPRNNGnyeg7
# l0lTiThFqE+2aOs6+heegqAdelGgNJKRHLWRuhGKuLIw5lkgx9Ky+QvZrn/Ddi8u
# TIgWKp+MGG8xY6PBvvjgt9jQShlnPrZ3UY8Bvwy6rynhXBaV0V0TTL0gEx7eh/K1
# o8Miaru6s/7FyqOLeUS4vTHh9TgBL5DtxCYurXbSBVtL1Fj44+Od/6cmC9mmvrti
# yG709Y3Rd3YdJj2f3GJq7Y7KdWq0QYhatKhBeg4fxjhg0yut2g6aM1mxjNPrE48z
# 6HWCNGu9gMK5ZudldRw4a45Z06Aoktof0CqOyTErvq0YjoE4Xpa0+87T/PVUXNqf
# 7Y+qSU7+9LtLQuMYR4w3cSPjuNusvLf9gBnch5RqM7kaDtYWDgLyB42EfsxeMqwK
# WwA+TVi0HrWRqfSx2olbE56hJcEkMjOSKz3sRuupFCX3UroyYf52L+2iVTrda8XW
# esPG62Mnn3T8AuLfzeJFuAbfOSERx7IFZO92UPoXE1uEjL5skl1yTZB3MubgOA4F
# 8KoRNhviFAEST+nG8c8uIsbZeb08SeYQMqjVEmkwggd6MIIFYqADAgECAgphDpDS
# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla
# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT
# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG
# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S
# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz
# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7
# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u
# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33
# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl
# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP
# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB
# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF
# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM
# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ
# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud
# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO
# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0
# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p
# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw
# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA
# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY
# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj
# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd
# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ
# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf
# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ
# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j
# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B
# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96
# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7
# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I
# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIZjzCCGYsCAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAlKLM6r4lfM52wAAAAACUjAN
# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQg/roNjY7+
# NA3UsfA/vBAYZjzlwSI6pWCZinC2S4x/8gswQgYKKwYBBAGCNwIBDDE0MDKgFIAS
# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN
# BgkqhkiG9w0BAQEFAASCAQA8I4ah5Ps03DidLHRgS7DoSOIo+lLolKagVpODDLws
# PGaiGYbZJt2B4KpTFY9R5CjheZAIhNUhCrTLm01aFQqOwi+xbN/es1yuPVmzKrnE
# dXf3qTVMJkBQQNcDv0y4IPLKooYVVUoLvhjxjE4FoA6J7EwWx0A24hmYMOvz9opp
# ucugoS82PAP0FoClbf+9pUL14K9Y3/2gkMrnyMv0/o8l+bk0XsAyR24SrEWIYtdY
# jmS5f3VvIOPem9mSn5JI1k/7K0Ep8JmnI/EAGj28hNFNMr4MDRVWNS9gnB94k4F6
# Bjlapcg+BGqK7tiOSsAD82TtCTZVc/FB4wAzXnSJ5YK8oYIXGTCCFxUGCisGAQQB
# gjcDAwExghcFMIIXAQYJKoZIhvcNAQcCoIIW8jCCFu4CAQMxDzANBglghkgBZQME
# AgEFADCCAVkGCyqGSIb3DQEJEAEEoIIBSASCAUQwggFAAgEBBgorBgEEAYRZCgMB
# MDEwDQYJYIZIAWUDBAIBBQAEIHBAywZVEE3+2uxxo0cPj7clv4nrHxhxBBw0K0fx
# +ikBAgZihktdYlYYEzIwMjIwNjAyMjAyMzI3LjY1NFowBIACAfSggdikgdUwgdIx
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1p
# Y3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEmMCQGA1UECxMdVGhh
# bGVzIFRTUyBFU046OEQ0MS00QkY3LUIzQjcxJTAjBgNVBAMTHE1pY3Jvc29mdCBU
# aW1lLVN0YW1wIFNlcnZpY2WgghFoMIIHFDCCBPygAwIBAgITMwAAAYguzcaBQeG8
# KgABAAABiDANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0Eg
# MjAxMDAeFw0yMTEwMjgxOTI3NDBaFw0yMzAxMjYxOTI3NDBaMIHSMQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQg
# SXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg
# RVNOOjhENDEtNEJGNy1CM0I3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt
# cCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAmucQCAQm
# kcXHyDrV4S88VeJg2XGqNKcWpsrapRKFWchhjLsf/M9XN9bgznLN48BXPAtOlwoe
# dB2kN4bZdPP3KdRNbYq1tNFUh8UnmjCCr+CjLlrigHcmS0R+rsN2gBMXlLEZh2W/
# COuD9VOLsb2P2jDp433V4rUAAUW82M7rg81d3OcctO+1XW1h3EtbQtS6QEkw6DYI
# uvfX7Aw8jXHZnsMugP8ZA1otprpTNUh/zRWC7CJyBzymQIDSCdWhVfD4shxe+Rs6
# 1axf27bTg5H/V/SkNd9hzM6Nq/y2OjDKrLtuN9hS53569uhTNQeAhAVDfeHpEzlM
# vtXOyX6MTme3jnHdHPj6GLT9AMRIrAf96hPYOiPEBvHtrg6MpiI3+l6NlbSOs16/
# FTeljT1+sdsWGtFTZvea9pAqV1aB795aDkmZ6sRm5jtdnVazfoWrHd3vDeh35WV0
# 8vW4TlOfEcV2+KbairPxaFkJ4+tlsJ+MfsVOiTr/ZnDgaMaHnzzogelI3AofDU9I
# TbMkTtTxrLPygTbRdtbptrnLzBn2jzR4TJfkQo+hzWuaMu5OtMZiKV2I5MO0m1mK
# uUAgoq+442Lw8CQuj9EC2F8nTbJb2NcUDg+74dgJis/P8Ba/OrlxW+Trgc6TPGxC
# OtT739UqeslvWD6rNQ6UEO9f7vWDkhd2vtsCAwEAAaOCATYwggEyMB0GA1UdDgQW
# BBRkebVQxKO7zru9+o27GjPljMlKSjAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJl
# pxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAx
# MCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3Rh
# bXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoG
# CCsGAQUFBwMIMA0GCSqGSIb3DQEBCwUAA4ICAQBAEFrb+1gIJsv/GKLS2zavm2ek
# 177mk4yu6BuS6ViIuL0e20YN2ddXeiUhEdhk3FRto/GD93k5SIyNJ6X+p8uQMOxI
# 23YOSdyEzLJwh7+ftu0If8y3x6AJ0S1d12OZ7fsYqljHUeccneS9DWqipHk8uM8m
# 2ZbBhRnUN8M4iqg4roJGmZKZ9Fc8Z7ZHJgM97i7fIyA9hJH017z25WrDJlxapD5d
# mMyNyzzfAVqaByemCoBn4VkRCGNISx0xRlcb93W6ENhJF1NBjMl3cKVEHW4d8Y0N
# ZhpdXDteLk9HgbJyeCI2fN9GBrCS1B1ak+194PGiZKL8+gtK7NorAoAMQvFkYgrH
# rWCYfjV6PouC3N+A6wOBrckVOHT9PUIDK5ADCH4ZraQideS9LD/imKHM3I4iazPk
# ocHcFHB9yo5d9lMJZ+pnAAWglQQjMWhUqnE/llA+EqjbO0lAxlmUtVioVUswhT3p
# K6DjFRXM/LUxwTttufz1zBjELkRIZ8uCy1YkMxfBFwEos/QFIlDaFSvUn4IiWZA3
# VLfAEjy51iJwK2jSIHw+1bjCI+FBHcCTRH2pP3+h5DlQ5AZ/dvcfNrATP1wwz25I
# r8KgKObHRCIYH4VI2VrmOboSHFG79JbHdkPVSjfLxTuTsoh5FzoU1t5urG0rwulo
# ZZFZxTkrxfyTkhvmjDCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUw
# DQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n
# dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9y
# YXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhv
# cml0eSAyMDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9z
# b2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
# ggIKAoICAQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg
# 4r25PhdgM/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aO
# RmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41
# JmTamDu6GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5
# LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL
# 64NF50ZuyjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9
# QZpGdc3EXzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj
# 0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqE
# UUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0
# kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435
# UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB
# 3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTE
# mr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwG
# A1UdIARVMFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93
# d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNV
# HSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNV
# HQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo
# 0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29m
# dC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5j
# cmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jv
# c29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDAN
# BgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4
# sQaTlz0xM7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th54
# 2DYunKmCVgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRX
# ud2f8449xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBew
# VIVCs/wMnosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0
# DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+Cljd
# QDzHVG2dY3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFr
# DZ+kKNxnGSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFh
# bHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7n
# tdAoGokLjzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+
# oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6Fw
# ZvKhggLXMIICQAIBATCCAQChgdikgdUwgdIxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
# EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv
# ZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh
# dGlvbnMgTGltaXRlZDEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046OEQ0MS00QkY3
# LUIzQjcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoB
# ATAHBgUrDgMCGgMVAOE8isx8IBeVPSweD805l5Qdeg5CoIGDMIGApH4wfDELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9z
# b2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwDQYJKoZIhvcNAQEFBQACBQDmQpV1MCIY
# DzIwMjIwNjAyMDk0ODM3WhgPMjAyMjA2MDMwOTQ4MzdaMHcwPQYKKwYBBAGEWQoE
# ATEvMC0wCgIFAOZClXUCAQAwCgIBAAICA7QCAf8wBwIBAAICEVYwCgIFAOZD5vUC
# AQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAKMAgCAQACAwehIKEK
# MAgCAQACAwGGoDANBgkqhkiG9w0BAQUFAAOBgQC0KesQE/zF03DTZiIFk1XOzumI
# E9qv8xsmwvwkmHufTOlLwZiNQJ/YCJvxT/MF2wbgLiHULtjuD4PnnG0WhQ76Go5l
# WrQa9TMNaRcBZb8bxnhzmdgTRCSWQc0LjsgQ89uboipJgsjR2wBQbAO7OCpl53BZ
# SzR0+3udkd++5h/FZzGCBA0wggQJAgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwAhMzAAABiC7NxoFB4bwqAAEAAAGIMA0GCWCGSAFlAwQCAQUAoIIB
# SjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEIGRE
# iWvgA/g73UpauJXGxipHM6vJyk2oVL3tEm+/jACoMIH6BgsqhkiG9w0BCRACLzGB
# 6jCB5zCB5DCBvQQgZune7awGN0aEgvjP7JyO3NKl7hstX8ChhrKmXtJJQKUwgZgw
# gYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAYguzcaBQeG8
# KgABAAABiDAiBCBUeIZxeQ+pHsVRU4Wf+0zdv8oYIEWrXHjU7vR0v5+yXDANBgkq
# hkiG9w0BAQsFAASCAgARUgTujTzd/pMbr1WiQ7V9KyFuiVY6olixRyFqd7kEL3kG
# OsvtBchWVA55sksHu8z8Xze3FSWRHXpamX+y+t42hvSmcMB9Igt+/uo8VdQDh1ZJ
# IIoBETWnGmsNie/XDSM2Y/UKcVRFI1xgcz/l/U6VE774KN5UDBe7A6gPjOEmyhFa
# nrP/hYFk8PUQHpmPc7Ehp47I9DtYz1WvNELJNKPcaTweBfUOgWOp9BWDJ/7GN/oJ
# b+GNeWQRH+OFHRF86iGreVuLstatx7QDCPEVkBUpMlNfrY2gnxC/w/U66lfiAeHj
# bnx6I+IiMUYzJ5nFoGfwlbjuEqJGXYn/ogSYKaiZKx5j3hhlGho9APlXfFeIavv/
# aOqHT17CeZTJWdf75YZ/EaJUZrinBwAXqq7wsm93CEy6njVGwRGyZXHZN+agLB70
# S30XK+tEYZvlbLacu/6v47O9idx/t9lyt0oEDWYeqVsgJVywz+ufbmXhas4/joSg
# zDdT0XIDNQYHLUNqfdqBcxowNqgM6oM96/cZWOQ0W1/fjkNOrfEDN20eBtiCjCsw
# OhwIXatGh0UWdwKSUZbTCtVQjFkHS29l5N6/X9kTclgCoMoncfvKz5+M9nWiPOdG
# m5PZW/9KYL+mlMGpoXb6InUCIz6/IefXv6a2T5CAVr2OWz7jrDEh1kcDpV/xUQ==
# SIG # End signature block