ArcHci.psm1

#########################################################################################
#
# Copyright (c) Microsoft Corporation. All rights reserved.
#
# ARC appliance self-service VM Day 0/2 Operations
#
#########################################################################################

#requires -runasadministrator

using module moc

# Imports Common.psm1 from Moc module
# this also imports $global:defaultworkingDir
Import-Module ((Get-Module "MOC" -ListAvailable | Sort-Object Version -Descending)[0].ModuleBase + "\common.psm1")

$moduleName = "ArcHci"

$global:ArcHciModule = $moduleName

$defaultTokenExpiryDays = 365
$global:kvaCtlFullPath = "C:\Program Files\AksHci\kvactl.exe"
$global:sshPrivateKeyFile = "C:\ProgramData\kva\.ssh\logkey"
$regexPatternAzureResourceGroup = "^[-\w\._\(\)]+"
$regexPatternRFC1123 = "^[-a-zA-Z0-9\.]+"
$regexPatternVersionNumber = "^[0-9]+(?:\.[0-9]+)+"
$regexPatternHostName = "^(?!-)[a-zA-Z0-9-]{1,}[^-]$"
$regexPatternDomainName = "^(?!-)[A-Za-z0-9-]+([\\-\\.]{1}[A-Za-z0-9-]+)*\.[A-Za-z]{2,6}$"
$regexPatternCIDRFormat = "^([0-9]{1,3}\.){3}[0-9]{1,3}\/\b(([0-9]|[1-2][0-9]|3[0-2]))?$"
$regexPatternProxyUrl = "^(?:https?:\/\/)(?:(\w+)(?::(\w*))@)?[a-zA-Z0-9][a-zA-Z0-9-_]{0,61}\.([a-zA-Z0-9][a-zA-Z0-9-_]{0,30}\.){0,8}([a-zA-Z]){1,62}(:((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{0,5})|([0-9]{1,4})))?$"
$space = ' '
$ipv4ValidationError = "{0} {1} is an invalid ip address"
$regexPatternAzureResourceGroupError = "Invalid string {0} detected. {1} can contain only letters, numbers and the following special characters: -,.,_,(,)"
$regexPatternRFC1123Error = "Invalid string {0} detected. {1} can contain only letters, numbers and the following special characters: -,."
$regexPatternVersionNumberError = "Invalid string {0} detected. {1} can contain only number(s) followed by a special character '.' followed again by number(s). E.g 1.2, 1.2.6 "
$regexPatternDomainNameError = ("Invalid string {0} detected. Please makes sure {1} matches the following criteria:" +
"`n1. The domain name should be a-z | A-Z | 0-9 and hyphen(-)" +
"`n2. Last Tld must be at least two characters, and a maximum of 6 characters" +
"`n3. The domain name should not start or end with hyphen (-) (e.g. -microsoft.com or microsoft.com-)" +
"`n4. The domain name can be a subdomain (e.g. sharepoint.microsoft.com)" +
"`n5. Or it can be an ip address e.g. 192.168.0.1, 1.1.1.1" +
"`n6. Or it can be a hostname and must be at least two characters e.g. localhost")
$regexPatternCIDRFormatError = "Invalid IPV4 address {0}. {1} address does not conform to cidr format eg. 192.0.1.1/16, 255.255.255.0/24"
$regexPatternProxyUrlError = "Invalid {0} {1} url"
$spaceError = "Value must not contain spaces"
$fileFolderError = "File or folder does not exist"
$folderError = "Folder does not exist"
New-ModuleEventLog -moduleName $script:moduleName

function Reset-ArcHciConfigurationKey
{
    <#
    .DESCRIPTION
        Resets the ARC HCI module configuration key
    #>

    $global:configurationKeys[$global:ArcHciModule] = "HKLM:SOFTWARE\Microsoft\${global:ArcHciModule}PS";
}

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

function Initialize-ArcHciConfiguration
{
    <#
    .DESCRIPTION
        Initialize ARC HCI Configuration
    #>


    Reset-ArcHciConfigurationKey

    if (-Not $global:config.ContainsKey($global:ArcHciModule)) {
        $global:config += @{
            $global:ArcHciModule = @{
            "workingDir" = $global:defaultworkingDir
            };
        }
    }

    if  (-Not (Test-Configuration -moduleName $global:ArcHciModule))
    {
        Set-ConfigurationValue -name "workingDir" -value $global:defaultworkingDir -module $global:ArcHciModule
        Save-ConfigurationDirectory -moduleName $global:ArcHciModule -WorkingDir $global:defaultworkingDir
        Save-Configuration -moduleName $global:ArcHciModule
    }
}

Initialize-ArcHciConfiguration

function Uninitialize-ArcHciConfiguration
{
    <#
    .DESCRIPTION
        Uninitializes ARC HCI Configuration
        Wipes off any existing cached configuration
    #>

    if ($global:config.ContainsKey($global:ArcHciModule)) {
        $global:config.Remove($global:ArcHciModule)
    }
}

function Get-ArcHciConfig
{
    <#
    .DESCRIPTION
        Loads and returns the current ARC HCI configuration.
    #>


    Import-ArcHciConfig
    return $global:config[$global:ArcHciModule]
}

function Get-ArcHciConfigValue
{
    <#
    .DESCRIPTION
        Reads a configuration value from the registry

    .PARAMETER name
        Name of the configuration value
    #>

    param (
        [String] $name
    )

    return Get-ConfigurationValue -name $name -module $global:ArcHciModule
}

function Import-ArcHciConfig
{
    <#
    .DESCRIPTION
        Loads a configuration from persisted storage. If no configuration is present
        then a default configuration can be optionally generated and persisted.
    
    #>



    Reset-ArcHciConfigurationKey
    if  (Test-Configuration -moduleName $global:ArcHciModule)
    {
        Import-Configuration -moduleName $global:ArcHciModule
    }
    else
    {
        throw "ARCHCI doesn't currently have a config file. Please make sure to reload all powershell windows before continuing"
    }
}

function Set-ArcHciConfigValue
{
    <#
    .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
    )
    Reset-ArcHciConfigurationKey
    Set-ConfigurationValue -name $name -value $value -module $global:ArcHciModule
}

function Set-ArcHciConfig
{
    <#
    .DESCRIPTION
        Configures ARCHCI by persisting the specified parameters to the registry.
        Any parameter which is not explictly provided by the user will be defaulted.
    
    .PARAMETER workingDir
        Path to the working directory
    #>


    [CmdletBinding()]
    param (
        [String] $workingDir = $global:defaultworkingDir
    )

    Reset-ArcHciConfigurationKey
    Set-ConfigurationValue -name "workingDir" -value $workingDir -module $global:ArcHciModule
    Save-ConfigurationDirectory -moduleName $global:ArcHciModule -WorkingDir $workingDir
    Save-Configuration -moduleName $global:ArcHciModule
}

function New-MocNetworkSetting {
    <#
    .DESCRIPTION
        Create network settings to be used for the Arc Hci deployment.

    .PARAMETER name
        The name of the vnet

    .PARAMETER vswitchName
        The name of the vswitch

    .PARAMETER MacPoolName
        The name of the mac pool

    .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
        Beginning of the IP address pool

    .PARAMETER vipPoolEnd
        End of the IP address pool
    
    .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
    .EXAMPLE
        New-MocNetworkSetting -name "External" -vipPoolStart "172.16.0.240" -vipPoolEnd "172.16.0.250"
    #>


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

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

        [Parameter(Mandatory=$false)]
        [String] $MacPoolName = $global:cloudMacPool,

        [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
    )

   return New-VirtualNetwork -name $name -vswitchName $vswitchName -MacPoolName $MacPoolName -vlanID $vlanID -ipaddressprefix $ipaddressprefix -gateway $gateway -dnsservers $dnsservers -vippoolstart $vippoolstart -vippoolend $vippoolend -k8snodeippoolstart $k8snodeippoolstart -k8snodeippoolend $k8snodeippoolend
}

function New-ArcHciMocTokenFile {
    <#
    .DESCRIPTION
        Creates a new MOC Admin Identity for the Appliance and writes it to the given file path.
    .PARAMETER arcTokenFilePath
        Optional parameter. Path to the file where the arc token would be output.
    #>

    Param (
        [Parameter(Mandatory=$false)]
        [String] $arcTokenFilePath = ($(Get-Location).Path + "\kvatoken.tok")
    )

    $clusterName = "Appliance"
    try { Remove-MocIdentity -name $clusterName } catch { }
    Remove-CertFiles
    $base64Identity = New-MocAdminIdentity -name $clusterName -validityDays $defaultTokenExpiryDays
    $utf8String = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($base64Identity))
    $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
    [System.IO.File]::WriteAllLines($arcTokenFilePath, $utf8String, $Utf8NoBomEncoding)

    Write-Output "HCI login file successfully generated in '$arcTokenFilePath'"
}

