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
# this also imports $global:defaultworkingDir
Import-Module ((Get-Module "MOC" -ListAvailable | Sort-Object Version -Descending)[0].ModuleBase + "\common.psm1")

$moduleName = "ArcHci"
$moduleVersion = "1.0.69"

$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-z0-9_@\!#\$\^%&*()+=\-[\]\\\';,\.\/\{\}\|\:<>\?]{2,10}$"
$regexPatternCIDRFormat = "^([0-9]{1,3}\.){3}[0-9]{1,3}\/\b(([0-9]|[1-2][0-9]|3[0-2]))?$"
$regexPatternProxyUrl = "^(?:https?:\/\/)(?:(.*)(?::(.*))@)?[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})))?$"
$regexPatternProxyIP = "^(:?https?:\/\/)(?:(.*)(?::(.*))@)?((?: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]?)(:((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})))(\/){0,1}$"
$space = "^\s+$"
$spaceWithChar = "\s"
$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 special characters" +
"`n2. Last Tld must be at least two characters, and a maximum of 10 characters" +
"`n3. The domain name can be a subdomain (e.g. sharepoint.microsoft.com)" +
"`n4. Or it can be an ip address e.g. 192.168.0.1, 1.1.1.1" +
"`n5. 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 whitespace(s)"
$spaceErrorForFolderFilePath = "Value should not only contain whitespace(s)"
$fileFolderPathError = "Path is incorrect or file\folder does not exist"
$sleepDuration = 5
$timeout = 1800 #seconds for 30min
$timeoutDeployARB = 7200 # seconds for 2 hours
$timeoutForExtensionCreate = 3600 #seconds for 60min
$timeoutForExtensionCreateInMinutes = 60 
$fileError = "Given path is not a valid file path"
$invalidaK8snodeendIP = "k8snodeippoolend ipaddress must be greater than k8snodeippoolstart ip address"
$installDirName = "ArcHci"
$installDir = "$env:ProgramData"
$numberOfArcMgmtPrechecks = 6
$numberOfArcHciMocPrechecks = 4
$logPath = ""
$logFile = ""
$arcVMEnabledMsg = "Arc vm management successfully enabled!"
$defaultNamespace = "default"
$nothingToDelete = "so nothing to delete"
New-ModuleEventLog -moduleName $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
    }
}

function Initialize-Telemetry {
    # Import helper functions
    Import-Module "$PSScriptRoot\TelemetryCommon.psm1" -Force

    # Import event writers
    Add-Type -Path "$PSScriptRoot\Microsoft.AzureStack.ArcHci.Ubercrud.Observability.Events.dll"
}

Initialize-Telemetry

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 explicitly provided by the user will be defaulted.
     
    .PARAMETER workingDir
        Path to the working directory
    #>


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

    Reset-ArcHciConfigurationKey
    $workingDir = $workingDir.TrimEnd('\\').Replace('\\','\')
    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
        Comma separate list of URLs and IP ranges that should not be proxied.
        When proxy is enabled, the no proxy list must include at the minimum: localhost,127.0.0.1,.svc,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
 
    .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({
            $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) -and ($_ -notmatch $regexPatternProxyIP)){
              $parameter = "proxyServerHTTP"
              throw $regexPatternProxyUrlError -f $parameter,$_
            }
            return $true
          })]
        [String] $proxyServerHTTP,

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

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

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

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

    $workDirectory = $workDirectory.TrimEnd('\\').Replace('\\','\')

    if (Test-IsHCIMachine) {
        Test-HCIMachineRequirements
    }
    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
        Comma separate list of URLs and IP ranges that should not be proxied.
        When proxy is enabled, the no proxy list must include at the minimum: localhost,127.0.0.1,.svc,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
 
    .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({
            $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 $_ -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) -and ($_ -notmatch $regexPatternProxyIP)){
                $parameter = "proxyServerHTTP"
                throw $regexPatternProxyUrlError -f $parameter,$_
            }
            return $true
          })]
        [String] $proxyServerHTTP,

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

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

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

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

    if (-Not [string]::IsNullOrEmpty($k8snodeippoolstart) -and -Not [string]::IsNullOrEmpty($k8snodeippoolend)){

        $verifyendIP = Test-K8snodeIPPoolRange -k8snodeippoolstart $k8snodeippoolstart -k8snodeippoolend $k8snodeippoolend
        if(!$verifyendIP) {
            throw "$k8snodeippoolend $invalidaK8snodeendIP"
        }

    }

    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 -Encoding UTF8 -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 -Encoding UTF8 -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 -Encoding UTF8 -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"
    try { Remove-MocIdentity -name $mocoperator } catch { }
}

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 $spaceErrorForFolderFilePath
            }  
            if(-not ($_ | Test-Path)){
              throw $fileFolderPathError
            }
            return $true
          })]
        [String] $workDirectory = $(Get-ArcHciConfigValue -name "workingDir")
    )

    # Remove Identities
    Remove-Item -Path "$($workDirectory)\kvatoken.tok" -Force -ErrorAction Ignore
    $clusterName = "Appliance"
    try { Remove-MocIdentity -name $clusterName } catch { }
    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 $spaceErrorForFolderFilePath
            }  
            if(-not ($_ | Test-Path)){
              throw $fileFolderPathError
            }
            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.
 
    .PARAMETER logDir
        Path to the directory to store the logs
    #>

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

    # 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.
    try {
        $mocVm = Get-MocVirtualMachine -group management -ErrorAction SilentlyContinue
        $mocVm = $mocVm | Where-Object { $_.name -match ".*control-plane.*" }
    }
    catch {
        $_ >> "$($arcHciLogDir)\commandlogs.txt"
    }
     
    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 Test-IfCluster {
    try {
        $cluster = Get-Cluster -ErrorAction Stop
    }
    catch {
        return $false
    }
    return ($cluster -ne $null)
}

function Get-ArcHciDefaultPath{
    <#
    .DESCRIPTION
        Return a default path for installation.
    .OUTPUTS
        N/A
    .EXAMPLE
        Get-ArcHciDefaultPath
    #>


    if (Test-IfCluster) {
        $volumes = (Get-ClusterSharedVolume -ErrorAction Ignore)
        if ($volumes.Count -gt 0) {
            $volume = $volumes | Where-Object { $_.Name -like "*Infrastructure*" }
            if ($volume.Count -gt 0) {
                $volumePath = $volume[0].SharedVolumeInfo.FriendlyVolumeName
            } 
        }
    }

    if ([String]::IsNullOrEmpty($volumePath)){
        $volumePath = $installDir
    }
    $volumePath = New-Item -Path $volumePath -Name $installDirName -ItemType Directory -Force
    return $volumePath
}

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)
 
    .PARAMETER clusterLogTimeSpan
        The time span in minutes for which to generate the cluster log
    #>


    param (
        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if($_ -match $space){
                  throw $spaceErrorForFolderFilePath
            }  
            if(-not ($_ | Test-Path)){
              throw $fileFolderPathError
            }
            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 $spaceErrorForFolderFilePath
            }  
            if(-not ($_ | Test-Path)){
              throw $fileFolderPathError
            }
            return $true
          })]
        [String]$logDir,

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

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

        [Parameter(Mandatory=$false)]
        [UInt32] $clusterLogTimeSpan = 10080
        )

    if ([String]::IsNullOrEmpty($logPath)) {
        $logPath = Get-ArcHciDefaultPath
        $logFile = Join-Path $logPath "ubercrud.log"
    }    
    
    if ([String]::IsNullOrEmpty($logDir)) {
        $logDir = $logPath
    }

    $timestamp = Get-Date -Format "yyyyMMddHHmmss"
    $arcHciLogDir = $(Join-Path $logDir "archcilogs_$timestamp")

    if (-not ($arcHciLogDir | Test-Path)) {
        New-Item -ItemType Directory -Path $arcHciLogDir -Force > $null
    }
    
    New-Item -ItemType Directory -Path "$($arcHciLogDir)\config\" -Force > $null


    try {
        $mocConfig = Get-MocConfig
    }
    catch {
        $_ >> "$($arcHciLogDir)\commandlogs.txt"
    }
     
    Get-ArcHciConfig -ErrorAction SilentlyContinue | Format-Table -AutoSize | Out-File "$($arcHciLogDir)\config\archciconfig.txt"
    $mocConfig | Format-Table -AutoSize | Out-File "$($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
    # Collecting uber crud logs
    Copy-Item -Path $logFile -Destination "$($arcHciLogDir)\ubercrud-logs.txt" -ErrorAction Ignore

    try {
        Get-MocLogs -path $arcHciLogDir -activity $activity -AgentLogs -NodeVirtualizationLogs
    }
    catch {
        $_ >> "$($arcHciLogDir)\commandlogs.txt"
    }
  
    Get-DownloadSdkLogs -Path $arcHciLogDir

    Write-StatusWithProgress -activity $activity -moduleName $script:moduleName -status $("Collecting ARC appliance Logs...")

    try {
        Get-ArcHciApplianceLogs -arcHciLogDir $arcHciLogDir -ip $ip -kvaTokenPath $kvaTokenPath
    }
    catch {
        $_ >> "$($arcHciLogDir)\commandlogs.txt"
    }

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

    try {
        Get-ClusterLog -Destination "$($arcHciLogDir)\clusterlog\" -TimeSpan $clusterLogTimeSpan
    }
    catch {
        $_ >> "$($arcHciLogDir)\commandlogs.txt"
    }

    az.cmd version > "$($arcHciLogDir)\cliversions.txt"
    Write-StatusWithProgress -activity $activity -moduleName $script:moduleName -status $("Compressing Logs...")
    $zipName = "$arcHciLogDir.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
    )

   Write-Warning "The New-KvaVirtualNetwork command will be deprecated in the next release, please shift to New-ArcHciVirtualNetwork command instead"

    if ($PSCmdlet.ParameterSetName -eq "DHCP") {
        New-ArcHciVirtualNetwork -name $name -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend
    }
    else {
        New-ArcHciVirtualNetwork -name $name -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend `
            -ipaddressprefix $ipaddressprefix -gateway $gateway -dnsservers $dnsservers -k8snodeippoolstart $k8snodeippoolstart `
            -k8snodeippoolend $k8snodeippoolend -vlanID $([int]$vlanID)
    }
}

function New-ArcHciVirtualNetwork {
    <#
    .DESCRIPTION
        Creates a new virtual network using mocctl
 
    .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 mocGroup
        The MOC Group in which the virtual network will be created
 
    .PARAMETER mocLocation
        The MOC Location in which the virtual network will be created
    #>


    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()]
        [Int] $vlanID = 0,

        [Parameter(Mandatory=$false, ParameterSetName="DHCP")]
        [Parameter(Mandatory=$false, ParameterSetName="Static")]
        [ValidateNotNullOrEmpty()]
        [String] $mocGroup = "target-group",

        [Parameter(Mandatory=$false, ParameterSetName="DHCP")]
        [Parameter(Mandatory=$false, ParameterSetName="Static")]
        [ValidateNotNullOrEmpty()]
        [String] $mocLocation = "MocLocation"
    )

    # Get or create the moc location
    $mocLoc = Get-MocLocation -name $mocLocation -ErrorAction SilentlyContinue
    if ($null -eq $mocLoc) {
        $mocLoc = New-MocLocation -name $mocLocation -ErrorAction Stop
    }

    # Get or create the moc group
    $mocGrp = Get-MocGroup -name $mocGroup -location $mocLocation -ErrorAction SilentlyContinue
    if ($null -eq $mocGrp) {
        $mocGrp = New-MocGroup -name $mocGroup -location $mocLocation -ErrorAction Stop
    }

    try {
        # Create the moc vnet
        $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 for mocctl
        $mocvnetyaml = @"
name: $name
tags:
  VSwitch-Name: $vswitchName
type: Transparent
virtualnetworkpropertiesformat:
 
"@


        if (-Not [string]::IsNullOrWhiteSpace($dnsserversYaml)) {
            $mocvnetyaml += @"
  dhcpoptions:
    dnsservers: $dnsserversYaml
 
"@

}

        if (-Not [string]::IsNullOrEmpty($ipaddressprefix)) {
            $mocvnetyaml += @"
  subnets:
    - name: $name-subnet
      subnetpropertiesformat:
        addressprefix: $ipaddressprefix
        ipallocationmethod: Static
        ippools:
          - end: $k8snodeippoolend
            type: vm
            name: vmpool0
            start: $k8snodeippoolstart
          - end: $vippoolend
            type: vippool
            name: vippool0
            start: $vippoolstart
        routetable:
          routetablepropertiesformat:
            routes:
              - routepropertiesformat:
                  addressprefix: 0.0.0.0
                  nexthopipaddress: $gateway
        vlan: $vlanID
 
"@

        }
        else {
            $ipaddressprefix = Get-AddressPrefix -startIP $vippoolstart -endIP $vippoolend
            $mocvnetyaml += @"
  subnets:
    - name: $name-subnet
      subnetpropertiesformat:
        addressprefix: $ipaddressprefix
        ipallocationmethod: Dynamic
        ippools:
          - end: $vippoolend
            type: vippool
            name: vippool
            start: $vippoolstart
 
"@

}

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

        # Create the moc virtual network
        $config = Get-MocConfig
        $mocctlPath = Join-Path $config.installationPackageDir "mocctl.exe"

        # Create the moc virtual network
        Invoke-Expression "$mocctlPath network vnet create --config '$yamlFile' --group $mocGroup --location $mocLocation --cloudFqdn $($config.cloudFqdn)"

        # Show the moc virtual network
        Invoke-Expression "$mocctlPath network vnet show --name $name --group $mocGroup --location $mocLocation --cloudFqdn $($config.cloudFqdn) -o yaml"
    }
    catch {
        $errMsg = "Error while creating MOC virtual network: " + $($_.Exception.Message)
        Write-Error $errMsg
    }
}

function Get-AddressPrefix {
    param (
        [string] $startIP,
        [string] $endIP
    )

    $start = [IPAddress]($startIP)
    $end = [IPAddress]($endIP)

    $startbytes = $start.GetAddressBytes()
    $endbytes = $end.GetAddressBytes()

    # Check and fix the order of the IP address bytes
    if ([BitConverter]::IsLittleEndian) {
        [Array]::Reverse($startbytes)
        [Array]::Reverse($endbytes)
    }

    $startAddress = [BitConverter]::ToUInt32($startbytes, 0)
    $endAddress = [BitConverter]::ToUInt32($endbytes, 0)

    $hostPrefixLength = 0
    # Bitwise XOR will convert all the bits that match to 0s
    $ipxor = $startAddress -bxor $endAddress

    # Keep shifting 1 bit at time till we reach only 0s (That will be the longest length of the host prefix)
    while (-not ($ipxor -eq 0)) {
        $ipxor = $ipxor -shr 1
        $hostPrefixLength += 1
    }

    # Get the prefix IP by shifting the start IP (or the end IP) by the host prefix length bitwise to right and then left by the same amount
    $prefixIP = [ipaddress](($startAddress -shr $hostPrefixLength) -shl $hostPrefixLength)
    $ipBytes = $prefixIP.GetAddressBytes()
    if ([BitConverter]::IsLittleEndian) {
        [Array]::Reverse($ipBytes)
    }
    $prefixIPString = ([ipaddress][BitConverter]::ToUInt32($ipBytes, 0)).IPAddressToString

    # IP Prefix length is the the number of 1s followed by 0s
    $prefixLength = 32 - $hostPrefixLength
    $addressPrefix = $prefixIPString + "/" + $prefixLength

    return $addressPrefix
}

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 outfile
        Path to the file in which target cluster kubeconfig will be stored
 
    .PARAMETER clusterName
        The name of the target cluster
 
    .PARAMETER controlPlaneIP
        IP Address of the control plane VM of target cluster. Defaults to the IP Address of the VM matching the name .*<clusterName>-control-plane.*
 
    .PARAMETER sshPrivateKey
        Path to the SSH private key file used for the target cluster. Defaults to "$env:USERPROFILE\.ssh\id_rsa"
    #>

    param (
        [Parameter(Mandatory=$true)]
        [ValidateScript({
            # Check if path exists
            if($_ | Test-Path){
              # Check if it is a file path
              if($_ | Test-Path -PathType Leaf){
                return $true
              }
              throw $fileError
            }
            # Check if the parent directory path exists
            if([System.IO.Path]::GetDirectoryName($_) | Test-Path){
              return $true
            }
            throw $fileFolderPathError
          })]
        [String] $outfile,

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

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

        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ([string]::IsNullOrEmpty($_)) {
                return $true
            }
            if(-not ($_ | Test-Path)){
              throw $fileFolderPathError
            }
            return $true
          })]
        [String] $sshPrivateKey = "$env:USERPROFILE\.ssh\id_rsa"
    )

    try
    {
        # Try getting the control plane IP for the target cluster
        if ([string]::IsNullOrWhiteSpace($controlPlaneIP)) {
            $vms = @()
            # Check all nodes in a multi node host cluster
            $hostClusterNodes = Get-ClusterNode -ErrorAction Ignore
            if ($hostClusterNodes.Length -gt 1) {
                ForEach ($node in $hostClusterNodes) {
                    $vms = Get-VM -ComputerName $node.Name | Where-Object { $_.Name -Match ".*$clusterName-control-plane.*" }
                    if ($vms.Length -gt 0) {
                        break
                    }
                }
            }
            else {
                $vms = Get-VM | Where-Object {$_.Name -Match ".*$clusterName-control-plane.*"}
            }

            if ($vms.Length -eq 0) {
                throw "Unable to find a VM with name matching .*$clusterName-control-plane.*"
            }

            $ips = [ipaddress[]]$vms[0].NetworkAdapters.IPAddresses | Where-Object {$_.AddressFamily -eq "InterNetwork"}
            if ($null -eq $ips) {
                throw "Unable to find the IP Address for the VM with name: $($vms[0].Name)"
            }
            $controlPlaneIP = $ips[0]
        }

        Write-Output "Fetching the target cluster kubeconfig from the control plane VM with IPv4 Address: $controlPlaneIP"

        Invoke-Expression "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i $sshPrivateKey clouduser@${controlPlaneIP} 'sudo cat /etc/kubernetes/admin.conf > /home/clouduser/admin.conf'"
        Invoke-Expression "scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i $sshPrivateKey clouduser@${controlPlaneIP}:/home/clouduser/admin.conf $outfile"
        Invoke-Expression "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i $sshPrivateKey clouduser@${controlPlaneIP} 'rm /home/clouduser/admin.conf'"

        Write-Output "Successfully saved the target cluster kubeconfig to $outfile"
    }
    catch {
        $errMsg = "Error while getting kubeconfig for cluster $clusterName. " + $_.Exception.Message
        Write-Output $errMsg

    }
}

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)

    if ([string]::IsNullOrEmpty($version)) {
        $version = $mocConfig.version
    }
    $imageName = Get-LegacyKubernetesGalleryImageName -imagetype $imageType -k8sVersion $k8sVersion
    $galleryImage = $null
    $mocLocation = $mocConfig.cloudLocation

    # 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 -releaseVersion $version

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

        # We need to find a MocStorageContainer that is not isolated
        $mocContainers = Get-MocContainer -location $mocLocation
        $storageContainer = $null
        foreach($container in $mocContainers)
        {
            if ($container.properties.isolated -ne $true)
            {
                $storageContainer = $container.name
                break
            }
        }
        # Error out if we can't find a MocContainer that is not isolated
        if ($null -eq $storageContainer)
        {
            errorMsg = ("Unable to find a suitable MocContainer. Please ensure a MocContainer exists and is not isolated.\n" + 
            "To create a new MocContainer, please run 'New-MocContainer -location MocLocation -Name <moc container name> -Path <path to a storage location where to store the images>'.")
            throw errorMsg
        } else {
            New-MocGalleryImage -name $imageName -location $mocLocation -imagePath $result -container $storageContainer
        }
        # Remove the downloaded bits
        Remove-Item -Path $result -Force -ErrorAction Ignore

        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. This command depends on kubernetes config file
        with admin permission to be saved as $env:USERPROFILE\.kube\config.
    #>


    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
    $kubectlPath = Join-Path $mocConfig.installationPackageDir "kubectl.exe"
    $configStr = ""
    try {
        $configStr = Invoke-Expression "$kubectlPath get secret controllerconfig -n moc-operator-system"
    }
    catch {
        Write-Output "Failed to get controllerconfig"
    }

    if ($configStr.Length -gt 1 -and $configStr[1].Contains("controllerconfig")) {
        Invoke-Expression "$kubectlPath patch secret controllerconfig -p '{\""data\"":{\""LoginString\"":\""$loginString\""}}' -n moc-operator-system"
    }
    else {
        $filePath = ".\controllerconfig.yaml"
        $secretContent = "apiVersion: v1`n"
        $secretContent += "kind: Secret`n"
        $secretContent += "metadata:`n"
        $secretContent += " name: controllerconfig`n"
        $secretContent += " namespace: moc-operator-system`n"
        $secretContent += "type: Opaque`n"
        $secretContent += "data:`n"
        $secretContent += " LoginString: $loginString`n"
        $secretContent += "stringData:`n"
        $secretContent += " CloudFQDN: " + $mocConfig.cloudFqdn + "`n"
        $secretContent += " Location: " + $mocConfig.cloudLocation + "`n"
        $secretContent += " ContainerName: MocStorageContainer`n"
        $secretContent | Out-File -FilePath $filePath

        if (Test-Path $filePath) {
            Invoke-Expression "$kubectlPath apply -f $filePath"
            Remove-Item -Path $filePath
        }
    }
    Invoke-Expression "$kubectlPath 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
    #>

    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 {
    <#
    .DESCRIPTION
        Checks
         1. Whether it is valid IPV4 ip
    .PARAMETER ip
        Ip address
    .OUTPUTS
        Boolean value true/false
    .EXAMPLE
        Test-IPV4Address -ip $ip
    #>

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

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

function Test-K8snodeIPPoolRange {
    <#
    .DESCRIPTION
        Validates k8sIPPool range for archci
     
    .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.
         
    #>

    param (
        [parameter(Mandatory = $true)] [string] $k8snodeippoolstart,
        [parameter(Mandatory = $true)] [string] $k8snodeippoolend
    )
    $to_bytes = [IPAddress]::Parse($k8snodeippoolstart).GetAddressBytes()
    [Array]::Reverse($to_bytes)
    $start_ip = [BitConverter]::ToUInt32($to_bytes, 0)

    $to_bytes = [IPAddress]::Parse($k8snodeippoolend).GetAddressBytes()
    [Array]::Reverse($to_bytes)
    $end_ip = [BitConverter]::ToUInt32($to_bytes, 0)
    if($end_ip -le $start_ip) {

        return $false

    }
    return $true
}

function Test-rbIpRange {
    <#
    .DESCRIPTION
        Validates k8sIPPool range for archci
     
    .PARAMETER $rbIpStart
        The starting ip address to use for VM's in the cluster.
 
    .PARAMETER $rbIpEnd
        The ending ip address to use for VM's in the cluster.
         
    #>

    param (
        [parameter(Mandatory = $true)] [string] $rbIpStart,
        [parameter(Mandatory = $true)] [string] $rbIpEnd
    )
    $to_bytes = [IPAddress]::Parse($rbIpStart).GetAddressBytes()
    [Array]::Reverse($to_bytes)
    $start_ip = [BitConverter]::ToUInt32($to_bytes, 0)

    $to_bytes = [IPAddress]::Parse($rbIpEnd).GetAddressBytes()
    [Array]::Reverse($to_bytes)
    $end_ip = [BitConverter]::ToUInt32($to_bytes, 0)
    if($start_ip -ge $end_ip ) {

        return $false

    } 
    return $true

}

function Write-Log {
    <#
    .DESCRIPTION
        Write logs to the log file
    .PARAMETER LogString
        Log string
    .OUTPUTS
        N/A
    .EXAMPLE
        Write-Log -LogString $LogString
    #>

    Param (
        [parameter(Mandatory = $true)] [object] $LogString
    )

    if([String]::IsNullOrEmpty($logPath)){
        $logPath = Get-ArcHciDefaultPath
        $logFile = Join-Path $logPath "ubercrud.log"
    }

    $Stamp = (Get-Date).toString("yyyy/MM/dd HH:mm:ss")
    $Hostname = $($env:computername)
    $LogMessage = "$Stamp [$Hostname] $LogString"
    $LogMessage >> $logFile
}

function Retry() {
    <#
    .DESCRIPTION
        Reruns a function maxAttempts times with a delay of retryDelaySeconds.
    .PARAMETER $action
        Function to run
    .PARAMETER $maxAttempts
        Max number of retries
    .PARAMETER $retryDelaySeconds
        Time in seconds to wait after failed attempt
    .PARAMETER $force_retry
        Force retry even if function doesn't fail
    .OUTPUTS
        N/A
    .EXAMPLE
        Retry {Install-ArcHciPrerequisites -subscriptionID $subscriptionID} -maxAttempts 3 -retryDelaySeconds 5
    #>

    param(
        [Parameter(Mandatory=$true)][Action]$action,
        [Parameter(Mandatory=$false)][int]$maxAttempts = 3,
        [Parameter(Mandatory=$false)][int]$retryDelaySeconds = 5,
        [Parameter(Mandatory=$false)][switch]$force_retry
    )

    $attempts = 1    
    $ErrorActionPreferenceToRestore = $ErrorActionPreference
    $ErrorActionPreference = "Stop"

    do
    {
        try
        {
            $action.Invoke();
            if (!($force_retry.IsPresent)) {
                break;
            }
        }
        catch [Exception]
        {
            Write-Host $_.Exception.Message
        }

        $attempts++
        if ($attempts -le $maxAttempts) {
            Write-Host("Action failed. Waiting " + $retryDelaySeconds + " seconds before attempt " + $attempts + " of " + $maxAttempts + ".")
            Start-Sleep $retryDelaySeconds 
        }
        else {
            $ErrorActionPreference = $ErrorActionPreferenceToRestore
            Write-Error $_.Exception.Message
        }
    } while ($attempts -le $maxAttempts)
    $ErrorActionPreference = $ErrorActionPreferenceToRestore
}

function Install-ArcHciPrerequisites {
    <#
    .DESCRIPTION
        Install ArcHci pre-requisites.
    .PARAMETER subscriptionID
        Subscription ID
    .OUTPUTS
        N/A
    .EXAMPLE
        Install-ArcHciPrerequisites -subscriptionID $subscriptionID
    #>


    Param(
        [parameter(Mandatory = $true)] 
        [GUID] $subscriptionID
    )
    Write-Output "Installing ArcHci Pre-requisites...."
    Write-Log "Installing ArcHci Pre-requisites...."    
    Invoke-ArcHciAzCommand "config set extension.use_dynamic_install=yes_without_prompt" -ignoreWarning
    Retry -action {Register-ResourceProviders -subscriptionID $subscriptionID 2>&1}

    Write-Output "ArcHci Pre-requisites Installed Successfully"
    Write-Log "ArcHci Pre-requisites Installed Successfully" 
}

function Register-ResourceProviders {
    <#
    .DESCRIPTION
        Onboard resource providers to the subscription
    .PARAMETER subscriptionID
        Subscription ID
    .OUTPUTS
        N/A
    .EXAMPLE
        Register-ResourceProviders -subscriptionID $subscriptionID
    #>


    # Set the default subscription
    Invoke-ArcHciAzCommand "account set -s $subscriptionID"

    Write-Verbose "Registering Resource Providers...."
    Write-Log "Registering Resource Providers...." 

    Invoke-ArcHciAzCommand "provider register --namespace Microsoft.Kubernetes --wait" -logOutput
    Invoke-ArcHciAzCommand "provider register --namespace Microsoft.KubernetesConfiguration --wait" -logOutput
    Invoke-ArcHciAzCommand "provider register --namespace Microsoft.ExtendedLocation --wait" -logOutput
    Invoke-ArcHciAzCommand "provider register --namespace Microsoft.ResourceConnector --wait" -logOutput
    Invoke-ArcHciAzCommand "provider register --namespace Microsoft.AzureStackHCI --wait" -logOutput
    Invoke-ArcHciAzCommand "provider register --namespace Microsoft.HybridConnectivity --wait" -logOutput
    Invoke-ArcHciAzCommand "provider register --namespace Microsoft.HybridContainerService --wait" -logOutput

    Write-Verbose "Resource Providers registered successfully if not were already registered!"
    Write-Log "Resource Providers registered successfully if not were already registered!" 
}

function Install-ArcHciMoc {
    <#
    .DESCRIPTION
        Install ArcHciMoc.
    .PARAMETER volumePath
        Optional parameter. Volume Path
    .PARAMETER cloudserviceIP
        Optional parameter. Cloud agent IP
    .PARAMETER workingDir
        Optional parameter. Working Directory Path
    .PARAMETER workingDirectory
        Optional parameter. Working Directory Path. This will be deprecated. Please use workingDir instead
    .PARAMETER imageDir
        Optional parameter. Image Directory Path
    .PARAMETER isolateImageDir
        Optional parameter. Flag to isolate image dir
    .PARAMETER cloudConfigLocation
        Optional parameter. Cloud Config Location Path
    .PARAMETER version
        Optional parameter. Moc version
    .PARAMETER catalog
        Optional parameter. Catalog
    .PARAMETER ring
        Optional parameter. Ring
    .PARAMETER skip_prechecks
        Optional parameter. Flag to skip prechecks
    .PARAMETER correlationId
        Optional parameter. Identifier used for telemetry
    .OUTPUTS
        N/A
    .EXAMPLE
        Install-ArcHciMoc -volumePath $volumePath -cloudserviceIP $cloudserviceIP -workingDir $workingDir -imageDir $imageDir -isolateImageDir $isolateImageDir -cloudConfigLocation $cloudConfigLocation -version $version -catalog $catalog -ring $ring -skip_prechecks -useStagingShare -stagingShare $stagingShare
    #>


    Param(
        [parameter(Mandatory = $false)]
        [ValidateScript({
                if ($_ -match $space) {
                    throw $spaceErrorForFolderFilePath
                }  
                if (-not ($_ | Test-Path)) {
                    throw $fileFolderPathError
                }
                return $true
            })]
        [string] $volumePath,

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

        [parameter(Mandatory = $false)]
        [ValidateScript({
                if ([string]::IsNullOrEmpty($_)) {
                    return $true
                }
                if ($_ -match $space) {
                    throw $spaceErrorForFolderFilePath
                }  
                if (-not ($_ | Test-Path)) {
                    throw $fileFolderPathError
                }
                return $true
            })]  
        [string] $workingDir,

        # This should be deprecated. Please use $workingDir instead
        [parameter(DontShow)]
        [ValidateScript({
                if ([string]::IsNullOrEmpty($_)) {
                    return $true
                }
                if ($_ -match $space) {
                    throw $spaceErrorForFolderFilePath
                }  
                if (-not ($_ | Test-Path)) {
                    throw $fileFolderPathError
                }
                return $true
            })]  
        [string] $workingDirectory,

        [parameter(Mandatory = $false)]
        [ValidateScript({
                if ([string]::IsNullOrEmpty($_)) {
                    return $true
                }
                if ($_ -match $space) {
                    throw $spaceErrorForFolderFilePath
                }  
                if (-not ($_ | Test-Path)) {
                    throw $fileFolderPathError
                }
                return $true
            })]  
        [string] $imageDirectory,

        [parameter(Mandatory = $false)]
        [switch] $isolateImageDir,

        [parameter(Mandatory = $false)]
        [ValidateScript({
                if ([string]::IsNullOrEmpty($_)) {
                    return $true
                }
                if ($_ -match $space) {
                    throw $spaceErrorForFolderFilePath
                }  
                if (-not ($_ | Test-Path)) {
                    throw $fileFolderPathError
                }
                return $true
            })]  
        [string] $cloudConfigLocation,

        [parameter(Mandatory = $false)]  
        [string] $version,

        [parameter(Mandatory = $false)]
        [string] $catalog = "aks-hci-stable-catalogs-ext",

        [parameter(Mandatory = $false)]
        [string] $ring = "stable",

        [Parameter(Mandatory = $false)]  
        [switch] $skip_prechecks,

        [Parameter(Mandatory = $false)]
        [switch] $useStagingShare,

        [parameter(Mandatory = $false)]
        [String] $stagingShare,

        [parameter(Mandatory = $false)]
        [string] $correlationId = [System.Guid]::NewGuid().ToString()
    )
    $global:ProgressPreference = 'SilentlyContinue'
    Write-Log "Entered Install-ArcHciMoc"
    $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Install-ArcHciMoc" -version $moduleVersion
    try {
        Write-Log "Running Get-ArcHciMoc"
        $mocConfig = Get-ArcHciMoc -correlationId $correlationId
        # Convert the hashtable to a table format and then to a string
        $mocConfigString = $mocConfig | Format-Table -AutoSize | Out-String
        Write-Log "Mocconfig: $mocConfigString"
    
        if ($mocConfig.installState -eq 'NotInstalled') {
    
            if ([String]::IsNullOrEmpty($volumePath)) {
                $volumePath = Get-ArcHciDefaultPath
            }
    
            if (-not $skip_prechecks.IsPresent) {
                Write-Log "Running Test-ArcHciEnableMoc -volumePath $volumePath -cloudServiceIP $cloudserviceIP"
                $mocTestRes = Test-ArcHciEnableMoc -volumePath $volumePath -cloudServiceIP $cloudserviceIP -correlationId $correlationId
                Write-Log "Moc test results: $mocTestRes"
            }
            else {
                Write-Log "Skipping Moc prechecks"
                Write-Output "Skipping Moc prechecks"
            }
    
            if ($skip_prechecks.IsPresent -or $mocTestRes[$mocTestRes.length - 1].TestResult -eq "Succeeded") {
                Write-Output "Installing Moc...."
                Write-Log "Installing Moc...."  

                if ([String]::IsNullOrEmpty($workingDir)) {
                    # As a temp work around, set workingDir to workingDirectory value, if workingDir is empty and workingDirectory is set
                     if (-not ([String]::IsNullOrEmpty($workingDirectory))) {
                        $workingDir = $workingDirectory
                     }

                    Write-Log "Running Join-Path -Path $volumePath -ChildPath 'WorkingDirectory'"
                    New-Item -Type Directory -Force -Path (Join-Path -Path $volumePath -ChildPath "WorkingDirectory")
                    $workingDir = Join-Path -Path $volumePath -ChildPath "WorkingDirectory"
                }   
    
                if ([String]::IsNullOrEmpty($imageDir)) {
                    Write-Log "Running Join-Path -Path $workingDir -ChildPath 'ImageStore'"
                    New-Item -Type Directory -Force -Path (Join-Path -Path $workingDir -ChildPath "ImageStore")
                    $imageDir = (Join-Path -Path $workingDir -ChildPath "ImageStore")
                }
    
                if ([String]::IsNullOrEmpty($cloudConfigLocation)) {
                    Write-Log "Running Join-Path -Path $workingDir -ChildPath 'CloudStore'"
                    New-Item -Type Directory -Force -Path (Join-Path -Path $workingDir -ChildPath "CloudStore")
                    $cloudConfigLoc = (Join-Path -Path $workingDir -ChildPath "CloudStore")
                }
    
                try {
                    if ($useStagingShare.IsPresent) {
                        Write-Log "Running Set-MocConfig -workingDir $workingDir -imageDir $imageDir -skipHostLimitChecks -cloudConfigLocation $cloudConfigLoc -version $version -catalog $catalog -ring $ring -cloudServiceCidr $cloudServiceIP -createAutoConfigContainers $false -useStagingShare -stagingShare $stagingShare"
                        Set-MocConfig -workingDir $workingDir -imageDir $imageDir -skipHostLimitChecks  -cloudConfigLocation $cloudConfigLoc -version $version -catalog $catalog -ring $ring -cloudServiceCidr $cloudServiceIP -createAutoConfigContainers $false -useStagingShare -stagingShare $stagingShare
                    }
                    else {
                        Write-Log "Running Set-MocConfig -workingDir $workingDir -imageDir $imageDir -skipHostLimitChecks -cloudConfigLocation $cloudConfigLoc -version $version -catalog $catalog -ring $ring -cloudServiceCidr $cloudServiceIP -createAutoConfigContainers $false"
                        Set-MocConfig -workingDir $workingDir -imageDir $imageDir -skipHostLimitChecks  -cloudConfigLocation $cloudConfigLoc -version $version -catalog $catalog -ring $ring -cloudServiceCidr $cloudServiceIP -createAutoConfigContainers $false
                    }
    
                    Write-Log "Running Install-Moc"
                    Install-Moc
                }
                catch {
                    Write-Log "Correlation ID: $correlationId. $_"
                    Write-Log "Uninstalling Moc..."
                    Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmocfailed" -message "Install MOC Failed - Exception: $_"
                    Uninstall-Moc
                    throw "Correlation ID: $correlationId. $_"
                }
    
    
                Write-Log "Running Get-ArcHciMoc"
                $mocConfig = Get-ArcHciMoc -correlationId $correlationId
                # Convert the hashtable to a table format and then to a string
                $mocConfigString = $mocConfig | Format-Table -AutoSize | Out-String
                Write-Log "Mocconfig: $mocConfigString"
    
                if ($mocConfig.installState -eq 'Installed') {
                    Write-Output "Moc version $($mocConfig.version) installed successfully!"
                    Write-Log "Moc version $($mocConfig.version) installed successfully" 
                }
                else {
                    Write-Output "Correlation ID: $correlationId. Moc install state: $($mocConfig.installState)"
                    Write-Log "Correlation ID: $correlationId. Moc install state: $($mocConfig.installState)"
                    Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmocfailed" -message "Install MOC Failed - Moc install state: $($mocConfig.installState)"
                }
            }
            else {
                Write-Log "Correlation ID: $correlationId. Terminating Moc Installation. $($mocTestRes[$mocTestRes.length - 1].Details)"
                Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmocprecheckfailed" -message "Install MOC Precheck Failed: $($mocTestRes[$mocTestRes.length - 1].Details)"
                throw "Correlation ID: $correlationId. $($mocTestRes[$mocTestRes.length - 1].Details)"
            }
        }
        else {
            Write-Log "Moc is already installed!"
            Write-Output "Moc is already installed!"
            Set-ArcHciTelemetryEventSkipped -eventConfig $eventConfig
            return $mocConfig
        }
        Write-Log "Exiting Install-ArcHciMoc"
        $global:ProgressPreference = 'Continue'
    }
    finally {
        Publish-ArcHciTelemetryEvent -eventConfig $eventConfig
    }
}

function Invoke-ArcHciAzCommand {
    <#
    .DESCRIPTION
        Executes an az cli command.
    .PARAMETER arguments
        Arguments to pass to az cli.
    .PARAMETER ignoreError
        Optionally, ignore errors from the command (don't throw).
    .PARAMETER argumentsToFormat
        Specific arguments to format with quotes.
    .OUTPUTS
        N/A
    .EXAMPLE
        Invoke-ArcHciAzCommand -arguments "provider show --namespace 'Microsoft.Resources'"
    #>

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

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

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

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

        [Parameter(Mandatory = $false)]
        [String[]]$argumentsToFormat = @('-n','--name','-g', '--resource-group', '--cluster-name', '-l', '--location')
    )

    $azCliFullPath = (Get-Command "az.cmd").Source
    if (-not $azCliFullPath) {
        throw $("Unable to find the `"az.cmd`" command. Please install azure cli client and then signoff from the machine and sign-in again")
    }
    if($ignoreWarning){
        $arguments = $arguments + " --only-show-errors"
    }
    Write-Log "Running command [[""$azCliFullPath"" $arguments]] ..." -InformationAction Continue
    $response = Invoke-ArcHciAzCommandLine -Command $azCliFullPath -Arguments $arguments -ignoreError:$ignoreError -logOutput:$logOutput -ArgumentsToFormat $argumentsToFormat
 
    return $response
}

function FormatArgumentsWithQuotes {
    <#
    .DESCRIPTION
        Appends quotes around select arguments so parameters can be formatted properly when we run the az command. If there is a space for example and the argument does not have quotes around it, it will fail so we need to append quotes.
    .PARAMETER arguments
        Arguments to pass to az cli.
    .PARAMETER argumentsToFormat
        Arguments to add quotes to
    .OUTPUTS
        N/A
    .EXAMPLE
        FormatArgumentsWithQuotes -arguments 'group show -n "sample group"', -argumentsToFormat '-n'
    #>

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

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

    foreach ($arg in $argumentsToFormat) {
        if ($arguments -match "($arg) `"(.+?)`"") { 
            # The argument already has surrounding quotes so we can skip it
            continue 
        } elseif ($arguments -match "($arg) (.+?) (?=-)") {
            # Case where the argument is any argument except the last argument in the argument list (meaning it is followed by a - symbol)
            # So for example if the arguments string looks like "-n custom location -l samplelocation" and the argumentsToFormat are "-n" the returned string will be "-n "custom location" -l samplelocation".
            $arguments = $arguments -replace "($arg) (.+?) (?=-)", '$1 "$2" '
        } else {
            # Case where the argument is the last argument in the argument list so there is no - after the parameter.
            # If the argumentToFormat is the last argument of the string, place quotes around everything following it.
            # So for example if the arguments string looks like "-n customlocation -l sample location" and the argumentsToFormat are "-l" the returned string will be "-n customlocation -l "sample location"" since -l is the last parameter in the string.
            $arguments = $arguments -replace "($arg) (.+)$", '$1 "$2"'
        }
    }

    return $arguments
}


function Invoke-ArcHciAzCommandLine {
    <#
    .DESCRIPTION
        Executes a command and optionally ignores errors.
 
    .PARAMETER command
        Command to execute.
 
    .PARAMETER arguments
        Arguments to pass to the command.
 
    .PARAMETER ignoreError
        Optionally, ignore errors from the command (don't throw).
 
    .PARAMETER logOutput
        Optionally, log live output from the executing command.
 
    .PARAMETER showOutputAsProgress
        Optionally, show output from the executing command as progress bar updates.
 
    .PARAMETER progressActivity
        The activity name to display when showOutputAsProgress was requested.
 
    .PARAMETER argumentsToFormat
        Specific arguments to format with quotes.
    #>


    param (
        [String]$command,
        [String]$arguments,
        [Switch]$ignoreError,
        [Switch]$logOutput,
        [Switch]$showOutputAsProgress,
        [String]$progressActivity,
        [String[]]$argumentsToFormat
    )
    $previousErrorAction = $errorActionPreference
    $errorActionPreference = "Continue"

    $arguments = FormatArgumentsWithQuotes -arguments $arguments -argumentsToFormat $argumentsToFormat
    Write-Log "Reformatting command as [[""$azCliFullPath"" $arguments]]" -InformationAction Continue

    # Splits on spaces unless the space is between quotes and hence is part of an argument like resource-group.
    $argumentArray = $arguments -split ' (?=(?:[^\"]|\"[^\"]*\")*$)'
    try {
        if ($showOutputAsProgress.IsPresent) {
            $errorResult = $($result = (& $command $argumentArray | ForEach-Object { $status = $_ -replace "`t", " - " })) 2>&1
        }
        elseif ($logOutput.IsPresent) {
            $errorResult = $($result = (& $command $argumentArray | ForEach-Object { Write-Log "$command $arguments $_"; return $_ })) 2>&1
        }
        else {
            $errorResult = $($result = (& $command $argumentArray)) 2>&1
        }
        $previousExitCode = $LASTEXITCODE
    }
    catch {
        if ($ignoreError.IsPresent) {
            return
        }
        Write-Log $_
        throw
    }
    finally {
        $errorActionPreference = $previousErrorAction
    }

    $allErrorRecords = $errorResult | Where-Object { $_.gettype().Name -eq "ErrorRecord" }

    # Ignore known warnings that are returned as errors
    $errorRecords = $allErrorRecords | Where-Object { $_.Exception.Message -notmatch "Please let us know how we are doing" `
        -and $_.Exception.Message -notmatch "The installed extension '.+' is experimental" `
        -and $_.Exception.Message -notmatch "The installed extension '.+' is in preview." `
        -and $_.Exception.Message -notmatch "Setting GA feature gate arcmonitoring=true" `
        -and $_.Exception.Message -notmatch "Command group '.+' is in preview and under development." `
        -and $_.Exception.Message -notmatch "UserWarning: You are using cryptography on a 32-bit Python on a 64-bit Windows Operating System. Cryptography will be significantly faster if you switch to using a 64-bit Python." }

    if ($null -ne $errorRecords) {
        $stack = Get-PSCallStack
        # An error message was returned, just throw that message
        $errMessage = "$command $arguments returned a non empty error stream [$errorRecords] at [$($stack)]"
        throw $errMessage
    }

    if ($null -ne $result) {
        $out = $result | Where-Object { $_.gettype().Name -ine "ErrorRecord" }  # On a non-zero exit code, this may contain the error
    }
    
    if ($previousExitCode) {
        if ($null -ne $result) {
            $err = $result | Where-Object { $_.gettype().Name -eq "ErrorRecord" }
        }
        $errMessage = "$command $arguments returned a non zero exit code $previousExitCode [$err]"
        if ($ignoreError.IsPresent) {
            $ignoreMessage = "[IGNORED ERROR] $errMessage"
            return $ignoreMessage
        }
        throw $errMessage
    }
    return $out
}

function New-ArcHciApplianceConfigs {
    <#
    .DESCRIPTION
        Creates the Arc HCI config files
    .PARAMETER subscriptionID
        The Azure subscription GUID
    .PARAMETER location
        Azure location
    .PARAMETER resourceGroup
        Name of the Azure resource group
    .PARAMETER resourceName
        Name of the Azure resource
    .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 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
        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 workingDir
        Optional parameter. Working Directory Path
    .PARAMETER controlPlaneIP
        IP Address to be used for the Arc Appliance control plane
    .PARAMETER rbIpStart
        The starting ip address to use for Arc RB.
    .PARAMETER rbIpEnd
        Optional parameter. The ending ip address to use for Arc RB.
    .PARAMETER gateway
        Optional parameter. The gateway to use when using static IP
    .PARAMETER ipaddressprefix
        Optional parameter. The address prefix to use for static IP assignment
    .PARAMETER dnsservers
        Optional parameter. The dnsservers to use when using static IP
    .PARAMETER vlanID
        Optional parameter. The VLAN ID for the vnet
    .PARAMETER arcHciProxyConfig
        Optional parameter. Proxy Config
    .OUTPUTS
        N/A
    .EXAMPLE
        New-ArcHciApplianceConfigs -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -workingDir $workingDir -controlPlaneIP $controlPlaneIP -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd -gateway $gateway -dnsservers $dnsservers -ipaddressPrefix $ipaddressPrefix -vLanID $vlanID -arcHciProxyConfig $arcHciProxyConfig
    #>
   

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

        [parameter(Mandatory = $true)]   
        [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=$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
                }
                if ($_ -notmatch $regexPatternRFC1123) {
                    $parameter = "vnetName"   
                    throw $regexPatternRFC1123Error -f $_, $parameter
                }
                return $true
            })] 
        [string] $vnetName, 

        # vswitchName can accept any characters
        [parameter(Mandatory = $true)]  
        [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 ($_ -match $space) {
                    throw $spaceErrorForFolderFilePath
                }  
                if (-not ($_ | Test-Path)) {
                    throw $fileFolderPathError
                }
                return $true
            })]  
        [string]  $workingDir,

        [Parameter(Mandatory=$true)]
        [ValidateScript({
            $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) {
                    $parameter = "rbIpStart"
                    throw "$ipv4ValidationError" -f $parameter, $_
                }
                return $true
            })]
        [string] $rbIpStart,

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

        [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 ([string]::IsNullOrEmpty($_)) {
                    return $true
                }
                if ($_ -notmatch $regexPatternCIDRFormat) {
                    $parameter = "ipaddressprefix"  
                    throw $regexPatternCIDRFormatError -f $_, $parameter
                }
                return $true
            })]
        [string] $ipAddressPrefix,

        [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)]
        [ValidateRange(0, 4094)] 
        [int] $vlanID = 0,

        [Parameter(Mandatory = $false)]
        $arcHciProxyConfig
    )
    
    Write-Log "Entered New-ArcHciApplianceConfigs"

    if ([String]::IsNullOrEmpty($workingDir)) {
        $mocConfig = Get-ArcHciMoc
        $workingDir = $mocConfig.workingDir
    }

    $workingDir = New-Item -Path $workingDir -Name "Appliance" -ItemType Directory -Force
    
    if ($null -ne $arcHciProxyConfig -and $arcHciProxyConfig.count -gt 0) {
        $proxyServerHTTP = $arcHciProxyConfig.proxyServerHTTP
        $proxyServerHTTPS = $arcHciProxyConfig.proxyServerHTTPS
        $proxyServerNoProxy = $arcHciProxyConfig.proxyServerNoProxy
        $certificateFilePath = $arcHciProxyConfig.certificateFilePath
    }

    Write-Log "Running Get-ArcHciParameters -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd"
    $arcHciParameters = Get-ArcHciParameters -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd
    $k8snodeippoolstart = $arcHciParameters.k8sNodeIpPoolStart
    $k8snodeippoolend = $arcHciParameters.k8sNodeIpPoolEnd

    Write-Log "Generating the config files..."
    Write-Log "Running New-ArcHciConfigFiles -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -workDirectory $workingDir -controlPlaneIP $controlPlaneIP -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -k8snodeippoolstart $k8snodeippoolstart -k8snodeippoolend $k8snodeippoolend -gateway $gateway -dnsservers $dnsservers -ipaddressprefix $ipaddressprefix -vLanID $vlanID -proxyServerHTTP <obfuscated> -proxyServerHTTPS <obfuscated> -proxyServerNoProxy $proxyServerNoProxy -certificateFilePath $certificateFilePath -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion"
    New-ArcHciConfigFiles -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -workDirectory $workingDir -controlPlaneIP $controlPlaneIP -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -k8sNodeIpPoolStart $k8sNodeIpPoolStart -k8sNodeIpPoolEnd $k8sNodeIpPoolEnd -gateway $gateway -dnsServers $dnsServers -ipAddressPrefix $ipAddressPrefix -vLanID $vlanID -proxyServerHTTP $proxyServerHTTP -proxyServerHTTPS $proxyServerHTTPS -proxyServerNoProxy $proxyServerNoProxy -certificateFilePath $certificateFilePath -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion

    Write-Log "Generated hci-appliance.yaml:"
    Write-Log "-----------------------------------"
    $content = Get-Content "$workingDir\hci-appliance.yaml"
    Write-Log $content
    Write-Log "-----------------------------------"
    Write-Log "Generated hci-infra.yaml:"
    Write-Log "-----------------------------------"
    $content = Get-Content "$workingDir\hci-infra.yaml"
    Write-Log $content
    Write-Log "-----------------------------------"
    Write-Log "Generated hci-resource.yaml:"
    Write-Log "-----------------------------------"
    $content = Get-Content "$workingDir\hci-resource.yaml"
    Write-Log $content
    Write-Log "-----------------------------------"
    Write-Log "Generated kvatoken.tok:"
    Write-Log "-----------------------------------"
    $content = Get-ChildItem -Path "$workingDir\kvatoken.tok" -File | Where-Object { $_.Name -eq "kvatoken.tok" } | Select-Object  PSChildName, CreationTime, Length
    Write-Log $content
    Write-Log "-----------------------------------"

    Write-Log "Exiting New-ArcHciApplianceConfigs"
}

function Install-ArcHciResourceBridge {
    <#
    .DESCRIPTION
        Installs an Arc Resource Bridge
    .PARAMETER subscriptionID
        The Azure subscription GUID
    .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 workingDir
        Optional parameter. Working Directory Path
    .PARAMETER correlationId
        Optional parameter. Identifier used for telemetry
    .OUTPUTS
        N/A
    .EXAMPLE
        Install-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir -correlationId $correlationId
    #>
   

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

        [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 = $false)]
        [ValidateScript({
                if ([string]::IsNullOrEmpty($_)) {
                    return $true
                }
                if ($_ -match $space) {
                    throw $spaceErrorForFolderFilePath
                }  
                if (-not ($_ | Test-Path)) {
                    throw $fileFolderPathError
                }
                return $true
            })]  
        [string]  $workingDir,

        [parameter(Mandatory = $false)]
        [string] $correlationId = [System.Guid]::NewGuid().ToString()        
    )

    Write-Log "Entered Install-ArcHciResourceBridge"    
    Write-Output "Installing Arc Resource Bridge...."
    Write-Log "Installing Arc Resource Bridge...."
    
    if ([String]::IsNullOrEmpty($workingDir)) {
        $mocConfig = Get-ArcHciMoc
        $workingDir = $mocConfig.workingDir
    }

    $workingDir = "$workingDir\Appliance"

    $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Install-ArcHciResourceBridge" -version $moduleVersion
    try {
        Write-Log "Running az arcappliance prepare hci --config-file ""$workingDir\hci-appliance.yaml"""
        Invoke-ArcHciAzCommand "arcappliance prepare hci --config-file ""$workingDir\hci-appliance.yaml""" -ignoreWarning -logOutput

        Write-Log "Running az arcappliance deploy hci --config-file ""$workingDir\hci-appliance.yaml"" --outfile ""$workingDir\kubeconfig"""
        Write-Output "Arc resource bridge deployment can take 20 minutes or more"
        Invoke-ArcHciAzCommand "arcappliance deploy hci --config-file ""$workingDir\hci-appliance.yaml"" --outfile ""$workingDir\kubeconfig""" -ignoreWarning -logOutput

        Wait-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir

        Write-Output "Arc Resource Bridge Installed Successfully"
        Write-Log "Arc Resource Bridge Installed Successfully"  
        Write-Log "Setting ARB Install State to Succeeded"
        Set-ArcHciConfigValue -name "arbInstallState" -Value "Succeeded"
        Write-Log "Exiting Install-ArcHciResourceBridge" 
    }
    catch {
        Write-Log "Correlation ID: $correlationId. $_"
        Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installarbfailed" -message "Failed to install arc resource bridge - Exception: $_"
        Write-Log "Setting ARB Install State to Failed"
        Set-ArcHciConfigValue -name "arbInstallState" -Value "Failed"
        throw "Correlation ID: $correlationId. $_"
    }
    finally {
        Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName
    }
}

function Remove-ArcHciResourceBridge {
    <#
    .DESCRIPTION
        Remove Arc Resource Bridge
    .PARAMETER subscriptionID
        The Azure subscription GUID
    .PARAMETER resourceGroup
        Name of the Azure resource group
    .PARAMETER resourceName
        Name of the Azure resource
    .PARAMETER workingDir
        Optional parameter. Working Directory Path
    .PARAMETER correlationId
        Optional parameter. Identifier used for telemetry
    .OUTPUTS
        N/A
    .EXAMPLE
        Remove-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir -correlationId $correlationId
    #>


    param (
        [parameter(Mandatory = $true)]
        [GUID] $subscriptionID,

        [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 = $false)]
        [ValidateScript({
                if ([string]::IsNullOrEmpty($_)) {
                    return $true
                }
                if ($_ -match $space) {
                    throw $spaceErrorForFolderFilePath
                }  
                if (-not ($_ | Test-Path)) {
                    throw $fileFolderPathError
                }
                return $true
            })]
        [string] $workingDir,

        [parameter(Mandatory = $false)]
        [string] $correlationId = [System.Guid]::NewGuid().ToString()
    )

    Write-Log "Entered Remove-ArcHciResourceBridge"

    Write-Output "Removing Arc Resource Bridge...."
    Write-Log "Removing Arc Resource Bridge...."   

    if ([String]::IsNullOrEmpty($workingDir)) {
        $mocConfig = Get-ArcHciMoc
        $workingDir = $mocConfig.workingDir
    }

    $workingDir = "$workingDir\Appliance"

    $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Remove-ArcHciResourceBridge" -version $moduleVersion

    try {
        Write-Log "Running az arcappliance delete hci --config-file ""$workingDir\hci-appliance.yaml"" --yes"
        Invoke-ArcHciAzCommand "arcappliance delete hci --config-file ""$workingDir\hci-appliance.yaml"" --yes" -ignoreWarning -logOutput
        Write-Log "Setting ARB Install State to NotInstalled"
        Set-ArcHciConfigValue -name "arbInstallState" -Value "NotInstalled"
        $timer = [Diagnostics.Stopwatch]::StartNew()
        $timeSinceStart = 0

        $arcRBDetails = ""
        while ($timeSinceStart -lt $timeout) {
            Write-Log "Waiting for ""az arcappliance delete hci --config-file $workingDir\hci-appliance.yaml --yes"" to finish..."
            Write-Log "Running Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName"
            $arcRBDetails = Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName
            Write-Log "RB details: $arcRBDetails"
            if ($arcRBDetails.provisioningState -eq "NotInstalled") {
                Write-Output "Arc Resource Bridge Removed Successfully"
                Write-Log "Arc Resource Bridge Removed Successfully"
                Write-Log "Exiting Remove-ArcHciResourceBridge"
                return
            }
            Start-Sleep $sleepDuration

            $timeSinceStart = $($timer.Elapsed.TotalSeconds)
            Write-Log "Time elapsed since start, in seconds: $timeSinceStart"
            Write-Log "Timeout in seconds: $timeout"
        }
        throw ("Timed out deleting appliance. Delete state: $($arcRBDetails.status)")
    }
    catch {
        Write-Log "Correlation ID: $correlationId. $_"
        if ($_.Exception -notmatch "notfound|not found") {
            Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "deletearbfailed" -message "Failed to delete arc resource bridge - Exception: $_"
            throw "Correlation ID: $correlationId. $_"
        }
    }
    finally {
        Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName
    }
}

function Wait-ArcHciResourceBridge {
    <#
    .DESCRIPTION
        Wait For Arc Resource Bridge deployment to finish
    .PARAMETER subscriptionID
        The Azure subscription GUID
    .PARAMETER resourceGroup
        Name of the Azure resource group
    .PARAMETER resourceName
        Name of the Azure resource
    .OUTPUTS
        N/A
    .EXAMPLE
        Wait-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName
    #>


    param
    (
        [parameter(Mandatory = $true)]
        [GUID] $subscriptionID,

        [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 = $false)]
        [ValidateScript({
                if ([string]::IsNullOrEmpty($_)) {
                    return $true
                }
                if ($_ -match $space) {
                    throw $spaceErrorForFolderFilePath
                }  
                if (-not ($_ | Test-Path)) {
                    throw $fileFolderPathError
                }
                return $true
            })]  
        [string]  $workingDir
    )

    if ([String]::IsNullOrEmpty($workingDir)) {
        $mocConfig = Get-ArcHciMoc
        $workingDir = $mocConfig.workingDir
        $workingDir = "$workingDir\Appliance"
    }

    $timer = [Diagnostics.Stopwatch]::StartNew()
    $timeSinceStart = 0

    $arcRBDetails = ""
    while ($timeSinceStart -lt $timeoutDeployARB) {
        Write-Log "Waiting for ""az arcappliance deploy hci --config-file $workingDir\hci-appliance.yaml --outfile $workingDir\kubeconfig"" to finish..."
        Write-Log "Running Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName"
        $arcRBDetails = Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName
        Write-Log "RB details: $arcRBDetails" 
        if ($arcRBDetails.status -eq "Running") {
            Write-Log "Deployed appliance successfully"
            return
        }
        if ($arcRBDetails.status -eq "Failed") {
            throw "Failed to deploy appliance"
        }
        Start-Sleep $sleepDuration

        $timeSinceStart = $($timer.Elapsed.TotalSeconds)
        Write-Log "Time elapsed since start, in seconds: $timeSinceStart"
        Write-Log "Timeout in seconds: $timeoutDeployARB"
    }
    throw ("Timed out deploying appliance. Deploy state: $($arcRBDetails.status)")
}

function Install-ArcHciVMExtension {
    <#
    .DESCRIPTION
        Install VM Extension
    .PARAMETER subscriptionID
        The Azure subscription GUID
    .PARAMETER resourceGroup
        Name of the Azure resource group
    .PARAMETER resourceName
        Name of the Azure resource
    .PARAMETER ssvmExtensionName
        Optional parameter. SSVM Extension Name
    .PARAMETER ssvmExtVersion
        Optional parameter. SSVM Extension Version
    .PARAMETER releaseTrain
        Optional parameter. Release Train
    .PARAMETER shareDiagnosticData
        Optional parameter. Whether to send diagnostic data to Microsoft. Default: "true"
        Diagnostic data is used to help keep the service secure and up to date, troubleshoot problems, and make product improvements.
    .PARAMETER workingDir
        Optional parameter. Working Directory Path
    .PARAMETER correlationId
        Optional parameter. Identifier used for telemetry
    .OUTPUTS
        N/A
    .EXAMPLE
        Install-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -ssvmExtVersion $ssvmExtVersion -releaseTrain $releaseTrain -workingDir $workingDir -correlationId $correlationId
    #>

    param
    (
        [parameter(Mandatory = $true)]
        [GUID] $subscriptionID,

        [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 = $false)] 
        [string] $ssvmExtensionName = "vmss-hci",

        [parameter(Mandatory = $false)] 
        [string] $ssvmExtVersion,

        [parameter(Mandatory = $false)] 
        [string] $releaseTrain = "stable",

        [parameter(Mandatory = $false)]
        [ValidateSet("true", "false")]
        [string] $shareDiagnosticData = "true",

        [parameter(Mandatory = $false)]
        [ValidateScript({
                if ([string]::IsNullOrEmpty($_)) {
                    return $true
                }
                if ($_ -match $space) {
                    throw $spaceErrorForFolderFilePath
                }  
                if (-not ($_ | Test-Path)) {
                    throw $fileFolderPathError
                }
                return $true
            })]
        [string] $workingDir,

        [parameter(Mandatory = $false)]
        [string] $correlationId = [System.Guid]::NewGuid().ToString()
    )
    Write-Log "Entered Install-ArcHciVMExtension"
    if ([String]::IsNullOrEmpty($workingDir)) {
        $mocConfig = Get-ArcHciMoc
        $workingDir = $($mocConfig.workingDir)
    }

    Set-ArcHciConfigValue -name "vmExtensionName" -Value $ssvmExtensionName
    $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Install-ArcHciVMExtension" -version $moduleVersion

    Write-Log "Checking whether Arc resource bridge status is running before installing SSVM extension"
    Wait-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName

    Write-Log "Installing SSVM k8s extension"
    Write-Output "Installing SSVM k8s extension"

    try {
        $K8SExtensionCreateCmd = "k8s-extension create --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup --name $ssvmExtensionName --extension-type Microsoft.AZStackHCI.Operator --scope cluster --release-namespace helm-operator2 --configuration-settings shareDiagnosticData=$shareDiagnosticData Microsoft.CustomLocation.ServiceAccount=$defaultNamespace AgentOperationTimeoutInMinutes=$timeoutForExtensionCreateInMinutes --config-protected-file ""$workingDir\Appliance\hci-config.json"" --release-train $releaseTrain --auto-upgrade $false"
        if (-Not [String]::IsNullOrEmpty($ssvmExtVersion)) {
            $K8SExtensionCreateCmd += " --version $ssvmExtVersion"
        }

        if (Test-IsHCIMachine) {
            $hciClusterId = (Get-AzureStackHci).AzureResourceUri
            $K8SExtensionCreateCmd += " --configuration-settings HCIClusterID=$hciClusterId"
        }
    
        Write-Log "Running az $K8SExtensionCreateCmd"
        Invoke-ArcHciAzCommand $K8SExtensionCreateCmd -ignoreWarning
        $timer = [Diagnostics.Stopwatch]::StartNew()
        $timeSinceStart = 0

        $arcVMExtDetails = ""

        while (($timeSinceStart -lt $timeoutForExtensionCreate)) {
            Write-Log "Waiting for $K8SExtensionCreateCmd to finish..."
            Write-Log "Running Get-ArcHciVMExtension -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName"
            $arcVMExtDetails = Get-ArcHciVMExtension -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName
            Write-Log "Arc VM extension details: $arcVMExtDetails"

            if ($arcVMExtDetails.provisioningState -eq "Succeeded") {
                Write-Log "SSVM k8s extension successfully installed"
                Write-Output "SSVM k8s extension successfully installed"
                Write-Log "Exiting Install-ArcHciVMExtension"
                return
            }
            if ($arcVMExtDetails.provisioningState -eq "Failed") {
                throw "Failed to install SSVM k8s extension"
            }
            Start-Sleep $sleepDuration

            $timeSinceStart = $($timer.Elapsed.TotalSeconds)
            Write-Log "Time elapsed since start, in seconds: $timeSinceStart"
            Write-Log "Timeout in seconds: $timeoutForExtensionCreate "
        }
        throw ("Timed out installing k8s-extension. Installation state: $($arcVMExtDetails.provisioningState)")
    }
    catch {
        Write-Log "Correlation ID: $correlationId. $_"
        Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installvmextfailed" -message "Failed to install ArcVM extension - Exception: $_"
        throw "Correlation ID: $correlationId. $_"
    }
    finally {
        Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName
    }
}

function Uninstall-ArcHciVMExtension {
    <#
    .DESCRIPTION
        Remove VM Extension
    .PARAMETER subscriptionID
        The Azure subscription GUID
    .PARAMETER resourceGroup
        Name of the Azure resource group
    .PARAMETER resourceName
        Name of the Azure resource
    .PARAMETER ssvmExtensionName
        Optional parameter. SSVM Extension Name
    .PARAMETER correlationId
        Optional parameter. Identifier used for telemetry
    .OUTPUTS
        N/A
    .EXAMPLE
        Uninstall-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -correlationId $correlationId
    #>


    param (
        [parameter(Mandatory = $true)]  
        [GUID] $subscriptionID,

        [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 = $false)] 
        [string] $ssvmExtensionName = (Get-ArcHciConfigValue -name "vmExtensionName"),

        [parameter(Mandatory = $false)]
        [string] $correlationId = [System.Guid]::NewGuid().ToString()
    )
    Write-Log "Entered Uninstall-ArcHciVMExtension"

    Write-Log "Deleting ArcHci VM Extension"
    Write-Output "Deleting ArcHci VM Extension"

    $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Uninstall-ArcHciVMExtension" -version $moduleVersion

    try {
        Write-Log "Running az k8s-extension delete --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup --name $ssvmExtensionName --yes"
        Invoke-ArcHciAzCommand "k8s-extension delete --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup --name $ssvmExtensionName --yes" -logOutput

        $timer = [Diagnostics.Stopwatch]::StartNew()
        $timeSinceStart = 0
        $arcVMExtDetails = ""
        while ($timeSinceStart -lt $timeout) {
            Write-Log "Waiting for ""k8s-extension delete --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup --name $ssvmExtensionName --yes"" to finish..."
            Write-Log "Running Get-ArcHciVMExtension -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName"
            $arcVMExtDetails = Get-ArcHciVMExtension -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName
            Write-Log "Arc VM extension details: $arcVMExtDetails"
            if ($arcVMExtDetails.provisioningState -eq "NotInstalled") {
                Write-Output "ArcHci VM Extension Deleted Successfully"
                Write-Log "ArcHci VM Extension Deleted Successfully"
                Write-Log "Exiting Uninstall-ArcHciVMExtension"
                return
            }
            Start-Sleep $sleepDuration

            $timeSinceStart = $($timer.Elapsed.TotalSeconds)
            Write-Log "Time elapsed since start, in seconds: $timeSinceStart"
            Write-Log "Timeout in seconds: $timeout"
        }
        throw ("Timed out deleting ArcHci VM Extension. Delete state: $($arcVMExtDetails.provisioningState)")
    }
    catch {
        Write-Log "Correlation ID: $correlationId. $_"
        # If resource doesn't exist the flow shouldnt terminate with exception nothing to delete
        if ($_.Exception -notmatch $nothingToDelete) {
            Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "deletevmextfailed" -message "Failed to delete ArcVM extension - Exception: $_"
            throw "Correlation ID: $correlationId. $_"
        }
    }
    finally {
        Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName
    }
}
function New-ArcHciCustomLocation {
    <#
    .DESCRIPTION
        New CustomLocation
    .PARAMETER subscriptionID
        The Azure subscription GUID
    .PARAMETER location
        Azure location
    .PARAMETER resourceGroup
        Name of the Azure resource group
    .PARAMETER resourceName
        Name of the Azure resource
    .PARAMETER extensionName
        Optional parameter. Name of the extension
    .PARAMETER customLocationName
        Optional parameter. Name of the Custom Location
    .PARAMETER correlationId
        Optional parameter. Identifier used for telemetry
    .OUTPUTS
        N/A
    .EXAMPLE
        New-ArcHciCustomLocation -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -extensionName $extensionName -customLocationName $customLocationName -correlationId $correlationId
    #>


    param
    (
        [parameter(Mandatory = $true)]  
        [GUID] $subscriptionID,

        [parameter(Mandatory = $true)]  
        [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 = $false)] 
        [string] $extensionName,

        [parameter(Mandatory = $false)] 
        [string] $customLocationName = "myResourceBridge-cl",

        [parameter(Mandatory = $false)]
        [string] $correlationId = [System.Guid]::NewGuid().ToString()
    )

    Write-Log "Entered New-ArcHciCustomLocation"
    $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "New-ArcHciCustomLocation" -version $moduleVersion

    try {
        Wait-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName

        $sub = $subscriptionID
        $rg = $resourceGroup
        $rn = $resourceName
        $en = $extensionName
        
        Write-Log "Creating Custom Location"
        Write-Output "Creating Custom Location"
        $applianceDetails = Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName

        $CustomLocCreateCmd = "customlocation create --resource-group $resourceGroup --name $customLocationName --location $location --namespace $defaultNamespace --host-resource-id $($applianceDetails.id) --cluster-extension-ids ""/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.ResourceConnector/appliances/$rn/providers/Microsoft.KubernetesConfiguration/extensions/$en"""
        Set-ArcHciConfigValue -name "customLocationName" -Value $customLocationName

        Write-Log "Running az $CustomLocCreateCmd"
        Invoke-ArcHciAzCommand $CustomLocCreateCmd -ignoreWarning
        $timer = [Diagnostics.Stopwatch]::StartNew()
        $timeSinceStart = 0

        $arcCusLocDetails = ""
        while (($timeSinceStart -lt $timeout)) {
            Write-Log "Waiting for $CustomLocCreateCmd to finish..."
            Write-Log "Running Get-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName"
            $arcCusLocDetails = Get-ArcHciCustomLocation -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName
            $arcCusLocDetailsString = $arcCusLocDetails | Format-Table -AutoSize | Out-String
            Write-Log "Archci custom location details: $arcCusLocDetailsString"

            if ($arcCusLocDetails.provisioningState -eq "Succeeded") {
                Write-Log "Custom location successfully created"
                Write-Output "Custom location successfully created"
                Write-Log "Exiting New-ArcHciCustomLocation"
                return
            }
            if ($arcCusLocDetails.provisioningState -eq "Failed") {
                throw "Failed to create custom location"
            }
            Start-Sleep $sleepDuration

            $timeSinceStart = $($timer.Elapsed.TotalSeconds)
            Write-Log "Time elapsed since start, in seconds: $timeSinceStart"
            Write-Log "Timeout in seconds: $timeout"
        }
        throw ("Timed out creating custom location. Installation state: $($arcCusLocDetails.provisioningState)")
    }
    catch {
        Write-Log "Correlation ID: $correlationId. $_"
        Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "createcustomlocationfailed" -message "Failed to create CustomLocation - Exception: $_"
        throw "Correlation ID: $correlationId. $_"
    }
    finally {
        Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName
    }
}

function Remove-ArcHciCustomLocation {
    <#
    .DESCRIPTION
        Remove Custom Location
    .PARAMETER subscriptionID
        The Azure subscription GUID
    .PARAMETER resourceGroup
        Name of the Azure resource group
    .PARAMETER resourceName
        Name of the Azure resource
    .PARAMETER customLocationName
        Optional parameter. Name of the Custom Location
    .PARAMETER correlationId
        Optional parameter. Identifier used for telemetry
    .OUTPUTS
        N/A
    .EXAMPLE
        Remove-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName -correlationId $correlationId
    #>


    param (
        [parameter(Mandatory = $true)]  
        [GUID] $subscriptionID,

        [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 = $false)] 
        [string] $customLocationName = (Get-ArcHciConfigValue -name "customLocationName"),

        [parameter(Mandatory = $false)]
        [string] $correlationId = [System.Guid]::NewGuid().ToString()
    )

    Write-Log "Entered Remove-ArcHciCustomLocation"
    $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Remove-ArcHciCustomLocation" -version $moduleVersion

    try {
        $applianceDetails = Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName
        $arcCusLocDetails = Get-ArcHciCustomLocation -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName
        Write-Log "Checking if custom location host resource id match with arc appliance id before deleting it"
        if(-Not [String]::IsNullOrEmpty($arcCusLocDetails.hostResourceId) -and $applianceDetails.id -ne $arcCusLocDetails.hostResourceId){
            Write-Log "ERROR: Custom location $customLocationName host resource id does not match with arc appliance id. Hence, uninstallation is terminated."
            throw "Custom location $customLocationName host resource id does not match with arc appliance id. Hence, uninstallation is terminated."
        }

        Write-Log "Deleting custom location"
        Write-Output "Deleting custom location"

        Write-Log "Running az customlocation delete --resource-group $resourceGroup --name $customLocationName --yes"
        Invoke-ArcHciAzCommand "customlocation delete --resource-group $resourceGroup --name $customLocationName --yes"

        $timer = [Diagnostics.Stopwatch]::StartNew()
        $timeSinceStart = 0
        $arcCusLocDetails = ""
        while ($timeSinceStart -lt $timeout) {
            Write-Log "Waiting for ""customlocation delete --resource-group $resourceGroup --name $customLocationName --yes"" to finish..."
            Write-Log "Running Get-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName"
            $arcCusLocDetails = Get-ArcHciCustomLocation -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName
            $arcCusLocDetailsString = $arcCusLocDetails | Format-Table -AutoSize | Out-String
            Write-Log "Archci custom location details: $arcCusLocDetailsString"
            if ($arcCusLocDetails.provisioningState -eq "NotInstalled") {
                Write-Output "Custom location deleted Successfully"
                Write-Log "Custom location deleted Successfully"
                Write-Log "Exiting Remove-ArcHciCustomLocation"
                return
            }
            Start-Sleep $sleepDuration
        
            $timeSinceStart = $($timer.Elapsed.TotalSeconds)
            Write-Log "Time elapsed since start, in seconds: $timeSinceStart"
            Write-Log "Timeout in seconds: $timeout"
        }
        throw ("Timed out deleting custom location. Delete state: $($arcCusLocDetails.provisioningState)")
    }
    catch {
        Write-Log "Correlation ID: $correlationId. $_"
        if ($_.Exception -notmatch "notfound|not found") {
            Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "deletecustomlocationfailed" -message "Failed to delete CustomLocation - Exception: $_"
            throw "Correlation ID: $correlationId. $_"
        }
    }
    finally {
        Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName
    }
}

function Install-ArcHciHybridAKSExtension {
    <#
    .DESCRIPTION
        Install Hybrid AKS extension
    .PARAMETER subscriptionID
        The Azure subscription GUID
    .PARAMETER resourceGroup
        Name of the Azure resource group
    .PARAMETER resourceName
        Name of the Azure resource
    .PARAMETER hydaksExtensionName
        Optional parameter. Name of the Hybrid AKS Extension
    .PARAMETER hydaksExtVersion
        Optional parameter. Version of the Hybrid AKS Extension
    .PARAMETER releaseTrain
        Optional parameter. Release Train
    .PARAMETER enableOfflineDownload
        Optional parameter. Flag to set image download offline
    .PARAMETER correlationId
        Optional parameter. Identifier used for telemetry
    .OUTPUTS
        N/A
    .EXAMPLE
        Install-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName -hydaksExtVersion $hydaksExtVersion -releaseTrain $releaseTrain -enableOfflineDownload -correlationId $correlationId
    #>

    param
    (
        [parameter(Mandatory = $true)]  
        [GUID] $subscriptionID,

        [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 = $false)] 
        [string] $hydaksExtensionName = "hybridaks-hci",

        [parameter(Mandatory = $false)] 
        [string] $hydaksExtVersion,

        [parameter(Mandatory = $false)] 
        [string] $releaseTrain = "stable",

        [Parameter(Mandatory = $false)]
        [switch] $enableOfflineDownload,

        [parameter(Mandatory = $false)]
        [string] $correlationId = [System.Guid]::NewGuid().ToString()
    )

    Write-Log "Installing HybridAKS extension"
    Write-Output "Installing HybridAKS extension"

    Set-ArcHciConfigValue -name "aksExtensionName" -Value $hydaksExtensionName

    $K8SExtensionCreateCmd = "k8s-extension create --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup --name $hydaksExtensionName --extension-type Microsoft.HybridAKSOperator --configuration-settings AgentOperationTimeoutInMinutes=$timeoutForExtensionCreateInMinutes --config Microsoft.CustomLocation.ServiceAccount=default --release-train $releaseTrain"
    if($enableOfflineDownload.IsPresent){
        $K8SExtensionCreateCmd += " --config offline-download=true"
    }
    if (-Not [String]::IsNullOrEmpty($hydaksExtVersion)) {
        $K8SExtensionCreateCmd += " --version $hydaksExtVersion"
        $K8SExtensionCreateCmd += " --auto-upgrade $false"
    }else{
        $K8SExtensionCreateCmd += " --auto-upgrade $true"
    }

    if(Test-IsHCIMachine){
        $hciClusterId = (Get-AzureStackHci).AzureResourceUri
        $K8SExtensionCreateCmd += " --configuration-settings HCIClusterID=$hciClusterId"
    }

    try {
        $isHyperThreadingEnabled = Get-MocHyperThreadingEnabled -ErrorAction SilentlyContinue
        $K8SExtensionCreateCmd += " --configuration-settings HTEnabled=$isHyperThreadingEnabled"
    }
    catch {
        Write-Log "Failed to get the hyperthreading status from MOC with the error below, skip this step."
        Write-Log $_
    }

    $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Install-ArcHciHybridAKSExtension" -version $moduleVersion
    
    try {
        Write-Log "Running $K8SExtensionCreateCmd"
        Invoke-ArcHciAzCommand $K8SExtensionCreateCmd -ignoreWarning
        $timer = [Diagnostics.Stopwatch]::StartNew()
        $timeSinceStart = 0

        $hybridAksInstallState = ""
        while (($timeSinceStart -lt $timeoutForExtensionCreate)) {
            Write-Log "Waiting for $K8SExtensionCreateCmd to finish..."
            Write-Log "Running Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName"
            $arcHciHybridAKSDetails = Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName 
            Write-Log "Hybrid AKS Details: $arcHciHybridAKSDetails"
            if ($arcHciHybridAKSDetails.provisioningState -eq "Succeeded") {
                Write-Log "Hybrid AKS k8s extension successfully installed!"
                Write-Output "Hybrid AKS k8s extension successfully installed!"
                return
            }
            if ($arcHciHybridAKSDetails.provisioningState -eq "Failed") {
                throw "Failed to install Hybrid AKS k8s extension"
            }
            Start-Sleep $sleepDuration

            $timeSinceStart = $($timer.Elapsed.TotalSeconds)
            Write-Log "Time elapsed since start, in seconds: $timeSinceStart"
            Write-Log "Timeout in seconds: $timeoutForExtensionCreate"
        }
        throw ("Timed out installing k8s-extension. Installation state: $hybridAksInstallState")
    }
    catch {
        Write-Log "Correlation ID: $correlationId. $_"
        Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installhybridaksfailed" -message "Failed to install hybridaks extension - Exception: $_"
        throw "Correlation ID: $correlationId. $_"
    }
    finally {
        Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName
    }
}

function Uninstall-ArcHciHybridAKS {
    <#
    .DESCRIPTION
        Remove Hybrid AKS
    .PARAMETER subscriptionID
        Optional parameter. The Azure subscription GUID
    .PARAMETER resourceGroup
        Optional parameter. Name of the Azure resource group
    .PARAMETER resourceName
        Optional parameter. Name of the Azure resource
    .PARAMETER hydaksExtensionName
        Optional parameter. Name of the Hybrid AKS Extension
    .PARAMETER correlationId
        Optional parameter. Identifier used for telemetry
    .OUTPUTS
        N/A
    .EXAMPLE
        Uninstall-ArcHciHybridAKS -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName -correlationId $correlationId
    #>

    param (
        [parameter(Mandatory = $false)]           
        [GUID] $subscriptionID = [System.Guid]::empty,

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

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

        [parameter(Mandatory = $false)] 
        [string] $hydaksExtensionName = (Get-ArcHciConfigValue -name "aksExtensionName"),

        [parameter(Mandatory = $false)]
        [string] $correlationId = [System.Guid]::NewGuid().ToString()
    )

    Write-Log "Entered Uninstall-ArcHciHybridAKS"
    $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Uninstall-ArcHciHybridAKS" -version $moduleVersion

    try {
        Write-Log "Running Get-ArcHciParameters -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location"
        $arcHciParameters = Get-ArcHciParameters -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location
        # Convert the hashtable to a table format and then to a string
        $arcHciParametersString = $arcHciParameters | Format-Table -AutoSize | Out-String 
        Write-Log "Archci parameters: $arcHciParametersString"
        $subscriptionID = $arcHciParameters.SubscriptionID
        $resourceGroup = $arcHciParameters.ResourceGroup
        $resourceName = $arcHciParameters.ResourceName

        Write-Log "Running Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName"
        $arcHciHybridAKSDetails = Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName 
        Write-Log "Hybrid AKS Details: $arcHciHybridAKSDetails"
            
        if ($arcHciHybridAKSDetails.provisioningState -eq "Succeeded") {
            Write-Log "Running Uninstall-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName"
            Uninstall-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName
        }else{
            Set-ArcHciTelemetryEventSkipped -eventConfig $eventConfig
            return $arcHciHybridAKSDetails
        }
    }
    catch {
        Write-Log "Correlation ID: $correlationId. $_"
        if ($_.Exception -notmatch $nothingToDelete) {
            Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "deletehybridaksfailed" -message "Failed to remove hybridaks extension - Exception $_"
            throw "Correlation ID: $correlationId. $_"
        }
    }
    finally {
        Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName
    }
    Write-Log "Exiting Uninstall-ArcHciHybridAKS"    
}

function Uninstall-ArcHciHybridAKSExtension {
    <#
    .DESCRIPTION
        Remove Hybrid AKS extension
    .PARAMETER subscriptionID
        The Azure subscription GUID
    .PARAMETER resourceGroup
        Name of the Azure resource group
    .PARAMETER resourceName
        Name of the Azure resource
    .PARAMETER hydaksExtensionName
        Optional parameter. Name of the Hybrid AKS Extension
    .PARAMETER correlationId
        Optional parameter. Identifier used for telemetry
    .OUTPUTS
        N/A
    .EXAMPLE
        Uninstall-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName -correlationId $correlationId
    #>


    param (
        [parameter(Mandatory = $true)]  
        [GUID] $subscriptionID,

        [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 = $false)] 
        [string] $hydaksExtensionName = (Get-ArcHciConfigValue -name "aksExtensionName"),

        [parameter(Mandatory = $false)]
        [string] $correlationId = [System.Guid]::NewGuid().ToString()
    )

    Write-Log "Entered Uninstall-ArcHciHybridAKSExtension"

    Write-Log "Removing HybridAKS Extension"
    Write-Output "Removing HybridAKS Extension"

    $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Uninstall-ArcHciHybridAKSExtension" -version $moduleVersion
    try {
        Write-Log "Running az k8s-extension delete --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup --name $hydaksExtensionName --yes"
        Invoke-ArcHciAzCommand "k8s-extension delete --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup --name $hydaksExtensionName --yes"

        $timer = [Diagnostics.Stopwatch]::StartNew()
        $timeSinceStart = 0
        $arcHciHybridAKSDetails = ""
        while ($timeSinceStart -lt $timeout) {
            Write-Log "Waiting for ""k8s-extension delete --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup --name $hydaksExtensionName --yes"" to finish..."
            Write-Log "Running Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName"
            $arcHciHybridAKSDetails = Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName 
            Write-Log "Hybrid AKS Details: $arcHciHybridAKSDetails"
            if ($arcHciHybridAKSDetails.provisioningState -eq "NotInstalled") {
                Write-Output "HybridAKS Extension Removed Successfully"
                Write-Log "HybridAKS Extension Removed Successfully"
                Write-Log "Exiting Uninstall-ArcHciHybridAKSExtension"
                return
            }
            Start-Sleep $sleepDuration

            $timeSinceStart = $($timer.Elapsed.TotalSeconds)
            Write-Log "Time elapsed since start, in seconds: $timeSinceStart"
            Write-Log "Timeout in seconds: $timeout"
        }
        throw ("Timed out deleting HybridAKS VM Extension. Delete state: $($arcHciHybridAKSDetails.provisioningState)")
    }
    catch {
        Write-Log "Correlation ID: $correlationId. $_"
        if ($_.Exception -notmatch $nothingToDelete) {
            Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "deletehybridaksfailed" -message "Failed to remove hybridaks extension - Exception $_"
            throw "Correlation ID: $correlationId. $_"
        }
    }
    finally {
        Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName
    }
}

function Update-ArcHciCustomLocation {
    <#
    .DESCRIPTION
        Update CustomLocation
    .PARAMETER subscriptionID
        The Azure subscription GUID
    .PARAMETER location
        Azure location
    .PARAMETER resourceGroup
        Name of the Azure resource group
    .PARAMETER resourceName
        Name of the Azure resource
    .PARAMETER customLocationName
        Optional parameter. Name of the Custom Location
    .PARAMETER correlationId
        Optional parameter. Identifier used for telemetry
    .OUTPUTS
        N/A
    .EXAMPLE
        Update-ArcHciCustomLocation -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName -correlationId $correlationId
    #>

    param
    (
        [parameter(Mandatory = $true)]  
        [GUID] $subscriptionID,

        [parameter(Mandatory = $true)]  
        [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 = $false)] 
        [string] $customLocationName = (Get-ArcHciConfigValue -name "customLocationName"),

        [parameter(Mandatory = $false)]
        [string] $correlationId = [System.Guid]::NewGuid().ToString()

    )

    Write-Log "Entered Update-ArcHciCustomLocation"
    $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Update-ArcHciCustomLocation" -version $moduleVersion
    
    $sub = $subscriptionID
    $rg = $resourceGroup
    $rn = $resourceName
        
    try {
        Write-Log "Updating Custom Location $customLocationName"
        Write-Output "Updating Custom Location $customLocationName"

        $ssvmExtDetails = Get-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName
        $hybridAKSExtDetails = Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName
        $applianceDetails = Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName

        Write-Log "Running customlocation update --resource-group $resourceGroup --name $customLocationName --location $location --cluster-extension-ids $($hybridAKSExtDetails.id) $($ssvmExtDetails.id) --namespace $defaultNamespace --host-resource-id $($applianceDetails.id)"
        Invoke-ArcHciAzCommand "customlocation update --resource-group $resourceGroup --name $customLocationName --location $location --cluster-extension-ids $($hybridAKSExtDetails.id) $($ssvmExtDetails.id) --namespace $defaultNamespace --host-resource-id $($applianceDetails.id)" -ignoreWarning

        Write-Log "$customLocationName successfully updated!"
        Write-Output "$customLocationName successfully updated!"

        Write-Log "Exiting Update-ArcHciCustomLocation"
    }
    catch {
        Write-Log "Correlation ID: $correlationId. $_"
        Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "updatecustomlocationfailed" -message "Failed to update CustomLocation - Exception $_"
        throw "Correlation ID: $correlationId. $_"
    }
    finally {
        Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName
    }
}


function Get-ArcHciHybridAKSKubernetesVersion {
    <#
    .DESCRIPTION
        List the supported Kubernetes versions in the specified custom location.
    .PARAMETER subscriptionID
        The Azure subscription GUID
    .PARAMETER resourceGroup
        Name of the Azure resource group
    .PARAMETER customLocationName
        Name of the Custom Location
    .OUTPUTS
        N/A
    .EXAMPLE
        Get-ArcHciHybridAKSKubernetesVersion -subscriptionID $subscriptionID -resourceGroup $resourceGroup -customLocationName $customLocationName
    #>

    param(

        [parameter(Mandatory = $true)]  
        [GUID] $subscriptionID,

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

        [parameter(Mandatory = $false)] 
        [string] $customLocationName = (Get-ArcHciConfigValue -name "customLocationName")
    )
    Write-Log "Entered Get-ArcHciHybridAKSKubernetesVersion"

    try {
        $customLocationId = "/subscriptions/$subscriptionId/resourcegroups/$resourceGroup/providers/microsoft.extendedlocation/customlocations/$customLocationName"
        Write-Log "Running az aksarc get-versions --custom-location $customLocationId"
        $res = (Invoke-ArcHciAzCommand -arguments " aksarc get-versions --custom-location ""$customLocationId""" -ignoreWarning)
    }
    catch {
        Write-Log $_
        if ($_.Exception -notmatch "notfound|not found") {
            throw $_
        }
    }

    if($null -eq $res -or $res.count -eq 0 -or $res.length -eq 0) {
        Write-Log "Not able to get K8s versions with custom location id $customLocationId"
    }

    Write-Log "Exiting Get-ArcHciHybridAKSKubernetesVersion"
    return $res
}

function Get-ArcHciHybridAKSVMSize {
    <#
    .DESCRIPTION
        List the supported vm sizes in the specified custom location.
    .PARAMETER subscriptionID
        The Azure subscription GUID
    .PARAMETER resourceGroup
        Name of the Azure resource group
    .PARAMETER customLocationName
        Name of the Custom Location
    .OUTPUTS
        N/A
    .EXAMPLE
        Get-ArcHciHybridAKSVMSize -subscriptionID $subscriptionID -resourceGroup $resourceGroup -customLocationName $customLocationName
    #>

    param(

        [parameter(Mandatory = $true)]  
        [GUID] $subscriptionID,

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

        [parameter(Mandatory = $false)] 
        [string] $customLocationName = (Get-ArcHciConfigValue -name "customLocationName")
    )
    Write-Log "Entered Get-ArcHciHybridAKSVMSize"

    try {
        $customLocationId = "/subscriptions/$subscriptionId/resourcegroups/$resourceGroup/providers/microsoft.extendedlocation/customlocations/$customLocationName"
        Write-Log "Running az aksarc vmsize list --custom-location $customLocationId"
        $res = (Invoke-ArcHciAzCommand -arguments " aksarc vmsize list --custom-location ""$customLocationId""" -ignoreWarning)
    }
    catch {
        Write-Log $_
        if ($_.Exception -notmatch "notfound|not found") {
            throw $_
        }
    }

    if($null -eq $res -or $res.count -eq 0 -or $res.length -eq 0) {
        Write-Log "Not able to get K8s versions with custom location id $customLocationId"
    }

    Write-Log "Exiting Get-ArcHciHybridAKSVMSize"
    return $res
}

function Get-ArcHciCustomLocation {
    <#
    .DESCRIPTION
        Get CustomLocation Details
    .PARAMETER subscriptionID
        The Azure subscription GUID
    .PARAMETER resourceGroup
        Name of the Azure resource group
    .PARAMETER customLocationName
        Optional parameter. Name of the Custom Location
    .OUTPUTS
        N/A
    .EXAMPLE
        Get-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -customLocationName $customLocationName
    #>

    param(
        [parameter(Mandatory = $true)]  
        [string] $subscriptionID,

        [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 = $false)] 
        [string] $customLocationName = (Get-ArcHciConfigValue -name "customLocationName")
    )
    Write-Log "Entered Get-ArcHciCustomLocation"

    if (-not([String]::IsNullOrWhiteSpace($customLocationName))) {
        try {
            Write-Log "Running az customlocation show --subscription $subscriptionID --resource-group $resourceGroup --name $customLocationName"
            $res = (Invoke-ArcHciAzCommand -arguments " customlocation show --subscription $subscriptionID --resource-group $resourceGroup --name $customLocationName" -ignoreWarning) | ConvertFrom-Json
        }
        catch {
            Write-Log $_
            if ($_.Exception -notmatch "notfound|not found") {
                throw $_
            }
        }
    }

    if($null -eq $res -or $res.count -eq 0 -or $res.length -eq 0) {
        $res = [pscustomobject]@{
            'provisioningState' = 'NotInstalled';
        }
        Write-Log "Custom location with the name $customLocationName not found"
    }

    Write-Log "Exiting Get-ArcHciCustomLocation"
    return $res
}

function Get-ArcHciHybridAKS {
    <#
    .DESCRIPTION
        Get Hybrid AKS details
    .PARAMETER subscriptionID
        Optional parameter. The Azure subscription GUID
    .PARAMETER resourceGroup
        Optional parameter. Name of the Azure resource group
    .PARAMETER resourceName
        Optional parameter. Name of the Azure resource
    .PARAMETER hydaksExtensionName
        Optional parameter. Name of the Hybrid AKS extension
    .OUTPUTS
        Hashtable containing Hybrid AKS details
    .EXAMPLE
        Get-ArcHciHybridAKS -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName
    #>

    param
    (
        [parameter(Mandatory = $false)]  
        [GUID] $subscriptionID = [System.Guid]::empty,

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

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

        [parameter(Mandatory = $false)] 
        [string] $hydaksExtensionName = (Get-ArcHciConfigValue -name "aksExtensionName") 
    )

    Write-Log "Entered Get-ArcHciHybridAKS"

    Test-ArcHciAzRequirements > $null
    
    $arcHciHybridAKSDetails = @{}

    Write-Log "Running Get-ArcHciParameters -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location"
    $arcHciParameters = Get-ArcHciParameters -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location
    # Convert the hashtable to a table format and then to a string
    $arcHciParametersString = $arcHciParameters | Format-Table -AutoSize | Out-String 
    Write-Log "Archci parameters: $arcHciParametersString"
    $subscriptionID = $arcHciParameters.SubscriptionID
    $resourceGroup = $arcHciParameters.ResourceGroup
    $resourceName = $arcHciParameters.ResourceName

    Register-ResourceProviders -subscriptionID $subscriptionID > $null

    Write-Log "Running Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName"
    $arcHciHybridAKSDetails = Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName 
    Write-Log "Hybrid AKS Details: $arcHciHybridAKSDetails"
    
    Write-Log "Exiting Get-ArcHciHybridAKS"
    return $arcHciHybridAKSDetails
}

function Get-ArcHciVMExtension {
    <#
    .DESCRIPTION
        Get ArcHci VM extension details
    .PARAMETER subscriptionID
        The Azure subscription GUID
    .PARAMETER resourceGroup
        Name of the Azure resource group
    .PARAMETER resourceName
        Name of the Azure resource
    .PARAMETER ssvmExtensionName
        Optional parameter. Name of the SSVM extension
    .OUTPUTS
        Hashtable containing SSVM extension details
    .EXAMPLE
        Get-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName
    #>

    param
    (
        [parameter(Mandatory = $true)]  
        [GUID] $subscriptionID,

        [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 = $false)] 
        [string] $ssvmExtensionName = (Get-ArcHciConfigValue -name "vmExtensionName")
    )
    Write-Log "Entered Get-ArcHciVMExtension"

    if (-not([String]::IsNullOrWhiteSpace($ssvmExtensionName))) {
        try{
            Write-Log "Running az k8s-extension show --subscription $subscriptionID --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup --name $ssvmExtensionName"
            $res = (Invoke-ArcHciAzCommand -arguments "k8s-extension show --subscription $subscriptionID --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup --name $ssvmExtensionName" -ignoreWarning) | ConvertFrom-Json
        }
        catch{
            Write-Log $_
            if($_ -match "The refresh token has expired or is invalid"){
                throw $_
            }
        }
    }

    if ($null -eq $res -or $res.count -eq 0 -or $res.length -eq 0) {
        $res = [pscustomobject]@{
            'provisioningState' = 'NotInstalled';
        }
        Write-Log "SSVM extension with the name $ssvmExtensionName is not installed"
    } 
    Write-Log "Exiting Get-ArcHciVMExtension"
    return $res
}


function Get-ArcHciHybridAKSExtension {
    <#
    .DESCRIPTION
        Get Hybrid AKS extension details
    .PARAMETER subscriptionID
        The Azure subscription GUID
    .PARAMETER resourceGroup
        Name of the Azure resource group
    .PARAMETER resourceName
        Name of the Azure resource
    .PARAMETER hydaksExtensionName
        Optional parameter. Name of the Hybrid AKS extension
    .OUTPUTS
        Hashtable containing Hybrid AKS extension details
    .EXAMPLE
        Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName
    #>

    param
    (
        [parameter(Mandatory = $true)]  
        [GUID]   $subscriptionID,

        [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 = $false)] 
        [string] $hydaksExtensionName = (Get-ArcHciConfigValue -name "aksExtensionName")
    )

    Write-Log "Entered Get-ArcHciHybridAKSExtension"

    if (-not([String]::IsNullOrWhiteSpace($hydaksExtensionName))) {
        try{
            Write-Log "Running az k8s-extension show --subscription $subscriptionID --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup --name $hydaksExtensionName"
            $res = (Invoke-ArcHciAzCommand -arguments "k8s-extension show --subscription $subscriptionID --cluster-type appliances --cluster-name $resourceName --resource-group $resourceGroup --name $hydaksExtensionName" -ignoreWarning) | ConvertFrom-Json
        }
        catch{
            Write-Log $_
            if($_ -match "The refresh token has expired or is invalid"){
                throw $_
            }
        }
    }

    if ($null -eq $res -or $res.count -eq 0 -or $res.length -eq 0) {
        $res = [pscustomobject]@{
            'provisioningState' = 'NotInstalled';
        }
        Write-Log "Hybrid AKS extension with name $hydaksExtensionName is not installed"
    } 

    Write-Log "Exiting Get-ArcHciHybridAKSExtension"
    return $res
}

function Get-ArcHciResourceBridge {
    <#
    .DESCRIPTION
        Get Arc Resource Bridge details
    .PARAMETER subscriptionID
        The Azure subscription GUID
    .PARAMETER resourceGroup
        Name of the Azure resource group
    .PARAMETER resourceName
        Name of the Azure resource
    .OUTPUTS
        Hashtable containing Arc Resource Bridge details
    .EXAMPLE
        Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName
    #>

    param
    (
        [parameter(Mandatory = $true)] 
        [GUID]   $subscriptionID,

        [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
    )
    Write-Log "Entered Get-ArcHciResourceBridge"
 
    try {
        Write-Log "Running az arcappliance show --resource-group $resourceGroup --subscription $subscriptionID --name $resourceName"
        $res = (Invoke-ArcHciAzCommand -arguments "arcappliance show --resource-group $resourceGroup --subscription $subscriptionID --name $resourceName" -ignoreWarning) | ConvertFrom-Json
    }
    catch {
        Write-Log $_
        if ($_.Exception -notmatch "notfound|not found") {
            throw $_
        }
    }

    if($null -eq $res -or $res.count -eq 0 -or $res.length -eq 0){
        $arbInstallState = Get-ArcHciConfigValue -name "arbInstallState"
        if($arbInstallState -eq "Failed"){
            $res = [pscustomobject]@{
                'provisioningState' = "Failed";
            }
        }else{
            $res = [pscustomobject]@{
                'provisioningState' = 'NotInstalled';
            }
        }
        Write-Log "Arc Resource Bridge with the name $resourceName is not installed"
    }

    Write-Log "Exiting Get-ArcHciResourceBridge"
    return $res
}

function Get-ArcHciMoc {
    <#
    .DESCRIPTION
        Get Moc details
    .PARAMETER correlationId
        Optional parameter. Identifier used for telemetry
    .OUTPUTS
        Hashtable containing Moc details
    .EXAMPLE
        Get-ArcHciMoc
    #>


    Param (
        [parameter(Mandatory = $false)]
        [string] $correlationId = [System.Guid]::NewGuid().ToString()
    )

    Write-Log "Entered Get-ArcHciMoc"
    $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Get-ArcHciMoc" -version $moduleVersion
    
    try {
        Write-Log "Running Get-MocConfig"
        $mocConfig = Get-MocConfig
    }
    catch {
        Write-Log "Correlation ID: $correlationId. $_"
        $mocConfig = [pscustomobject]@{
            'installState' = 'NotInstalled';
        }
        Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "getmocfailed" -message "Failed To Retrieve MOC config - Exception $_"
    }
    finally {
        Publish-ArcHciTelemetryEvent -eventConfig $eventConfig
    }
    Write-Log "Exiting Get-ArcHciMoc"
    return $mocConfig
}

function Get-ArcHciMgmt {
    <#
    .DESCRIPTION
        Get ArcHciMgmt details
    .PARAMETER subscriptionID
        Optional parameter. The Azure subscription GUID
    .PARAMETER resourceGroup
        Optional parameter. Name of the Azure resource group
    .PARAMETER resourceName
        Optional parameter. Name of the Azure resource
    .PARAMETER ssvmExtensionName
        Optional parameter. Name of the SSVM extension
    .PARAMETER hydaksExtensionName
        Optional parameter. Name of the Hybrid AKS extension
    .PARAMETER customLocationName
        Optional parameter. Name of the Custom Location
    .PARAMETER correlationId
        Optional parameter. Identifier used for telemetry
    .OUTPUTS
        Hashtable containing Mgmt details
    .EXAMPLE
        Get-ArcHciMgmt -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -customLocationName $customLocationName
    #>

    param
    (
        [parameter(Mandatory = $false)]           
        [GUID] $subscriptionID = [System.Guid]::empty,

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

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

        [parameter(Mandatory = $false)] 
        [string] $ssvmExtensionName = (Get-ArcHciConfigValue -name "vmExtensionName") ,

        [parameter(Mandatory = $false)] 
        [string] $hydaksExtensionName = (Get-ArcHciConfigValue -name "aksExtensionName") ,

        [parameter(Mandatory = $false)]
        [string] $customLocationName = (Get-ArcHciConfigValue -name "customLocationName") ,

        [parameter(Mandatory = $false)]
        [string] $correlationId = [System.Guid]::NewGuid().ToString()
    )

    Write-Log "Entered Get-ArcHciMgmt"
    $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Get-ArcHciMgmt" -version $moduleVersion
    
    try {
        Test-ArcHciAzRequirements > $null

        $arcHciMgmtDetails = @{}
        Write-Log "Running Get-ArcHciParameters -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location"
        $arcHciParameters = Get-ArcHciParameters -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location
        # Convert the hashtable to a table format and then to a string
        $arcHciParametersString = $arcHciParameters | Format-Table -AutoSize | Out-String    
        Write-Log "Archci parameters: $arcHciParametersString"
        $subscriptionID = $arcHciParameters.SubscriptionID
        $resourceGroup = $arcHciParameters.ResourceGroup
        $resourceName = $arcHciParameters.ResourceName
        $location = $arcHciParameters.Location
    
        Register-ResourceProviders -subscriptionID $subscriptionID > $null

        Write-Log "Running Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName"
        $arcRBDetails = Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName
        Write-Log "RB details: $arcRBDetails" 
        $arcHciMgmtDetails.Add("ResourceBridge",$arcRBDetails) 
        Write-Log "Running Get-ArcHciVMExtension -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName"
        $arcVMExtDetails = Get-ArcHciVMExtension -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName
        Write-Log "Arc VM extension details: $arcVMExtDetails"
        $arcHciMgmtDetails.Add("VMExtension",$arcVMExtDetails)
    
        Write-Log "Running Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName"
        $HybridAksExtDetails = Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName 
        Write-Log "Hybrid AKS Details: $HybridAksExtDetails"
        $arcHciMgmtDetails.Add("HybridaksExtension",$HybridAksExtDetails)
    
        Write-Log "Running Get-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName"
        $arcCusLocDetails = Get-ArcHciCustomLocation -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName
        $arcCusLocDetailsString = $arcCusLocDetails | Format-Table -AutoSize | Out-String
        Write-Log "Archci custom location details: $arcCusLocDetailsString"
        $arcHciMgmtDetails.Add("CustomLocation",$arcCusLocDetails)
    
        Write-Log "Exiting Get-ArcHciMgmt"
        return $arcHciMgmtDetails
    }
    catch {
        Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "getmgmtfailed" -message "Get ArcHci Mgmt Failed - Exception $_"
        throw "Correlation ID: $correlationId. $_"
    }
    finally {
        Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $arcCusLocDetails.name
    }
}

function Install-ArcHciMgmt {
    <#
    .DESCRIPTION
        Installs ArcHciMgmt(Arc Resource Bridge, SSVM Extension, and Custom Location)
    .PARAMETER subscriptionID
        Optional parameter. The Azure subscription GUID
    .PARAMETER location
        Optional parameter. Azure location
    .PARAMETER resourceGroup
        Optional parameter. Name of the Azure resource group in which the Arc HCI appliance will be created
    .PARAMETER resourceName
        Optional parameter. Name of the Azure resource
    .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
        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 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 ssvmExtensionName
        Optional parameter. Name of the SSVM extension
    .PARAMETER ssvmExtVersion
        Optional parameter. Version of the SSVM extension
    .PARAMETER ssvmReleaseTrain
        Optional parameter. Name of the ssvm release train
    .PARAMETER ssvmShareDiagnosticData
        Optional parameter. Whether to send diagnostic data to Microsoft for the ssvm extension. Default: "true"
        Diagnostic data is used to help keep the service secure and up to date, troubleshoot problems, and make product improvements.
    .PARAMETER hybridaksExtensionName
        Optional parameter. Name of the Hybrid AKS extension
    .PARAMETER hybridaksExtVersion
        Optional parameter. Version of the Hybrid AKS extension
    .PARAMETER hybridaksReleaseTrain
        Optional parameter. Name of the hybridaks release train
    .PARAMETER customLocationName
        Optional parameter. Name of the Custom Location
    .PARAMETER workingDir
        Optional parameter. Working Directory Path
    .PARAMETER controlPlaneIP
        IP Address to be used for the Arc Appliance control plane
    .PARAMETER rbIpStart
        The starting ip address to use for Arc RB.
    .PARAMETER rbIpEnd
        Optional parameter. The ending ip address to use for Arc RB.
    .PARAMETER gateway
        Optional parameter. The gateway to use when using static IP
    .PARAMETER ipaddressprefix
        Optional parameter. The address prefix to use for static IP assignment
    .PARAMETER dnsservers
        Optional parameter. The dnsservers to use when using static IP
    .PARAMETER vlanID
        Optional parameter. The VLAN ID for the vnet
    .PARAMETER arcHciProxyConfig
        Optional parameter. Proxy Config
    .PARAMETER enableOfflineDownload
        Optional parameter. Flag to set image download offline
    .PARAMETER skip_prechecks
        Optional parameter. Flag to skip prechecks
    .PARAMETER skip_cleanup
        Optional parameter. Flag to avoid cleaning up installed components
    .PARAMETER correlationId
        Optional parameter. Identifier used for telemetry
    .PARAMETER isolateImageDir
        Optional parameter. Flag to isolate image dir
    .OUTPUTS
        N/A
    .EXAMPLE
        Install-ArcHciMgmt -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -ssvmExtensionName $ssvmExtensionName -ssvmExtVersion $ssvmExtVersion -releaseTrain $releaseTrain -customLocationName $customLocationName -workingDir $workingDir -controlPlaneIP $controlPlaneIP -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd -gateway $gateway -dnsservers $dnsservers -ipaddressPrefix $ipaddressPrefix -vLanID $vlanID -arcHciProxyConfig $arcHciProxyConfig -enableOfflineDownload -skip_prechecks -skip_cleanup -isolateImageDir $isolateImageDir -enableArcVMExtension -enableHybridAKS
    #>
 
    param
    (
        [parameter(Mandatory = $false)]   
        [GUID] $subscriptionID = [System.Guid]::empty,

        [parameter(Mandatory = $false)]   
        [string] $location,

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

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

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

        # vswitchName can accept any characters
        [parameter(Mandatory = $true)]  
        [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 $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)]  
        [string] $ssvmExtensionName = "vmss-hci",

        [parameter(Mandatory = $false)]  
        [string] $ssvmExtVersion, 

        [parameter(Mandatory = $false)]  
        [string] $ssvmReleaseTrain = "stable",

        [parameter(Mandatory = $false)]
        [ValidateSet("true", "false")]
        [string] $ssvmShareDiagnosticData = "true",

        [parameter(Mandatory = $false)]  
        [string] $hybridaksExtensionName = "hybridaks-hci",

        [parameter(Mandatory = $false)]  
        [string] $hybridaksExtVersion = "", 

        [parameter(Mandatory = $false)]  
        [string] $hybridaksReleaseTrain = "stable",  

        [parameter(Mandatory = $false)]  
        [string] $customLocationName = "myResourceBridge-cl",

        [Parameter(Mandatory = $true)]
        [ValidateScript({
                $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) {
                    $parameter = "rbIpStart"
                    throw "$ipv4ValidationError" -f $parameter, $_
                }
                return $true
            })]
        [string] $rbIpStart,

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

        [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)]  
        [string] $ipAddressPrefix,

        [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)]
        [ValidateRange(0, 4094)] 
        [int] $vlanID,

        [parameter(Mandatory = $false)]
        [ValidateScript({
                if ([string]::IsNullOrEmpty($_)) {
                    return $true
                }
                if ($_ -match $space) {
                    throw $spaceErrorForFolderFilePath
                }  
                if (-not ($_ | Test-Path)) {
                    throw $fileFolderPathError
                }
                return $true
            })]  
        [string] $workingDir,

        [Parameter(Mandatory = $false)]
        $arcHciProxyConfig,

        [Parameter(Mandatory = $false)]
        [switch] $enableOfflineDownload,

        [Parameter(Mandatory = $false)]  
        [switch] $skip_prechecks,

        [Parameter(Mandatory = $false)] 
        [switch] $skip_cleanup,

        [parameter(Mandatory = $false)]
        [string] $correlationId = [System.Guid]::NewGuid().ToString(),

        [parameter(Mandatory = $false)]
        [switch] $isolateImageDir,

        [parameter(Mandatory = $false)]
        [switch] $enableHybridAKS = [switch]::Present,
        
        [parameter(Mandatory = $false)]
        [switch] $enableArcVMExtension = [switch]::Present
    )
    $global:ProgressPreference = 'SilentlyContinue'
    Write-Log "Entered Install-ArcHciMgmt"
    $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Install-ArcHciMgmt" -version $moduleVersion

    try {
        Write-Log "Running Get-ArcHciMoc"
        $mocConfig = Get-ArcHciMoc -correlationId $correlationId
        # Convert the hashtable to a table format and then to a string
        $mocConfigString = $mocConfig | Format-Table -AutoSize | Out-String
        Write-Log "Mocconfig: $mocConfigString"

        if ($mocConfig.installState -eq 7) {
            Write-Log "Running Get-ArcHciParameters -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location"
            $arcHciParameters = Get-ArcHciParameters -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location
            # Convert the hashtable to a table format and then to a string
            $arcHciParametersString = $arcHciParameters | Format-Table -AutoSize | Out-String                
            Write-Log "Archci parameters: $arcHciParametersString"
            $subscriptionID = $arcHciParameters.SubscriptionID
            $resourceGroup = $arcHciParameters.ResourceGroup
            $resourceName = $arcHciParameters.ResourceName
            $location = $arcHciParameters.Location

            Write-Log "Running Install-ArcHciPrerequisites -subscriptionID $subscriptionID"
            try {
                Install-ArcHciPrerequisites -subscriptionID $subscriptionID
            }
            catch {
                Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmgmtprereqfailed" -message "Failed to install ArcHci prerequisites - Exception $_"
                throw "Correlation ID: $correlationId. $_"
            }

            # New approach is to always remove any existing arcmgmt setup and do fresh installation every time Install-ArcHciMgmt is called
            Write-Output "Remove any existing arcmgmt setup"
            Write-Log "Remove any existing arcmgmt setup"
            Uninstall-ArcHciMgmt

            if (-not $skip_prechecks.IsPresent) {
                Write-Log "Running Test-ArcHciEnableArcMgmt -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -workingDir $workingDir -controlPlaneIP $controlPlaneIP -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd -gateway $gateway -ipaddressPrefix $ipaddressPrefix -dnsservers $dnsservers -vlanID $vlanID -arcHciProxyConfig $arcHciProxyConfig"
                $mgmtTestRes = Test-ArcHciEnableArcMgmt -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -workingDir $workingDir -controlPlaneIP $controlPlaneIP -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd -gateway $gateway -ipAddressPrefix $ipAddressPrefix -dnsServers $dnsServers -vlanID $vlanID -arcHciProxyConfig $arcHciProxyConfig -correlationId $correlationId
                Write-Log "ArcHciMgmt test result: $($mgmtTestRes[$mgmtTestRes.length - 1].TestResult)"
                Write-Log "$($mgmtTestRes[$mgmtTestRes.length - 1].Details)"
            }
            else {
                Write-Log "Skipping Mgmt prechecks"
                Write-Output "Skipping Mgmt prechecks"
            }

            if ($skip_prechecks.IsPresent -or $mgmtTestRes[$mgmtTestRes.length - 1].TestResult -eq "Succeeded") {
                Write-Log "Running Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName"
                $arcRBDetails = Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName
                Write-Log "RB details: $arcRBDetails" 



                try {
                    if ($arcRBDetails.provisioningState -eq "NotInstalled") {
                        Write-Log "Running New-ArcHciApplianceConfigs -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -workingDir $workingDir -controlPlaneIP $controlPlaneIP -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd -gateway $gateway -ipaddressPrefix $ipaddressPrefix -dnsservers $dnsservers -vlanID $vlanID -arcHciProxyConfig $arcHciProxyConfig"
                        New-ArcHciApplianceConfigs -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -workingDir $workingDir -controlPlaneIP $controlPlaneIP -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd -gateway $gateway -ipAddressPrefix $ipAddressPrefix -dnsServers $dnsServers -vlanID $vlanID -arcHciProxyConfig $arcHciProxyConfig
                        Write-Log "Running Install-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir -correlationId $correlationId"
                        Install-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir -correlationId $correlationId
                        #Temporary logic to mark container as isolated. We will get rid of this once permanent fix is available from ARB team
                        if ($isolateImageDir.IsPresent) {
                            Write-Log "Running Set-MocContainer -name MocStorageContainer -location MocLocation -isolated"
                            Set-MocContainer -name "MocStorageContainer" -location "MocLocation" -isolated
                        }
                    }
                    else {
                        Write-Output "Arc resource bridge is already installed. Kindly run Get-ArcHciMgmt to know the status."
                        Write-Log "Arc resource bridge is already installed. Kindly run Get-ArcHciMgmt to know the status."
                    }    
                }
                catch {
                    Write-Log "Correlation ID: $correlationId. $_"
                    Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installarbfailed" -message "Failed to install arc resource bridge - Exception $_"
                    if (-not $skip_cleanup.IsPresent) {
                        try {
                            Write-Log "Collecting Logs..."
                            Write-Output "Collecting Logs..."
                            Get-ArcHciLogs
                        }
                        catch {
                            Write-Log $_
                        }
                        Write-Output "Rolling back Arcmgmt installation due to error $_"
                        Write-Log "Rolling back Arcmgmt installation due to error $_"
                        Write-Log "Running Remove-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir -correlationId $correlationId"
                        Remove-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir -correlationId $correlationId
                        Write-Log "Running Remove-ArcHciConfigFiles"
                        Remove-ArcHciConfigFiles
                    }
                    throw "Correlation ID: $correlationId. $_" 
                }

                # SSVM installation is turned off on non-hci machine
                if (Test-IsHCIMachine) {
                    try {
                        Write-Log "Running Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName"
                        $arcRBDetails = Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName

                        Write-Log  "RB details: $arcRBDetails"  
                        if ($arcRBDetails.provisioningState -eq "Succeeded") {
                            if($enableArcVMExtension.IsPresent){
                            Write-Log "Running Get-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName"
                            $arcVMExtDetails = Get-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName
                            Write-Log "Arc VM extension details: $arcVMExtDetails"

                            if ($arcVMExtDetails.provisioningState -eq "NotInstalled") {
                                Write-Log "Running Install-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -ssvmExtVersion $ssvmExtVersion -releaseTrain $ssvmReleaseTrain -shareDiagnosticData $ssvmShareDiagnosticData -workingDir $workingDir -correlationId $correlationId"
                                Install-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -ssvmExtVersion $ssvmExtVersion -releaseTrain $ssvmReleaseTrain -shareDiagnosticData $ssvmShareDiagnosticData -workingDir $workingDir -correlationId $correlationId
                            }
                            else {
                                Write-Output "SSVM extension is already installed. Kindly run Get-ArcHciMgmt to know the status."
                                Write-Log "SSVM extension is already installed. Kindly run Get-ArcHciMgmt to know the status."
                            }
                        }
                        }
                        else {
                            Write-Output "Arc resource bridge is not installed. Please install Arc resource bridge first."
                            Write-Log "Arc resource bridge is not installed. Please install Arc resource bridge first."
                        }
                    }
                    catch {
                        Write-Log "Correlation ID: $correlationId. $_" 
                        Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installvmextfailed" -message "Failed to install ArcVM extension - Exception $_"
                        if (-not $skip_cleanup.IsPresent) {
                            Write-Output "Rolling back Arcmgmt installation due to error $_"
                            Write-Log "Rolling back Arcmgmt installation due to error $_"
                            Write-Log "Running Uninstall-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -correlationId $correlationId"
                            Uninstall-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -correlationId $correlationId
                            Write-Log "Running Remove-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir -correlationId $correlationId"
                            Remove-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir -correlationId $correlationId
                            Write-Log "Running Remove-ArcHciConfigFiles"
                            Remove-ArcHciConfigFiles
                        }
                        throw "Correlation ID: $correlationId. $_"
                    } 
                    try {
                        Write-Log "Running Get-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName"
                        $arcVMExtDetails = Get-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName

                        Write-Log "Arc VM extension details: $arcVMExtDetails"

                        Write-Log "Running Get-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName"
                        $arcCusLocDetails = Get-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName
                        $arcCusLocDetailsString = $arcCusLocDetails | Format-Table -AutoSize | Out-String
                        Write-Log "Archci custom location details: $arcCusLocDetailsString"

                        if ($arcCusLocDetails.provisioningState -eq "Succeeded") {
                            Write-Log "Running Update-ArcHciCustomLocation -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location -customLocationName $customLocationName -correlationId $correlationId"
                            Update-ArcHciCustomLocation -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location -customLocationName $customLocationName -correlationId $correlationId
                        }
                        elseif ($arcCusLocDetails.provisioningState -eq "NotInstalled") {
                            Write-Log "Running New-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location -extensionName $ssvmExtensionName -customLocationName $customLocationName -correlationId $correlationId"
                            New-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location -extensionName $ssvmExtensionName -customLocationName $customLocationName -correlationId $correlationId
                        }
                        else {
                            Write-Output "Custom location with the name $customLocationName already exist. Kindly run Get-ArcHciMgmt to know the status."
                            Write-Log "Custom location with the name $customLocationName already exist. Kindly run Get-ArcHciMgmt to know the status."
                            $arcVMEnabledMsg = $null
                        }
                    }
                    catch {
                        Write-Log "Correlation ID: $correlationId. $_"
                        Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "createorupdatecustomlocationfailed" -message "Create or update custom location failed during Mgmt installation - Exception $_"
                        if (-not $skip_cleanup.IsPresent) {
                            Write-Output "Rolling back Arcmgmt installation due to error $_"
                            Write-Log "Rolling back Arcmgmt installation due to error $_"
                            Write-Log "Running Remove-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName -correlationId $correlationId"
                            Remove-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName -correlationId $correlationId
                            Write-Log "Running Uninstall-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -correlationId $correlationId"
                            Uninstall-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -correlationId $correlationId
                            Write-Log "Running Remove-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir -correlationId $correlationId"
                            Remove-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir -correlationId $correlationId
                            Write-Log "Running Remove-ArcHciConfigFiles"
                            Remove-ArcHciConfigFiles
                        }
                        throw "Correlation ID: $correlationId. $_"
                    }

                    try {
                         if ($enableHybridAKS.IsPresent){
                            Write-Log "Running Install-ArcHciHybridAKS -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hybridaksExtensionName -hybridaksExtVersion $hybridaksExtVersion -releaseTrain $hybridaksReleaseTrain -customLocationName $customLocationName -correlationId $correlationId -enableOfflineDownload:$enableOfflineDownload.IsPresent -skip_cleanup:$($skip_cleanup.IsPresent)"
                            Install-ArcHciHybridAKS -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hybridaksExtensionName -hybridaksExtVersion $hybridaksExtVersion -releaseTrain $hybridaksReleaseTrain -customLocationName $customLocationName -correlationId $correlationId -enableOfflineDownload:$enableOfflineDownload.IsPresent -skip_cleanup:$skip_cleanup.IsPresent
                        }
                    }
                    catch {
                        Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installhybridaksfailed" -message "Install hybrid AKS failed during Mgmt installation - Exception $_"
                        if (-not $skip_cleanup.IsPresent) {
                            Write-Log "Running Remove-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName -correlationId $correlationId"
                            Remove-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName -correlationId $correlationId
                            Write-Log "Running Uninstall-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -correlationId $correlationId"
                            Uninstall-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -correlationId $correlationId
                            Write-Log "Running Remove-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir -correlationId $correlationId"
                            Remove-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir -correlationId $correlationId
                            Write-Log "Running Remove-ArcHciConfigFiles"
                            Remove-ArcHciConfigFiles
                        }
                        throw "Correlation ID: $correlationId. $_"
                    }
                }
                else {
                    Write-Log "SSVM and hybridaks installation is turned off on non-hci machine. Hence SSVM, hybridaks extension and custom location will not be installed/created."
                    Write-Output "SSVM and hybridaks installation is turned off on non-hci machine. Hence SSVM, hybridaks extension and custom location will not be installed/created."
                }
                if ($null -ne $arcVMEnabledMsg) {
                    Write-Log $arcVMEnabledMsg
                    Write-Output $arcVMEnabledMsg 
                }
                Write-Log "Exiting Install-ArcHciMgmt"
            }
            else {
                $err = "Correlation ID: $correlationId. Terminating ArcHciMgmt Installation. At least one of the prechecks required for ArcHciMgmt installation failed."
                Write-Log $err
                Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmgmtprereqfailed" -message "ArcHciMgmt precheck failed: $err"
                throw $err
            }
        }
        else {
            Write-Log "Correlation ID: $correlationId. Terminating ArcHciMgmt Installation. Moc is not installed, please install Moc first"
            Set-ArcHciTelemetryEventSkipped -eventConfig $eventConfig
            throw "Correlation ID: $correlationId. Terminating ArcHciMgmt Installation. Moc is not installed, please install Moc first"
        }
        $global:ProgressPreference = 'Continue'
    }
    finally {
        Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName
    }
}

function Install-ArcHciHybridAKS {
    <#
    .DESCRIPTION
        Installs ArcHci Hybrid AKS
    .PARAMETER subscriptionID
        Optional parameter. The Azure subscription GUID
    .PARAMETER location
        Optional parameter. Azure location
    .PARAMETER resourceGroup
        Optional parameter. Name of the Azure resource group
    .PARAMETER resourceName
        Optional parameter. Name of the Azure resource
    .PARAMETER hydaksExtensionName
        Optional parameter. Name of the Hybrid AKS extension
    .PARAMETER hybridaksExtVersion
        Optional parameter. Version of the Hybrid AKS extension
    .PARAMETER releaseTrain
        Optional parameter. Name of the release train
    .PARAMETER customLocationName
        Optional parameter. Name of the Custom Location
    .PARAMETER skip_cleanup
        Optional parameter. Flag to avoid cleaning up installed components
    .PARAMETER correlationId
        Optional parameter. Identifier used for telemetry
    .PARAMETER enableOfflineDownload
        Optional parameter. Flag to set image download offline
    .OUTPUTS
        N/A
    .EXAMPLE
        Install-ArcHciHybridAKS -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName -hybridaksExtVersion $hybridaksExtVersion -releaseTrain $releaseTrain -customLocationName $customLocationName -skip_cleanup -enableOfflineDownload
    #>

    param
    (
        [parameter(Mandatory = $false)]   
        [GUID] $subscriptionID = [System.Guid]::empty,

        [parameter(Mandatory = $false)]  
        [string] $location,

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

        [parameter(Mandatory = $false)]
        [ValidateScript({
                if ([string]::IsNullOrEmpty($_)) {
                    return $true
                }
                if ($_ -notmatch $regexPatternRFC1123) {
                    $parameter = "ResourceName"  
                    throw $regexPatternRFC1123Error -f $_, $parameter
                }
                return $true
            })] 
        [string] $resourceName,
        
        [parameter(Mandatory = $false)]  
        [string] $hydaksExtensionName = "hybridaks-hci",

        [parameter(Mandatory = $false)]  
        [string] $hybridaksExtVersion = "", 

        [parameter(Mandatory = $false)]  
        [string] $releaseTrain = "stable",   

        [parameter(Mandatory = $false)] 
        [string] $customLocationName = (Invoke-Command -ScriptBlock {
            $clName = $(Get-ArcHciConfigValue -name "customLocationName")
            if (-not [string]::IsNullOrEmpty($clName)) {
                $clName
            }
            else {
                "myResourceBridge-cl"
            }}),

        [Parameter(Mandatory = $false)] 
        [switch] $skip_cleanup,

        [parameter(Mandatory = $false)]
        [string] $correlationId = [System.Guid]::NewGuid().ToString(),

        [Parameter(Mandatory = $false)]
        [switch] $enableOfflineDownload
    )
    $global:ProgressPreference = 'SilentlyContinue'
    Write-Log "Entered Install-ArcHciHybridAKS"
    $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Install-ArcHciHybridAKS" -version $moduleVersion

    try {
        Write-Log "Running Get-ArcHciParameters -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location"
        $arcHciParameters = Get-ArcHciParameters -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location
        # Convert the hashtable to a table format and then to a string
        $arcHciParametersString = $arcHciParameters | Format-Table -AutoSize | Out-String    
        Write-Log "Archci parameters: $arcHciParametersString"
        $subscriptionID = $arcHciParameters.SubscriptionID
        $resourceGroup = $arcHciParameters.ResourceGroup
        $resourceName = $arcHciParameters.ResourceName
        $location = $arcHciParameters.Location

        Write-Log "Running Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName"
        $arcHciHybridAKSDetails = Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName
        Write-Log "Hybridaks extension details: $arcHciHybridAKSDetails"

        if($arcHciHybridAKSDetails.provisioningState -eq "NotInstalled"){
            Write-Log "Running Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName"
            $arcRBDetails = Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName

            Write-Log  "RB details: $arcRBDetails"

            if ($arcRBDetails.provisioningState -eq "Succeeded") {

                try {
                    Write-Log "Running Install-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName -hydaksExtVersion $hybridaksExtVersion -releaseTrain $releaseTrain -enableOfflineDownload:$enableOfflineDownload.IsPresent"
                    Install-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName -hydaksExtVersion $hybridaksExtVersion -releaseTrain $releaseTrain -enableOfflineDownload:$enableOfflineDownload.IsPresent
                }
                catch {
                    Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installhybridaksfailed" -message "Failed to create hybridaks extension - Exception $_"
                    Write-Log "Correlation ID: $correlationId. $_"
                    if (-not $skip_cleanup.IsPresent) {
                        Write-Log "Running Uninstall-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName"
                        Uninstall-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName
                    }
                    throw "Correlation ID: $correlationId. $_"
                }

                try {
                    Write-Log "Running Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName"    
                    $arcHybridAksExtDetails = Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName

                    Write-Log "Hybrid AkS details: $arcHybridAksExtDetails"

                    if ($arcHybridAksExtDetails.provisioningState -eq "Succeeded") {
                        $hydaksExtensionName = $arcHybridAksExtDetails.name

                        Write-Log "Running Get-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName"
                        $arcCusLocDetails = Get-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName
                        $queryK8sVersionAndSizes = $false
                        $arcCusLocDetailsString = $arcCusLocDetails | Format-Table -AutoSize | Out-String
                        Write-Log " Archci custom location details: $arcCusLocDetailsString"

                        if ($arcCusLocDetails.provisioningState -eq "Succeeded") {
                            $customLocationName = $arcCusLocDetails.name

                            Write-Log "Running Update-ArcHciCustomLocation -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location -customLocationName $customLocationName"
                            Update-ArcHciCustomLocation -subscription $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location -customLocationName $customLocationName -correlationId $correlationId
                            $queryK8sVersionAndSizes = $true
                        }
                        elseif ($arcCusLocDetails.provisioningState -eq "NotInstalled") {
                            Write-Log "Running New-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location -extensionName $hydaksExtensionName -customLocationName $customLocationName"
                            New-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location -extensionName $hydaksExtensionName -customLocationName $customLocationName -correlationId $correlationId
                            $queryK8sVersionAndSizes = $true
                        }
                        else {
                            Write-Output "Custom location with the name $customLocationName couldn't be updated. Kindly run Get-ArcHciMgmt to know the status."
                            Write-Log "Custom location with the name $customLocationName couldn't be updated. Kindly run Get-ArcHciMgmt to know the status."
                        }
                        
                        # Make another call to get the latest custom location details.
                        # Relook at the code changes when the feature is resolved https://dev.azure.com/msazure/One/_workitems/edit/24617280
                        if ($queryK8sVersionAndSizes)
                        {
                            $null = Retry -action {Get-ArcHciHybridAKSKubernetesVersion -subscriptionID $subscriptionID -resourceGroup $resourceGroup -customLocationName $customLocationName}
                            $null = Retry -action {Get-ArcHciHybridAKSVMSize -subscriptionID $subscriptionID -resourceGroup $resourceGroup -customLocationName $customLocationName}
                        }
                    }
                    else {
                        Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installhybridaksfailed" -message "Install HybridAks K8s extension did not return success result - Provisioning State: $($arcHybridAksExtDetails.provisioningState)"
                    }
                }
                catch {
                    Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "createorupdatecustomlocationfailed" -message "Failed to create or update CustomLocation as part of hybrid AKS install - Exception $_"
                    Write-Log "Correlation ID: $correlationId. $_"
                    if (-not $skip_cleanup.IsPresent) {
                        Write-Output "Rolling back Arcmgmt installation due to error $_"
                        Write-Log "Rolling back Arcmgmt installation due to error $_"
                        Write-Log "Running Uninstall-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName"
                        Uninstall-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName
                    }
                    throw "Correlation ID: $correlationId. $_"
                }
            }
            else{
                Set-ArcHciTelemetryEventSkipped -eventConfig $eventConfig
                Write-Log "Arc Resource Bridge is not installed. Please install Arc Resource Bridge first."
                throw "Arc Resource Bridge is not installed. Please install Arc Resource Bridge first."
            }
        }
        else {
            Write-Output "Hybridaks extension is already installed. Kindly run Get-ArcHciMgmt to know the status."
            Write-Log "Hybridaks extension is already installed. Kindly run Get-ArcHciMgmt to know the status."
        }
    }
    finally {
        Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName
    }
    Write-Log "Exiting Install-ArcHciHybridAKS"
    $global:ProgressPreference = 'Continue'
}

function Uninstall-ArcHciMgmt {
    <#
    .DESCRIPTION
        Uninstall ArcHciMgmt
    .PARAMETER subscriptionID
        Optional parameter. The Azure subscription GUID
    .PARAMETER resourceGroup
        Optional parameter. Name of the Azure resource group
    .PARAMETER resourceName
        Optional parameter. Name of the Azure resource
    .PARAMETER ssvmExtensionName
        Optional parameter. Name of the SSVM extension
    .PARAMETER hydaksExtensionName
        Optional parameter. Name of the Hybrid AKS Extension
    .PARAMETER customLocationName
        Optional parameter. Name of the Custom Location
    .PARAMETER workingDir
        Optional parameter. Working Directory Path
    .PARAMETER correlationId
        Optional parameter. Identifier used for telemetry
    .OUTPUTS
        N/A
    .EXAMPLE
        Uninstall-ArcHciMgmt -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -customLocationName $customLocationName -workingDir $workingDir
    #>

    param (
        [parameter(Mandatory = $false)]  
        [GUID] $subscriptionID = [System.Guid]::empty,

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

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

        [parameter(Mandatory = $false)]  
        [string]  $ssvmExtensionName = (Get-ArcHciConfigValue -name "vmExtensionName"),   

        [parameter(Mandatory = $false)]  
        [string]  $hydaksExtensionName = (Get-ArcHciConfigValue -name "aksExtensionName"),   

        [parameter(Mandatory = $false)]  
        [string]  $customLocationName = (Get-ArcHciConfigValue -name "customLocationName"),

        [parameter(Mandatory = $false)]
        [ValidateScript({
                if ([string]::IsNullOrEmpty($_)) {
                    return $true
                }
                if ($_ -match $space) {
                    throw $spaceErrorForFolderFilePath
                }  
                if (-not ($_ | Test-Path)) {
                    throw $fileFolderPathError
                }
                return $true
            })]  
        [string] $workingDir,
        
        [parameter(Mandatory = $false)]
        [string] $correlationId = [System.Guid]::NewGuid().ToString()
    )
    $global:ProgressPreference = 'SilentlyContinue'
    Write-Log "Entered Uninstall-ArcHciMgmt"
    $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Uninstall-ArcHciMgmt" -version $moduleVersion

    try {
        Write-Log "Running Get-ArcHciParameters -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location"
        $arcHciParameters = Get-ArcHciParameters -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location
        # Convert the hashtable to a table format and then to a string
        $arcHciParametersString = $arcHciParameters | Format-Table -AutoSize | Out-String 
        Write-Log "Archci parameters: $arcHciParametersString"
        $subscriptionID = $arcHciParameters.SubscriptionID
        $resourceGroup = $arcHciParameters.ResourceGroup
        $resourceName = $arcHciParameters.ResourceName

        Write-Log "Running Get-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName"
        try {
            $arcCusLocDetails = Get-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName
            $arcCusLocDetailsString = $arcCusLocDetails | Format-Table -AutoSize | Out-String
            Write-Log " Archci custom location details: $arcCusLocDetailsString"
    
            if ($arcCusLocDetails.provisioningState -ne 'NotInstalled') {
    
                $customLocationName = $arcCusLocDetails.name
    
                Write-Log "Running Remove-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName"
                Remove-ArcHciCustomLocation -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName -correlationId $correlationId
            }
        }        
        catch {
            Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "deletecustomlocationfailed" -message "Failed to delete CustomLocation - Exception $_"
            throw "Correlation ID: $correlationId. $_"
        }

        Write-Log "Running Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName"
        try {
            $arcHybridAksExtDetails = Get-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName

            Write-Log "Hybrid AKS details: $arcHybridAksExtDetails"
    
            if($arcHybridAksExtDetails.provisioningState -ne 'NotInstalled'){
                $hydaksExtensionName = $arcHybridAksExtDetails.name            
                Write-Log "Running Uninstall-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName"
                Uninstall-ArcHciHybridAKSExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -hydaksExtensionName $hydaksExtensionName
            }
        }
        catch {
            Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "deletehybridaksfailed" -message "Failed to delete hybrid AKS - Exception $_"
            throw "Correlation ID: $correlationId. $_"
        }

        Write-Log "Running Get-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName"
        try {
            $arcVMExtDetails = Get-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName

            Write-Log "Archci VM extension details: $arcVMExtDetails"
    
            if ($arcVMExtDetails.provisioningState -ne 'NotInstalled') {
    
                $ssvmExtensionName = $arcVMExtDetails.name
    
                Write-Log "Running Uninstall-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -correlationId $correlationId"
                Uninstall-ArcHciVMExtension -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -ssvmExtensionName $ssvmExtensionName -correlationId $correlationId
            }
        }
        catch {
            Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "deletevmextfailed" -message "Failed to delete VM extension - Exception $_"
            throw "Correlation ID: $correlationId. $_"
        }

        Write-Log "Running Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName"
        try {
            $arcRBDetails = Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName
            Write-Log "RB details: $arcRBDetails"
    
            if ($arcRBDetails.provisioningState -ne 'NotInstalled') {
                Write-Log "Running Remove-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir"
                Remove-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -workingDir $workingDir
    
                Write-Log "Running Remove-ArcHciConfigFiles"
                Remove-ArcHciConfigFiles 
            }
        }
        catch {
            Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "deletearbfailed" -message "Failed to delete Azure resource bridge - Exception $_"
            throw "Correlation ID: $correlationId. $_"
        }

        Write-Log "Exiting Uninstall-ArcHciMgmt" 
        $global:ProgressPreference = 'Continue'
    }
    finally {
        Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName
    }
}

function Get-ArcHciParameters {
    <#
    .DESCRIPTION
        Get ArcHciParameters like subscriptionID, location, resourceGroup, resourceName
    .PARAMETER subscriptionID
        Optional parameter. The Azure subscription GUID
    .PARAMETER location
        Optional parameter. Azure location
    .PARAMETER resourceGroup
        Optional parameter. Name of the Azure resource group
    .PARAMETER resourceName
        Optional parameter. Name of the Azure resource
    .PARAMETER rbIpStart
        Optional parameter. The starting ip address to use for Arc RB.
    .PARAMETER rbIpEnd
        Optional parameter. The ending ip address to use for Arc RB.
    .OUTPUTS
        Hashtable containing parameters like subscriptionID, location, resourceGroup, resourceName
    .EXAMPLE
        Get-ArcHciParameters -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName
    #>

    param (    
        [parameter(Mandatory = $false)]   
        [GUID] $subscriptionID = [System.Guid]::empty,

        [parameter(Mandatory = $false)]  
        [string] $location,

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

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

        [parameter(Mandatory = $false)]
        [ValidateScript({
                if ([string]::IsNullOrEmpty($_)) {
                    return $true
                }
                $response = Test-IPV4Address -ip $_
                if (!$response) {
                    $parameter = "rbIpStart"
                    throw "$ipv4ValidationError" -f $parameter, $_
                }
                return $true
            })]
        [string] $rbIpStart,
    
        [parameter(Mandatory = $false)]
        [ValidateScript({
                if ([string]::IsNullOrEmpty($_)) {
                    return $true
                }
                $response = Test-IPV4Address -ip $_
                if (!$response) {
                    $parameter = "rbIpEnd"
                    throw "$ipv4ValidationError" -f $parameter, $_
                }
                return $true
            })]  
        [string] $rbIpEnd
    )
    Write-Log "Entered Get-ArcHciParameters"
    $arcHciParameters = @{}
    if (Test-IsHCIMachine) {
        try {
            $azureStackHciConfig = Get-AzureStackHci
        }
        catch {
            Write-Log "Azure Stack HCI cluster was not detected"
        }
    }

    $mocConfig = Get-ArcHciMoc
    $workingDir = $($mocConfig.workingDir)
    $workingDir = "$workingDir\Appliance"

    $fileExist = Test-Path -Path "$workingDir\hci-resource.yaml" -PathType leaf
    if ($fileExist) {
        $filecontent = Get-Content -Path "$workingDir\hci-resource.yaml"
        
        if ([String]::IsNullOrEmpty($subscriptionID) -or $subscriptionID -eq "00000000-0000-0000-0000-000000000000") {
            $subscriptionID = ($filecontent | Where-Object { $_ -like "*subscription:*" }).Split(':')[1].TrimStart()
        }

        if ([String]::IsNullOrEmpty($resourceGroup)) {
            $resourceGroup = ($filecontent | Where-Object { $_ -like "*resource_group:*" }).Split(':')[1].TrimStart()
        }

        if ([String]::IsNullOrEmpty($resourceName)) {
            $resourceName = ($filecontent | Where-Object { $_ -like "*name:*" }).Split(':')[1].TrimStart()
        }
    }
    elseif ($null -ne $azureStackHciConfig) {
        $uri = $azureStackHciConfig.AzureResourceUri
        $splitUriString = $uri -split "/"

        if ([String]::IsNullOrEmpty($subscriptionID) -or $subscriptionID -eq "00000000-0000-0000-0000-000000000000") {
            $subscriptionID = $splitUriString[2]
        }

        if ([String]::IsNullOrEmpty($resourceGroup)) {
            $resourceGroup = $splitUriString[4]
        }

        if ([String]::IsNullOrEmpty($resourceName)) {
            $resourceName = $azureStackHciConfig.AzureResourceName + "-arcbridge"
        }
    }

    $arcHciParameters.Add("SubscriptionID", $subscriptionID)
    $arcHciParameters.Add("ResourceGroup", $resourceGroup)
    $arcHciParameters.Add("ResourceName", $resourceName)

    if (-not[String]::IsNullOrEmpty($resourceName) -and [String]::IsNullOrEmpty($location)) {
        try{
            $location = Invoke-ArcHciAzCommand "group show --name $resourceGroup --query ""location"" -o tsv"
        }catch{
            Write-Log $_
        }
    }
    $arcHciParameters.Add("Location", $location)

    $k8sNodeIpPoolEnd = ""
    $k8sNodeIpPoolStart = ""

    if (-not [string]::IsNullOrEmpty($rbIpEnd)){
        $k8sNodeIpPoolEnd = $rbIpEnd

        $to_bytes = [IPAddress]::Parse($rbIpStart).GetAddressBytes()
        [Array]::Reverse($to_bytes)
        $k8sNodeIpPoolStartBytes = [BitConverter]::ToUInt32($to_bytes, 0) 
        $k8sNodeIpPoolStartBytes = [BitConverter]::GetBytes($k8sNodeIpPoolStartBytes)
        [Array]::Reverse($k8sNodeIpPoolStartBytes)
        $k8sNodeIpPoolStart = $([System.Net.IPAddress]$k8sNodeIpPoolStartBytes).IPAddressToString 
    } 
 
    $arcHciParameters.Add("k8sNodeIpPoolStart", $k8sNodeIpPoolStart)
    $arcHciParameters.Add("k8sNodeIpPoolEnd", $k8sNodeIpPoolEnd)

    Write-Log "Exiting Get-ArcHciParameters"
    return $arcHciParameters
}

function Test-IsHCIMachine {
    <#
    .DESCRIPTION
        Mgmt Proxy Configuration
    .OUTPUTS
        Boolean value
    .EXAMPLE
        Test-IsHCIMachine
    #>

    # 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)) {
        return $false
    }
    return $true  
}

function New-ArcHciMgmtProxyConfiguration {
    <#
    .DESCRIPTION
        Mgmt Proxy Configuration
    .PARAMETER proxyServerHTTP
        http urls for proxy
    .PARAMETER proxyServerHTTPS
        https urls for proxy
    .PARAMETER proxyServerNoProxy
        Comma separate list of URLs and IP ranges that should not be proxied.
        When proxy is enabled, the no proxy list must include at the minimum: localhost,127.0.0.1,.svc,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
    .PARAMETER certificateFilePath
        Name of the cert File Path for proxy
    .OUTPUTS
        Hashtable containing proxy parameters
    .EXAMPLE
        New-ArcHciMgmtProxyConfiguration -proxyServerHTTP $proxyServerHTTP -proxyServerHTTPS $proxyServerHTTPS -proxyServerNoProxy $proxyServerNoProxy -certificateFilePath $certificateFilePath
    #>

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

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

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

        [Parameter(Mandatory = $false)]
        [ValidateScript({
                if ($_ -match $space) {
                    throw $spaceErrorForFolderFilePath
                }  
                if (-not ($_ | Test-Path)) {
                    throw $fileFolderPathError
                }
                return $true
            })]
        [String] $certificateFilePath
    )

    Write-Log "Entered New-ArcHciMgmtProxyConfiguration"

    $arcHciProxyConfig = @{}

    $arcHciProxyConfig.Add("proxyServerHTTP", $proxyServerHTTP)
    $arcHciProxyConfig.Add("proxyServerHTTPS", $proxyServerHTTPS)
    $arcHciProxyConfig.Add("proxyServerNoProxy", $proxyServerNoProxy)
    $arcHciProxyConfig.Add("certificateFilePath", $certificateFilePath)
 
    Write-Log "Exiting New-ArcHciMgmtProxyConfiguration"
    return $arcHciProxyConfig
}

function Test-ArcHciVmSwitchExists {
    <#
    .DESCRIPTION
        Checks for the existence of the vm switch on all nodes of the cluster.
 
    .PARAMETER vswitchName
        The name of the vswitch
    #>

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

    $testReport = [pscustomobject]@{
                'TestName' = 'Test VM Switch Exists';
                'TestResult' = "Succeeded";
                'Details' = ""
            }
    $nodes = Get-ClusterNode

    ForEach ($node in $nodes){
        $switchObj = Get-VMSwitch -name $vswitchName  -ComputerName $node.Name -ErrorAction:Ignore
        if ($null -eq $switchObj) {
            $testReport.TestResult = "Failed"
            $testReport.Details = "Test-ArcHciVmSwitchExists : A virtual switch with name $vswitchName does not exist on node $($node.Name). Please verify a virtual switch with name $vswitchName exists on all nodes of the cluster."
            Write-Error "Test-ArcHciVmSwitchExists : A virtual switch with name $vswitchName does not exist on node $($node.Name). Please verify a virtual switch with name $vswitchName exists on all nodes of the cluster."
            return $testReport
        }
        if ($switchObj.SwitchType -ne "External"){
            $testReport.TestResult = "Failed"
            $testReport.Details = "Test-ArcHciVmSwitchExists : A virtual switch with name $vswitchName exists on node $($node.Name) but is not an external switch. Please verify an external switch with the same name exists on all nodes of the cluster."
            Write-Error "Test-ArcHciVmSwitchExists : A virtual switch with name $vswitchName exists on node $($node.Name) but is not an external switch. Please verify an external switch with the same name exists on all nodes of the cluster."
            return $testReport
        }
    }
    return $testReport
}

function Test-ArcHciClusterHealth {
    <#
    .DESCRIPTION
        Checks
         1. All nodes of cluster are up.
         2. Cluster network is up.
         3. CSV is accessible and has more than 50 GB memory.
 
    .PARAMETER volumePath
        The path of the volume
    #>

    param
    (
        [Parameter(Mandatory = $false)]  
        [GUID]        $subscriptionID = [System.Guid]::empty,

        [Parameter(Mandatory = $false)]  
        [string]      $resourceGroup,

        [Parameter(Mandatory = $false)]  
        [string]      $resourceName,

        [Parameter(Mandatory = $false)]   
        [String]      $volumePath
    )

    $testReport = [pscustomobject]@{
                'TestName' = 'Test ArcHCI Cluster Health';
                'TestResult' = "Succeeded";
                'Details' = ""
            }

    if (Test-IsHCIMachine){
        try {
            $azureStackHciConfig = Get-AzureStackHci
        } catch {
            $testReport.TestResult = "Failed"
            $testReport.Details = "Get-AzureStackHci failed."       
            return $testReport
        }

        if ($azureStackHciConfig.RegistrationStatus -ne "Registered"){
            $regStatus =  $azureStackHciConfig.RegistrationStatus
            $testReport.TestResult = "Failed"
            $testReport.Details = "Azure Stack HCI cluster is not in the proper state. Current registration state is $regStatus. Please fix cluster registration and retry."
            return $testReport
        }       
    }

    $nodes = Get-ClusterNode -ErrorAction:Ignore
    if ($null -eq $nodes){
        $testReport.TestResult = "Failed"
        $testReport.Details = "Test-ArcHciClusterHealth : No nodes found."
        Write-Error "Test-ArcHciClusterHealth : No nodes found."
        return $testReport
    }

    Get-ClusterNode -ErrorAction Stop | ForEach-Object {
       if ($_.State -ine "Up") 
       {
            $testReport.TestResult = "Failed"
            $testReport.Details = "Test-ArcHciClusterHealth : At least one node is down."
            Write-Error "Test-ArcHciClusterHealth : At least one node is down."
       }
    }

    if ($testReport.TestResult -eq "Failed"){
        return $testReport
    }

    Get-ClusterNetwork -ErrorAction Stop | ForEach-Object {
        # we only want to fail when the network state is not 'up' AND the network role is not 'None'
       if ($_.State -ine "Up" -and $_.Role -eq "ClusterAndClient") 
       {
            $testReport.TestResult = "Failed"
            $testReport.Details = "Test-ArcHciClusterHealth : At least one clusternetwork is down."
       }
    }   
    
    if ($testReport.TestResult -eq "Failed"){
        Write-Error "Test-ArcHciClusterHealth : At least one clusternetwork is down."
        return $testReport
    } 

    if(![string]::IsNullOrEmpty($volumePath)){
        $volumeFound = $false
        $notEnoughSpaceinVolume = $false
        $volumePathLower = $volumePath.ToLower() 
        Get-ClusterSharedVolume -ErrorAction Stop | ForEach-Object {
            if ($_.State -eq "Online")
            {
                $sharedVolumePath  = $_.SharedVolumeInfo.FriendlyVolumeName.ToLower() 
                $lenSharedVolumePath = $sharedVolumePath.Length

                if ($lenSharedVolumePath -le $volumePathLower.Length){
                    $volumePathLower = $volumePathLower.substring(0,$lenSharedVolumePath)
                }
                if ($volumePathLower -eq $sharedVolumePath){
                    $freespaceGb = [Math]::Round($_.SharedVolumeInfo.Partition.FreeSpace / 1GB)
                    if ($freespaceGb -gt 50){
                        $volumeFound = $true
                    } else {
                        $notEnoughSpaceinVolume = $true
                    }
                }
            }
        }
        if ($notEnoughSpaceinVolume){
            $testReport.TestResult = "Failed"
            $testReport.Details = "Test-ArcHciClusterHealth : 50 GB of space not available in specified volume $volumePath"
        }
        if ($volumeFound -eq $false){
            $testReport.TestResult = "Failed"
            $testReport.Details = "Test-ArcHciClusterHealth : specified volume $volumePath was not found."
        }
    } else {
        $volumeUp = $false
        Get-ClusterSharedVolume -ErrorAction Stop | ForEach-Object {
            if ($_.State -eq "Online")
            {
                $volumeUp = $true
            }
        }
        if ($volumeUp -eq $false)
        {
            $testReport.TestResult = "Failed"
            $testReport.Details = "Test-ArcHciClusterHealth : No Cluster Shared Volume is online."
            Write-Error "Test-ArcHciClusterHealth : No Cluster Shared Volume is online."
            return $testReport
        
        }
    }

    return $testReport
}
function Test-ArcHciMemoryRequirements{ 
    <#
    .DESCRIPTION
        Checks
         1. If 8GB of memory is available.
         2. If 4 vCPU's are available.
    #>


    $testReport = [pscustomobject]@{
                'TestName' = 'Test Arc Mgmt Memory and vCPU Requirement';
                'TestResult' = "Succeeded";
                'Details' = ""
            }
    $testReportFailed = [pscustomobject]@{
                'TestName' = 'Test Arc Mgmt Memory and vCPU Requirement';
                'TestResult' = "Failed";
                'Details' = ""
            }
    $nodes = Get-ClusterNode
    $minimumRequirementMet = $false
    
    # threads * cores * physical number of cpu
    ForEach ($node in $nodes){
        $memoryRequirementMet = $false
        $vCPURequirementMet = $false
    
        $nodeOSInfo = Get-CIMInstance Win32_OperatingSystem -ComputerName $node.name
        $mem = ($nodeOSInfo.FreePhysicalMemory) * 1KB / 1GB  
        if ($mem -ge 8){
            $memoryRequirementMet = $true
        } 

        $processors = Get-CimInstance -ClassName Win32_Processor -ComputerName $node.name
        $totalVCpu = 0
        $vCPUsInUse = 0
    
        ForEach ($processor in $processors){
            $totalVCpu += $processor.ThreadCount * $processor.NumberOfCores
        } 
        $totalVmOnNode = Get-Vm -ComputerName $node.name
        ForEach ($vm in $totalVmOnNode){
            $vCPUsInUse += $vm.ProcessorCount
        }
        $availablevCPUs = $totalVCpu - $vCPUsInUse
        if ($availablevCPUs -ge 4){
            $vCPURequirementMet = $true
        } 
        if ($memoryRequirementMet -and $vCPURequirementMet){
            $minimumRequirementMet = $true
        }
        if (!$minimumRequirementMet) {
            $testReportFailed.Details += "The cluster node $($node.name) doesn't meet the minimum memory and vCPU requirements. At least 8 GB of free memory is required and at least 4 vCPU's should be available. Total memory available $($nodeOSInfo.TotalVisibleMemorySize) and memory in use $mem. Total vCPU's $totalVCpu and vCPU's in use $vCPUsInUse. "  
        }else{
            return $testReport
        }
    }
        
    Write-Error $testReportFailed.Details
    return $testReportFailed
}

function Test-ArcHciMocPowershellModulesExists{
    $testReport = [pscustomobject]@{
                'TestName' = 'Validate Powershell Module Installation';
                'TestResult' = "Succeeded";
                'Details' = ""
            }
    #update once AzStackHci Module has been created
    $installed = Get-Module -ListAvailable -Name 'Moc'
    if ($installed -eq $null){
        $testReport.TestResult = "Failed"
        $testReport.Details = "Moc powershell module not installed. Please install the latest version"
        return $testReport
    } 

    return $testReport
}

function Test-ArcHciAzRequirements {
    <#
    .DESCRIPTION
        Checks if az login has been properly executed and access token exists for the current user.
    #>

    $testReport = [pscustomobject]@{
                'TestName' = 'Validate Az Requirements';
                'TestResult' = "Succeeded";
                'Details' = ""
            }
    
    try {
        Invoke-ArcHciAzCommand -arguments " account get-access-token"     
    }
    catch {
        Write-Log $_
        $err = $error[0]
        $testReport.TestResult = "Failed"
        $testReport.Details = $err.Exception.message
        Write-Error $err.Exception.message
    }
    return $testReport
}

function Test-ArcHciHyperVEnabled{
    <#
    .DESCRIPTION
        Checks if required Hyper-V features are installed.
    #>


    $requiredServerFeatures = @(
    "Hyper-V",
    "Hyper-V-PowerShell",
    "RSAT-Clustering-PowerShell")
    
    $requiredServerFeaturesStandalone = @(
    "Microsoft-Hyper-V",
    "Microsoft-Hyper-V-Management-PowerShell")
    

    $testReport = [pscustomobject]@{
            'TestName' = 'Validate Windows HyperV Features Enabled';
            'TestResult' = "Succeeded";
            'Details' = ""
        }

    if (Test-IfCluster){
        $nodes = Get-ClusterNode
        ForEach ($node in $nodes){
            Invoke-Command -ComputerName $node.NodeName -ScriptBlock {
                $rebootRequired = $false
                $GenericLocMessage = $args[1]
                foreach($feature in $requiredServerFeatures)
                {

                    $wf = Get-WindowsFeature -Name "$feature" 
                    if ($null -eq $wf)
                    {
                        $testReport.TestResult = "Failed"
                        $testReport.Details = "Windows feature - $feature was not found on $node.NodeName.Please enable this feature."
                        Write-Error "Windows feature - $feature was not found on $node.NodeName.Please enable this feature."
                    }

                    if ($wf.InstallState -ne "Installed")
                    {
                        $testReport.TestResult = "Failed"
                        $testReport.Details = "Please check the state of windows feature - $feature on $node.NodeName"
                        Write-Error "Please check the state of windows feature - $feature on $node.NodeName"
                    }
                }
            }
        }
    } else {
        foreach($feature in $requiredServerFeaturesStandalone)
            {
                $wf = Get-WindowsOptionalFeature -Online -FeatureName "$feature" 
                if ($null -eq $wf)
                {
                    $testReport.TestResult = "Failed"
                    $testReport.Details = "Windows feature - $feature was not found.Please enable this feature."
                    Write-Error "Windows feature - $feature was not found.Please enable this feature."
                }
                if ($wf.State -ne "Enabled")
                {
                    $testReport.TestResult = "Failed"
                    $testReport.Details = "Please check the state of windows feature - $feature."
                    Write-Error "Please check the state of windows feature - $feature."
                }
            }
    }

    return $testReport
}

function Test-ArcHciResourceBridgeIps {
    Param(
        [Parameter(Mandatory=$true)]
        [ValidateScript({
            $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) {
                    $parameter = "rbIpStart"
                    throw "$ipv4ValidationError" -f $parameter, $_
                }
                return $true
            })]  
        [string] $rbIpStart,    

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

        [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 ([string]::IsNullOrEmpty($_)) {
                    return $true
                }
                if ($_ -notmatch $regexPatternCIDRFormat) {
                    $parameter = "ipaddressprefix"  
                    throw $regexPatternCIDRFormatError -f $_, $parameter
                }
                return $true
            })]  
        [string] $ipAddressPrefix,

        [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 = @()
    )

    $testReport = [pscustomobject]@{
            'TestName' = 'Test resource bridge ips';
            'TestResult' = "Succeeded";
            'Details' = ""
        }

    $dhcpEnabledCluster = $True

    $clusterNetworks = Get-ClusterNetwork -ErrorAction SilentlyContinue | Where-Object { ($_.Role -eq "ClusterAndClient" ) `
                    -and ($_.Ipv4Addresses.Count -gt 0 ) } | Select-Object -property Name, IPv4Addresses, IPV4PrefixLengths
    
    foreach ($clusterNetwork in $clusterNetworks)
    {
        for($i = 0; $i -lt $clusterNetwork.Ipv4Addresses.Count; $i++) 
        {
            if ((Get-ClusterNetworkInterface -Network $($clusterNetwork.Name) | Select-Object -expandProperty "DhcpEnabled") -ne 1)
            {
                $dhcpEnabledCluster = $False 
            }
        }
    }

    if ([string]::IsNullOrEmpty($rbIpStart)){
        if (-Not $dhcpEnabledCluster){
            $testReport.TestResult = "Failed"
            $testReport.Details = "This is not a dhcp enabled cluster. rbIpStart parameter is required."
            return $testReport
        }      
    }
    
    if ([string]::IsNullOrEmpty($rbIpEnd)){
        if (-Not $dhcpEnabledCluster){
            $testReport.TestResult = "Failed"
            $testReport.Details = "This is not a dhcp enabled cluster. rbIpEnd parameter is required."
            return $testReport
        }      
    }

    if ([string]::IsNullOrEmpty($gateway)){
        if (-Not $dhcpEnabledCluster){
            $testReport.TestResult = "Failed"
            $testReport.Details = "This is not a dhcp enabled cluster. gateway parameter is required."
            return $testReport
        }      
    }

    
    if ([string]::IsNullOrEmpty($ipAddressPrefix)){
        if (-Not $dhcpEnabledCluster){
            $testReport.TestResult = "Failed"
            $testReport.Details = "This is not a dhcp enabled cluster. ipAddressPrefix parameter is required."
            return $testReport
        }      
    }

    
    if ($dnsServers -eq $null -or $dnsServers.count -eq 0 -or $dnsServers.length -eq 0){
        if (-Not $dhcpEnabledCluster){
            $testReport.TestResult = "Failed"
            $testReport.Details = "This is not a dhcp enabled cluster. dnsServers parameter is required."
            return $testReport
        }      
    }

    
    <# $foundInClusterNetwork = $False
     
    foreach ($clusterNetwork in $clusterNetworks)
    {
        for($i = 0; $i -lt $clusterNetwork.Ipv4Addresses.Count; $i++)
        {
            [System.Net.IPAddress]$ipv4 = $null
            $clusIpv4 = $clusterNetwork.Ipv4Addresses[$i]
            if (-Not [System.Net.IPAddress]::TryParse($clusIpv4, [ref] $ipv4))
            {
                Write-Warning $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_ignore_failover_ip , $clusIpv4))
                continue
            }
             
            $lastIp = [AKSHCI.IPUtilities]::GetLastIpInCidr($ipv4, $clusterNetwork.Ipv4PrefixLengths[$i])
            if([AKSHCI.IPUtilities]::CompareIpAddresses($rbIpStart, $ipv4) -ge 0 -AND [AKSHCI.IPUtilities]::CompareIpAddresses($rbIpStart, $lastIp) -le 0)
            {
                $foundInClusterNetwork = $True
                #The cloud service CIDR is in the range of the cluster network IP! Compare prefix lengths now!
                break
            }
        }
    }
 
    if ($foundInClusterNetwork -ne $True)
    {
        $testReport.TestResult = "Failed"
        $testReport.Details = "Resource bridge Ip not in cluster network."
    } #>


    if(-Not $dhcpEnabledCluster){
        $testRangeResult = Test-rbIpRange -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd
        if (-not $testRangeResult){
            $testReport.TestResult = "Failed"
            $testReport.Details = "This is not a valid Ip range. Please specify a valid range for start and end ip address. There must be at least 2 Ips in the range."
        }
    }

    return $testReport

}