function New-ArcHciIdentityFiles {
    <#
    .DESCRIPTION
        Creates the Arc HCI token files
    
    .PARAMETER workDirectory
        Optional parameter. Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster.

    .PARAMETER azstackhciImage
        Optional parameter. Name of the azstackhci-operator image to use in place of the default image.

    .PARAMETER azstackhciVersion
        Optional parameter. Version of the azstackhci-operator image to use in place of the default image.

    .PARAMETER mocImage
        Optional parameter. Name of the moc-operator image to use in place of the default image.

    .PARAMETER mocVersion
        Optional parameter. Version of the moc-operator image to use in place of the default image.

    .PARAMETER mocConfigFilePath
        Optional parameter. Path to the hci-config.json file.

    .OUTPUTS
        N/A
    
    .EXAMPLE
        New-ArcHciIdentityFiles
    #>

    Param (
        [Parameter(Mandatory=$false)]
        [String] $workDirectory = $(Get-ArcHciConfigValue -name "workingDir"),

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

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

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

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

        [Parameter(Mandatory=$false)]
        [String] $mocConfigFilePath = ($workDirectory + "\hci-config.json")
    )
    
    $mocConfig = Get-MocConfig
    $mocoperator = "moc-operator"
    try { Remove-MocIdentity -name $mocoperator } catch { }
    $mocOperatorIdentity = New-MocIdentity -name $mocoperator -validityDays $defaultTokenExpiryDays -fqdn $mocConfig.cloudFqdn -location $mocConfig.cloudLocation -port $mocConfig.cloudAgentPort -authport $mocConfig.cloudAgentAuthorizerPort -encode
    New-MocRoleAssignmentWhenAvailable -identityName $mocoperator -roleName "Contributor" -location $mocConfig.cloudLocation | Out-Null

    $configData = @{}
    $configData["secret.loginString"] = $mocOperatorIdentity
    $configData["secret.cloudFQDN"] = $mocConfig.cloudFqdn

    if ($azstackhciImage) {
        $configData["azstackhciOperator.image"] = $azstackhciImage
    }
    if ($azstackhciVersion) {
        $configData["azstackhciOperator.version"] = $azstackhciVersion
    }

    if ($mocImage) {
        $configData["mocOperator.image"] = $mocImage
    }
    if ($mocVersion) {
        $configData["mocOperator.version"] = $mocVersion
    }

    $jsonData = convertto-json $configData
    Remove-Item -Path $mocConfigFilePath -Force -ErrorAction Ignore
    Set-Content -Path $mocConfigFilePath -Value $jsonData -ErrorVariable err

    Write-Output "MOC config file successfully generated in '$mocConfigFilePath'"
    Write-Output "Cloud agent service FQDN/IP: '$($mocConfig.cloudFqdn)'"
}

function New-ArcHciConfigFiles {
    <#
    .DESCRIPTION
        Creates the Arc HCI config files
    
    .PARAMETER subscriptionID
        The Azure subscription GUID
    
    .PARAMETER location
        Azure location (optional parameter. defaults to "eastus")
    
    .PARAMETER resourceGroup
        Name of the Azure resource group in which the Arc HCI appliance will be created
    
    .PARAMETER resourceName
        Name of the Azure resource
    
    .PARAMETER controlPlaneIP
        IP Address to be used for the Arc Appliance control plane
    
    .PARAMETER azstackhciImage
        Optional parameter. Name of the azstackhci-operator image to use in place of the default image.

    .PARAMETER azstackhciVersion
        Optional parameter. Version of the azstackhci-operator image to use in place of the default image.

    .PARAMETER mocImage
        Optional parameter. Name of the moc-operator image to use in place of the default image.

    .PARAMETER mocVersion
        Optional parameter. Version of the moc-operator image to use in place of the default image.

    .PARAMETER cloudFqdn
        Optional parameter. Fully qualified domain name (FQDN) or IP address of the cloud agent service.

    .PARAMETER vnetName
        Optional parameter. Name of the virtual network the ARC resource bridge will connect to. The vnet will be automatically created if it doesn't exist. NOTE: this name must be all lower characters.

    .PARAMETER vswitchName
        Optional parameter. Name of the virtual switch the ARC resource bridge will connect to. On HCI, this virtual switch must be an external virtual switch.

    .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 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 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.

    .PARAMETER vlanID
        The VLAN ID for the vnet

    .PARAMETER proxyServerHTTP
        http urls for proxy
    .PARAMETER proxyServerHTTPS
        https urls for proxy

    .PARAMETER proxyServerNoProxy
        urls which can bypass proxy

    .PARAMETER certificateFilePath
        Name of the cert File Path for proxy

    .PARAMETER workDirectory
        Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster.
    .OUTPUTS
        N/A
    .EXAMPLE
        New-ArcHciConfigFiles -subscriptionID "12345678-4567-1234-8888-c64fc26bd67e" -location "eastus" -resourceGroup "MyResourceGroup" -resourceName "MyAppliance"
        
        #Example if the proxy params are provided
        New-ArcHciConfigFiles -subscriptionID "12345678-4567-1234-8888-c64fc26bd67e" -location "eastus" -resourceGroup "MyResourceGroup" -resourceName "MyAppliance" -proxyServerHTTP "http://myproxy:8080" -proxyServerHTTPS "https://myproxy:8080" -proxyServerNoProxy "localhost,127.0.0.1" -certificateFilePath "C:\Proxy.cert"
        
    #>

    Param (
        [Parameter(Mandatory=$true)]
        [GUID] $subscriptionID,

        [Parameter(Mandatory=$false)]
        [String] $location = "eastus",

        [Parameter(Mandatory=$true)]
        [ValidateScript({
            if($_ -notmatch $regexPatternAzureResourceGroup){
              $parameter = "ResourceGroup"  
              throw $regexPatternAzureResourceGroupError -f $_,$parameter
            }
            return $true
          })]
        [String] $resourceGroup,

        [Parameter(Mandatory=$true)]
        [ValidateScript({
            if($_ -notmatch $regexPatternRFC1123){
              $parameter = "ResourceName"  
              throw $regexPatternRFC1123Error -f $_,$parameter
            }
            return $true
          })]
        [String] $resourceName,

        [Parameter(Mandatory=$true)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            $response = Test-IPV4Address -ip $_
            if(!$response){
              $parameter = "ControlPlaneIP"
              throw "$ipv4ValidationError" -f $parameter,$_
            }
            return $true
          })]
        [String] $controlPlaneIP,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -notmatch $regexPatternRFC1123){
              $parameter = "azstackhciImage"
              throw $regexPatternRFC1123Error -f $_,$parameter
            }
            return $true
          })]
        [String] $azstackhciImage,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -notmatch $regexPatternVersionNumber){
              $parameter = "azstackhciVersion"
              throw $regexPatternVersionNumberError -f $_,$parameter
            }
            return $true
          })]
        [String] $azstackhciVersion,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -notmatch $regexPatternRFC1123){
              $parameter = "mocImage"
              throw $regexPatternRFC1123Error -f $_,$parameter
            }
            return $true
          })]
        [String] $mocImage,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -notmatch $regexPatternVersionNumber){
              $parameter = "mocVersion"  
              throw $regexPatternVersionNumberError -f $_,$parameter 
            }
            return $true
          })]
        [String] $mocVersion,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            $response = Test-IPV4Address -ip $_
            if($response){
                return $true
            }
            if($_ -match $regexPatternHostName){
                return $true
            }
            if($_ -notmatch $regexPatternDomainName){
              $parameter = "cloudFqdn"  
              throw $regexPatternDomainNameError -f $_,$parameter
            }
            return $true
          })]
        [String] $cloudFqdn,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -notmatch $regexPatternRFC1123){
              $parameter = "vnetName"   
              throw $regexPatternRFC1123Error -f $_,$parameter
            }
            return $true
          })]
        [String] $vnetName,

        # vswitchName can accept any characters
        [Parameter(Mandatory=$false)]
        [String] $vswitchName,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            $response = Test-IPV4Address -ip $_
            if(!$response){
              $parameter = "VipPoolStart"
              throw "$ipv4ValidationError" -f $parameter,$_
            }
            return $true
          })]
        [String] $vippoolstart,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            $response = Test-IPV4Address -ip $_
            if(!$response){
              $parameter = "VipPoolEnd"
              throw "$ipv4ValidationError" -f $parameter,$_
            }
            return $true
          })]
        [String] $vippoolend,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -notmatch $regexPatternCIDRFormat){
              $parameter = "ipaddressprefix"  
              throw $regexPatternCIDRFormatError -f $_,$parameter
            }
            return $true
          })]
        [String] $ipaddressprefix,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            $response = Test-IPV4Address -ip $_
            if(!$response){
              $parameter = "Gateway"
              throw "$ipv4ValidationError" -f $parameter,$_
            }
            return $true
          })]
        [String] $gateway,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ($_ -eq $null -or $_.count -eq 0 -or $_.length -eq 0) {
                return $true
            }
            foreach($i in $_){
            $response = Test-IPV4Address -ip $i
            if(!$response){
              $parameter = "DnsServers"
              throw "$ipv4ValidationError" -f $parameter,$i
            }
        }
            return $true
          })]
        [String[]] $dnsservers = @(),

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            $response = Test-IPV4Address -ip $_
            if(!$response){
              $parameter = "K8sNodeIpPoolStart"
              throw "$ipv4ValidationError" -f $parameter,$_
            }
            return $true
          })]
        [String] $k8snodeippoolstart,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            $response = Test-IPV4Address -ip $_
            if(!$response){
              $parameter = "K8sNodeIpPoolEnd"
              throw "$ipv4ValidationError" -f $parameter,$_
            }
            return $true
          })]
        [String] $k8snodeippoolend,

        [Parameter(Mandatory=$false)]
        [ValidateRange(0, 4094)]
        [Int] $vlanID,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -notmatch $regexPatternProxyUrl){
              $parameter = "proxyServerHTTP"
              throw $regexPatternProxyUrlError -f $parameter,$_
            }
            return $true
          })]
        [String] $proxyServerHTTP,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -notmatch $regexPatternProxyUrl){
              $parameter = "proxyServerHTTPS"
              throw $regexPatternProxyUrlError -f $parameter,$_
            }
            return $true
          })]
        [String] $proxyServerHTTPS,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -match $space){
              throw $spaceError
            }
            return $true
          })]
        [String] $proxyServerNoProxy,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -match $space){
                  throw $spaceError
            }  
            if(-not ($_ | Test-Path)){
              throw $fileFolderError
            }
            return $true
          })]
        [String] $certificateFilePath,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -match $space){
                  throw $spaceError
            }  
            if(-not ($_ | Test-Path)){
              throw $folderError
            }
            return $true
          })]
        [String] $workDirectory = $global:defaultworkingDir
    )

    Test-HCIMachineRequirements

    New-ArcHciConfigFilesInternal -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName `
        -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -controlPlaneIP $controlPlaneIP -workDirectory $workDirectory `
        -cloudFqdn $cloudFqdn -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -gateway $gateway -dnsservers ($dnsservers -split ",") -ipaddressprefix $ipaddressprefix -k8snodeippoolstart $k8snodeippoolstart -k8snodeippoolend $k8snodeippoolend -vlanID $vlanID `
        -proxyServerHTTP $proxyServerHTTP -proxyServerHTTPS $proxyServerHTTPS -proxyServerNoProxy $proxyServerNoProxy -certificateFilePath $certificateFilePath

}

function New-ArcHciConfigFilesInternal {
    <#
    .DESCRIPTION
        Creates the Arc HCI config files
        Documentation:
            See 'New-ArcHciAksConfigFiles' and 'New-ArcHciIdentityFiles'
    #>

    Param (
        [Parameter(Mandatory=$true)]
        [GUID] $subscriptionID,
        
        [Parameter(Mandatory=$false)]
        [String] $location = "eastus",

        [Parameter(Mandatory=$true)]
        [ValidateScript({
            if($_ -notmatch $regexPatternAzureResourceGroup){
              $parameter = "ResourceGroup"  
              throw $regexPatternAzureResourceGroupError -f $_,$parameter
            }
            return $true
          })]
        [String] $resourceGroup,

        [Parameter(Mandatory=$true)]
        [ValidateScript({
            if($_ -notmatch $regexPatternRFC1123){
              $parameter = "ResourceName"  
              throw $regexPatternRFC1123Error -f $_,$parameter
            }
            return $true
          })]
        [String] $resourceName,

        [Parameter(Mandatory=$true)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            $response = Test-IPV4Address -ip $_
            if(!$response){
              $parameter = "ControlPlaneIp"  
              throw "$ipv4ValidationError" -f $parameter,$_
            }
            return $true
          })]
        [String] $controlPlaneIP,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -notmatch $regexPatternRFC1123){
              $parameter = "azstackhciImage"
              throw $regexPatternRFC1123Error -f $_,$parameter
            }
            return $true
          })]
        [String] $azstackhciImage,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -notmatch $regexPatternVersionNumber){
              $parameter = "azstackhciVersion"
              throw $regexPatternVersionNumberError -f $_,$parameter
            }
            return $true
          })]
        [String] $azstackhciVersion,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -notmatch $regexPatternRFC1123){
              $parameter = "mocImage"
              throw $regexPatternRFC1123Error -f $_,$parameter
            }
            return $true
          })]
        [String] $mocImage,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -notmatch $regexPatternVersionNumber){
              $parameter = "mocVersion"  
              throw $regexPatternVersionNumberError -f $_,$parameter
            }
            return $true
          })]
        [String] $mocVersion,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            $response = Test-IPV4Address -ip $_
            if($response){
                return $true
            }
            if($_ -match $regexPatternHostName){
                return $true
            }
            if($_ -notmatch $regexPatternDomainName){
              $parameter = "cloudFqdn"  
              throw $regexPatternDomainNameError -f $_,$parameter
            }
            return $true
          })]
        [String] $cloudFqdn,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -notmatch $regexPatternRFC1123){
              $parameter = "vnetName"   
              throw $regexPatternRFC1123Error -f $_,$parameter
            }
            return $true
          })]
        [String] $vnetName,

        # vswitchName can accept any characters
        [Parameter(Mandatory=$false)]
        [String] $vswitchName,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            $response = Test-IPV4Address -ip $_
            if(!$response){
              $parameter = "VipPoolStart"
              throw "$ipv4ValidationError" -f $parameter,$_
            }
            return $true
          })]
        [String] $vippoolstart,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            $response = Test-IPV4Address -ip $_
            if(!$response){
              $parameter = "VipPoolEnd"
              throw "$ipv4ValidationError" -f $parameter,$_
            }
            return $true
          })]
        [String] $vippoolend,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -notmatch $regexPatternCIDRFormat){
              $parameter = "ipaddressprefix"  
              throw $regexPatternCIDRFormatError -f $_,$parameter
            }
            return $true
          })]
        [String] $ipaddressprefix,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            $response = Test-IPV4Address -ip $_
            if(!$response){
              $parameter = "Gateway"
              throw "$ipv4ValidationError" -f $parameter,$_
            }
            return $true
          })]
        [String] $gateway,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ($_ -eq $null -or $_.count -eq 0 -or $_.length -eq 0) {
                return $true
            }
            foreach($i in $_){
            $response = Test-IPV4Address -ip $i
            if(!$response){
              $parameter = "DnsServers"
              throw "$ipv4ValidationError" -f $parameter,$i
            }
        }
            return $true
          })]
        [String[]] $dnsservers = @(),

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            $response = Test-IPV4Address -ip $_
            if(!$response){
              $parameter = "K8sNodeIpPoolStart"
              throw "$ipv4ValidationError" -f $parameter,$_
            }
            return $true
          })]
        [String] $k8snodeippoolstart,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            $response = Test-IPV4Address -ip $_
            if(!$response){
              $parameter = "K8sNodeIpPoolEnd"
              throw "$ipv4ValidationError" -f $parameter,$_
            }
            return $true
          })]
        [String] $k8snodeippoolend,

        [Parameter(Mandatory=$false)]
        [ValidateRange(0, 4094)]
        [Int] $vlanID,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -notmatch $regexPatternProxyUrl){
                $parameter = "proxyServerHTTP"
                throw $regexPatternProxyUrlError -f $parameter,$_
            }
            return $true
          })]
        [String] $proxyServerHTTP,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -notmatch $regexPatternProxyUrl){
                $parameter = "proxyServerHTTPS"
                throw $regexPatternProxyUrlError -f $parameter,$_
            }
            return $true
          })]
        [String] $proxyServerHTTPS,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -match $space){
              throw $spaceError
            }
            return $true
          })]
        [String] $proxyServerNoProxy,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -match $space){
                  throw $spaceError
            }  
            if(-not ($_ | Test-Path)){
              throw $fileFolderError
            }
            return $true
          })]
        [String] $certificateFilePath,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -match $space){
                  throw $spaceError
            }  
            if(-not ($_ | Test-Path)){
              throw $folderError
            }
            return $true
          })]
        [String] $workDirectory = $global:defaultworkingDir
    )

    New-ArcHciIdentityFiles -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -workDirectory $workDirectory

    New-ArcHciAksConfigFiles -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -workDirectory $workDirectory -controlPlaneIP $controlPlaneIP `
        -cloudFqdn $cloudFqdn -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -gateway $gateway -dnsservers $dnsservers -ipaddressprefix $ipaddressprefix -k8snodeippoolstart $k8snodeippoolstart -k8snodeippoolend $k8snodeippoolend -vlanID $vlanID `
        -proxyServerHTTP $proxyServerHTTP -proxyServerHTTPS $proxyServerHTTPS -proxyServerNoProxy $proxyServerNoProxy -certificateFilePath $certificateFilePath
}

function New-ArcHciAksConfigFiles {
    <#
    .DESCRIPTION
        Creates the Arc Appliance config files

    .PARAMETER subscriptionID
        The Azure subscription GUID
    
    .PARAMETER location
        Azure location (optional parameter. defaults to "eastus")
    
    .PARAMETER resourceGroup
        Name of the Azure resource group in which the Arc HCI appliance will be created
    
    .PARAMETER resourceName
        Name of the Azure resource
    
    .PARAMETER controlPlaneIP
        IP Address to be used for the Arc Appliance control plane
    
    .PARAMETER cloudFqdn
        Optional parameter. Fully qualified domain name (FQDN) or IP address of the cloud agent service.

    .PARAMETER vnetName
        Optional parameter. Name of the virtual network the ARC resource bridge will connect to. The vnet will be automatically created if it doesn't exist. NOTE: this name must be all lower characters.

    .PARAMETER vswitchName
        Optional parameter. Name of the virtual switch the ARC resource bridge will connect to. On HCI, this virtual switch must be an external virtual switch.

    .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 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 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.

    .PARAMETER vlanID
        The VLAN ID for the vnet
        
    .PARAMETER workDirectory
        Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster.
    
    .PARAMETER proxyServerHTTP
        http urls for proxy server

    .PARAMETER proxyServerHTTPS
        https urls for proxy server

    .PARAMETER proxyServerNoProxy
        urls which can bypass proxy server

    .PARAMETER certificateFilePath
        Name of the cert File Path for proxy
      
    .OUTPUTS
        N/A
    .EXAMPLE
        New-ArcHciAksConfigFiles -subscriptionID "12345678-4567-1234-8888-c64fc26bd67e" -location "eastus" -resourceGroup "MyResourceGroup" -resourceName "MyAppliance"
    #>

    Param (
        [Parameter(Mandatory=$true)]
        [GUID] $subscriptionID,

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

        [Parameter(Mandatory=$true)]
        [ValidateScript({
            if($_ -notmatch $regexPatternAzureResourceGroup){
              $parameter = "ResourceGroup"  
              throw $regexPatternAzureResourceGroupError -f $_,$parameter
            }
            return $true
          })]
        [String] $resourceGroup,

        [Parameter(Mandatory=$true)]
        [ValidateScript({
            if($_ -notmatch $regexPatternRFC1123){
              $parameter = "ResourceName"  
              throw $regexPatternRFC1123Error -f $_,$parameter
            }
            return $true
          })]
        [String] $resourceName,

        [Parameter(Mandatory=$true)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            $response = Test-IPV4Address -ip $_
            if(!$response){
              $parameter = "ControlPlaneIp"
              throw "$ipv4ValidationError" -f $parameter,$_
            }
            return $true
          })]
        [String] $controlPlaneIP,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            $response = Test-IPV4Address -ip $_
            if($response){
                return $true
            }
            if($_ -match $regexPatternHostName){
                return $true
            }
            if($_ -notmatch $regexPatternDomainName){
              $parameter = "cloudFqdn"  
              throw $regexPatternDomainNameError -f $_,$parameter
            }
            return $true
          })]
        [String] $cloudFqdn,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -notmatch $regexPatternRFC1123){
              $parameter = "vnetName"   
              throw $regexPatternRFC1123Error -f $_,$parameter
            }
            return $true
          })]
        [String] $vnetName,

        # vswitchName can accept any characters
        [Parameter(Mandatory=$false)]
        [String] $vswitchName,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            $response = Test-IPV4Address -ip $_
            if(!$response){
              $parameter = "VipPoolStart"
              throw "$ipv4ValidationError" -f $parameter,$_
            }
            return $true
          })]
        [String] $vippoolstart,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            $response = Test-IPV4Address -ip $_
            if(!$response){
              $parameter = "VipPoolEnd"
              throw "$ipv4ValidationError" -f $parameter,$_
            }
            return $true
          })]
        [String] $vippoolend,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -notmatch $regexPatternCIDRFormat){
              $parameter = "ipaddressprefix"  
              throw $regexPatternCIDRFormatError -f $_,$parameter
            }
            return $true
          })]
        [String] $ipaddressprefix,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            $response = Test-IPV4Address -ip $_
            if(!$response){
              $parameter = "Gateway"
              throw "$ipv4ValidationError" -f $parameter,$_
            }
            return $true
          })]
        [String] $gateway,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ($_ -eq $null -or $_ -eq 0) {
                return $true
            }
            foreach($i in $_){
            $response = Test-IPV4Address -ip $i
            if(!$response){
              $parameter = "DnsServers"
              throw "$ipv4ValidationError" -f $parameter,$i
            }
        }
            return $true
          })]
        [String[]] $dnsservers = @(),

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            $response = Test-IPV4Address -ip $_
            if(!$response){
              $parameter = "K8sNodeIpPoolStart"
              throw "$ipv4ValidationError" -f $parameter,$_
            }
            return $true
          })]
        [String] $k8snodeippoolstart,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            $response = Test-IPV4Address -ip $_
            if(!$response){
              $parameter = "K8sNodeIpPoolEnd"
              throw "$ipv4ValidationError" -f $parameter,$_
            }
            return $true
          })]
        [String] $k8snodeippoolend,

        [Parameter(Mandatory=$false)]
        [ValidateRange(0, 4094)]
        [String] $vlanID,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -notmatch $regexPatternProxyUrl){
              $parameter = "proxyServerHTTP"
              throw $regexPatternProxyUrlError -f $parameter,$_
            }
            return $true
          })]
        [String] $proxyServerHTTP,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -notmatch $regexPatternProxyUrl){
              $parameter = "proxyServerHTTPS"
              throw $regexPatternProxyUrlError -f $parameter,$_
            }
            return $true
          })]
        [String] $proxyServerHTTPS,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -match $space){
              throw $spaceError
            }
            return $true
          })]
        [String] $proxyServerNoProxy,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -match $space){
                  throw $spaceError
            }  
            if(-not ($_ | Test-Path)){
              throw $fileFolderError
            }
            return $true
          })]
        [String] $certificateFilePath,

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -match $space){
                  throw $spaceError
            }  
            if(-not ($_ | Test-Path)){
              throw $folderError
            }
            return $true
          })]
        [String] $workDirectory = $global:defaultworkingDir
    )
    Set-ArcHciConfig -workingDir $workDirectory
    Set-ArcHciConfigValue -name "configFileLocation" -Value $workDirectory

    $arcTokenFilePath = Join-Path -Path $workDirectory -ChildPath "kvatoken.tok"

    Set-ArcHciConfigValue -name "kvaTokenLocation" -Value $arcTokenFilePath

    New-ArcHciMocTokenFile -arcTokenFilePath $arcTokenFilePath
 

    Write-Output "Generating ARC HCI configuration files..."

    $mocConfig = Get-MocConfig

    $kubectlLoc = $mocConfig.installationPackageDir

    $oldPath = $($Env:PATH)
    if (-Not $oldPath.Split(';').Contains($kubectlLoc))
    {
        $Env:PATH="$($Env:PATH);$kubectlLoc"
    }

    if([string]::IsNullOrEmpty($cloudFqdn)) {
        $cloudFqdn = $mocConfig.cloudFqdn
    } 

    Set-ArcHciConfigValue -name "controlPlaneIP" -Value $controlPlaneIP
    Set-ArcHciConfigValue -name "cloudFqdn" -Value $cloudFqdn
    Set-ArcHciConfigValue -name "vswitchName" -Value $vswitchName
    Set-ArcHciConfigValue -name "vippoolstart" -Value $vippoolstart
    Set-ArcHciConfigValue -name "vippoolend" -Value $vippoolend
    Set-ArcHciConfigValue -name "ipaddressprefix" -Value $ipaddressprefix
    Set-ArcHciConfigValue -name "dnsservers" -Value $dnsservers
    Set-ArcHciConfigValue -name "gateway" -Value $gateway
    Set-ArcHciConfigValue -name "k8snodeippoolstart" -Value $k8snodeippoolstart
    Set-ArcHciConfigValue -name "k8snodeippoolend" -Value $k8snodeippoolend
    Set-ArcHciConfigValue -name "vlanID" -Value $vlanID
    
    $dnsserver = ""
    if(-Not [string]::IsNullOrEmpty($ipaddressprefix))
    {
        foreach ($dns in $dnsservers)
        {
            $dns = $dns.Trim(" ")
            if(-Not [string]::IsNullOrEmpty($dns))
            {
                $dnsserver += "`n - " +  $dns
            }
        }
    }
    $yaml = @"