function Test-ArcHciAzArcApplianceValidate {
    <#
    .DESCRIPTION
        Checks if az arcappliance validate command is successful.
    .PARAMETER subscriptionID
        The Azure subscription GUID
    .PARAMETER location
        Azure location
    .PARAMETER resourceGroup
        Name of the Azure resource group
    .PARAMETER resourceName
        Name of the Azure resource
    .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
        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 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 workingDir
        Optional parameter. Working Directory Path
    .PARAMETER rbIpStart
        The starting ip address to use for Arc RB.
    .PARAMETER rbIpEnd
        Optional parameter. The ending ip address to use for Arc RB.
    .PARAMETER gateway
        Optional parameter. The gateway to use when using static IP
    .PARAMETER ipaddressprefix
        Optional parameter. The address prefix to use for static IP assignment
    .PARAMETER dnsservers
        Optional parameter. The dnsservers to use when using static IP
    .PARAMETER vlanID
        Optional parameter. The VLAN ID for the vnet
    .PARAMETER arcHciProxyConfig
        Optional parameter. Proxy Config
    .OUTPUTS
        N/A
    .EXAMPLE
        Test-ArcHciAzArcApplianceValidate -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -workingDir $workingDir -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd -gateway $gateway -dnsservers $dnsservers -ipaddressPrefix $ipaddressPrefix -vLanID $vlanID -arcHciProxyConfig $arcHciProxyConfig
    #>
   

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

        [parameter(Mandatory = $true)]   
        [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 = $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 = $true)]  
        [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)]
        [ValidateNotNullOrEmpty()]
        [String] $clusterName,

        [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
                }
                if ($_ -match $space) {
                    throw $spaceErrorForFolderFilePath
                }  
                if (-not ($_ | Test-Path)) {
                    throw $fileFolderPathError
                }
                return $true
            })]  
        [string]  $workingDir,

        [Parameter(Mandatory=$true)]
        [ValidateScript({
            $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) {
                    $parameter = "rbIpStart"
                    throw "$ipv4ValidationError" -f $parameter, $_
                }
                return $true
            })]  
        [string] $rbIpStart,    

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

        [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 ([string]::IsNullOrEmpty($_)) {
                    return $true
                }
                if ($_ -notmatch $regexPatternCIDRFormat) {
                    $parameter = "ipaddressprefix"  
                    throw $regexPatternCIDRFormatError -f $_, $parameter
                }
                return $true
            })]  
        [string] $ipAddressPrefix,

        [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)]
        [ValidateRange(0, 4094)] 
        [int] $vlanID = 0,

        [Parameter(Mandatory = $false)]
        $arcHciProxyConfig
    )

    $testReport = [pscustomobject]@{
            'TestName' = 'Test az arcappliance validate';
            'TestResult' = "Succeeded";
            'Details' = ""
        }

    $arcRBDetails = Get-ArcHciResourceBridge -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName

    if ($arcRBDetails.provisioningState -eq 'NotInstalled') {
        try {    
            Write-Log "Running New-ArcHciApplianceConfigs -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -workingDir $workingDir -controlPlaneIP $controlPlaneIP -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -gateway $gateway -dnsservers $dnsservers -ipaddressprefix $ipaddressprefix -vLanID $vlanID -arcHciProxyConfig $arcHciProxyConfig"
            New-ArcHciApplianceConfigs -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -workingDir $workingDir -controlPlaneIP $controlPlaneIP -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -gateway $gateway -dnsServers $dnsServers -ipAddressprefix $ipAddressPrefix -vLanID $vlanID -arcHciProxyConfig $arcHciProxyConfig
        }
        catch {
            Write-Log "New-ArcHciApplianceConfigs failed with error $_"
            $testReport.TestResult = "Failed"
            $testReport.Details = $_
            return $testReport
        }
    }
    try {
        Invoke-ArcHciAzCommand "provider register --namespace Microsoft.ResourceConnector --wait"
    } catch {
        $testReport.TestResult = "Failed"
        $testReport.Details = $_
        return $testReport
    }
    

    try {
        Write-Log "Running az arcappliance validate hci --config-file ""$workingDir\Appliance\hci-appliance.yaml"""
        Invoke-ArcHciAzCommand "arcappliance validate hci --config-file ""$workingDir\Appliance\hci-appliance.yaml""" -ignoreWarning  
    } catch {
        Write-Log "az arcappliance validate failed with error $_"
        $testReport.TestResult = "Failed"
        $testReport.Details = $_
    }

    return $testReport
}