# hci-appliance.yaml [config file]

# Absolute path to vCenter/HCI/other Fabric/infra specific credentials and configuration details
infrastructureConfigPath: "$($workDirectory.Replace("\","\\"))\\hci-infra.yaml"

# Specify admin cluster settings
applianceClusterConfig:
  # Used to connect to the Kubernetes API
  controlPlaneEndpoint: "$controlPlaneIP"
"@

# if proxy params are provided publish networking block in appliance yaml
if ((-Not [string]::IsNullOrEmpty($proxyServerHTTP)) -or (-Not [string]::IsNullOrEmpty($proxyServerHTTPS))) {
    $yaml += @"
`n networking:
    proxy:
      certificateFilePath: "$($certificateFilePath.Replace("\","\\"))"
"@

Set-ArcHciConfigValue -name "certificateFilePath" -Value $certificateFilePath

#Appliance require authentication present within the url itself.
#For example: http: "http://username:password@contoso.com:3128"
    $yaml += @"
`n http: "$proxyServerHTTP"
      https: "$proxyServerHTTPS"
"@

    Set-ArcHciConfigValue -name "proxyServerHTTP" -Value $proxyServerHTTP
    Set-ArcHciConfigValue -name "proxyServerHTTPS" -Value $proxyServerHTTPS

$yaml += @"
`n noproxy: "$proxyServerNoProxy"
"@

Set-ArcHciConfigValue -name "proxyServerNoProxy" -Value $proxyServerNoProxy
}
    $yaml += @"
`n # Relative or absolute path to Arc Appliance ARM resource definition to be created in Azure
applianceResourceFilePath: "$($workDirectory.Replace("\","\\"))\\hci-resource.yaml"
"@


    $yamlFile = "$($workDirectory)\hci-appliance.yaml"
    Remove-Item -Path $yamlFile -Force -ErrorAction Ignore
    Set-Content -NoNewline -Path $yamlFile -Value $yaml -ErrorVariable err

    $yaml = @"
# hci-infra.yaml [as referenced in file above]

# infra file
azurestackhciprovider:
  # Cloud agent configuration
  cloudagent:
    # Address of machine on which HCI Environment is deployed
    address: "$cloudFqdn"
    # port and authenticationport
    port: 55000
    authenticationport: 65000
    # Relative path to the cloudconfig file to access HCI Environment
    loginconfigfile: "$($arcTokenFilePath.Replace("\","\\"))"
  location: MocLocation
  group: management
  storagecontainer: MocStorageContainer
  virtualnetwork:
    name: "$vnetname"
    vswitchname: "$vswitchName"
"@


if (-Not [string]::IsNullOrEmpty($vlanID)) {
    $yaml += @"
`n vlanid: $($vlanID)
"@

}

if (-Not [string]::IsNullOrEmpty($vippoolstart)) {
    $yaml += @"
`n vippoolstart: $vippoolstart
    vippoolend: $vippoolend
"@

}

if (-Not [string]::IsNullOrEmpty($ipaddressprefix))
{
    $yaml += @"
`n ipaddressprefix: $ipaddressprefix
    gateway: $gateway
    dnsservers: $dnsserver
    k8snodeippoolstart: $k8snodeippoolstart
    k8snodeippoolend: $k8snodeippoolend
"@

}
    $yaml += @"
`n appliancevm:
    vmsize: Standard_A4_v2
download:
  workingdirectory: "$($workDirectory.Replace("\","\\"))"
"@


    $yamlFile = "$($workDirectory)\hci-infra.yaml"
    Remove-Item -Path $yamlFile -Force -ErrorAction Ignore
    Set-Content -Path $yamlFile -Value $yaml -ErrorVariable err

    $yaml = @"
# hci-resource.yaml [as reference in hci-appliance.yaml above]

# resource file
resource:
    # Resource group must exist
    resource_group: $resourceGroup
    # Resource name
    name: $resourceName
    # Location
    location: $location
    # Subscription should match CLI context
    subscription: $subscriptionID
"@


    $yamlFile = "$($workDirectory)\hci-resource.yaml"
    Remove-Item -Path $yamlFile -Force -ErrorAction Ignore
    Set-Content -Path $yamlFile -Value $yaml -ErrorVariable err

    Write-Output "Config file successfully generated in '$workDirectory'"
}

function Remove-ArcHciIdentityFiles {
    <#
    .DESCRIPTION
        Removes the Arc HCI token files
    .PARAMETER workDirectory
        Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster.
    .OUTPUTS
        N/A
    .EXAMPLE
        Remove-ArcHciIdentityFiles
    #>


    Param (
        [Parameter(Mandatory=$false)]
        [String] $workDirectory = $(Get-ArcHciConfigValue -name "workingDir")
    )

    # Remove Identities
    Remove-Item -Path "$($workDirectory)\hci-config.json" -Force -ErrorAction Ignore
    $mocoperator = "moc-operator"
    Remove-MocIdentity -name $mocoperator
}

function Remove-ArcHciAksConfigFiles {
    <#
    .DESCRIPTION
        Removes the Arc Appliance config files
    .PARAMETER workDirectory
        Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster.
    .OUTPUTS
        N/A
    .EXAMPLE
        Remove-ArcHciAksConfigFiles
    #>


    Param (
        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -match $space){
                  throw $spaceError
            }  
            if(-not ($_ | Test-Path)){
              throw $folderError
            }
            return $true
          })]
        [String] $workDirectory = $(Get-ArcHciConfigValue -name "workingDir")
    )

    # Remove Identities
    Remove-Item -Path "$($workDirectory)\kvatoken.tok" -Force -ErrorAction Ignore
    $clusterName = "Appliance"
    Remove-MocIdentity -name $clusterName
    Remove-CertFiles
    # Remove the appliance config files
    Remove-Item -Path "$($workDirectory)\hci-appliance.yaml" -Force -ErrorAction Ignore
    Remove-Item -Path "$($workDirectory)\hci-infra.yaml" -Force -ErrorAction Ignore
    Remove-Item -Path "$($workDirectory)\hci-resource.yaml" -Force -ErrorAction Ignore
    Remove-Item -Path "$($workDirectory)\kubeconfig" -Force -ErrorAction Ignore
}

function Remove-ArcHciConfigFiles {
    <#
    .DESCRIPTION
        Removes the Arc HCI config files
    .PARAMETER workDirectory
        Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster.
    .OUTPUTS
        N/A
    .EXAMPLE
        Remove-ArcHciConfigFiles
    #>


    Param (
        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -match $space){
                  throw $spaceError
            }  
            if(-not ($_ | Test-Path)){
              throw $folderError
            }
            return $true
          })]
        [String] $workDirectory = $(Get-ArcHciConfigValue -name "workingDir")
    )

    Remove-ArcHciIdentityFiles -workDirectory $workDirectory
    Remove-ArcHciAksConfigFiles -workDirectory $workDirectory
}

function Get-ArcHciFirstControlPlaneNodeIp {
    <#
    .DESCRIPTION
        Finds the ARC resource bridge control plane VM and retrieves its non-control plane IPv4.
    #>


    # Assumption: we assume that the ARC control plane VM is the VM that contains "control-plane" in the name and is located in the **management** group
    Get-MocConfig > $null # Workaround for Get-MocVirtualMachine initialization. Call into Get-MocConfig first to initialize MOC context.
    $mocVm = (Get-MocVirtualMachine -group management | where { $_.name -match ".*control-plane.*" })
    if ($mocVm -ne $null) {
        $controlPlaneIp = ([ipaddress](Get-ArcHciConfig).controlPlaneIP)
        $hypervVm = Get-VM -Name "$($mocVm.name)*" -ComputerName $mocVm.virtualmachineproperties.host.id
        $vmIpAddresses = ([ipaddress[]]$hypervVm.NetworkAdapters.IPAddresses) | where { $_.AddressFamily -eq "InterNetwork" -And $_ -ne $controlPlaneIp}
        
        if ($vmIpAddresses -ne $null) {
            return $vmIpAddresses[0]
        }
    }
    return $null
}

function Get-ArcHciLogs {
    <#
    .DESCRIPTION
        Collects all the logs from the deployment and compresses them.

    .PARAMETER workDirectory
        Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster.

    .PARAMETER activity
        Activity name to use when updating progress

    .PARAMETER ip
        IP address of the ARC appliance VM or kubernetes api server

    .PARAMETER logDir
        Path to the directory to store the logs

    .PARAMETER kvaTokenPath
        Path to the KVA token (which was generated during the installation of the ARC resource bridge)
    #>


    param (
        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -match $space){
                  throw $spaceError
            }  
            if(-not ($_ | Test-Path)){
              throw $folderError
            }
            return $true
          })]
        [String] $workDirectory =  $(Get-ArcHciConfigValue -name "workingDir"),

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

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -match $space){
                  throw $spaceError
            }  
            if(-not ($_ | Test-Path)){
              throw $folderError
            }
            return $true
          })]
        [String]$logDir =  [System.Io.Path]::GetTempPath(),

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            $response = Test-IPV4Address -ip $_
            if(!$response){
              $parameter = "Ip"
              throw "$ipv4ValidationError" -f $parameter,$_
            }
            return $true
          })]
        [ipaddress]$ip = $(Get-ArcHciFirstControlPlaneNodeIp),

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -match $space){
                  throw $spaceError
            }  
            if(-not ($_ | Test-Path)){
              throw $folderError
            }
            return $true
          })]
        [String] $kvaTokenPath = (Get-ArcHciConfigValue -name "kvaTokenLocation")
        )

    $arcHciLogDir = $(Join-Path $logDir "archcilogs")

    Remove-Item $arcHciLogDir -Force -Recurse -Confirm:$false -ErrorAction SilentlyContinue > $null

    New-Item -ItemType Directory -Path $arcHciLogDir -Force > $null
    New-Item -ItemType Directory -Path "$($arcHciLogDir)\config\" -Force > $null

    $mocConfig = Get-MocConfig -ErrorAction SilentlyContinue
    Get-ArcHciConfig -ErrorAction SilentlyContinue | Format-Table -AutoSize > "$($arcHciLogDir)\config\archciconfig.txt"
    $mocConfig | Format-Table -AutoSize > "$($arcHciLogDir)\config\mocconfig.txt"
    Copy-Item -Path "$($workDirectory)\hci-appliance.yaml" -Destination "$($arcHciLogDir)\config\"  -ErrorAction Ignore
    Copy-Item -Path "$($workDirectory)\hci-infra.yaml" -Destination "$($arcHciLogDir)\config\" -ErrorAction Ignore
    Copy-Item -Path "$($workDirectory)\hci-resource.yaml" -Destination "$($arcHciLogDir)\config\" -ErrorAction Ignore
    
    Get-MocLogs -path $arcHciLogDir -activity $activity -AgentLogs -EventLogs
    Get-DownloadSdkLogs -Path $arcHciLogDir

    Write-StatusWithProgress -activity $activity -moduleName $script:moduleName -status $("Collecting ARC appliance Logs...")
    $cloudFqdn = $mocConfig.cloudFqdn
    az.cmd arcappliance logs hci --ip $ip --out-dir $arcHciLogDir --loginconfigfile $kvaTokenPath --cloudagent $cloudFqdn > "$($arcHciLogDir)\arcappliancelogsout.txt"

    az.cmd version > "$($arcHciLogDir)\cliversions.txt"
    Write-StatusWithProgress -activity $activity -moduleName $script:moduleName -status $("Compressing Logs...")
    $zipName = Join-Path $logDir "archcilogs.zip"

    Remove-Item $zipName -Force -Recurse -Confirm:$false -ErrorAction SilentlyContinue
    Compress-Directory -ZipFilename $zipName -SourceDir $arcHciLogDir

    Remove-Item $arcHciLogDir -Force -Recurse -Confirm:$false -ErrorAction SilentlyContinue
    Write-Output "Logs were successfully saved to ""$zipName"""
    return $zipName
}