function Test-ArcHciMocInstallation{
    <#
    .DESCRIPTION
        Checks if MOC is installed.
    #>


    $testReport = [pscustomobject]@{
                'TestName' = 'Validate MOC Installation';
                'TestResult' = "Succeeded";
                'Details' = ""
            }
    try {
        $mocInstallationState = Get-ArcHciMoc
    } catch {
        $testReport.TestResult = "Failed"
        $testReport.Details = $_.Exception.Message
        return $testReport
    }
   
    if ($mocInstallationState.installState -ne 7){
        $testReport.TestResult = "Failed"
        $curState = [InstallState]$mocInstallationState.installState
        $testReport.Details = "MOC is in state $curState. Please ensure MOC is properly installed before proceeding."
        return $testReport
    }  

    $mocConfig = Get-ArcHciMoc
    
    $tmpGroup = Get-ClusterGroup -Name $mocConfig.clusterRoleName -ErrorAction Ignore
    if ($tmpGroup.State -ne "Online")
    {   
        $testReport.TestResult = "Failed"
        $testReport.Details = "wssdcloudagent is not running. Please confirm that MOC was installed properly."
        return $testReport
    }

    Get-ClusterNode -ErrorAction Stop | ForEach-Object {
        $nodeName = ${_}.Name
        $tmpService = Get-Service wssdagent -ComputerName $nodeName
        if ($tmpService.Status -ne "Running")
        {
            $testReport.TestResult = "Failed"
            $testReport.Details = "wssdagent is not running on at least one node. Please confirm that MOC was installed properly and wssdagent service is running on all nodes."
        }
    }

    return $testReport
    
}

function Test-ArcHciEnableMoc {
    <#
    .DESCRIPTION
        Checks if requirements are satisfied for MOC to be installed.
     
    .PARAMETER volumePath
        The path of the volume
    .PARAMETER cloudserviceIP
        Optional parameter. Cloud agent IP
    .PARAMETER correlationId
        Optional parameter. Identifier used for telemetry
    #>

    param
    (
        [Parameter(Mandatory = $false)]   
        [ValidateScript({
                if ($_ -match $space) {
                    throw $spaceErrorForFolderFilePath
                }  
                if (-not ($_ | Test-Path)) {
                    throw $fileFolderPathError
                }
                return $true
            })]

        [string] $volumePath,

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

        [parameter(Mandatory = $false)]
        [string] $correlationId = [System.Guid]::NewGuid().ToString()
    )

    $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Test-ArcHciEnableMoc" -version $moduleVersion
    
    if ([String]::IsNullOrEmpty($cloudserviceIP)) {
        $numberOfArcHciMocPrechecks = 3
    }

    try {
        $testResults = @()
        $overallResult = $true
        $numOfFailedMocPrechecks = 0

        $isCluster = Test-IfCluster

        if ([String]::IsNullOrEmpty($volumePath)) {
            $volumePath = Get-ArcHciDefaultPath
        }


        #check if moc module is installed
        Write-Host '===============================================================================' -ForegroundColor White
        $outputHeader = "Test ({0} of {1}): `"{2}`"" -f 1,$numberOfArcHciMocPrechecks,"Test Moc Powershell Module Installation"
        Write-Host $outputHeader -ForegroundColor White
        try {
            $testReport = Test-ArcHciMocPowershellModulesExists
        }
        catch {
            $testReport = [pscustomobject]@{
                'TestName'   = 'Test Moc Powershell Module Installation';
                'TestResult' = "Failed";
                'Details'    = $_.Exception.Message
            }
        }
        $testResults += $testReport
        if ($testReport.TestResult -eq "Failed") {
                $overallResult = $false
                $numOfFailedMocPrechecks += 1
                Write-Host "Test Failed" -ForegroundColor Red
                Write-Host "Details:"$testReport.Details -ForegroundColor Red
                Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmocprecheckfailed" -message "Test Moc Powershell Module Installation failed: $($testReport.Details)"
                Publish-ArcHciTelemetryEvent -eventConfig $eventConfig
        } else {
                Write-Host "Test Succeeded" -ForegroundColor Green            
        }

        # check if 50 gb exists in chsared memorycluster nodes and cluster network up
        Write-Host '===============================================================================' -ForegroundColor White
        $outputHeader = "Test ({0} of {1}): `"{2}`"" -f 2,$numberOfArcHciMocPrechecks,"Test ArcHci Cluster Health"
        Write-Host $outputHeader -ForegroundColor White
        if ($isCluster){
            try {
                $testReport = Test-ArcHciClusterHealth -volumePath $volumePath
            }
            catch {
                $testReport = [pscustomobject]@{
                    'TestName'   = 'Validate Cluster Health';
                    'TestResult' = "Failed";
                    'Details'    = $_.Exception.Message
                }
            }
        }
        else {
            $testReport = [pscustomobject]@{
                'TestName'   = 'Validate Cluster Health';
                'TestResult' = "Succeeded";
                'Details'    = "Test not required for standalone machine."
            }
        }
        $testResults += $testReport
        if ($testReport.TestResult -eq "Failed") {
                $overallResult = $false
                $numOfFailedMocPrechecks += 1
                Write-Host "Test Failed" -ForegroundColor Red
                Write-Host "Details:"$testReport.Details -ForegroundColor Red
                Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmocprecheckfailed" -message "Test Validate Cluster Health failed: $($testReport.Details)"
                Publish-ArcHciTelemetryEvent -eventConfig $eventConfig
        } else {
                Write-Host "Test Succeeded" -ForegroundColor Green            
        }

        #hyperv enabled
        Write-Host '===============================================================================' -ForegroundColor White
        $outputHeader = "Test ({0} of {1}): `"{2}`"" -f 3,$numberOfArcHciMocPrechecks,"Test Windows HyperV Features are enabled"
        Write-Host $outputHeader -ForegroundColor White
        try {
            $testReport = Test-ArcHciHyperVEnabled
        }
        catch {
            $testReport = [pscustomobject]@{
                'TestName'   = 'Test Windows HyperV Features are enabled';
                'TestResult' = "Failed";
                'Details'    = $_.Exception.Message
            }
        }
        $testResults += $testReport
        if ($testReport.TestResult -eq "Failed") {
                $overallResult = $false
                $numOfFailedMocPrechecks += 1
                Write-Host "Test Failed" -ForegroundColor Red
                Write-Host "Details:"$testReport.Details -ForegroundColor Red
                Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmocprecheckfailed" -message "Test Windows HyperV Features are enabled failed: $($testReport.Details)"
                Publish-ArcHciTelemetryEvent -eventConfig $eventConfig
        } else {
                Write-Host "Test Succeeded" -ForegroundColor Green            
        }

        if ($numberOfArcHciMocPrechecks -eq 4) {
            #cloudserviceIp check
            Write-Host '===============================================================================' -ForegroundColor White
            $outputHeader = "Test ({0} of {1}): `"{2}`"" -f 4, $numberOfArcHciMocPrechecks, "Test ArcHci Moc Cloud Service Ip"
            Write-Host $outputHeader -ForegroundColor White
            if ($isCluster) {
                try {
                    $testReport = Test-ArcHciMocCloudServiceIP -cloudserviceIp $cloudServiceIP
                }
                catch {
                    $testReport = [pscustomobject]@{
                        'TestName'   = 'Test ArcHci Moc Cloud Service Ip';
                        'TestResult' = "Failed";
                        'Details'    = $_.Exception.Message
                    }
                }
            }
            else {
                $testReport = [pscustomobject]@{
                    'TestName'   = 'Test ArcHci Moc Cloud Service Ip';
                    'TestResult' = "Succeeded";
                    'Details'    = "Test not required for standalone machine."
                }
            }
            $testResults += $testReport
            if ($testReport.TestResult -eq "Failed") {
                $overallResult = $false
                Write-Host "Test Failed" -ForegroundColor Red
                Write-Host "Details:"$testReport.Details -ForegroundColor Red
                Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmocprecheckfailed" -message "Test ArcHci Moc Cloud Service Ip failed: $($testReport.Details)"
                Publish-ArcHciTelemetryEvent -eventConfig $eventConfig
            }
            else {
                Write-Host "Test Succeeded" -ForegroundColor Green            
            }
        }
    
        #overall result test
        Write-Host '===============================================================================' -ForegroundColor White
        if ($overallResult -eq $false) {
            $testReport = [pscustomobject]@{
                'TestName'   = 'Overall Enable Moc Precheck Result';
                'TestResult' = "Failed";
                'Details'    = 'At least one of the prechecks required for MOC installation failed. See above tests for more details.'
            }
        }
        else {
            $testReport = [pscustomobject]@{
                'TestName'   = 'Overall Enable Moc Precheck Result';
                'TestResult' = "Succeeded";
                'Details'    = 'All prechecks required for MOC installation succeeded.'
            }
        }
        $testResults += $testReport
        
        Write-Host '===============================================================================' -ForegroundColor White
        if ($overallResult -eq $false){
            Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmocprecheckfailed" -message "Test enable MOC enable failed "
            Write-Host "Correlation ID: $correlationId. Moc installation prechecks were not successful. $numOfFailedMocPrechecks/$numberOfArcHciMocPrechecks Prechecks Failed" -ForegroundColor Red
        } else{
            Write-Host "All Moc installation precheck tests were successful" -ForegroundColor Green
        }
        Write-Host '===============================================================================' -ForegroundColor White


        return $testResults
    }
    finally {
        Publish-ArcHciTelemetryEvent -eventConfig $eventConfig
    }

}