function New-KvaVirtualNetwork {
    <#
    .DESCRIPTION
        Creates a new virtual network usig kvactl

    .PARAMETER name
        The name of the vnet

    .PARAMETER vswitchName
        The name of the vswitch

    .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 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 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.

    .PARAMETER vlanID
        The VLAN ID for the vnet

    .PARAMETER kubeconfig
        Path to the appliance kubeconfig
    #>


    param (
        [Parameter(Mandatory=$true, ParameterSetName="DHCP")]
        [Parameter(Mandatory=$true, ParameterSetName="Static")]
        [ValidateNotNullOrEmpty()]
        [String] $name,

        [Parameter(Mandatory=$true, ParameterSetName="DHCP")]
        [Parameter(Mandatory=$true, ParameterSetName="Static")]
        [ValidateNotNullOrEmpty()]
        [String] $vswitchName,

        [Parameter(Mandatory=$true, ParameterSetName="DHCP")]
        [Parameter(Mandatory=$true, ParameterSetName="Static")]
        [ValidateNotNullOrEmpty()]
        [String] $vippoolstart,

        [Parameter(Mandatory=$true, ParameterSetName="DHCP")]
        [Parameter(Mandatory=$true, ParameterSetName="Static")]
        [ValidateNotNullOrEmpty()]
        [String] $vippoolend,

        [Parameter(Mandatory=$true, ParameterSetName="Static")]
        [ValidateNotNullOrEmpty()]
        [String] $ipaddressprefix,

        [Parameter(Mandatory=$true, ParameterSetName="Static")]
        [ValidateNotNullOrEmpty()]
        [String] $gateway,

        [Parameter(Mandatory=$true, ParameterSetName="Static")]
        [ValidateNotNullOrEmpty()]
        [String] $dnsservers,

        [Parameter(Mandatory=$true, ParameterSetName="Static")]
        [ValidateNotNullOrEmpty()]
        [String] $k8snodeippoolstart,

        [Parameter(Mandatory=$true, ParameterSetName="Static")]
        [ValidateNotNullOrEmpty()]
        [String] $k8snodeippoolend,

        [Parameter(Mandatory=$false, ParameterSetName="Static")]
        [ValidateNotNullOrEmpty()]
        [String] $vlanID,

        [Parameter(Mandatory=$true, ParameterSetName="DHCP")]
        [Parameter(Mandatory=$true, ParameterSetName="Static")]
        [ValidateNotNullOrEmpty()]
        [String] $kubeconfig
    )

    $dnsserversYaml = ""
    if(-Not [string]::IsNullOrEmpty($ipaddressprefix))
    {
        foreach ($dns in $dnsservers.Split(","))
        {
            $dns = $dns.Trim(" ")
            if(-Not [string]::IsNullOrEmpty($dns))
            {
                $dnsserversYaml += "`n - " +  $dns
            }
        }
    }

    # Create the network config file

    $akshcinetworkyaml = @"
apiVersion: msft.microsoft/v1
kind: AksHciNetwork
metadata:
  labels:
    msft.microsoft/capi-name: capi-for-moc
  name: $name
spec:
  group: target-group
  location: MocLocation
  name: $name
  type: Transparent
  vippoolstart: $vippoolstart
  vippoolend: $vippoolend
  vswitchname: $vswitchName
  ipaddressprefix: $ipaddressprefix
  gateway: $gateway
  dnsservers: $dnsserversYaml
  k8snodeippoolstart: $k8snodeippoolstart
  k8snodeippoolend: $k8snodeippoolend
"@


if (-Not [string]::IsNullOrEmpty($vlanID)) {
    $akshcinetworkyaml += @"
`n vlanid: $vlanID
"@

}

    $yamlFile = Join-Path $(Get-Location).Path "vnet-$name.yaml"
    Set-Content -Path $yamlFile -Value $akshcinetworkyaml -ErrorVariable err

    # Create the kva virtual network
    Get-KubectlExeInternal
    $config = Get-MocConfig
    $kubectlPath = Join-Path $config.installationPackageDir "kubectl.exe"

    try {
        # Create the kva virtual network
        Invoke-Expression "$kubectlPath apply -f '$yamlFile' --kubeconfig '$kubeconfig'"

        # Get the list of kva virtual networks
        Invoke-Expression "$kubectlPath get akshcinetworks -A --kubeconfig '$kubeconfig'"
    }
    catch {
        Write-Error "Error while creating KVA virtual network: " + $_.Exception.Message
    }
}

function Get-KubectlExeInternal {
    <#
    .DESCRIPTION
        Downloads kubectl.exe and copies it to the MOC installationPackageDir
    #>

    try {
        $config = Get-MocConfig
        $kubectlPath = Join-Path $config.installationPackageDir "kubectl.exe"
        # If kubectl.exe is not present, download and copy it to the moc installation directory
        if (-not (Test-Path $kubectlPath)) {
            Write-Output "Downloading kubectl.exe to $($config.installationPackageDir)"
            curl.exe -LO "https://dl.k8s.io/release/stable.txt"
            $latest_version = Get-Content "stable.txt"
            $url = "https://dl.k8s.io/release/" + $latest_version + "/bin/windows/amd64/kubectl.exe"
            curl.exe -LO $url
            mv .\kubectl.exe $($config.installationPackageDir)
        }
    }
    catch {
        Write-Error "Error getting kubectl: " + $_.Exception.Message
    }
}

function Get-TargetClusterAdminCredentials {
    <#
    .DESCRIPTION
        Gets the kubeconfig of target cluster

    .PARAMETER clusterName
        The name of the target cluster
    
    .PARAMETER outfile
        Path to the file in which target cluster kubeconfig will be stored

    .PARAMETER kubeconfig
        Path to the appliance kubeconfig
    #>

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

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String] $outfile,

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String] $kubeconfig
    )

    Get-KubectlExeInternal
    $config = Get-MocConfig
    $kubectlPath = Join-Path $config.installationPackageDir "kubectl.exe"

    try {
        # Get the target cluster kubeconfig
        $secretName = $clusterName.ToLower() + '-kubeconfig'
        $encodedSecret = Invoke-Expression "$kubectlPath get secrets '$secretName' -o jsonpath='{.data.value}' --kubeconfig '$kubeconfig'"

        $targetKubeconfig = [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($encodedSecret))

        Set-Content -Path $outfile -Value $targetKubeconfig
    }
    catch {
        Write-Output "Error while retrieving the target cluster kubeconfig"
    }
}

function Add-ArcHciK8sGalleryImage {
    <#
    .DESCRIPTION
        Downaload and adds a kubernetes image to the gallery

    .PARAMETER k8sVersion
        Version of kubernetes that the image will use

    .PARAMETER imageType
        Type of the image: Linux or Windows

    .PARAMETER version
        AksHci/Moc version to get the image download information from the release manifest. Defaults to the installed Moc version
    #>

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

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

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

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

    $mocConfig = Get-MocConfig
    $moduleName = "Moc"
    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($GenericLocMessage.comm_provisioning_galleryimage)

    $imageName = Get-KubernetesGalleryImageName -imagetype $imageType -k8sVersion $k8sVersion
    $galleryImage = $null
    $mocLocation = $mocConfig.cloudLocation

    if ([string]::IsNullOrEmpty($version)) {
        $version = $mocConfig.version
    }

    # Check if requested k8s gallery image is already present
    try {
        $galleryImage = Get-MocGalleryImage -name $imageName -location $mocLocation
    } catch {}

    if ($null -ine $galleryImage) {
        Write-SubStatus -moduleName $moduleName $($GenericLocMessage.comm_image_already_present_in_gallery)
        Write-Output "$imageType $k8sVersion k8s gallery image already present"
        return
    }

    # Try downloading and adding the requested k8s gallery image
    try {
        # Get image download information from the release manifest
        $imageRelease = Get-ImageReleaseManifest -imageVersion $version -operatingSystem $imageType -k8sVersion $k8sVersion -moduleName $moduleName

        # Download the k8s gallery image
        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 $mocConfig.imageDir -moduleName $moduleName

        # Add the downloaded k8s gallery image to Moc
        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 "MocStorageContainer"

        Write-Output "Successfully added $imageType $k8sVersion k8s gallery image"
    }
    catch {
        Write-Output "Error while adding $imageType $k8sVersion k8s gallery image"
        Write-Output $_
    }

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

function Invoke-CommandLine
{
    <#
    .DESCRIPTION
        Executes a command and optionally ignores errors.

    .PARAMETER command
        Comamnd to execute.

    .PARAMETER arguments
        Arguments to pass to the command.

    .PARAMETER showOutput
        Optionally, show live output from the executing command.
    #>


    param (
        [String]$command,
        [String]$arguments,
        [Switch]$showOutput
    )

    if ($showOutput.IsPresent)
    {
        $result = (& $command $arguments.Split(" ") | Out-Default) 2>&1
    }
    else
    {
        $result = (& $command $arguments.Split(" ") 2>&1)
    }
  
    $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)
    {
        $err = $result | Where-Object {$_.gettype().Name -eq "ErrorRecord"}
        $errMessage = "$command $arguments (Error: $LASTEXITCODE [$err])"
        throw $errMessage
    }
    return $out
}

function Repair-ArcHciApplianceCerts {
    <#
    .DESCRIPTION
        Attempts to repair failed TLS to cloudagent

    .PARAMETER sshPrivateKeyFile
        SSH private key file

    .PARAMETER force
        Force repair(without checks)

    .PARAMETER kubeconfig
        Path to the kubeconfig file

    .PARAMETER group
        Appliance group

    .PARAMETER clusterName
        Name of the appliance cluster

    .PARAMETER arcTokenFilePath
        File path to the ARC indentity token
    
    .PARAMETER kvactlPath
        Location of the kvactl.exe tool (which may need to be installed separately)
    #>

    [CmdletBinding(DefaultParameterSetName = 'Default')]
    param (
        [Parameter(Mandatory=$false)]
        [String] $sshPrivateKeyFile = $global:sshPrivateKeyFile,

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

        [Parameter(Mandatory=$false)]
        [string] $kubeconfig = $(Join-Path $(Get-ArcHciConfigValue -name "workingDir") "kubeconfig"),

        [Parameter(Mandatory=$false)]
        [string] $group = "management",

        [Parameter(Mandatory=$false)]
        [string] $clusterName = "Appliance",

        [Parameter(Mandatory=$false)]
        [String] $arcTokenFilePath = $(Join-Path $(Get-ArcHciConfigValue -name "workingDir") "kvatoken.tok"),

        [Parameter(Mandatory=$false)]
        [String] $kvactlpath = $global:kvaCtlFullPath
    )
    if (-Not (Test-Path $kvactlpath)) {
        throw "Please make sure you install KvaCtl.exe in '$kvactlpath' before running repair"
    }

    $kvaIdentity = Invoke-MocIdentityRotate -name $clusterName -encode
    $utf8String = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($kvaIdentity)) 
    $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
    [System.IO.File]::WriteAllLines($arcTokenFilePath, $utf8String, $Utf8NoBomEncoding)

    Invoke-CommandLine -command $kvactlpath -arguments $("repair --kubeconfig ""$kubeconfig"" --sshprivatekey ""$sshPrivateKeyFile"" --tags ""Group=$group"" --force=$force --verbose") -showOutput
}

function Repair-MocOperatorToken {
    <#
    .DESCRIPTION
        Refresh the moc-operator token
    #>


    Repair-Moc
    $mocConfig = Get-MocConfig
    $mocoperator = "moc-operator"
    try { Remove-MocIdentity -name $mocOperator } catch { }
    $mocOperatorIdentity = New-MocIdentity -name $mocoperator -validityDays $defaultTokenExpiryDays -fqdn $mocConfig.cloudFqdn -location $mocConfig.cloudLocation -port $mocConfig.cloudAgentPort -authport $mocConfig.cloudAgentAuthorizerPort -encode
    New-MocRoleAssignmentWhenAvailable -identityName $mocoperator -roleName "Contributor" -location $mocConfig.cloudLocation | Out-Null

    $loginString = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($mocOperatorIdentity))

    Get-KubectlExeInternal
    kubectl.exe patch secret controllerconfig -p "{\""data\"":{\""LoginString\"":\""$loginString\""}}" -n moc-operator-system
    kubectl.exe delete pods -n moc-operator-system -l control-plane=controller-manager
}

function Repair-ArcHciVmInfra {
    <#
    .DESCRIPTION
        Rotates the security certificates and tokens in MOC and ARC resource bridge
    
    .PARAMETER workDirectory
    TBD
    #>

    param (
        [Parameter(Mandatory=$false)]
        [String] $workDirectory =  $(Get-ArcHciConfigValue -name "workingDir"),

        [Parameter()]
        [String] $sshPrivateKeyFile = $global:sshPrivateKeyFile,

        [Parameter()]
        [Switch] $force,

        [Parameter()]
        [string] $kubeconfig,

        [Parameter()]
        [string] $group,

        [Parameter()]
        [string] $clusterName,

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

        [Parameter(Mandatory=$false)]
        [String] $kvactlpath = $global:kvaCtlFullPath
    )
    if (-Not (Test-Path $kvactlpath)) {
        throw "Please make sure you install KvaCtl.exe in '$kvactlpath' before running repair"
    }

    Repair-MocOperatorToken
    Repair-ArcHciApplianceCerts -sshprivatekey $sshPrivateKeyFile -force:$force.IsPresent -kubeconfig $kubeconfig -group "management" -clusterName "Appliance" -arcTokenFilePath $($workDirectory + "\kvatoken.tok") -kvactlpath $kvactlpath
}

function Test-HCIMachineRequirements {
    <#
    .DESCRIPTION
        Checks
         1. The substrate is Azure Stack HCI
         2. HCI node is registered
    #>


    # Check if the machine is Azure Stack HCI
    # The ps module "AzureStackHCI" is available only on the azstackhci machines
    if($null -eq (Get-Module -ListAvailable -Name "AzureStackHCI" -ErrorAction:Ignore)) {
        throw "The machine is not Azure Stack HCI. Installation is not supported in other machines."
    }

    Import-Module -Name "AzureStackHCI"

    $hciStatus = Get-AzureStackHCI

    # Check if the HCI machine is registered; if not, throw error
    if ($hciStatus.RegistrationStatus -ine "Registered") {
        throw "The HCI machine ($env:computername) is not registered. Please register your HCI cluster."
    }
}

function Test-IPV4Address {
    param(
        [parameter(Mandatory = $true)] [string] $ip
    )

    $ipv4 = '^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'

    if($ip -notmatch $ipv4){
        return $false
    }

    return $true
}

function Get-Nodes {
    <#
     .DESCRIPTION
      Returns the nodes of the cluster or the local host in case of standalone.
    #>
 

    # Check if failover cluster powershell module was installed and the cluster was deployed
    # and only run Get-ClusterNode in that case
    if ((Get-Command "Get-ClusterNode" -errorAction SilentlyContinue) -and $null -ne (Get-Cluster -errorAction SilentlyContinue)) {
        return (Get-ClusterNode -ErrorAction SilentlyContinue).Name
    }
    return $env:computername
}

function Remove-CertFiles {
    <#
     .DESCRIPTION
      Removes the certificate file on all the nodes
    #>


    Get-Nodes | ForEach-Object {
        Invoke-Command -ComputerName $_ -ScriptBlock {
            rmdir $env:USERPROFILE\.wssd\python -Recurse -Force -ErrorAction SilentlyContinue
        }
    }
}

# SIG # Begin signature block
# MIIn0AYJKoZIhvcNAQcCoIInwTCCJ70CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDJGl+F6w/d9Kre
# oGmdJoL4AkjF2uY6VEIpEt8mUakIaaCCDYUwggYDMIID66ADAgECAhMzAAACzfNk
# v/jUTF1RAAAAAALNMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjIwNTEyMjA0NjAyWhcNMjMwNTExMjA0NjAyWjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDrIzsY62MmKrzergm7Ucnu+DuSHdgzRZVCIGi9CalFrhwtiK+3FIDzlOYbs/zz
# HwuLC3hir55wVgHoaC4liQwQ60wVyR17EZPa4BQ28C5ARlxqftdp3H8RrXWbVyvQ
# aUnBQVZM73XDyGV1oUPZGHGWtgdqtBUd60VjnFPICSf8pnFiit6hvSxH5IVWI0iO
# nfqdXYoPWUtVUMmVqW1yBX0NtbQlSHIU6hlPvo9/uqKvkjFUFA2LbC9AWQbJmH+1
# uM0l4nDSKfCqccvdI5l3zjEk9yUSUmh1IQhDFn+5SL2JmnCF0jZEZ4f5HE7ykDP+
# oiA3Q+fhKCseg+0aEHi+DRPZAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU0WymH4CP7s1+yQktEwbcLQuR9Zww
# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh
# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ3MDUzMDAfBgNVHSMEGDAW
# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw
# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx
# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB
# AE7LSuuNObCBWYuttxJAgilXJ92GpyV/fTiyXHZ/9LbzXs/MfKnPwRydlmA2ak0r
# GWLDFh89zAWHFI8t9JLwpd/VRoVE3+WyzTIskdbBnHbf1yjo/+0tpHlnroFJdcDS
# MIsH+T7z3ClY+6WnjSTetpg1Y/pLOLXZpZjYeXQiFwo9G5lzUcSd8YVQNPQAGICl
# 2JRSaCNlzAdIFCF5PNKoXbJtEqDcPZ8oDrM9KdO7TqUE5VqeBe6DggY1sZYnQD+/
# LWlz5D0wCriNgGQ/TWWexMwwnEqlIwfkIcNFxo0QND/6Ya9DTAUykk2SKGSPt0kL
# tHxNEn2GJvcNtfohVY/b0tuyF05eXE3cdtYZbeGoU1xQixPZAlTdtLmeFNly82uB
# VbybAZ4Ut18F//UrugVQ9UUdK1uYmc+2SdRQQCccKwXGOuYgZ1ULW2u5PyfWxzo4
# BR++53OB/tZXQpz4OkgBZeqs9YaYLFfKRlQHVtmQghFHzB5v/WFonxDVlvPxy2go
# a0u9Z+ZlIpvooZRvm6OtXxdAjMBcWBAsnBRr/Oj5s356EDdf2l/sLwLFYE61t+ME
# iNYdy0pXL6gN3DxTVf2qjJxXFkFfjjTisndudHsguEMk8mEtnvwo9fOSKT6oRHhM
# 9sZ4HTg/TTMjUljmN3mBYWAWI5ExdC1inuog0xrKmOWVMIIHejCCBWKgAwIBAgIK
# YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm
# aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw
# OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD
# VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG
# 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la
# UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc
# 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D
# dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+
# lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk
# kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6
# A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd
# X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL
# 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd
# sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3
# T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS
# 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI
# bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL
# BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD
# uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv
# c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
# MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
# MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF
# BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h
# cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA
# YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn
# 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7
# v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b
# pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/
# KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy
# CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp
# mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi
# hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb
# BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS
# oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL
# gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX
# cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGaEwghmdAgEBMIGVMH4x
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p
# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAALN82S/+NRMXVEAAAAA
# As0wDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw
# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIOGQ
# ADzj4eyRNsR2R98IsUQxdSMJXXa16gOB5nQ9mcdaMEIGCisGAQQBgjcCAQwxNDAy
# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20wDQYJKoZIhvcNAQEBBQAEggEAOLUrPGG1q1H5ulRdor97KeB3LhYcSqpXfByD
# Ty8t1r2lyFJ9gu+8AC1oGL22g8d39SPmtrvahRaOjELFaMYLZQdTdZj3BqVBJ125
# s+bvbSyt0eKjIw+zw08XKdzDHifwzsNKSm2hN3CMi56dgxgHGVU5QB8f/QjpnnvX
# 6B6Nlm4TnUKNkPVUj3u7et2I0Qixo+nzJ+V/nT6Bct9vhlzxkv3ARr7eEG7RffeV
# p5CvuDbzbtpYCV+HeBC8stgHSTDaUIWir0BZM7gfDunsgoPLIpG0NHG5t6c3plFi
# QYLu9DjU2NB2necOlyhercDA0460gRyePypDk5TBAxwj14TelaGCFyswghcnBgor
# BgEEAYI3AwMBMYIXFzCCFxMGCSqGSIb3DQEHAqCCFwQwghcAAgEDMQ8wDQYJYIZI
# AWUDBAIBBQAwggFZBgsqhkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGE
# WQoDATAxMA0GCWCGSAFlAwQCAQUABCCgpJbC/Od1BdRHi/mss+OS4ypsoCtmapur
# EpFLdOTdSwIGY+WJ6gtqGBMyMDIzMDIyMjIxNDczNy4yNzlaMASAAgH0oIHYpIHV
# MIHSMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQL
# EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsT
# HVRoYWxlcyBUU1MgRVNOOjhENDEtNEJGNy1CM0I3MSUwIwYDVQQDExxNaWNyb3Nv
# ZnQgVGltZS1TdGFtcCBTZXJ2aWNloIIRejCCBycwggUPoAMCAQICEzMAAAGz/iXO
# KRsbihwAAQAAAbMwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# UENBIDIwMTAwHhcNMjIwOTIwMjAyMjAzWhcNMjMxMjE0MjAyMjAzWjCB0jELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9z
# b2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMg
# VFNTIEVTTjo4RDQxLTRCRjctQjNCNzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt
# U3RhbXAgU2VydmljZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALR8
# D7rmGICuLLBggrK9je3hJSpc9CTwbra/4Kb2eu5DZR6oCgFtCbigMuMcY31QlHr/
# 3kuWhHJ05n4+t377PHondDDbz/dU+q/NfXSKr1pwU2OLylY0sw531VZ1sWAdyD2E
# QCEzTdLD4KJbC6wmAConiJBAqvhDyXxJ0Nuvlk74rdVEvribsDZxzClWEa4v62EN
# j/HyiCUX3MZGnY/AhDyazfpchDWoP6cJgNCSXmHV9XsJgXJ4l+AYAgaqAvN8N+Ep
# N+0TErCgFOfwZV21cg7vgenOV48gmG/EMf0LvRAeirxPUu+jNB3JSFbW1WU8Z5xs
# LEoNle35icdET+G3wDNmcSXlQYs4t94IWR541+PsUTkq0kmdP4/1O4GD54ZsJ5eU
# nLaawXOxxT1fgbWb9VRg1Z4aspWpuL5gFwHa8UNMRxsKffor6qrXVVQ1OdJOS1Jl
# evhpZlssSCVDodMc30I3fWezny6tNOofpfaPrtwJ0ukXcLD1yT+89u4uQB/rqUK6
# J7HpkNu0fR5M5xGtOch9nyncO9alorxDfiEdb6zeqtCfcbo46u+/rfsslcGSuJFz
# lwENnU+vQ+JJ6jJRUrB+mr51zWUMiWTLDVmhLd66//Da/YBjA0Bi0hcYuO/WctfW
# k/3x87ALbtqHAbk6i1cJ8a2coieuj+9BASSjuXkBAgMBAAGjggFJMIIBRTAdBgNV
# HQ4EFgQU0BpdwlFnUgwYizhIIf9eBdyfw40wHwYDVR0jBBgwFoAUn6cVXQBeYl2D
# 9OXSZacbUzUZ6XIwXwYDVR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3Nv
# ZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUy
# MDIwMTAoMSkuY3JsMGwGCCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1l
# LVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUB
# Af8EDDAKBggrBgEFBQcDCDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQAD
# ggIBAFqGuzfOsAm4wAJfERmJgWW0tNLLPk6VYj53+hBmUICsqGgj9oXNNatgCq+j
# Ht03EiTzVhxteKWOLoTMx39cCcUJgDOQIH+GjuyjYVVdOCa9Fx6lI690/OBZFlz2
# DDuLpUBuo//v3e4Kns412mO3A6mDQkndxeJSsdBSbkKqccB7TC/muFOhzg39mfij
# GICc1kZziJE/6HdKCF8p9+vs1yGUR5uzkIo+68q/n5kNt33hdaQ234VEh0wPSE+d
# CgpKRqfxgYsBT/5tXa3e8TXyJlVoG9jwXBrKnSQb4+k19jHVB3wVUflnuANJRI9a
# zWwqYFKDbZWkfQ8tpNoFfKKFRHbWomcodP1bVn7kKWUCTA8YG2RlTBtvrs3CqY3m
# ADTJUig4ckN/MG6AIr8Q+ACmKBEm4OFpOcZMX0cxasopdgxM9aSdBusaJfZ3Itl3
# vC5C3RE97uURsVB2pvC+CnjFtt/PkY71l9UTHzUCO++M4hSGSzkfu+yBhXMGeBZq
# LXl9cffgYPcnRFjQT97Gb/bg4ssLIFuNJNNAJub+IvxhomRrtWuB4SN935oMfvG5
# cEeZ7eyYpBZ4DbkvN44ZvER0EHRakL2xb1rrsj7c8I+auEqYztUpDnuq6BxpBIUA
# lF3UDJ0SMG5xqW/9hLMWnaJCvIerEWTFm64jthAi0BDMwnCwMIIHcTCCBVmgAwIB
# AgITMwAAABXF52ueAptJmQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UE
# BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc
# BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0
# IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1
# WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCC
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O
# 1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZn
# hUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t
# 1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxq
# D89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmP
# frVUj9z6BVWYbWg7mka97aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSW
# rAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv
# 231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zb
# r17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYcten
# IPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQc
# xWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17a
# j54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQAB
# MCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQU
# n6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEw
# QTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9E
# b2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQB
# gjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/
# MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJ
# oEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01p
# Y1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYB
# BQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9v
# Q2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3h
# LB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x
# 5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74p
# y27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1A
# oL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbC
# HcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB
# 9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNt
# yo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3
# rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcV
# v7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A24
# 5oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lw
# Y1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGCAtYwggI/AgEBMIIBAKGB2KSB1TCB
# 0jELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMk
# TWljcm9zb2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1U
# aGFsZXMgVFNTIEVTTjo4RDQxLTRCRjctQjNCNzElMCMGA1UEAxMcTWljcm9zb2Z0
# IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUAcYtE6JbdHhKlwkJe
# KoCV1JIkDmGggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAN
# BgkqhkiG9w0BAQUFAAIFAOeggr4wIhgPMjAyMzAyMjIyMDAyMDZaGA8yMDIzMDIy
# MzIwMDIwNlowdjA8BgorBgEEAYRZCgQBMS4wLDAKAgUA56CCvgIBADAJAgEAAgEi
# AgH/MAcCAQACAhGDMAoCBQDnodQ+AgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisG
# AQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEFBQAD
# gYEAFsnVhCwQgAX8UqVDA67+Re3cxLst5Sj3/Cv/Zjuj7Tqc5yb1pILkKn1g3HBL
# D1aSvoed1FSRnEQcVcSzThfxR7k1AyLYakHYAmXQSRoeU//a7n9l0kZOdtUduTeG
# nHMhVBCGGEeZ9wrsteKZ0fcIGobbv+Wj0dOy58FJv0tA6HgxggQNMIIECQIBATCB
# kzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAbP+Jc4pGxuKHAAB
# AAABszANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJ
# EAEEMC8GCSqGSIb3DQEJBDEiBCCBoAE81ffgs1pg3mrLtno20pO24iuPkDft64Qr
# d5SJGDCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIIahM9UqENIHtkbTMlBl
# QzaOT+WXXMkaHoo6GfvqT79CMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# UENBIDIwMTACEzMAAAGz/iXOKRsbihwAAQAAAbMwIgQg69nGPrWYR+2uEdBCcZRD
# ymKBb6KKOKUAoJJn6vB7J/gwDQYJKoZIhvcNAQELBQAEggIAbsdHnFH5+RurWU0A
# oUJ48GwO+J9GeuynYMQOBaj/8+Ls7/YB2VXgh0yB7j8HSr8of9AEsyjKUOjah22y
# y1yjIFnVIRWRuM890sZWsECHanZFfZXCgX7Mz/O9OishLaZbrd7B5HlH7qILln4V
# JcZmPB7HeE5RvUVNmiZs2gQBOzXzZRN9vG1x7uSXhIEX2cs2ZFWC/Fj5ktIhlojG
# g8IytOv03lPtAkqxflOhmiK5/ojCKt0gqD0J1lo6asbRHVHZz2OaKpu7ml0NW5fP
# ZAollK2ti0TNNdEDYNMGGIRhK/6tZKR3hI2R4ZC8Rvkx8WKoOidYo3DVseHsBAw3
# foEXzPWPdjAlLgFoYTLBkYTZTfpzPYKPI7PvDqRo/EtPNEPwKqvqlT/j4eH3gZwm
# LoetJJIRqWtLFE7NFQtP4UMDv5EvyztPObRKE/9CFtJS83mKOrOp0T8XD7Uzr4wP
# 47Ug1dP2g9TrNgf4zUwmiKMqA/SStSSFR1UYD+nsONMb6f8Lfr4NsdnVYiRPqU9s
# HbOw2vWg1JnEVuUuYmJM8r5axPQzpGBOVT8C62YNRzHTOokZIStJfossUVxZqoCe
# ZNF3Lita5D60k2A1FFUDxRiBuW8mJ2ZFWNWs5ZVTb3Sn4IpbfY8ym/0U11KgM4wR
# pWyTNygy2AwK6PFxmsLFO82+u9c=
# SIG # End signature block