function Test-ArcHciEnableArcMgmt {   
    <#
    .DESCRIPTION
        Checks if all requirements for installtion arc mgmt are met.
    .PARAMETER subscriptionID
        The Azure subscription GUID
    .PARAMETER location
        Azure location
    .PARAMETER resourceGroup
        Name of the Azure resource group
    .PARAMETER resourceName
        Name of the Azure resource
    .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
        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 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 workingDir
        Optional parameter. Working Directory Path
    .PARAMETER controlPlaneIP
        IP Address to be used for the Arc Appliance control plane
    .PARAMETER rbIpStart
        The starting ip address to use for Arc RB.
    .PARAMETER rbIpEnd
        Optional parameter. The ending ip address to use for Arc RB.
    .PARAMETER gateway
        Optional parameter. The gateway to use when using static IP
    .PARAMETER ipAddressPrefix
        Optional parameter. The address prefix to use for static IP assignment
    .PARAMETER dnsServers
        Optional parameter. The dnsservers to use when using static IP
    .PARAMETER vlanID
        Optional parameter. The VLAN ID for the vnet
    .PARAMETER arcHciProxyConfig
        Optional parameter. Proxy Config
    .PARAMETER correlationId
        Optional parameter. Identifier used for telemetry
    .OUTPUTS
        N/A
    .EXAMPLE
        Test-ArcHciEnableArcMgmt -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -workingDir $workingDir -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd -gateway $gateway -dnsservers $dnsservers -ipaddressPrefix $ipaddressPrefix -vLanID $vlanID -arcHciProxyConfig $arcHciProxyConfig
    #>
   

    Param(
        [parameter(Mandatory = $false)]
        [GUID] $subscriptionID = [System.Guid]::empty,

        [parameter(Mandatory = $false)]   
        [string] $location,

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

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

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

        # vswitchName can accept any characters
        [parameter(Mandatory = $true)]  
        [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 $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
                }
                if ($_ -match $space) {
                    throw $spaceErrorForFolderFilePath
                }  
                if (-not ($_ | Test-Path)) {
                    throw $fileFolderPathError
                }
                return $true
            })]  
        [string]  $workingDir,

        [Parameter(Mandatory=$true)]
        [ValidateScript({
            $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) {
                    $parameter = "rbIpStart"
                    throw "$ipv4ValidationError" -f $parameter, $_
                }
                return $true
            })]  
        [string] $rbIpStart,    

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

        [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 ([string]::IsNullOrEmpty($_)) {
                    return $true
                }
                if ($_ -notmatch $regexPatternCIDRFormat) {
                    $parameter = "ipaddressprefix"  
                    throw $regexPatternCIDRFormatError -f $_, $parameter
                }
                return $true
            })]  
        [string] $ipAddressPrefix,

        [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)]
        [ValidateRange(0, 4094)] 
        [int] $vlanID,

        [Parameter(Mandatory = $false)]
        $arcHciProxyConfig,

        [parameter(Mandatory = $false)]
        [string] $correlationId = [System.Guid]::NewGuid().ToString()
    )
    $eventConfig = New-ArcHciTelemetryEventConfig -correlationId $correlationId -operationName "Test-ArcHciEnableArcMgmt" -version $moduleVersion

    try {
        $testResults = @()
        $overallResult = $true
        $numOfFailedPrechecks = 0

        if ([String]::IsNullOrEmpty($workingDir)) {
            $mocConfig = Get-ArcHciMoc
            $workingDir = $($mocConfig.workingDir)
        }

        $arcHciParameters = Get-ArcHciParameters -subscriptionID $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -location $location
        $subscriptionID = $arcHciParameters.SubscriptionID
        $resourceGroup = $arcHciParameters.ResourceGroup
        $resourceName = $arcHciParameters.ResourceName
        $location = $arcHciParameters.Location


        #cluster health check
        Write-Host '===============================================================================' -ForegroundColor White
        $outputHeader = "Test ({0} of {1}): `"{2}`"" -f 1,$numberOfArcMgmtPrechecks,"Test ArcHci Cluster Health"
        Write-Host $outputHeader -ForegroundColor White
        try {
            $testReport = Test-ArcHciClusterHealth 
        }
        catch {
            $testReport = [pscustomobject]@{
                'TestName'   = 'Test ArcHci Cluster Health';
                'TestResult' = "Failed";
                'Details'    = $_.Exception.Message
            }
        }
        $testResults += $testReport
        if ($testReport.TestResult -eq "Failed") {
                $overallResult = $false
                $numOfFailedPrechecks += 1
                Write-Host "Test Failed" -ForegroundColor Red
                Write-Host "Details:"$testReport.Details -ForegroundColor Red
                Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmgmtprereqfailed" -message "Test ArcHci Cluster Health Failed: $($testReport.Details)"
                Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName
        } else {
                Write-Host "Test Succeeded" -ForegroundColor Green            
        }

        #MOC installation check
        Write-Host '===============================================================================' -ForegroundColor White
        $outputHeader = "Test ({0} of {1}): `"{2}`"" -f 2,$numberOfArcMgmtPrechecks,"Test ArcHci MOC Installation state"
        Write-Host $outputHeader -ForegroundColor White
        try {
            $testReport = Test-ArcHciMocInstallation
        }
        catch {
            $testReport = [pscustomobject]@{
                'TestName'   = 'Test ArcHci MOC Installation state';
                'TestResult' = "Failed";
                'Details'    = $_.Exception.Message
            }
        }
        $testResults += $testReport
        if ($testReport.TestResult -eq "Failed") {
                $overallResult = $false
                $numOfFailedPrechecks += 1
                Write-Host "Test Failed" -ForegroundColor Red
                Write-Host "Details:"$testReport.Details -ForegroundColor Red
                Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmgmtprereqfailed" -message "Test ArcHci Moc Installation Failed: $($testReport.Details)"
                Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName
        } else {
                Write-Host "Test Succeeded" -ForegroundColor Green            
        }

        #vm switch test
        Write-Host '===============================================================================' -ForegroundColor White
        $outputHeader = "Test ({0} of {1}): `"{2}`"" -f 3,$numberOfArcMgmtPrechecks,"Test VM Switch Exists"
        Write-Host $outputHeader -ForegroundColor White
        try {
            $testReport = Test-ArcHciVmSwitchExists -vswitchName $vswitchName
        }
        catch {
            $testReport = [pscustomobject]@{
                'TestName'   = 'Test VM Switch Exists';
                'TestResult' = "Failed";
                'Details'    = $_.Exception.Message
            }
        }
        $testResults += $testReport
        if ($testReport.TestResult -eq "Failed") {
                $overallResult = $false
                $numOfFailedPrechecks += 1
                Write-Host "Test Failed" -ForegroundColor Red
                Write-Host "Details:"$testReport.Details -ForegroundColor Red
                Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmgmtprereqfailed" -message "Test ArcHci VM Switch Exists Failed: $($testReport.Details)"
                Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName
        } else {
                Write-Host "Test Succeeded" -ForegroundColor Green            
        }

        #memory test
        # Write-Host '===============================================================================' -ForegroundColor White
        # $outputHeader = "Test ({0} of {1}): `"{2}`"" -f 4,$numberOfArcMgmtPrechecks,"Test Arc Mgmt Memory and vCPU Requirement"
        # Write-Host $outputHeader -ForegroundColor White
        # try {
        # $testReport = Test-ArcHciMemoryRequirements
        # }
        # catch {
        # $testReport = [pscustomobject]@{
        # 'TestName' = 'Test Arc Mgmt Memory and vCPU Requirement';
        # 'TestResult' = "Failed";
        # 'Details' = $_.Exception.Message
        # }
        # }
        # $testResults += $testReport
        # if ($testReport.TestResult -eq "Failed") {
        # $overallResult = $false
        # $numOfFailedPrechecks += 1
        # Write-Host "Test Failed" -ForegroundColor Red
        # Write-Host "Details:"$testReport.Details -ForegroundColor Red
        # Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmgmtprereqfailed" -message "Test Arc Mgmt Memory and vCPU Requirement Failed: $($testReport.Details)"
        # Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName
        # } else {
        # Write-Host "Test Succeeded" -ForegroundColor Green
        # }

        #az requirements test
        Write-Host '===============================================================================' -ForegroundColor White
        $outputHeader = "Test ({0} of {1}): `"{2}`"" -f 4,$numberOfArcMgmtPrechecks,"Test Az Requirements"
        Write-Host $outputHeader -ForegroundColor White
        try {
                $testReport  = Test-ArcHciAzRequirements
        } catch {
                $testReport = [pscustomobject]@{
                    'TestName' = 'Test Az Requirements';
                    'TestResult' = "Failed";
                    'Details' = $_.Exception.Message
                }
        }
        $testResults += $testReport
        if ($testReport.TestResult -eq "Failed") {
                $overallResult = $false
                $numOfFailedPrechecks += 1
                Write-Host "Test Failed" -ForegroundColor Red
                Write-Host "Details:"$testReport.Details -ForegroundColor Red
                Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmgmtprereqfailed" -message "Test Az Requirements Failed: $($testReport.Details)"
                Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName
        } else {
                Write-Host "Test Succeeded" -ForegroundColor Green            
        }

        #resource bridge ip address range check
        Write-Host '===============================================================================' -ForegroundColor White
        $outputHeader = "Test ({0} of {1}): `"{2}`"" -f 5,$numberOfArcMgmtPrechecks,"Test Resource Bridge IPs"
        Write-Host $outputHeader -ForegroundColor White
        try {
                $testReport  = Test-ArcHciResourceBridgeIps -controlPlaneIP $controlPlaneIP -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd -ipAddressPrefix $ipAddressPrefix -gateway $gateway -dnsServers $dnsServers
        } catch {
                $testReport = [pscustomobject]@{
                    'TestName' = 'Test Resource Bridge IPs';
                    'TestResult' = "Failed";
                    'Details' = $_.Exception.Message
                }
        }

        $testResults += $testReport
        if ($testReport.TestResult -eq "Failed") {
                $overallResult = $false
                $numOfFailedPrechecks += 1
                Write-Host "Test Failed" -ForegroundColor Red
                Write-Host "Details:"$testReport.Details -ForegroundColor Red
                Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmgmtprereqfailed" -message "Test Resource Bridge IPs Failed: $($testReport.Details)"
                Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName
        } else {
                Write-Host "Test Succeeded" -ForegroundColor Green            
        }

        #ip address test
        Write-Host '===============================================================================' -ForegroundColor White
        $outputHeader = "Test ({0} of {1}): `"{2}`"" -f 6,$numberOfArcMgmtPrechecks,"Test Az ArcAppliance Validate"
        Write-Host $outputHeader -ForegroundColor White
    
        try {
            Write-Log "Running Test-ArcHciAzArcApplianceValidate -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -workingDir $workingDir -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd -gateway $gateway -ipaddressPrefix $ipaddressPrefix -dnsservers $dnsservers -vlanID $vlanID -arcHciProxyConfig $arcHciProxyConfig"
            $testReport = Test-ArcHciAzArcApplianceValidate -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -workingDir $workingDir -controlPlaneIP $controlPlaneIP -rbIpStart $rbIpStart -rbIpEnd $rbIpEnd -gateway $gateway -ipAddressPrefix $ipAddressPrefix -dnsServers $dnsServers -vlanID $vlanID -arcHciProxyConfig $arcHciProxyConfig
        }
        catch {
            $testReport = [pscustomobject]@{
                'TestName'   = 'Test Az ArcAppliance Validate';
                'TestResult' = "Failed";
                'Details'    = $_.Exception.Message
            }
        }

        $testResults += $testReport
        if ($testReport.TestResult -eq "Failed") {
                $overallResult = $false
                $numOfFailedPrechecks += 1
                Write-Host "Test Failed" -ForegroundColor Red
                Write-Host "Details:"$testReport.Details -ForegroundColor Red
                Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmgmtprereqfailed" -message "Test Az ArcAppliance Validate Failed: $($testReport.Details)"
                Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName
        } else {
                Write-Host "Test Succeeded" -ForegroundColor Green            
        }

        #overall result test
        Write-Host '===============================================================================' -ForegroundColor White
        if ($overallResult -eq $false) {
            $testReport = [pscustomobject]@{
                'TestName'   = 'Overall ArcHci Mgmt Precheck Result';
                'TestResult' = "Failed";
                'Details'    = 'At least one of the prechecks required for Arc Mgmt installation failed. See above tests for more details.'
            }
        }
        else {
            $testReport = [pscustomobject]@{
                'TestName'   = 'Overall ArcHci Mgmt Precheck Result';
                'TestResult' = "Succeeded";
                'Details'    = 'All prechecks required for Arc Mgmt installation succeeded.'
            }
        }
        $testResults += $testReport
        
        Write-Host '===============================================================================' -ForegroundColor White
        if ($overallResult -eq $false) {
            Set-ArcHciTelemetryEventFailed -eventConfig $eventConfig -code "installmgmtprereqfailed" -message "Test enable ArcHci failed"
            Write-Host "Correlation ID: $correlationId. All Arc Mgmt precheck tests were not successful" -ForegroundColor Red
        }
        else {
            Write-Host 'All Arc Mgmt precheck tests were successful' -ForegroundColor Green
        }
        Write-Host '===============================================================================' -ForegroundColor White

        return $testResults
    }
    finally {
        Publish-ArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionID -resourceGroup $resourceGroup -resourceName $resourceName -customLocationName $customLocationName
    }
}

function Get-ArcHciApplianceLogs {
    <#
    .DESCRIPTION
        Collect Arc Appliance logs
    .PARAMETER resourceGroup
        Optional parameter. Name of the Azure resource group in which the Arc HCI appliance will be created
    .PARAMETER resourceName
        Optional parameter. Name of the Azure resource
    .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)
    .OUTPUTS
        N/A
    .EXAMPLE
        Get-ArcHciApplianceLogs -ip $ip -logDir $logDir -kvaTokenPath $kvaTokenPath
    #>
 
    param (
        [Parameter(Mandatory = $false)]
        [ValidateScript({
                if ([string]::IsNullOrEmpty($_)) {
                    return $true
                }
                $response = Test-IPV4Address -ip $_
                if (!$response) {
                    $parameter = "Ip"
                    throw "$ipv4ValidationError" -f $parameter, $_
                }
                return $true
            })]
        [String]$ip,

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

        [Parameter(Mandatory = $false)]
        [ValidateScript({
                if ([string]::IsNullOrEmpty($_)) {
                    return $true
                }
                if ($_ -match $space) {
                    throw $spaceErrorForFolderFilePath
                }  
                if (-not ($_ | Test-Path)) {
                    throw $fileFolderPathError
                }
                return $true
            })]
        [String] $kvaTokenPath = (Get-ArcHciConfigValue -name "kvaTokenLocation")
    )
    
    if ([String]::IsNullOrEmpty($arcHciLogDir)) {
        $timestamp = Get-Date -Format "yyyyMMddHHmmss"
        $arcHciLogDir = $(Join-Path $arcHciLogDir "archcilogs_$timestamp")
    }

    if (-not ($arcHciLogDir | Test-Path)) {
        New-Item -ItemType Directory -Path $arcHciLogDir -Force > $null
    }

    try {
        $mocConfig = Get-MocConfig
    }
    catch {
        $_ >> "$($arcHciLogDir)\commandlogs.txt"
    }
     
    try {
        #Downloading logkey
        Write-Log "Running az arcappliance get-credentials --config-file ""$($mocConfig.workingDir)\Appliance\hci-appliance.yaml"" --overwrite-existing true --yes"
        Invoke-ArcHciAzCommand "arcappliance get-credentials --config-file ""$($mocConfig.workingDir)\Appliance\hci-appliance.yaml"" --overwrite-existing true --yes" -ignoreWarning | Out-Null   
    }
    catch {
        $_ >> "$($arcHciLogDir)\commandlogs.txt"
    }

    $cloudFqdn = $mocConfig.cloudFqdn
    if ([String]::IsNullOrEmpty($ip)) {
        $ip = $(Get-ArcHciFirstControlPlaneNodeIp -arcHciLogDir $arcHciLogDir)
    }
    try {
        Write-Log "Running az arcappliance logs hci --ip $ip --out-dir $arcHciLogDir --loginconfigfile $kvaTokenPath --cloudagent $cloudFqdn"
        Invoke-ArcHciAzCommand "arcappliance logs hci --ip $ip --out-dir $arcHciLogDir --loginconfigfile $kvaTokenPath --cloudagent $cloudFqdn" -ignoreWarning >> "$($arcHciLogDir)\commandlogs.txt"
    }
    catch {
        $_ >> "$($arcHciLogDir)\commandlogs.txt"
    }
}
function Publish-ArcHciTelemetryEvent {
    param (
        [Parameter(Mandatory=$true)]
        [PSTypeName('ArcHciTelemetryEventConfig')]
        $eventConfig,

        [Parameter(Mandatory=$false)]
        [guid]
        $subscriptionId = [System.Guid]::Empty,

        [Parameter(Mandatory=$false)]
        [string]
        $resourceGroup,

        [Parameter(Mandatory=$false)]
        [string]
        $resourceName,

        [Parameter(Mandatory=$false)]
        [string]
        $customLocationName
    )
    
    try {
        $customLocationId = $null
        # generate CustomLocationId if required fields are present
        if(![string]::IsNullOrEmpty($customLocationName) -and ![string]::IsNullOrEmpty($subscriptionId) -and ![string]::IsNullOrEmpty($resourceGroup) -and ![string]::IsNullOrEmpty($resourceGroup)) {
            $customLocationId = "/subscriptions/$subscriptionId/resourcegroups/$resourceGroup/providers/microsoft.extendedlocation/customlocations/$customLocationName"
        }

        # Emit the event
        EmitArcHciTelemetryEvent -eventConfig $eventConfig -subscriptionId $subscriptionId -resourceGroup $resourceGroup -customLocationId $customLocationId
    }
    catch {
        # Preference is to not impact user flow if telemetry fails to emit
        Write-Host "Failed to emit telemetry event error={$_}"
        Write-Log "Failed to emit telemetry event error={$_}"
    }
}

function Test-ArcHciMocCloudServiceIP {
    <#
   .DESCRIPTION
       Checks if the cloudservice ip addresses is in the proper format and is in the same subnet as host network.
    
   .PARAMETER cloudServiceIP
       The value of the cloud service IP
   #>

    param
    (
        [Parameter(Mandatory = $false)]  
        [String]      $cloudServiceIP         
    )

    $testReport = [pscustomobject]@{
        'TestName'   = 'Test MOC cloud service ip';
        'TestResult' = "Succeeded";
        'Details'    = ""
    }
    #Checks against cloudServiceIP

  
    $mocInstallationState = Get-ArcHciMoc
    if ($mocInstallationState.installState -eq 7) {
        $testReport.Details = "MOC is already Installed."
        return $testReport
    }  

    $dhcpEnabledCluster = $True

    $clusterNetworks = Get-ClusterNetwork -ErrorAction SilentlyContinue | Where-Object { ($_.Role -eq "ClusterAndClient" ) `
            -and ($_.Ipv4Addresses.Count -gt 0 ) } | Select-Object -property Name, IPv4Addresses, IPV4PrefixLengths
   
    foreach ($clusterNetwork in $clusterNetworks) {
        for ($i = 0; $i -lt $clusterNetwork.Ipv4Addresses.Count; $i++) {
            #Multiple interfaces can be linked to a cluster network.
            if ((Get-ClusterNetworkInterface -Network $($clusterNetwork.Name) | Select-Object -expandProperty "DhcpEnabled") -ne 1) {
                $dhcpEnabledCluster = $False 
            }
        }
    }

    if ([string]::IsNullOrEmpty($cloudServiceIP)) {
        if ($dhcpEnabledCluster) {
            $testReport.Details = "This is a dhcp enabled cluster. cloudServiceIp is not required"

        }
        else {
            $testReport.TestResult = "Failed"
            $testReport.Details = "This is a static ip cluster, cloudServiceIp is required. Please provide an ip address."
        }
        return $testReport
    } 

    #Check if Cloud Service IP is in valid format.
    if (-Not [System.Net.IPAddress]::TryParse($cloudServiceIP, [ref]$null)) {
        $testReport.TestResult = "Failed"
        $testReport.Details = "CloudServiceIP is not in the proper format"
        return $testReport
    }
   
    #Check if cloud service IP is already in use
    if (Test-NetConnection $cloudServiceIP -InformationLevel "Quiet"  -ErrorAction Ignore -WarningAction SilentlyContinue) {
        $testReport.TestResult = "Failed"
        $testReport.Details = "CloudServiceIP is already in use. Please specify a different ip address."
        return $testReport
    }

   
   
    $foundInClusterNetwork = $False
   
    #Check if Cloud service CIDR is part of Cluster network
    foreach ($clusterNetwork in $clusterNetworks) {
        for ($i = 0; $i -lt $clusterNetwork.Ipv4Addresses.Count; $i++) {
            [System.Net.IPAddress]$ipv4 = $null
            $clusIpv4 = $clusterNetwork.Ipv4Addresses[$i]
            if (-Not [System.Net.IPAddress]::TryParse($clusIpv4, [ref] $ipv4)) {
                Write-Warning $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_ignore_failover_ip , $clusIpv4))
                continue
            }
           
            $lastIp = [AKSHCI.IPUtilities]::GetLastIpInCidr($ipv4, $clusterNetwork.Ipv4PrefixLengths[$i])
            if ([AKSHCI.IPUtilities]::CompareIpAddresses($cloudServiceIP, $ipv4) -ge 0 -AND [AKSHCI.IPUtilities]::CompareIpAddresses($cloudServiceIP, $lastIp) -le 0) {
                $foundInClusterNetwork = $True
                #The cloud service CIDR is in the range of the cluster network IP! Compare prefix lengths now!
                break
            }
        }
    }

    if ($foundInClusterNetwork -ne $True) {
        $testReport.TestResult = "Failed"
        $testReport.Details = "CloudServiceIP not in subnet of host network."
    } 

    return $testReport
}

function Reset-ArcHciConfigurationDirectory {
    <#
    .DESCRIPTION
        Set ArcHci configuration directory to different path.
    .PARAMETER volumePath
        Mandatory parameter. Volume Path
    .OUTPUTS
        N/A
    .EXAMPLE
        Reset-ArcHciConfigurationDirectory -volumePath $volumePath
    #>

    Param(
        [parameter(Mandatory = $true)]
        [ValidateScript({
                if ($_ -match $space) {
                    throw $spaceErrorForFolderFilePath
                }  
                if (-not ($_ | Test-Path)) {
                    throw $fileFolderPathError
                }
                return $true
            })]
        [string] $volumePath
    )
    Reset-ArcHciConfigurationKey
    $workingDir = Join-Path -Path $volumePath -ChildPath "WorkingDirectory\Appliance"
    if (Test-Path $workingDir) {
        Save-ConfigurationDirectory -moduleName $global:ArcHciModule -WorkingDir $workingDir
        $configDir = Get-ConfigurationDirectory -moduleName $global:ArcHciModule
        Write-Log "ArcHci configuration directory is set to $configDir"
        Write-Output "ArcHci configuration directory is set to $configDir"
    }
    else {
        Write-Log "Couldn't reset Archci configuration directory. $workingDir path doesn't exist"
        Write-Output "Couldn't reset Archci configuration directory. $workingDir path doesn't exist"
    }
}
# SIG # Begin signature block
# MIIoLQYJKoZIhvcNAQcCoIIoHjCCKBoCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBVQwZ9tUvmt1zQ
# eZzxD/y4hl3NLX8HCL5HZnBiZIi026CCDXYwggX0MIID3KADAgECAhMzAAADrzBA
# DkyjTQVBAAAAAAOvMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMxMTE2MTkwOTAwWhcNMjQxMTE0MTkwOTAwWjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDOS8s1ra6f0YGtg0OhEaQa/t3Q+q1MEHhWJhqQVuO5amYXQpy8MDPNoJYk+FWA
# hePP5LxwcSge5aen+f5Q6WNPd6EDxGzotvVpNi5ve0H97S3F7C/axDfKxyNh21MG
# 0W8Sb0vxi/vorcLHOL9i+t2D6yvvDzLlEefUCbQV/zGCBjXGlYJcUj6RAzXyeNAN
# xSpKXAGd7Fh+ocGHPPphcD9LQTOJgG7Y7aYztHqBLJiQQ4eAgZNU4ac6+8LnEGAL
# go1ydC5BJEuJQjYKbNTy959HrKSu7LO3Ws0w8jw6pYdC1IMpdTkk2puTgY2PDNzB
# tLM4evG7FYer3WX+8t1UMYNTAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQURxxxNPIEPGSO8kqz+bgCAQWGXsEw
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMTgyNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAISxFt/zR2frTFPB45Yd
# mhZpB2nNJoOoi+qlgcTlnO4QwlYN1w/vYwbDy/oFJolD5r6FMJd0RGcgEM8q9TgQ
# 2OC7gQEmhweVJ7yuKJlQBH7P7Pg5RiqgV3cSonJ+OM4kFHbP3gPLiyzssSQdRuPY
# 1mIWoGg9i7Y4ZC8ST7WhpSyc0pns2XsUe1XsIjaUcGu7zd7gg97eCUiLRdVklPmp
# XobH9CEAWakRUGNICYN2AgjhRTC4j3KJfqMkU04R6Toyh4/Toswm1uoDcGr5laYn
# TfcX3u5WnJqJLhuPe8Uj9kGAOcyo0O1mNwDa+LhFEzB6CB32+wfJMumfr6degvLT
# e8x55urQLeTjimBQgS49BSUkhFN7ois3cZyNpnrMca5AZaC7pLI72vuqSsSlLalG
# OcZmPHZGYJqZ0BacN274OZ80Q8B11iNokns9Od348bMb5Z4fihxaBWebl8kWEi2O
# PvQImOAeq3nt7UWJBzJYLAGEpfasaA3ZQgIcEXdD+uwo6ymMzDY6UamFOfYqYWXk
# ntxDGu7ngD2ugKUuccYKJJRiiz+LAUcj90BVcSHRLQop9N8zoALr/1sJuwPrVAtx
# HNEgSW+AKBqIxYWM4Ev32l6agSUAezLMbq5f3d8x9qzT031jMDT+sUAoCw0M5wVt
# CUQcqINPuYjbS1WgJyZIiEkBMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGg0wghoJAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIGBiKIFhbMfj4sOesbxeLTNz
# zkDwWKZ8gFWsX+Z45K6OMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAof413mPlmazm88lkNW5P2XopDQSEZGf7/C838+8O7Gw8H+Z4T4HlMvFn
# D5us6QNn9Af6jSsS2fjkc+Pbwy+QFJSUs/eeeRh0NM6s+YTCBLTfFq/yMA08O3AS
# 8UiPykQCFPW3TRgWqZgSxZcpJ93PSTXDkW1I0TK6hdpgMGTAjnpfCIndRDgnypa6
# jaw11mBYwmZgpEglr4t9zhdWQKt1el6okCEDF80U4oOZdju6GAeuao0cEmRlQkcG
# r3yxbvQW2wrP3IpJzPVxTjtxBh7M3mJHv0V4B0aSj+yac+ZG+RWHNQydKuoma7x9
# TCnHSqpiA5lNIj9AL0UQ3bQnY955zaGCF5cwgheTBgorBgEEAYI3AwMBMYIXgzCC
# F38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq
# hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCBYL4/JgwyNmMS3InYpFpqBSoH75BGduroWsgRaRuhdywIGZpV2le2J
# GBMyMDI0MDgwMjIyMTMwNS41NjhaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l
# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046MzcwMy0w
# NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg
# ghHtMIIHIDCCBQigAwIBAgITMwAAAeqaJHLVWT9hYwABAAAB6jANBgkqhkiG9w0B
# AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzEyMDYxODQ1
# MzBaFw0yNTAzMDUxODQ1MzBaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z
# MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046MzcwMy0wNUUwLUQ5NDcxJTAjBgNV
# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQC1C1/xSD8gB9X7Ludoo2rWb2ksqaF65QtJkbQpmsc6
# G4bg5MOv6WP/uJ4XOJvKX/c1t0ej4oWBqdGD6VbjXX4T0KfylTulrzKtgxnxZh7q
# 1uD0Dy/w5G0DJDPb6oxQrz6vMV2Z3y9ZxjfZqBnDfqGon/4VDHnZhdas22svSC5G
# HywsQ2J90MM7L4ecY8TnLI85kXXTVESb09txL2tHMYrB+KHCy08ds36an7IcOGfR
# mhHbFoPa5om9YGpVKS8xeT7EAwW7WbXL/lo5p9KRRIjAlsBBHD1TdGBucrGC3TQX
# STp9s7DjkvvNFuUa0BKsz6UiCLxJGQSZhd2iOJTEfJ1fxYk2nY6SCKsV+VmtV5ai
# PzY/sWoFY542+zzrAPr4elrvr9uB6ci/Kci//EOERZEUTBPXME/ia+t8jrT2y3ug
# 15MSCVuhOsNrmuZFwaRCrRED0yz4V9wlMTGHIJW55iNM3HPVJJ19vOSvrCP9lsEc
# EwWZIQ1FCyPOnkM1fs7880dahAa5UmPqMk5WEKxzDPVp081X5RQ6HGVUz6ZdgQ0j
# cT59EG+CKDPRD6mx8ovzIpS/r/wEHPKt5kOhYrjyQHXc9KHKTWfXpAVj1Syqt5X4
# nr+Mpeubv+N/PjQEPr0iYJDjSzJrqILhBs5pytb6vyR8HUVMp+mAA4rXjOw42vkH
# fQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFCuBRSWiUebpF0BU1MTIcosFblleMB8G
# A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG
# Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy
# MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w
# XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy
# dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG
# A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD
# AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQAog61WXj9+/nxVbX3G37KgvyoNAnuu2w3H
# oWZj3H0YCeQ3b9KSZThVThW4iFcHrKnhFMBbXJX4uQI53kOWSaWCaV3xCznpRt3c
# 4/gSn3dvO/1GP3MJkpJfgo56CgS9zLOiP31kfmpUdPqekZb4ivMR6LoPb5HNlq0W
# bBpzFbtsTjNrTyfqqcqAwc6r99Df2UQTqDa0vzwpA8CxiAg2KlbPyMwBOPcr9hJT
# 8sGpX/ZhLDh11dZcbUAzXHo1RJorSSftVa9hLWnzxGzEGafPUwLmoETihOGLqIQl
# Cpvr94Hiak0Gq0wY6lduUQjk/lxZ4EzAw/cGMek8J3QdiNS8u9ujYh1B7NLr6t3I
# glfScDV3bdVWet1itTUoKVRLIivRDwAT7dRH13Cq32j2JG5BYu/XitRE8cdzaJmD
# VBzYhlPl9QXvC+6qR8I6NIN/9914bTq/S4g6FF4f1dixUxE4qlfUPMixGr0Ft4/S
# 0P4fwmhs+WHRn62PB4j3zCHixKJCsRn9IR3ExBQKQdMi5auiqB6xQBADUf+F7hSK
# ZfbA8sFSFreLSqhvj+qUQF84NcxuaxpbJWVpsO18IL4Qbt45Cz/QMa7EmMGNn7a8
# MM3uTQOlQy0u6c/jq111i1JqMjayTceQZNMBMM5EMc5Dr5m3T4bDj9WTNLgP8SFe
# 3EqTaWVMOTCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI
# hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy
# MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
# AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg
# M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF
# dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6
# GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp
# Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu
# yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E
# XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0
# lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q
# GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ
# +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA
# PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw
# EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG
# NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV
# MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj
# cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK
# BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC
# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX
# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v
# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI
# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG
# 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x
# M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC
# VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449
# xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM
# nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS
# PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d
# Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn
# GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs
# QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL
# jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL
# 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNQ
# MIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn
# MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjM3MDMtMDVFMC1EOTQ3MSUwIwYDVQQD
# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQCJ
# 2x7cQfjpRskJ8UGIctOCkmEkj6CBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6leujDAiGA8yMDI0MDgwMjE5MTQy
# MFoYDzIwMjQwODAzMTkxNDIwWjB3MD0GCisGAQQBhFkKBAExLzAtMAoCBQDqV66M
# AgEAMAoCAQACAgTyAgH/MAcCAQACAhLoMAoCBQDqWQAMAgEAMDYGCisGAQQBhFkK
# BAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJ
# KoZIhvcNAQELBQADggEBACGvQxq79Oym3ypI/GX4QmWHPgu9l8uKBNRErIIPRbRk
# W9VQpiaudWt4vZQAQZoXduiQtRSsXBNXzWkpVL49TFaUo2dBjknrCfzbkMaZfhvI
# MORqxBOiZq0oJJMVs8rUyG1WC9CT3H7vfCRpSUoEyhgt25qNpuV2B5ZRBaE12xxA
# aathmDoCRoAC0j8JXCsUC6ldT7LeeCtCKRdoVmRgmasrx/zf4XSf9I/iMh3Z+q1P
# cg/qFtHrdUcYHnCpNQ3huEKNKrjLQjUexSSxUK8g69dJCSZO3vff35nJ9SYcG6In
# oqjfssUDUEhQihSR8LNsRHs1vVhJmvwbi9znDBQoWzcxggQNMIIECQIBATCBkzB8
# MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk
# bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N
# aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAeqaJHLVWT9hYwABAAAB
# 6jANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEE
# MC8GCSqGSIb3DQEJBDEiBCBuSR87Yz9Hv4504VQjLyW5mK4xRns+3VAtLadxRmiL
# 1TCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EICmPodXjZDR4iwg0ltLANXBh
# 5G1uKqKIvq8sjKekuGZ4MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB
# IDIwMTACEzMAAAHqmiRy1Vk/YWMAAQAAAeowIgQgOuRj1Kulvkf+Og+BPvCELag7
# QfdDx5Dg2aKZ2qAhISgwDQYJKoZIhvcNAQELBQAEggIASB1ChyW1DR1v9n6q28o3
# j0kpTQYT1dh3alrqz/wZlyjtgCDesYyMqzm2U3OG6M1MzheBK+PbCQp/Z91tihMR
# 0gnw42EjAISHduHD4zR/qhqyGPvZWxaOQvsb3tk9AQ635h68LbSjP2SHiCbPfSDh
# lwy3FEYFq3x7nwYXPy6ygxvHQkajZJPuDPhMwhNhg+CbhBqN6QMbl4MQYN4ugA+p
# ISUZKHSu2rRZEgGb/K2i6H6GsWWycLZ18TRQzLblNFhnd5n9c06X+f9iA6EqGNMl
# FEuQATi2n72clvjOr8aUkNtqWiG/SGlaJELmbcTlbFq8XamoaY/7oZj5IF2MC2Dz
# y9nxAGTqgZXQl2rdAhc23055v13mAzMu4CFQE0tpLcquWb87yTjFgCFQoqCm5gto
# SoUsXMlnCQckl2DBGpMBzT6LMZ/kyYHG6kQotUDSlZLywSSQVs5d120DkedaTR68
# T+3P1l+gknsPCZcLGS+4kdNXMpfPvDKLK23OgJrz92db5042S0uW8SHM+6lAAvH6
# NurlPfKEGnfLPQfyffNtO8f4E9+m1i1WojogTrfTwNYSW5IQyZPgWBi19gKtwKbh
# TkTJSlqwjzzzKq4R1+R1zXyQ08+Rkx6snhhpeDUKPJW6GV2cF976V3edoi5lrBBn
# 1ryedxmgOo4uV4weRUO3KGE=
# SIG # End signature block