Moc.psm1

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

#requires -runasadministrator

using module .\Common.psm1

#region Module Constants

$moduleName       = "Moc"
$moduleVersion    = "1.0.38"

#endregion

#region Download catalog constants

# Default until MOC has it's own catalog
$catalogName = "aks-hci-stable-catalogs-ext"
$ringName    = "stable"
$productName = "mocstack"

#endregion

#region Global Config

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

#region Script Constants

$mocBinaries = @(
    "wssdagent.exe"
    "wssdcloudagent.exe"
    "mocctl.exe"
    "nodectl.exe"
)

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

$svcFailureRestartMs     = 60000
$svcFailureResetSecond   = 86400
$svcNodeAgentDependency  = "winmgmt"
$script:defaultTokenExpiryDays = 90
$script:mocVersionSept    = "1.0.4.10928"
$script:mocVersionNov    = "1.0.6.11122"
$script:mocVersionJan    = "1.0.7.10118"
$script:mocVersionMar    = "1.0.9.10413"
$script:mocVersionMay    = "1.0.10.10513"
$script:mocVersionJune    = "1.0.11.10707"
$script:mocVersionAug     = "1.0.13.10907"


$global:cloudAgentRegistryPath         = "HKLM:\SOFTWARE\Microsoft\wssdcloudagent"
$global:nodeAgentRegistryPath          = "HKLM:\SOFTWARE\Microsoft\wssdagent"
$global:cloudAgentCertName             = "cloudagent.pem"
$global:nodeAgentCertName              = "nodeagent.pem"
$global:cloudloginYAMLName             = "cloudlogin.yaml"
$global:nodeloginYAMLName              = "nodelogin.yaml"
$global:nodeToCloudloginYAMLName       = "nodeToCloudlogin.yaml"
$global:VMMSSpec                       = "vmms"
$global:failoverClusterSpec            = "fc"
$global:cloudAgentCACertName           = "CloudAgent"
$global:cloudAgentServerCertName       = "Server"
$global:smallBinConcurrentDownloads    = 1

$mocBinariesMap = @{
    $global:nodeCtlBinary = $global:nodeCtlFullPath;
    $global:cloudCtlBinary = $global:cloudCtlFullPath;
    $global:cloudAgentBinary = $global:cloudAgentFullPath;
    $global:nodeAgentBinary = $global:nodeAgentFullPath;
}

#endregion

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

Import-LocalizedData -BindingVariable "GenericLocMessage" -FileName commonLocalizationMessages
Import-LocalizedData -BindingVariable "MocLocMessage" -FileName MocLocalizationMessages

#region Private Function

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

    if ($global:config.ContainsKey($moduleName)) {
        $global:config.Remove($moduleName)
    }
    $global:config += @{
        $moduleName = @{
            "cloudAgentAuthorizerPort" = 0
            "cloudAgentPort"           = 0
            "cloudConfigLocation"      = $global:defaultCloudConfigLocation
            "cloudFqdn"                = "localhost"
            "cloudLocation"            = ""
            "cloudServiceCidr"         = ""
            "clusterRoleName"          = ""
            "deploymentType"           = [DeploymentType]::None
            "dnsservers"               = ""
            "forceDnsReplication"      = $false
            "gateway"                  = ""
            "imageDir"                 = ""
            "insecure"                 = $false
            "installationPackageDir"   = ""
            "installState"             = [InstallState]::NotInstalled
            "ipaddressprefix"          = ""
            "k8snodeippoolstart"       = ""
            "k8snodeippoolend"         = ""
            "macPoolEnd"               = ""
            "macpoolname"              = ""
            "macPoolStart"             = ""
            "manifestCache"            = ""
            "mocCertLocation"          = [io.Path]::Combine($global:defaultCloudConfigLocation, $global:cloudAgentCertName)
            "mocLoginYAML"             = [io.Path]::Combine($global:defaultNodeConfigLocation, $global:cloudloginYAMLName)
            "moduleVersion"            = $moduleVersion
            "nodeAgentAuthorizerPort"  = 0
            "nodeAgentPort"            = 0
            "nodeCertLocation"         = [io.Path]::Combine($global:defaultCloudConfigLocation, $global:nodeAgentCertName)
            "nodeConfigLocation"       = $defaultNodeConfigLocation
            "nodeLoginYAML"            = [io.Path]::Combine($global:defaultNodeConfigLocation, $global:nodeloginYAMLName)
            "nodeToCloudLoginYAML"     = [io.Path]::Combine($global:defaultNodeConfigLocation, $global:nodeToCloudloginYAMLName)
            "skipHostLimitChecks"      = $false
            "skipUpdates"              = $false
            "sshPrivateKey"            = ""
            "sshPublicKey"             = ""
            "stagingShare"             = ""
            "tokenExpiryDays"          = 0
            "useStagingShare"          = $false
            "version"                  = ""
            "vlanid"                   = 0
            "vnetName"                 = ""
            "vswitchName"              = ""
            "vnetvippoolend"           = ""
            "vnetvippoolstart"         = ""
            "workingDir"               = ""
            "catalog"                  = ""
            "ring"                     = ""
            "proxyServerCertFile"      = ""
            "proxyServerHTTP"          = ""
            "proxyServerHTTPS"         = ""
            "proxyServerNoProxy"       = ""
            "proxyServerPassword"      = ""
            "proxyServerUsername"      = ""
            "certificateValidityFactor" = $global:certificateValidityFactor
            "caCertificateValidityFactor" = $global:caCertificateValidityFactor
            "nodeCertificateValidityFactor" = $global:nodeCertificateValidityFactor
            "cloudAgentTimeout"        = $global:cloudAgentTimeout
            "deploymentId"             = ""
            "nodeCount"                = 1
            "useNetworkController" = $false
            "networkControllerFqdnOrIpAddress" = ""
            "networkControllerClientCertificateName" = ""
            "networkControllerLbSubnetRef" = ""
            "networkControllerLnetRef" = ""
            "defaultVipPoolName"        = ""
            "vipPoolStart"       = ""
            "vipPoolEnd"         = ""
            "offlineDownload"         = $false
            "downloadCompleted"       = $true
            "accessFileDirPath"       = ""
            "accessFilePath"          = ""
        };
    }
}

#endregion

# Initialize
Initialize-MocConfiguration
#endregion


#region invoke helpers

function Invoke-MocShowCommand
{
    <#
    .DESCRIPTION
        Executes a cloudagent command either against a local cloudagent (single node deployment) or a cluster generic
        service (multi-node/cluster deployments).
 
    .PARAMETER arguments
        Arguments to pass to cloud ctl.
 
    .PARAMETER ignoreError
        Optionally, ignore errors from the command (don't throw).
    #>


    param (
        [String]$arguments,
        [Switch]$ignoreError,
        [ValidateSet("tsv", "csv", "yaml", "json")]
        [string]$output = "json"
    )

    $arguments += " --output $output"
    $out = Invoke-MocCommand -arguments $arguments -ignoreError:$ignoreError.IsPresent
    if ([string]::IsNullOrWhiteSpace($out))
    {
        return
    }
    return $out | ConvertFrom-Json
}
function Invoke-MocListCommand
{
    <#
    .DESCRIPTION
        Executes a cloudagent command either against a local cloudagent (single node deployment) or a cluster generic
        service (multi-node/cluster deployments).
 
    .PARAMETER arguments
        Arguments to pass to cloud ctl.
 
    .PARAMETER ignoreError
        Optionally, ignore errors from the command (don't throw).
    #>


    param (
        [String]$arguments,
        [Switch]$ignoreError,
        [ValidateSet("tsv", "csv", "yaml", "json")]
        [string]$output = "json",
        [string]$filter
    )

    $arguments += " --output $output"
    if ($filter)
    {
        $arguments += " --query ""$filter"""
    }
    $out = Invoke-MocCommand -arguments $arguments -ignoreError:$ignoreError.IsPresent 
    if ([string]::IsNullOrWhiteSpace($out) -or $out -like "No *")
    {
        return
    }
    Write-Verbose "$out"
    return $out | ConvertFrom-Json
}
function Invoke-MocCommand
{
    <#
    .DESCRIPTION
        Executes a cloudagent command either against a local cloudagent (single node deployment) or a cluster generic
        service (multi-node/cluster deployments).
 
    .PARAMETER arguments
        Arguments to pass to cloud ctl.
 
    .PARAMETER ignoreError
        Optionally, ignore errors from the command (don't throw).
 
    .PARAMETER argDictionary
        Dictionary of arguments (e.g. obtained from $PSBoundParameters).
 
    .PARAMETER boolFlags
        List of boolean flags to be passed
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    param (
        [String]$arguments,
        [Switch]$ignoreError,
        [System.Collections.IDictionary] $argDictionary,
        [string[]] $boolFlags,
        [String]$activity = $MyInvocation.MyCommand.Name
    )

    Initialize-MocEnvironment -activity $activity

    if (-not (Get-Command $global:cloudCtlFullPath -ErrorAction SilentlyContinue)) {
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_missing_cmd , $global:cloudCtlFullPath))
    }

    $cloudFqdn = Get-CloudFqdn

    $cmdArgs = "--cloudFqdn $cloudFqdn $arguments"
    if ($global:config[$global:MocModule]["insecure"])
    {
        $cmdArgs += " --debug" 
    }

    $argString = ConvertTo-ArgString -argDictionary $argDictionary -boolFlags $boolFlags

    if ($argString)
    {
        $cmdArgs += " " + $argString
    }

    try 
    {
        Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.invoke_moc_command, $cmdArgs))
        $response = Invoke-CommandLine -Command $global:cloudCtlFullPath -Arguments $cmdArgs -ignoreError:$ignoreError -moduleName $global:MocModule
    } 
    catch 
    {
        $isCertificateExpired = $_.Exception -like "*Error: Certificate has expired: Expired*"
        if($isCertificateExpired) {
            Write-Host "Warning: The Certificate was expired. Renewing now."
            Repair-MocLogin
            $response = Invoke-CommandLine -Command $global:cloudCtlFullPath -Arguments $cmdArgs -ignoreError:$ignoreError -moduleName $global:MocModule
        } 
        else 
        {
            if ($_.Exception.Message)
            {
                Write-StatusWithProgress -activity $activity -moduleName $moduleName  `
                    -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.invoke_moc_command_failed, $cmdArgs, $_.Exception.Message))
            }
            throw $_
        }
    }
    Write-Status $response -moduleName $global:MocModule

    return $response
}

function Invoke-NodeCommand
{
    <#
    .DESCRIPTION
        Executes a nodeagent command.
 
    .PARAMETER arguments
        Arguments to pass to node ctl.
    #>


    param (
        [String]$arguments
    )

    if (-not (Get-Command $global:nodeCtlFullPath -ErrorAction SilentlyContinue)) {
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_missing_cmd , $global:nodeCtlFullPath))
    }

    $cmdArgs = "$arguments"
    if ($global:config[$global:MocModule]["insecure"])
    {
        $cmdArgs += " --debug" 
    }

    Invoke-CommandLine -Command $global:nodeCtlFullPath -Arguments $cmdArgs -moduleName $global:MocModule
}

function Invoke-MocLogin
{
    <#
    .DESCRIPTION
        Provisions the Script to have access to node ctl
 
    .PARAMETER nodeName
        The node to execute on.
    #>

    param (
        [Parameter(Mandatory=$true)]
        [String]$loginYaml
    )
    Invoke-MocCommand $(" security login --loginpath ""$loginYaml"" --identity")
}

#endregion

#region Exported Functions

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


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

    Initialize-MocEnvironment -createConfigIfNotPresent -activity $activity

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

    try
    {
        Install-MocInternal -activity $activity
    }
    catch [Exception]
    {
        $errorMessage = Write-ModuleEventException -message $MocLocMessage.moc_install_failed -exception $_ -moduleName $modulename
        Uninstall-Moc -SkipConfigCleanup:$True -activity $activity
        throw [System.Exception]::new($errorMessage, $_.Exception)
    }

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

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


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

    Initialize-MocEnvironment -activity $activity

    Uninstall-Moc -SkipConfigCleanup:$True -activity $activity

    Install-MocInternal -activity $activity

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

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


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

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

    Set-MocConfigValue -name "installState" -value ([InstallState]::Uninstalling)
    try
    {
        # Confirm-Remoting - We assume that the remoting check was already done and nothing has changed
        Reset-Host -removeAll -skipConfigDeletion:$SkipConfigCleanup.IsPresent
    }
    catch [Exception]
    {
        Write-ModuleEventLog -moduleName $moduleName -entryType Error -eventId 100 -message "$activity - $_"
    }

    Set-MocConfigValue -name "installState" -value ([InstallState]::NotInstalled)
    if (!$SkipConfigCleanup.IsPresent)
    {
        Reset-Configuration -moduleName $moduleName
    }
    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
}

function Initialize-MocNode
{
    <#
    .SYNOPSIS
        Run checks on every physical node to see if all requirements are satisfied to install.
 
    .DESCRIPTION
        Run checks on every physical node to see if all requirements are satisfied to install.
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


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

    Write-StatusWithProgress -activity $activity -module $moduleName -status $($MocLocMessage.moc_initializing_node)
    
    #Initialize-MocEnvironment

    Enable-Remoting

    Confirm-Remoting -localhostOnly

    Test-ForWindowsFeatures -features $script:requiredServerFeatures -nodeName $env:computername

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

function Update-Moc
{
    <#
    .DESCRIPTION
        Update MOC to specified version. If not use the latest version
 
    .PARAMETER version
        The Optional version to update to. If none specified, it would be
        updated to latest
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


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

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

    $currentVersion = Get-MocVersion
    Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_current_version, $currentVersion))


    # This code is needed as a temporary solution till May2022 release is obselete.
    # the deployment ID was not part of MocConfig till June2022 release. During upgrade of AksHci from any previous
    # versions the deployment ID is passed from the upgrade-akshci.
    #
    # The only catch we have is if the update-Moc is called directly then we would not have the deployment ID
    # we would need to generate a new deployment ID if the set-mocconfig did not set the deployment ID
    if (-not [string]::IsNullOrWhiteSpace($deploymentId))
    {
        Set-MocConfigValue -name "deploymentId" -value $deploymentId
    } 
    else {
        # If the deploymentID passed is empty, We check the global moc config to see if the value already exists.
        # If it does not we genreate a new ID
        $deploymentId = Get-MocConfigValue -name "deploymentId"
        if ([string]::IsNullOrWhiteSpace($deploymentId)) {
            $deploymentId = [Guid]::NewGuid().ToString()
            Set-MocConfigValue -name "deploymentId" -value $deploymentId
        }
    }

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

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

    try {
        $installationPackageDir = ([io.Path]::Combine($workingDir, $version))
        Set-MocConfigValue -name "installationPackageDir" -value $installationPackageDir
        New-Item -ItemType Directory -Force -Path $installationPackageDir | Out-Null
        Update-MocInternal -activity $activity -version $version
    } catch {
        $errorMessage = Write-ModuleEventException -message "Update-Moc failed." -exception $_ -moduleName $modulename

        # Collect logs
        try 
        {
            $tpath = Get-MocLogs
            $errorMessage += "`r`n Logs are available at $tpath"
        } 
        catch 
        {
            Write-ModuleEventLog -moduleName $moduleName -entryType Warning -eventId 2 -message "$activity - $_"
        }

        Set-MocConfigValue -name "version" -value $currentVersion
        Set-MocConfigValue -name "installationPackageDir" -value $([io.Path]::Combine($workingDir, $currentVersion))
        # Revert
        Update-MocInternal -activity $activity -version $currentVersion
        throw [System.Exception]::new($errorMessage, $_.Exception)
    }
}

function Update-MocInternal
{
    <#
    .DESCRIPTION
        Upgrade MOC to selected version.
 
    .PARAMETER activity
        Activity name to use when updating progress
 
    .PARAMETER version
        Moc version to upgrade
    #>


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

    trap
    {
        Set-MocConfigValue -name "installState" -value ([InstallState]::UpdateFailed)
        throw $_ 
    }

    Set-MocConfigValue -name "installState" -value ([InstallState]::Updating)

    if([version]$global:config[$moduleName]["version"] -ge [version]$script:mocVersionAug) {
        Set-MocConfigValue -name "accessFileDirPath" -value ([io.Path]::Combine($global:config[$moduleName]["workingDir"], $global:accessFileDir))
        Set-MocConfigValue -name "accessFilePath" -value ([io.Path]::Combine($global:config[$moduleName]["accessFileDirPath"], $global:accessFileDirMoc, $global:accessFileName))
    }
    $version = Get-MocVersion
    Write-StatusWithProgress -activity $activity -module $moduleName -status $($GenericLocMessage.comm_updating_to_version)

    New-Item -ItemType Directory -Force -Path $([io.Path]::Combine($global:config[$modulename]["installationPackageDir"], $global:yamlDirectoryName)) | Out-Null

    Get-MocRelease -version $version -activity $activity

    # 1. Stop the agents
    # 2. Upgrade Agents
    $isMultiNodeDeployment = Test-MultiNodeDeployment 
    if ($isMultiNodeDeployment)
    {
        Get-ClusterGroup $global:config[$modulename]["clusterRoleName"].ToString()  -ErrorAction Ignore | ForEach-Object {
            Stop-ClusterGroup -InputObject $_ -IgnoreLocked -ErrorAction Ignore
        }

        Get-ClusterNode -ErrorAction Stop | ForEach-Object {
            Invoke-Command -ComputerName $_ -ScriptBlock {
                Stop-Service wssdagent -Force
            }
            Update-MocNode -nodeName $_.Name -activity $activity
            # remove this function call when Sept release is not supported
            Update-CloudAgent -cloudAgentName $global:cloudAgentAppName -version $version
            Update-NodeAgent -nodeName $_.Name -activity $activity -version $version
        }
    }
    else
    {
        Stop-Service wssdcloudagent -Force
        Stop-Service wssdagent -Force
        Update-CloudAgent -cloudAgentName $global:cloudAgentAppName -version $version
        Update-NodeAgent -nodeName ($env:computername) -activity $activity -version $version
        Update-MocNode -nodeName ($env:computername) -activity $activity
    }
    # 3. Start Cloud agent

    if ($isMultiNodeDeployment)
    {
        Get-ClusterGroup $global:config[$modulename]["clusterRoleName"].ToString()  -ErrorAction Stop | ForEach-Object { 
            Start-ClusterGroup -InputObject $_ -IgnoreLocked -ErrorAction Stop
        }
    }
    else
    {
        Start-Service wssdcloudagent -ErrorAction Ignore -WarningAction:SilentlyContinue
    }

    # We must wait for the generic service VIP to become usable
    Wait-ForCloudAgentEndpoint -activity $activity

    # 4. Start Node agents
    if ($isMultiNodeDeployment)
    {   
        $nodeCloudLoginFile = $global:config[$modulename]["nodeToCloudLoginYAML"]
        Get-ClusterNode -ErrorAction Stop | ForEach-Object {
            if (-not $global:config[$modulename]["insecure"])
            {
                if (([version]$version) -eq ([version]$script:mocVersionMar)) {
                    Set-MocConfigValue -name "tokenExpiryDays" -value $script:defaultTokenExpiryDays
                    Update-MocIdentity -name $_.Name -validityDays $global:config[$moduleName]["tokenExpiryDays"] -location $global:config[$modulename]["cloudLocation"] | Out-Null
                }
                $nodeIdentity = Invoke-MocIdentityRotate -name $_.Name
            }
            Invoke-Command -ComputerName $_ -ScriptBlock {
                $nodeToCloudLoginFile = $args[0]
                $cloudagentlogin = $args[1]
                Set-Content -Path $nodeToCloudLoginFile -Value $cloudagentlogin -ErrorVariable err
                Start-Service wssdagent -WarningAction:SilentlyContinue
            } -ArgumentList $nodeCloudLoginFile, $nodeIdentity
        }
    }
    else
    {
        $nodeCloudLoginFile = $global:config[$modulename]["nodeToCloudLoginYAML"]
        if (-not $global:config[$modulename]["insecure"])
        {
            if (([version]$version) -eq ([version]$script:mocVersionMar)) {
                Set-MocConfigValue -name "tokenExpiryDays" -value 90
                Update-MocIdentity -name ($env:computername) -validityDays $global:config[$moduleName]["tokenExpiryDays"] -location $global:config[$modulename]["cloudLocation"] | Out-Null
            }
            $nodeIdentity = Invoke-MocIdentityRotate -name ($env:computername)
        }
        Set-Content -Path $nodeCloudLoginFile -Value $nodeIdentity -ErrorVariable err
        Start-Service wssdagent -ErrorAction Ignore -WarningAction:SilentlyContinue
    }
    $mocLocation = $global:config[$modulename]["cloudLocation"]
    Wait-ForActiveNodes -location $mocLocation -activity $activity
    Set-MocConfigValue -name "installState" -value ([InstallState]::Installed)
}

function Repair-MocLogin
{
    <#
    .DESCRIPTION
        Relogin to Admin using updated cloudlogin.
     
    .PARAMETER activity
        Activity name to use when updating progress
    #>

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

    Write-StatusWithProgress -activity $activity -module $moduleName -status $($GenericLocMessage.comm_repairing_admin_login)
    Initialize-MocEnvironment -activity $activity
    $loginpath = $global:config[$modulename]["mocLoginYAML"]
    Invoke-MocCommand " security login --loginpath ""$loginpath"" --identity"
}

function Repair-Moc
{
    <#
    .DESCRIPTION
        Repair cloudagent cert & nodeagent loginconfig.
 
    .PARAMETER activity
        Activity name to use when updating progress
 
    #>


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

    Write-StatusWithProgress -activity $activity -module $moduleName -status $($MocLocMessage.moc_repairing_nodeagent)
    Initialize-MocEnvironment -activity $activity
    Write-StatusWithProgress -activity $activity -module $moduleName -status $($MocLocMessage.moc_stopping_nodeagent)

    ## Set cloudAgentTimeout value if not set
    $cloudAgentTimeout = $global:config[$modulename]["cloudAgentTimeout"]
    if ((-not $cloudAgentTimeout) -or ($cloudAgentTimeout -eq 0)) {
        Set-ConfigurationValue -name "cloudAgentTimeout" -value $global:cloudAgentTimeout -module $moduleName
    }

    # 1. Stop the agents
    $isMultiNodeDeployment = Test-MultiNodeDeployment 
    $version = Get-MocVersion
    if ($isMultiNodeDeployment)
    {
        Get-ClusterGroup $global:config[$modulename]["clusterRoleName"].ToString()  -ErrorAction Ignore | ForEach-Object {
            Stop-ClusterGroup -InputObject $_ -IgnoreLocked -ErrorAction Ignore
        }

        Get-ClusterNode -ErrorAction Stop | ForEach-Object {
            Invoke-Command -ComputerName $_ -ScriptBlock {
                Stop-Service wssdagent -Force
            }
        }
    }
    else
    {
        Stop-Service wssdcloudagent -Force
        Stop-Service wssdagent -Force
    }
    
    # 2. Start the cloud agent
    if ($isMultiNodeDeployment)
    {
        Get-ClusterGroup $global:config[$modulename]["clusterRoleName"].ToString()  -ErrorAction Stop | ForEach-Object { 
            Start-ClusterGroup -InputObject $_ -IgnoreLocked -ErrorAction Stop 
        }
    }
    else
    {
        Start-Service wssdcloudagent -ErrorAction Ignore -WarningAction:SilentlyContinue
    }

    $cloudAgentTimeout = $global:config[$modulename]["cloudAgentTimeout"]
    Wait-ForCloudAgentEndpoint -timeout $cloudAgentTimeout -activity $activity

    Write-StatusWithProgress -activity $activity -module $moduleName -status $($MocLocMessage.moc_rotating_login_token)

    # 3. Rotate Token
    # 4. Restart agents
    if ($isMultiNodeDeployment)
    {   
        $nodeCloudLoginFile = $global:config[$modulename]["nodeToCloudLoginYAML"]
        Get-ClusterNode -ErrorAction Stop | ForEach-Object {
            if (-not $global:config[$modulename]["insecure"])
            {
                $nodeIdentity = Invoke-MocIdentityRotate -name $_.Name
            }
            Invoke-Command -ComputerName $_ -ScriptBlock {
                $nodeToCloudLoginFile = $args[0]
                $cloudagentlogin = $args[1]
                Set-Content -Path $nodeToCloudLoginFile -Value $cloudagentlogin -ErrorVariable err
                Start-Service wssdagent -WarningAction:SilentlyContinue
            } -ArgumentList $nodeCloudLoginFile, $nodeIdentity
        }
    }
    else
    {
        $nodeCloudLoginFile = $global:config[$modulename]["nodeToCloudLoginYAML"]
        if (-not $global:config[$modulename]["insecure"])
        {
            $nodeIdentity = Invoke-MocIdentityRotate -name ($env:computername)
        }
        Set-Content -Path $nodeCloudLoginFile -Value $nodeIdentity -ErrorVariable err
        Start-Service wssdagent -ErrorAction Ignore -WarningAction:SilentlyContinue
    }
    $mocLocation = $global:config[$modulename]["cloudLocation"]
    Wait-ForActiveNodes -location $mocLocation -activity $activity
}

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


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

    Initialize-MocEnvironment -activity $activity

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

function Update-MocNode {
    <#
    .DESCRIPTION
        Upgrade Moc binaries to latest on a node
 
    .PARAMETER nodeName
        The node to execute on.
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$nodeName,
        [String]$activity = $MyInvocation.MyCommand.Name
    )

    Write-StatusWithProgress -activity $activity -module $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_upgrading_node, $nodeName))

    Install-MocBinaries -nodeName $nodeName

    # Cleanup nodeagent console logs
    Invoke-Command -ComputerName $nodeName -ScriptBlock {
        $nodeLog = $args[0]
        Get-ChildItem -Recurse -Path $nodeLog -Filter wssd*.log -ErrorAction Ignore | ForEach-Object {
            Remove-Item -Path $_ -ErrorAction Ignore -Force
        }
    } -ArgumentList $global:config[$modulename]["nodeConfigLocation"]

    # Cleanup cloudagent console logs
    $cloudConfigPath = $global:config[$moduleName]["cloudConfigLocation"]
    Get-ChildItem -Recurse -Path $cloudConfigPath -Filter wssd*.log -ErrorAction Ignore | ForEach-Object {
        Remove-Item -Path $_ -ErrorAction Ignore -Force
    }
}

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


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

    Install-Binaries -nodeName $nodeName -binariesMap $mocBinariesMap -module $moduleName
}

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


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

    Uninstall-Binaries -nodeName $nodeName -binariesMap $mocBinariesMap -module $moduleName
}

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


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

    Import-MocConfig -activity $activity

    Write-StatusWithProgress -activity $activity -module $moduleName -status $($MocLocMessage.moc_get_configuration)
    # Fixup the type for readability reasons
    $global:config[$modulename]["installState"] = Get-ConfigurationValue -module $moduleName -type ([Type][InstallState]) -name "installState"
    $global:config[$modulename]["deploymentType"] = Get-ConfigurationValue -module $moduleName -type ([Type][DeploymentType]) -name "deploymentType"
    return $global:config[$modulename]
}

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


    [CmdletBinding()]
    param (
        [switch]$createIfNotPresent,
        [string]$activity = $MyInvocation.MyCommand.Name
    )

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

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

    if ($global:config[$moduleName]["cloudConfigLocation"] -ine $defaultCloudConfigLocation)
    {
        $tmp = ([io.Path]::Combine($global:config[$moduleName]["cloudConfigLocation"], $global:cloudAgentCertName))
        $currentValue = Get-MocConfigValue -name "mocCertLocation"
        if ($tmp -ne $currentValue)
        {
            Set-MocConfigValue -name "mocCertLocation" -value $tmp
        }

        $tmp = ([io.Path]::Combine($global:config[$moduleName]["cloudConfigLocation"], $global:cloudloginYAMLName))
        $currentValue = Get-MocConfigValue -name "mocLoginYAML"
        if ($tmp -ne $currentValue)
        {
            Set-MocConfigValue -name "mocLoginYAML" -value $tmp
        }
    }

    if ($global:config[$moduleName]["nodeConfigLocation"] -ine $defaultNodeConfigLocation)
    {
        $tmp = ([io.Path]::Combine($global:config[$moduleName]["nodeConfigLocation"], $global:nodeAgentCertName))
        $currentValue = Get-MocConfigValue -name "nodeCertLocation"
        if ($tmp -ne $currentValue)
        {
            Set-MocConfigValue -name "nodeCertLocation" -value $tmp
        }
    }

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

    if (-not (Test-LocalFilePath -path $global:config[$moduleName]["nodeConfigLocation"]))
    {
        throw $($MocLocMessage.moc_nodeConfigLocation_rerun)
    }
}

function Set-MocOffsiteConfig
{
    param (
        [parameter()]
        [String] $workingDir,

        [parameter()]
        [String] $catalog,

        [parameter()]
        [String] $ring,

        [parameter()]
        [String] $stagingShare,

        [parameter()]
        [String] $version
    )

    Set-MocConfigValue -name "catalog" -value $catalog
    Set-MocConfigValue -name "ring" -value $ring
    Set-MocConfigValue -name "workingDir" -value $workingDir
    Set-MocConfigValue -name "manifestCache" -value ([io.Path]::Combine($workingDir, $("$catalog.json")))
    Set-MocConfigValue -name "stagingShare" -value $stagingShare
    Set-MocConfigValue -name "offlineDownload" -value $true

    if (-not $version)
    {
        $version = Get-ConfigurationValue -Name "version" -module $moduleName
        if (-not $version)
        {
            # If no version is specified, use the latest
            $version = Get-MocLatestVersion
            Set-MocConfigValue -name "version" -value $version
        }
    }
    else
    {
        Get-MocLatestVersion | out-null # This clears the cache
        Get-ProductRelease -Version $version -module $moduleName | Out-Null
        Set-MocConfigValue -name "version" -value $version
    }

    Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_new_configuration_for_module_saved, $moduleName))
}

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


    [CmdletBinding()]
    param (
        [string] $activity = $MyInvocation.MyCommand.Name,
        [String] $workingDir = $global:defaultWorkingDir,
        [String] $imageDir,
        [String] $version,
        [String] $stagingShare = $global:defaultStagingShare,
        [String] $cloudConfigLocation = $global:defaultCloudConfigLocation,
        [String] $nodeConfigLocation = $global:defaultNodeConfigLocation,
        [String] $cloudLocation = $global:defaultCloudLocation,
        [VirtualNetwork] $vnet,
        [int] $nodeAgentPort = $global:defaultNodeAgentPort,
        [int] $nodeAgentAuthorizerPort = $global:defaultNodeAuthorizerPort,
        [int] $cloudAgentPort = $global:defaultCloudAgentPort,
        [int] $cloudAgentAuthorizerPort = $global:defaultCloudAuthorizerPort,
        [int] $tokenExpiryDays = $script:defaultTokenExpiryDays,
        [String] $clusterRoleName = $($global:cloudAgentAppName + "-" + [guid]::NewGuid()),
        [Alias("cloudServiceIP")]
        [String] $cloudServiceCidr = "",
        [String] $sshPublicKey,
        [Switch] $skipUpdates,
        [Switch] $skipHostLimitChecks,
        [Switch] $skipRemotingChecks,
        [Switch] $insecure,
        [Switch] $forceDnsReplication,
        [String] $macPoolStart,
        [String] $macPoolEnd,
        [switch] $useStagingShare,
        [String] $catalog = $script:catalogName,
        [String] $ring = $script:ringName,
        [bool] $createAutoConfigContainers = $global:defaultCreateAutoConfigContainers,
        [ProxySettings] $proxySettings = $null,
        [String] $deploymentId = [Guid]::NewGuid().ToString(),
        [parameter(DontShow)]
        [float] $certificateValidityFactor = $global:certificateValidityFactor,
        [parameter(DontShow)]
        [float] $caCertificateValidityFactor = $global:caCertificateValidityFactor,
        [parameter(DontShow)]
        [Switch] $enablePreview,
        [parameter(DontShow)]
        [float] $nodeCertificateValidityFactor = $global:nodeCertificateValidityFactor,
        [int] $cloudAgentTimeout = $global:cloudAgentTimeout,
        [Parameter(ParameterSetName = 'NetworkController')]
        [Switch] $useNetWorkController,
        [Parameter(ParameterSetName = 'NetworkController')]
        [string] $networkControllerFqdnOrIpAddress,
        [Parameter(ParameterSetName = 'NetworkController')]
        [string] $networkControllerClientCertificateName,
        [Parameter(ParameterSetName = 'NetworkController')]
        [string] $networkControllerLbSubnetRef,
        [Parameter(ParameterSetName = 'NetworkController')]
        [string] $networkControllerLnetRef,
        [VipPoolSettings] $vipPool,
        [Switch] $skipValidationCheck
    )
    # First get the existing config and find out if we are in the middle of something.
    try {
        Import-MocConfig -activity $activity
    } catch {}
    $currentState = Get-ConfigurationValue -module $moduleName -type ([Type][InstallState]) -name "installState"
    if ($currentState -ne $nil)
    {
        switch ($currentState) {
            ([InstallState]::NotInstalled) {
                # Fresh install
                break
            }
            Default {
                Write-Status -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_current_state, $currentState))
                throw [CustomException]::new( $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_no_new_config_in_current_state, $moduleName, $currentState)), $true)
            }
        }
    }

    if ([string]::IsNullOrWhiteSpace($workingDir))
    {
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_empty, "workingDir"))
    }

    if ([string]::IsNullOrWhiteSpace($imageDir))
    {
        $imageDir = [io.Path]::Combine($workingDir, $global:imageDirectoryName)
    }

    if ([string]::IsNullOrWhiteSpace($cloudConfigLocation))
    {
        $cloudConfigLocation = [io.Path]::Combine($workingDir, $global:cloudConfigDirectoryName)
    }

    if ($skipValidationCheck.IsPresent) {
        # if we are good, Validate the input configuration
        Confirm-Configuration -workingDir $workingDir -skipHostLimitChecks:$skipHostLimitChecks.IsPresent `
            -imageDir $imageDir -cloudConfigLocation $cloudConfigLocation -skipRemotingChecks:$skipRemotingChecks.IsPresent `
            -useStagingShare:$useStagingShare.IsPresent -stagingShare $stagingShare `
            -vnet $vnet -cloudServiceCidr $cloudServiceCidr `
            -useNetWorkController:$useNetWorkController.IsPresent `
            -networkControllerClientCertificateName $networkControllerClientCertificateName `
            -networkControllerLbSubnetRef $networkControllerLbSubnetRef `
            -networkControllerLnetRef $networkControllerLnetRef `
            -networkControllerFqdnOrIpAddress $networkControllerFqdnOrIpAddress
    } else {
        if ($skipHostLimitChecks.IsPresent)
        {
            Set-MocConfigValue -name "skipHostLimitChecks" -value $true
        }
        if ($skipRemotingChecks.IsPresent)
        {
            Set-MocConfigValue -name "skipRemotingChecks" -value $true
        }
    }
    Set-ProxyConfiguration -proxySettings $proxySettings -moduleName $moduleName

    # if we are good to proceed, create the requested configuration
    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_creating_configuration, $moduleName))

    Set-MocConfigValue -name "workingDir" -value $workingDir
    Set-MocConfigValue -name "manifestCache" -value ([io.Path]::Combine($workingDir, $("$catalog.json")))
    Set-MocConfigValue -name "imageDir" -value $imageDir
    Set-MocConfigValue -name "cloudConfigLocation" -value $cloudConfigLocation
    Set-MocConfigValue -name "moduleVersion" -value $moduleVersion
    Set-MocConfigValue -name "installState" -value ([InstallState]::NotInstalled)
    Set-MocConfigValue -name "stagingShare" -value $stagingShare
    Set-MocConfigValue -name "nodeConfigLocation" -value $nodeConfigLocation
    Set-MocConfigValue -name "cloudLocation" -value $cloudLocation
    Set-MocConfigValue -name "catalog" -value $catalog
    Set-MocConfigValue -name "ring" -value $ring
    Set-MocConfigValue -name "createAutoConfigContainers" -value $createAutoConfigContainers
    Set-MocConfigValue -name "deploymentId" -value $deploymentId
    Set-MocConfigValue -name "certificateValidityFactor" -value $certificateValidityFactor
    Set-MocConfigValue -name "caCertificateValidityFactor" -value $caCertificateValidityFactor
    Set-MocConfigValue -name "nodeCertificateValidityFactor" -value $nodeCertificateValidityFactor
    Set-MocConfigValue -name "cloudAgentTimeout" -value $cloudAgentTimeout
    
    if ($useNetworkController.IsPresent)
    {
        Set-MocConfigValue -name "useNetworkController" -value $true
        Set-MocConfigValue -name "networkControllerFqdnOrIpAddress" -value $networkControllerFqdnOrIpAddress
        Set-MocConfigValue -name "networkControllerClientCertificateName" -value $networkControllerClientCertificateName 
        Set-MocConfigValue -name "networkControllerLbSubnetRef" -value $networkControllerLbSubnetRef 
        Set-MocConfigValue -name "networkControllerLnetRef" -value $networkControllerLnetRef
    }
    else
    {
        Set-MocConfigValue -name "useNetworkController" -value $false
    }

    if ($vnet) {
        Set-VNetConfiguration -module $moduleName -vnet $vnet
    }

    if ($sshPublicKey)
    {
        # Set the SSH Key of moc Module to the one already being passed
        Set-MocConfigValue -name "sshPublicKey" -value $sshPublicKey
        Set-MocConfigValue -name "sshPrivateKey" `
            -value ([io.Path]::Combine([io.Path]::GetDirectoryName($sshPublicKey), [io.Path]::GetFileNameWithoutExtension($sshPublicKey)))
    }
    else
    {
        # There seems to be an issue with ssh_keygen. It doesnt create public key when using share.
        if ($workingDir.StartsWith("\\"))
        {
            Set-MocConfigValue -name "sshPublicKey" -value ("$env:USERPROFILE\.ssh\akshci_rsa.pub")
            Set-MocConfigValue -name "sshPrivateKey" -value ("$env:USERPROFILE\.ssh\akshci_rsa")
        }
        else
        {
            Set-MocConfigValue -name "sshPublicKey" -value ([io.Path]::Combine($workingDir, ".ssh", "akshci_rsa.pub"))
            Set-MocConfigValue -name "sshPrivateKey" -value ([io.Path]::Combine($workingDir, ".ssh", "akshci_rsa"))
        }
    }

    Set-MocConfigValue -name "nodeAgentPort" -value $nodeAgentPort
    Set-MocConfigValue -name "nodeAgentAuthorizerPort" -value $nodeAgentAuthorizerPort
    Set-MocConfigValue -name "cloudAgentPort" -value $cloudAgentPort
    Set-MocConfigValue -name "cloudAgentAuthorizerPort" -value $cloudAgentAuthorizerPort
    Set-MocConfigValue -name "clusterRoleName" -value $clusterRoleName
    Set-MocConfigValue -name "skipUpdates" -value $skipUpdates.IsPresent
    Set-MocConfigValue -name "skipHostLimitChecks" -value $skipHostLimitChecks.IsPresent
    Set-MocConfigValue -name "insecure" -value $insecure.IsPresent
    Set-MocConfigValue -name "forceDnsReplication" -value $forceDnsReplication.IsPresent
    Set-MocConfigValue -name "useStagingShare" -value $useStagingShare.IsPresent
    Set-MocConfigValue -name "macPoolStart" -value $macPoolStart
    Set-MocConfigValue -name "macPoolEnd" -value $macPoolEnd
    Set-MocConfigValue -name "cloudServiceCidr" -value $cloudServiceCidr

    if ($vipPool -ne $null)
    {
        Set-MocConfigValue -name "defaultVipPoolName" -value $vipPool.Name
        Set-MocConfigValue -name "vipPoolStart" -value $vipPool.VipPoolStart
        Set-MocConfigValue -name "vipPoolEnd" -value $vipPool.VipPoolEnd
    }

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

    Set-MocConfigValue -name "installationPackageDir" -value ([io.Path]::Combine($workingDir, $version))
    Set-MocConfigValue -name "mocCertLocation" -value ([io.Path]::Combine($global:config[$moduleName]["cloudConfigLocation"], $global:cloudAgentCertName))
    Set-MocConfigValue -name "nodeCertLocation" -value ([io.Path]::Combine($global:config[$moduleName]["nodeConfigLocation"], $global:nodeAgentCertName))
    Set-MocConfigValue -name "mocLoginYAML" -value ([io.Path]::Combine($global:config[$moduleName]["cloudConfigLocation"], $global:cloudloginYAMLName))
    Set-MocConfigValue -name "nodeLoginYAML" -value ([io.Path]::Combine($global:config[$moduleName]["nodeConfigLocation"], $global:nodeloginYAMLName))
    Set-MocConfigValue -name "nodeToCloudLoginYAML" -value ([io.Path]::Combine($global:config[$moduleName]["nodeConfigLocation"], $global:nodeToCloudloginYAMLName))
    Set-MocConfigValue -name "tokenExpiryDays" -value $tokenExpiryDays

    Initialize-Directories

    #Multi-Admin
    Set-MocConfigValue -name "accessFileDirPath" -value ([io.Path]::Combine($global:config[$moduleName]["workingDir"], $global:accessFileDir))
    Set-MocConfigValue -name "accessFilePath" -value ([io.Path]::Combine($global:config[$moduleName]["accessFileDirPath"], $global:accessFileDirMoc, $global:accessFileName))
    [System.Environment]::SetEnvironmentVariable('ACCESSFILE_DIR_PATH', $global:config[$moduleName]["accessFileDirPath"])

    Set-DeploymentType

    # There is one use case, where configuration is set when it is lost and restored.
    # Setting the install state appropriately
    if ((Test-Path $global:cloudCtlFullPath))
    {
        try
        {
            Wait-ForCloudAgentEndpoint -timeout 1
            Set-MocConfigValue -name "installState" -value ([InstallState]::Installed)
            Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_existing_configuration_loaded)
        }
        catch 
        {
            # Ingoring this explicitly as no existing deploying is found
        }
    }
    else
    {
        Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_new_configuration_saved)
    }

    Save-ConfigurationDirectory -moduleName $moduleName -WorkingDir $workingDir
    Save-Configuration -moduleName $moduleName

    if (-not $skipValidationCheck.IsPresent) {
        $testResults = Test-MocConfiguration -skip
        $overallResult = $true
        $resultDetails = ""
        foreach($result in $testResults) {
            if ($result.TestResult -eq "Failed") {
                $overallResult = $false
                $resultDetails = $result.Details
                break
            }
        }

        if (-not $overallResult) {
            $testResults | ConvertTo-Html -Title $mocLocMessage.moc_validation_report_title | Out-File -FilePath moc_validation_report.html
            $reportFileName = "moc_validation_report.html"
            
            Reset-Configuration -moduleName $moduleName
            Write-Host ''
            Write-Host $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_validation_set_configuration_failure, $reportFileName)) -ForegroundColor Red 
            Write-Host ''
            Write-Host $resultDetails -ForegroundColor Red
            Write-Host ''
            Write-Host ''
            throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_validation_set_configuration_failure, $reportFileName))
        }
        else {
            Write-Host ''
            Write-Host '======================================================'
            Write-Host $MocLocMessage.moc_validation_set_configuration_success -ForegroundColor Green 
            Write-Host '======================================================'
            Write-Host ''
        }
    } else {
        Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_new_configuration_for_module_saved, $moduleName))
    }
}   

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


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

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

function Get-MocConfigValue {
    <#
   .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
   )

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

function Get-MocHyperThreadingEnabled
{
    <#
   .DESCRIPTION
       Check if HyperThreading is enabled on the current node comparing
       number of actual cores and the logical processors
    #>

 
    $hyperThreadingEnabled = $true 
    $moclocation = $global:config[$modulename]["cloudLocation"]
    $coresInfo = Invoke-MocCommand " cloud node list -o json --query ""[?properties.statuses.Info]"" --location $moclocation"
    if ($null -ne $coresInfo) 
    {
        $cores = [UInt32]::Parse([regex]::Match($coresInfo, "cores:(\d+)").captures.groups[1].value)
        $logicalProcessors = [UInt32]::Parse([regex]::Match($coresInfo, "logicalprocessors:(\d+)").captures.groups[1].value)
        $hyperthreadingEnabled = ($cores -ne $logicalProcessors)
    }
    return $hyperthreadingEnabled
}

function Invoke-MocRotateCACertificate
{
    <#
    .DESCRIPTION
        The main deployment method for MOC. This function is responsible for provisioning files,
        deploying the agents.
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    [CmdletBinding()]
    param (
        [int]$sleepDuration=20,
        [int]$timeout=3600, #seconds in a hour
        [String]$activity = $MyInvocation.MyCommand.Name
    )

    Initialize-MocEnvironment -createConfigIfNotPresent -activity $activity
    Write-Status -moduleName $moduleName $($GenericLocMessage.moc_rotate_ca_certificate)
    $currentDate = (Get-Date).ToUniversalTime()
    try {
        Update-MocCertificate -name $global:cloudAgentCACertName
        # Sleeping for the server certificate to be generated
        sleep 5
        Repair-MocLogin
        Wait-ForCACertificateUpdate -sleepDuration $sleepDuration -timeout $timeout -currentDate $currentDate
        Wait-ForServerCertificateUpdate -sleepDuration $sleepDuration -timeout $timeout -currentDate $currentDate
    }
    catch
    {
        throw $_
    }
    Write-Status -moduleName $moduleName $($GenericLocMessage.moc_rotate_ca_certificate_complete)
}


#region Installation and Provisioning functions

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


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

    trap
    {
        Set-MocConfigValue -name "installState" -value ([InstallState]::InstallFailed)
        throw $_ 
    }

    Set-MocConfigValue -name "installState" -value ([InstallState]::Installing)

    Reset-Host -removeAll -skipImageDeletion -skipConfigDeletion -activity $activity

    # Calling Initialize again, since Reset-Host cleans up everything
    Initialize-Directories

    Get-MocRelease -version $(Get-MocVersion) -activity $activity

    Initialize-Cloud -activity $activity

    Test-CloudConfiguration

    Set-MocConfigValue -name "installState" -value ([InstallState]::Installed)

    Write-Status -moduleName $moduleName $($MocLocMessage.moc_installation_complete)
}

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


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

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

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

    #Multi-Admin
    # Make sure that ACCESSFILE_DIR_PATH is setup. ACCESSFILE_DIR_PATH is constructed from accessFileDirPath
    # So, we need to be sure that accessFileDirPath is available before setting the env var.
    if([version]$global:config[$moduleName]["version"] -ge [version]$script:mocVersionAug) {

        if($null -eq $global:config[$moduleName]["accessFileDirPath"]){
            Set-MocConfigValue -name "accessFileDirPath" -value ([io.Path]::Combine($global:config[$moduleName]["workingDir"], $global:accessFileDir))
            Set-MocConfigValue -name "accessFilePath" -value ([io.Path]::Combine($global:config[$moduleName]["accessFileDirPath"], $global:accessFileDirMoc, $global:accessFileName))
        }

        [System.Environment]::SetEnvironmentVariable('ACCESSFILE_DIR_PATH', $global:config[$moduleName]["accessFileDirPath"])
    }

}

function Initialize-Cloud
{
    <#
    .DESCRIPTION
        Provision an onPremise cloud
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


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

    Write-StatusWithProgress -activity $activity -module $moduleName -status $($MocLocMessage.moc_provisioning_cloud)

    # 1. Provision Moc Agents
    Install-MocAgents -activity $activity

    # 2. Wait for cloud nodes to be active
    Write-StatusWithProgress -activity $activity -module $moduleName -status $($MocLocMessage.moc_provisioning_cloud_resources)
    $mocLocation = $global:config[$modulename]["cloudLocation"]

    Wait-ForActiveNodes -location $mocLocation -activity $activity
    
    # 3. Provision Cloud MacPool
    $mocLocation = $global:config[$modulename]["cloudLocation"]
    if (-not [string]::IsNullOrWhiteSpace($global:config[$modulename]["macPoolStart"]))
    {
        New-MocMacPool -name $global:cloudMacPool `
            -location $($mocLocation) `
            -macPoolStart $global:config[$modulename]["macPoolStart"] `
            -macPoolEnd $global:config[$modulename]["macPoolEnd"] | Out-Null
    }
    
    # 4. Provision Global Vipool
    if (-not [string]::IsNullOrWhiteSpace($global:config[$modulename]["defaultVipPoolName"]))
    {
        New-MocVipPool -name $global:config[$modulename]["defaultVipPoolName"] `
            -vipPoolStart $global:config[$modulename]["vipPoolStart"] `
            -vipPoolEnd $global:config[$modulename]["vipPoolEnd"] `
            -location $mocLocation
    }

    # 5. Add cloud resources
    New-MocContainer -name $global:cloudStorageContainer -location $mocLocation -path $global:config[$modulename]["imageDir"] | Out-Null


    # 6. Add storge container for each CSV for multinode failover cluster setup
    $createAutoConfigContainers = $global:config[$modulename]["createAutoConfigContainers"]
    if ($createAutoConfigContainers -And (Test-MultiNodeDeployment)) {
        $table = Get-ClusterSharedVolume | Select-Object SharedVolumeInfo, State
        $i = 1
        foreach ($row in $table) {
            if ($row.State -ne "Online") {continue}
            $scname = "auto-config-container-"
            $scname = $scname + $i
            $CSVPath = $row.SharedVolumeInfo.FriendlyVolumeName
            if (!$CSVPath)
            {
                # if volume is not online, we can skip it
                continue
            }
            $CSVPath = Join-Path $CSVPath $scname
            if (!(Test-Path($CSVPath))) {
                New-Item -path $CSVPath -ItemType Directory
            }
            New-MocContainer -name $scname -location $mocLocation -path $CSVPath
            $i = $i + 1
        }
    }
}

function Install-MocAgents
{
    <#
    .DESCRIPTION
        Provision an onPremise cloud
 
    .PARAMETER activity
        Activity name to use when updating progress
   #>


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

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_provisioning_agents)

    # 1. Provision Nodes
    if (Test-MultiNodeDeployment)
    {
        Get-ClusterNode -ErrorAction Stop | ForEach-Object { 
            $nodeName = ${_}.Name
            Initialize-Node -nodeName $nodeName
        }
    }
    else
    {
        Initialize-Node -nodeName ($env:computername)
    }

    # 2. Provision Cloud Agent
    Install-CloudAgent -cloudAgentName $global:cloudAgentAppName -activity $activity
    New-MocLocation -name $global:config[$modulename]["cloudLocation"]  | Out-Null

    $nodeLoginYaml = $(Get-MocConfigValue -name "nodeLoginYAML")
    # 4. Provision Nodes
    if (Test-MultiNodeDeployment)
    {
        $failoverCluster = Get-FailoverCluster
        New-MocCluster -name $failoverCluster.Name -fqdn $($failoverCluster.Name+"."+$failoverCluster.Domain) -location $global:config[$modulename]["cloudLocation"]  | Out-Null

        Get-ClusterNode -ErrorAction Stop | ForEach-Object {
            $nodeName = ${_}.Name
            Install-NodeAgent -nodeName $nodeName -activity $activity
            Invoke-NodeLogin -nodeName $nodeName -loginYaml $nodeLoginYaml
        }
    }
    else
    {
        Install-NodeAgent -nodeName ($env:computername) -activity $activity
        Invoke-NodeCommand "security login --loginpath ""$nodeLoginYaml"" --identity"
    }
}

function Install-CloudAgent
{
    <#
    .DESCRIPTION
        Provision a Cloud Agent
        1. Download Cloud Agent Binary
        2. Configure Service
        3. Start the Service
 
    .PARAMETER cloudAgentName
        Cloud agent name
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


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

    # Install the service as Cluster Gen Service

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_provisioning_cloudagent, $cloudAgentName))

    $authArgs = ""
    if ($global:config[$modulename]["insecure"])
    {
        $authArgs += "--debug"
    }
    $cloudFqdn = Get-CloudFqdn
    $cloudConfigPath = $global:config[$moduleName]["cloudConfigLocation"]
    $certificateValidityFactor = $global:config[$modulename]["certificateValidityFactor"]
    $caCertificateValidityFactor = $global:config[$modulename]["caCertificateValidityFactor"]
    $cloudConfigLocation = $global:config[$modulename]["cloudConfigLocation"]
    $useNetworkController = $global:config[$modulename]["useNetworkController"]
    $networkControllerFqdnOrIpAddress = $global:config[$modulename]["networkControllerFqdnOrIpAddress"]
    $networkControllerLbSubnetRef = $global:config[$modulename]["networkControllerLbSubnetRef"]
    $networkControllerLnetRef = $global:config[$modulename]["networkControllerLnetRef"]
    $networkControllerClientCertificateName = $global:config[$modulename]["networkControllerClientCertificateName"]
    $ncParameters = " --usenetworkcontroller --networkcontrollerfqdnoripaddress ""$networkControllerFqdnOrIpAddress"" --networkcontrollerlbsubnetref ""$networkControllerLbSubnetRef"" --networkcontrollerlnetref ""$networkControllerLnetRef"" --networkcontrollerclientcertificatename ""$networkControllerClientCertificateName"""

    New-Item -ItemType Directory -Force -Path $cloudConfigPath | Out-Null
    Set-SecurePermissionFolder -Path $cloudConfigPath
 
    if ($global:config[$modulename]["deploymentId"])
    {
        $deploymentId = "--deploymentid " + $global:config[$modulename]["deploymentId"]
    }

    if (Test-MultiNodeDeployment)
    {
        $nodes = Get-ClusterNode -ErrorAction Stop
        $clusterRoleName = $global:config[$modulename]["clusterRoleName"]

        $cloudServiceParameters = "--service $authArgs --basedir ""$cloudConfigLocation"" --cloudagentfqdn $cloudFqdn --dotfolderpath ""$cloudConfigLocation"" --objectdatastore ""Cluster-Registry"" --clusterresourcename ""$clusterRoleName"" --certificatevalidityfactor $certificateValidityFactor --cacertificatevalidityfactor $caCertificateValidityFactor $deploymentId"

        if ($useNetworkController)
        {
            $cloudServiceParameters = $cloudServiceParameters + $ncParameters 
        }

        $nodes.Name | ForEach-Object {
            Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_installing_cloudagent_service_on, $_))
            Invoke-Command -ComputerName $_ -ScriptBlock {
                $cloudAgentFullPath = $args[0]
                $cloudServiceParameters = $args[1]
                $service = Get-WmiObject -Class Win32_Service -Filter "Name='wssdcloudagent'"
                if ($null -ne $service) {
                    $service.delete() | Out-Null
                }
                Remove-Item 'HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Application\wssdcloudagent\' -force -ErrorAction Ignore
                New-Service -Name "wssdcloudagent" -BinaryPath """$cloudAgentFullPath"" $cloudServiceParameters" -StartupType "Manual" -DisplayName "WSSD Cloud Agent Service" | Out-Null
            } -ArgumentList $global:cloudAgentFullPath, $cloudServiceParameters
        }

        Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_adding_wssdcloudagent_cluster_generic_service_role, $global:config[$modulename]["clusterRoleName"]))

        Add-FailoverClusterGenericRole -serviceDisplayName "MOC Cloud Agent Service" -serviceName wssdcloudagent -staticIpCidr $global:config[$modulename]["cloudServiceCidr"] -clusterGroupName $global:config[$modulename]["clusterRoleName"] -serviceParameters $cloudServiceParameters

        Request-DnsReplication
    }
    else
    {

        Remove-Item 'HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Application\wssdcloudagent\' -force -ErrorAction Ignore
        $cloudServiceParameters = "--service $authArgs --basedir ""$cloudConfigLocation"" --cloudagentfqdn $cloudFqdn --dotfolderpath ""$cloudConfigLocation"" --objectdatastore ""registry"" --certificatevalidityfactor $certificateValidityFactor --cacertificatevalidityfactor $caCertificateValidityFactor $deploymentId"

        if ($useNetworkController)
        {
            $cloudServiceParameters = $cloudServiceParameters + $ncParameters 
        }


        New-Service -Name "wssdcloudagent" -BinaryPath """$cloudAgentFullPath"" $cloudServiceParameters" -StartupType "Automatic" -DisplayName "WSSD Cloud Agent Service" | Out-Null

        # Allow the service to restart twice on failure. Reset the failure counter every hour.
        $result = sc.exe failure "wssdcloudagent" actions= "restart/$script:svcFailureRestartMs/restart/$script:svcFailureRestartMs//0" reset= $script:svcFailureResetSecond
        if ($LASTEXITCODE -ne 0)
        {
            throw "Error " + $LASTEXITCODE + ". " + $result
        }

        Start-Service "wssdcloudagent" -WarningAction:SilentlyContinue | Out-Null
    }

    # We must wait for the generic service VIP to become usable (i.e. wait for DNS to propogate) otherwise calls to moc will fail and the script will exit prematurely.
    Wait-ForCloudAgentEndpoint -activity $activity

    if (Test-MultiNodeDeployment)
    {
        if (-Not $global:config[$modulename]["insecure"] -and ($null -eq $env:ACCESSFILE_DIR_PATH))
        {
            Write-SubStatus -moduleName $moduleName  -msg $($MocLocMessage.moc_replicating_cloudconfig)
            # TODO: Switch to using individual tokens
            Get-ClusterNode -ErrorAction Stop | ForEach-Object {
                $nodeName = $nodeName = ${_}.Name
                if ($nodeName -ine $env:computername)
                {
                    Copy-FileToRemoteNode -remoteNode $nodeName -source $global:accessFileLocation -destination $global:accessFileLocation
                }
            }
        }
    }
}

function Initialize-Node
{
    <#
    .DESCRIPTION
        Provision a Node Agent
        1. Download Node Agent Binary to the Node
        2. Register as Windows service
        3. Configure Service
        4. Start the Service
 
    .PARAMETER nodeName
        The node to execute on.
    #>

    param (
        [String]$nodeName
    )

    Write-Status -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_provisioning_node, $nodeName))

    Install-MocBinaries -nodeName $nodeName

    Initialize-HostOs -NodeName $nodeName
}

function Install-NodeAgent
{
    <#
    .DESCRIPTION
        Provision a Node Agent
        1. Download Node Agent Binary to the Node
        2. Register as Windows service
        3. Configure Service
        4. Start the Service
 
    .PARAMETER nodeName
        The node to execute on.
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>

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

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_provisioning_nodeagent, $nodeName))
    
    $cloudFqdn = Get-CloudFqdn
    $debugArgs = ""
    if ($global:config[$modulename]["insecure"])
    {
        $debugArgs += "--debug" 
    } else {
        $nodeIdentity = New-MocIdentity -name $nodeName -validityDays $global:config[$moduleName]["tokenExpiryDays"] -fqdn $cloudFqdn -location $global:config[$modulename]["cloudLocation"] -port $global:config[$modulename]["cloudAgentPort"] -authport $global:config[$modulename]["cloudAgentAuthorizerPort"]
        New-MocRoleAssignmentWhenAvailable -identityName $nodeName -roleName "NodeContributor" -location $global:config[$modulename]["cloudLocation"] | Out-Null
    }
    $providerSpecArgs = $global:VMMSSpec
    if (Test-MultiNodeDeployment)
    {
        $failoverCluster = Get-FailoverCluster
        $nodeFqdn = $nodeName + "." + $failoverCluster.Domain
        $providerSpecArgs = $global:failoverClusterSpec
    }
    else 
    {
        # Pick the host domain
        $nodeFqdn = ([System.Net.Dns]::GetHostByName($env:computerName)).HostName
    }

    $nodeCertificateValidityFactor = ""
    if ($global:config[$modulename]["nodeCertificateValidityFactor"])
    {
        $nodeCertificateValidityFactor = "--certificatevalidityfactor " + $global:config[$modulename]["nodeCertificateValidityFactor"]
    }

    $deploymentId = "--deploymentid " + $global:config[$modulename]["deploymentId"]

    return Invoke-Command -ComputerName $nodeName -ScriptBlock {
        $nodeAgentFullPath = $args[0]
        $nodeConfigLocation = $args[1]
        $debug = $args[2]
        $dotFolderPath = $args[3]
        $cloudagentlogin = $args[4]
        $nodeagentfqdn = $args[5]
        $svcFailureRestartMs = $args[6]
        $svcFailureResetSecond = $args[7]
        $svcNodeAgentDependency = $args[8]
        $nodeToCloudLoginFile = $args[9]
        $nodeAgentRegistryPath = $args[10]
        $providerSpec = $args[11]
        $nodeName = $args[12]
        $nodeCertificateValidityFactor = $args[13]
        $deploymentId = $args[14]

        New-Item -ItemType Directory -Force -Path $nodeConfigLocation | Out-Null
        $acl = Get-Acl $nodeConfigLocation
        $acl.SetAccessRuleProtection($true,$false)
        $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("BUILTIN\Administrators","FullControl","ContainerInherit,ObjectInherit", "None", "Allow")
        $acl.SetAccessRule($accessRule)
        $acl | Set-Acl $nodeConfigLocation

        New-Item -ItemType File -Force -Path $nodeToCloudLoginFile | Out-Null
        Set-Content -Path $nodeToCloudLoginFile -Value $cloudagentlogin -ErrorVariable err

        $service = Get-WmiObject -Class Win32_Service -Filter "Name='wssdagent'"
        if ($null -ne $service) {
            $service.delete() | Out-Null
        }
        Remove-Item 'HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Application\wssdagent\' -force -ErrorAction Ignore
        $nodeServiceParams = " --service $debug --basedir ""$nodeConfigLocation"" --cloudloginfile ""${nodeToCloudLoginFile}"" --dotfolderpath ""$dotFolderPath"" --nodeagentfqdn $nodeagentfqdn --nodename $nodeName --objectdatastore ""registry"" --wssdproviderspec ""$providerSpec"" $nodeCertificateValidityFactor $deploymentId"
        New-Service -Name "wssdagent" -BinaryPath """$nodeAgentFullPath"" $nodeServiceParams " -StartupType "Automatic" -DisplayName "MOC NodeAgent Service" | Out-Null
        
        # Allow the service to restart twice on failure. Reset the failure counter every hour.
        $result = sc.exe failure "wssdagent" actions= "restart/$svcFailureRestartMs/restart/$svcFailureRestartMs//0" reset= $svcFailureResetSecond
        if ($LASTEXITCODE -ne 0)
        {
            throw "Error " + $LASTEXITCODE + ". " + $result
        }

        # Make the service dependant on WMI
        $result = sc.exe config "wssdagent" depend= $svcNodeAgentDependency
        if ($LASTEXITCODE -ne 0)
        {
            throw "Error " + $LASTEXITCODE + ". " + $result
        }

        Start-Service wssdagent -WarningAction:SilentlyContinue | Out-Null
        # Set Registry Permissions
        $acl = Get-Acl $nodeAgentRegistryPath
        $acl.SetAccessRuleProtection($true,$false)
        $accessRule = New-Object System.Security.AccessControl.RegistryAccessRule("BUILTIN\Administrators","FullControl","ContainerInherit,ObjectInherit", "None", "Allow")
        $acl.SetAccessRule($accessRule)
        $acl | Set-Acl $nodeAgentRegistryPath
    } -ArgumentList $global:nodeAgentFullPath, $global:config[$modulename]["nodeConfigLocation"], $debugArgs, $global:config[$modulename]["nodeConfigLocation"], $nodeIdentity, $nodeFqdn, $script:svcFailureRestartMs, $script:svcFailureResetSecond, $script:svcNodeAgentDependency, $global:config[$modulename]["nodeToCloudLoginYAML"], $global:nodeAgentRegistryPath, $providerSpecArgs, $nodeName, $nodeCertificateValidityFactor, $deploymentId
}


function Update-NodeAgent
{
    <#
    .DESCRIPTION
        Method to apply workaround to nodeagent during upgrade
 
    .PARAMETER nodeName
        The node to execute on.
 
    .PARAMETER activity
        Activity name to use when updating progress
 
    .PARAMETER version
        Moc version to upgrade
 
    #>

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

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_provisioning_nodeagent, $nodeName))
    $providerSpecArgs = $global:VMMSSpec

    if (Test-MultiNodeDeployment)
    {
        $failoverCluster = Get-FailoverCluster
        $nodeFqdn = $nodeName + "." + $failoverCluster.Domain
        $providerSpecArgs = $global:failoverClusterSpec
    }
    else 
    {
        $nodeFqdn = ([System.Net.Dns]::GetHostByName($env:computerName)).HostName
    }

    $deploymentIdParams = ""
    if ((Test-MultiNodeDeployment) -and (([version]$version) -eq ([version]$script:mocVersionSept))) {
        # If the version is Sep
        $providerSpecArgs = $global:failoverClusterSpec
    } elseif ((Test-MultiNodeDeployment) -and ((([version]$version) -eq ([version]$script:mocVersionNov)) -or (([version]$version) -eq ([version]$script:mocVersionJan)))) {
        # If the version is Nov
        $providerSpecArgs = $global:failoverClusterSpec
        $extraParams = "--nodename $nodeName"
        $nodeName = $nodeFqdn
    } elseif (([version]$version) -eq ([version]$script:mocVersionJune)) {
        # If the version is June only then add deploymentid
        $deploymentIdParams += " --deploymentid " + $global:config[$modulename]["deploymentId"]
    } elseif (([version]$version) -eq ([version]$script:mocVersionMay)) {
        # If downgrading to May, make sure to reset deployment ID
        $deploymentIdParams = ""
    }
    else {
        # The existing service configuration is the same
        # Other versions, nothing to do
        return
    }

    if ($global:config[$modulename]["insecure"])
    {
        $debugArgs += "--debug"
    }

    Write-Status -moduleName $moduleName  "Migrating NodeAgent on [$nodeName] and patching with the params [$providerSpecArgs] [$extraParams] and [$deploymentIdParams]"

    

    $result = Invoke-Command -ComputerName $nodeName -ScriptBlock {
        $nodeAgentFullPath = $args[0]
        $nodeConfigLocation = $args[1]
        $debug = $args[2]
        $dotFolderPath = $args[3]
        $nodeagentfqdn = $args[4]
        $svcFailureRestartMs = $args[5]
        $svcFailureResetSecond = $args[6]
        $svcNodeAgentDependency = $args[7]
        $nodeToCloudLoginFile = $args[8]
        $nodeAgentRegistryPath = $args[9]
        $providerSpec = $args[10]
        $extraParams = $args[11]
        $deploymentIdParams = $args[12]

        $service = Get-WmiObject -Class Win32_Service -Filter "Name='wssdagent'"
        if ($null -ne $service) {
            $service.delete() | Out-Null
        }
        Remove-Item 'HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Application\wssdagent\' -force -ErrorAction Ignore
        $nodeServiceParams = " --service $debug --basedir ""$nodeConfigLocation"" --cloudloginfile ""${nodeToCloudLoginFile}"" --dotfolderpath ""$dotFolderPath"" --nodeagentfqdn $nodeagentfqdn --objectdatastore ""registry"" --wssdproviderspec ""$providerSpec"" $extraParams $deploymentIdParams"
        New-Service -Name "wssdagent" -BinaryPath """$nodeAgentFullPath"" $nodeServiceParams " -StartupType "Automatic" -DisplayName "MOC NodeAgent Service" | Out-Null

        # Allow the service to restart twice on failure. Reset the failure counter every hour.
        $result = sc.exe failure "wssdagent" actions= "restart/$svcFailureRestartMs/restart/$svcFailureRestartMs//0" reset= $svcFailureResetSecond
        if ($LASTEXITCODE -ne 0)
        {
            throw "Error " + $LASTEXITCODE + ". " + $result
        }
        # Make the service dependant on WMI
        $result = sc.exe config "wssdagent" depend= $svcNodeAgentDependency
        if ($LASTEXITCODE -ne 0)
        {
            throw "Error " + $LASTEXITCODE + ". " + $result
        }
    } -ArgumentList $global:nodeAgentFullPath, $global:config[$modulename]["nodeConfigLocation"], $debugArgs, $global:config[$modulename]["nodeConfigLocation"], $nodeName, $script:svcFailureRestartMs, $script:svcFailureResetSecond, $script:svcNodeAgentDependency, $global:config[$modulename]["nodeToCloudLoginYAML"],` $global:nodeAgentRegistryPath, $providerSpecArgs, $extraParams, $deploymentIdParams

    Write-Status -moduleName $moduleName "Migration completed on [$nodeName] with result [$result]"
}

function Update-CloudAgent
{
    <#
    .DESCRIPTION
        Method to apply workaround to cloudagent during upgrade
 
    .PARAMETER cloudAgentName
        Cloud agent name
 
    .PARAMETER activity
        Activity name to use when updating progress
 
    .PARAMETER version
        Moc version to upgrade
 
    #>

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

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_provisioning_cloudagent, $cloudAgentName))
  
    $authArgs = ""
    $deploymentIdParams =""
    if ($global:config[$modulename]["insecure"])
    {
        $authArgs += "--debug"
    }

    $cloudFqdn = Get-CloudFqdn
    $cloudConfigPath = $global:config[$moduleName]["cloudConfigLocation"]
    $certificateValidityFactor = $global:config[$modulename]["certificateValidityFactor"]
    $cloudConfigLocation = $global:config[$modulename]["cloudConfigLocation"]
    $useNetworkController = $global:config[$modulename]["useNetworkController"]
    $networkControllerFqdnOrIpAddress = $global:config[$modulename]["networkControllerFqdnOrIpAddress"]
    $networkControllerLbSubnetRef = $global:config[$modulename]["networkControllerLbSubnetRef"]
    $networkControllerLnetRef = $global:config[$modulename]["networkControllerLnetRef"]
    $networkControllerClientCertificateName = $global:config[$modulename]["networkControllerClientCertificateName"]
    $ncParameters = " --usenetworkcontroller --networkcontrollerfqdnoripaddress ""$networkControllerFqdnOrIpAddress"" --networkcontrollerlbsubnetref ""$networkControllerLbSubnetRef"" --networkcontrollerlnetref ""$networkControllerLnetRef"" --networkcontrollerclientcertificatename ""$networkControllerClientCertificateName"""

    if (([version]$version) -eq ([version]$script:mocVersionJune)) {
        # If the version is June then add the deploymendid
        $deploymentIdParams = "--deploymentid " + $global:config[$modulename]["deploymentId"] 
    } 
    elseif (([version]$version) -eq ([version]$script:mocVersionMay)) {
        # If downgrading to May, make sure to reset deployment ID
        $deploymentIdParams = ""
    }
    else {
        # The existing service configuration is the same
        # Other versions, nothing to do
        return
    }

    Write-Status -moduleName $moduleName  "Migrating CloudAgent and patching with the params [$deploymentIdParams]"

    if (Test-MultiNodeDeployment)
    {
        $nodes = Get-ClusterNode -ErrorAction Stop
        $clusterRoleName = $global:config[$modulename]["clusterRoleName"]
        $cloudServiceParameters = "--service $authArgs --basedir ""$cloudConfigLocation"" --cloudagentfqdn $cloudFqdn --dotfolderpath ""$cloudConfigLocation"" --objectdatastore ""Cluster-Registry"" --clusterresourcename ""$clusterRoleName"" --certificatevalidityfactor $certificateValidityFactor $deploymentIdParams"
        if ($useNetworkController)
        {
            $cloudServiceParameters = $cloudServiceParameters + $ncParameters 
        }

        $nodes.Name | ForEach-Object {
            Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_installing_cloudagent_service_on, $_))
            Invoke-Command -ComputerName $_ -ScriptBlock {
                $cloudAgentFullPath = $args[0]
                $cloudServiceParameters = $args[1]
                $service = Get-WmiObject -Class Win32_Service -Filter "Name='wssdcloudagent'"
                if ($null -ne $service) {
                    $service.delete() | Out-Null
                }
                Remove-Item 'HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Application\wssdcloudagent\' -force -ErrorAction Ignore
                New-Service -Name "wssdcloudagent" -BinaryPath """$cloudAgentFullPath"" $cloudServiceParameters" -StartupType "Manual" -DisplayName "WSSD Cloud Agent Service" | Out-Null
            } -ArgumentList $global:cloudAgentFullPath, $cloudServiceParameters
        }

        Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_adding_wssdcloudagent_cluster_generic_service_role, $global:config[$modulename]["clusterRoleName"]))
    }
    else
    {
        $service = Get-WmiObject -Class Win32_Service -Filter "Name='wssdcloudagent'"
        if ($null -ne $service) {
            $service.delete() | Out-Null
        }
        Remove-Item 'HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Application\wssdcloudagent\' -force -ErrorAction Ignore
        $cloudServiceParameters = "--service $authArgs --basedir ""$cloudConfigLocation"" --cloudagentfqdn $cloudFqdn --dotfolderpath ""$cloudConfigLocation"" --objectdatastore ""registry"" --certificatevalidityfactor $certificateValidityFactor $deploymentIdParams"
        if ($useNetworkController)
        {
            $cloudServiceParameters = $cloudServiceParameters + $ncParameters 
        }
        New-Service -Name "wssdcloudagent" -BinaryPath """$cloudAgentFullPath"" $cloudServiceParameters" -StartupType "Automatic" -DisplayName "WSSD Cloud Agent Service" | Out-Null

        # Allow the service to restart twice on failure. Reset the failure counter every hour.
        $result = sc.exe failure "wssdcloudagent" actions= "restart/$script:svcFailureRestartMs/restart/$script:svcFailureRestartMs//0" reset= $script:svcFailureResetSecond
        if ($LASTEXITCODE -ne 0)
        {
            throw "Error " + $LASTEXITCODE + ". " + $result
        }
    }
}

function Request-DnsReplication
{
    <#
    .DESCRIPTION
        This optionally attempts to speed up DNS replication by manually replicating data between DNSs.
        In order for this method to succeed, the current user needs to have sufficient access permissions
        to update entries in the DNS server. In case of a failure, the code is skiped and this function is
        a best-effort.
    #>


    try
    {
        $forceDnsReplication = Get-ConfigurationValue -module $moduleName -type ([Type][System.Boolean]) -name "forceDnsReplication"
        if ($forceDnsReplication)
        {
            # Only proceed if the Get-DnsServerResourceRecord cmdlet is available (i.e. if the DNS-Server-Tools are installed)
            if ((Get-Module "DnsServer" -All -ErrorAction Ignore).Count -eq 0)
            {
                $wf = Get-WindowsOptionalFeature -FeatureName "DNS-Server-Tools" -Online
                if ($null -eq $wf)
                {
                    # no-op
                    return
                }

                if ($wf.State -ine "Enabled")
                {
                    Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_installing_missing_feature)

                    Enable-WindowsOptionalFeature -Online -FeatureName "DNS-Server-Tools" -All -NoRestart -WarningAction SilentlyContinue
                }

            }

            Import-Module "DnsServer"
            # Get the Ip associated with the cluster role and the host NIC associated with the vnetName network
            $ClusterResourceIP = (Get-ClusterGroup $global:config[$modulename]["clusterRoleName"].ToString() -ErrorAction Ignore | Get-ClusterResource -ErrorAction Ignore | Where-Object { $_.ResourceType -eq "IP Address" } | Get-ClusterParameter -name Address -ErrorAction Ignore).Value

            # Get the external route with the lowest metric
            $externalRoute = (Get-NetRoute "0.0.0.0/0" -ErrorAction Ignore | Sort-Object -Property InterfaceMetric) | select -First
            
            if ($ClusterResourceIP -ne $null -and $externalRoute -ne $null)
            {
                $externalInterfaceIpConfig = $externalRoute | Get-NetAdapter | Get-NetIPConfiguration
                $dnsZone = $externalInterfaceIpConfig.NetProfile.Name
                $dnsServers = ($externalInterfaceIpConfig.DNSServer | Where-Object { $_.AddressFamily -eq 2 }).ServerAddresses

                foreach ($dnsServer in $dnsServers)
                {
                    Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_registering_cloudagent_on_dns_server, $dnsServer))
                    Get-DnsServerResourceRecord -ComputerName $dnsServer -Name $global:config[$modulename]["clusterRoleName"] -ZoneName $dnsZone -ErrorAction Ignore | Remove-DnsServerResourceRecord -ComputerName $dnsServer -Force -ZoneName $dnsZone

                    Add-DnsServerResourceRecordA -ComputerName $dnsServer -Name $global:config[$modulename]["clusterRoleName"] -ZoneName $dnsZone -AllowUpdateAny -IPv4Address $ClusterResourceIP -TimeToLive 01:00:00
                }
            }
        }
    }
    catch
    {
        Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_could_not_force_dns_replication)
    }
}

#endregion

#region Verification Functions

function Test-CloudConfiguration
{
    <#
    .DESCRIPTION
        A basic sanity test of cloud and all node configuration.
    #>


    if (Test-MultiNodeDeployment)
    {
        # Check for Cloud Agent Gen App Service
        Get-ClusterGroup $global:config[$modulename]["clusterRoleName"].ToString() -ErrorAction Stop | Out-Null
        Get-ClusterResource -Name $global:config[$modulename]["clusterRoleName"].ToString() -ErrorAction Stop | Out-Null
        Get-ClusterNode -ErrorAction Stop | ForEach-Object {
            Test-NodeConfiguration -nodeName $_.Name
        }
    }
    else
    {
        Test-NodeConfiguration -nodeName ($env:computername)
    }

    Test-CloudResource
}

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


    param (
        [String]$nodeName
    )

    Test-ForWindowsFeatures -nodeName $nodeName

    Write-Status -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_performing_sanity_checks, $nodeName))

    Test-MocInstallation -nodeName $nodeName

    Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_testing_for_ssh_key)

    if ( !(Test-Path $global:config[$modulename]["sshPrivateKey"])) {
       throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_does_not_exist, $global:config[$modulename]["sshPrivateKey"]))
    }

    Test-Process -processName "wssdagent" -nodeName $nodeName

    Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_testing_if_cloud_node_resource_is_provisioned)
    Get-MocNode -name $nodeName -location $global:config[$modulename]["cloudLocation"] | Out-Null
}

function Test-CloudResource
{
    <#
    .DESCRIPTION
        A basic sanity test to ensure that cloudagent has been provisioned with the expected resources.
    #>


    Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_testing_if_cloud_node_resource_is_provisioned)
    Get-MocLocation -name $global:config[$modulename]["cloudLocation"] | Out-Null
    Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_testing_if_cloud_container_resource_is_provisioned)
    Get-MocContainer -name $global:cloudStorageContainer -location $global:config[$modulename]["cloudLocation"] | Out-Null
}

function Test-HostNetworking
{
    <#
    .DESCRIPTION
        Sanity check the host networking configuration.
 
    .PARAMETER nodeName
        The node to execute on.
 
    .PARAMETER vswitchName
        The name of the vnet switch to be checked.
    #>


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

    Write-Status -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_checking_host_networking_configuration, $nodeName))

    $isMultinode = Test-MultiNodeDeployment

    Invoke-Command -ComputerName $nodeName -ScriptBlock {
        $vswitchName = $args[0]
        $nodeName = $args[1]
        $isMultinode = $args[2]
        $MocLocMessage = $args[3]
        $GenericLocMessage = $args[4]

        if ([string]::IsNullOrWhitespace($vswitchName))
        {
            throw $($MocLocMessage.moc_no_vswitchname)
        }

        write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_checking_for_virtual_switch_with_name, $vswitchName, $nodeName))

        if($isMultinode) {
            $existing = Get-VMSwitch -SwitchType External -Name $vswitchName -ErrorAction Ignore
            if ($null -eq $existing)
            {
                throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_external_vswitchname, $vswitchName, $nodeName))
            }
        }
        else
        {
            $existing = Get-VMSwitch -Name $vswitchName -ErrorAction Ignore
            if ($null -eq $existing)
            {
                throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_missing_vswitchname, $vswitchName, $nodeName))
            }
        }

        return
    } -ArgumentList $vswitchName, $nodeName, $isMultinode, $MocLocMessage, $GenericLocMessage
}

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


    param (
        [String]$nodeName
    )

    Write-SubStatus -moduleName $moduleName $($GenericLocMessage.generic_testing_expected_binaries)
    Test-Binary -nodeName $nodeName -binaryName $global:nodeAgentFullPath
    Test-Binary -nodeName $nodeName -binaryName $global:cloudAgentFullPath
    Test-Binary -nodeName $nodeName -binaryName $global:nodeCtlFullPath
    Test-Binary -nodeName $nodeName -binaryName $global:cloudCtlFullPath
}

function Wait-ForConnectionFromNodes
{
    <#
    .DESCRIPTION
        Waits for all nodes to be able to resolve the dns name of cloudagent and connect to it.
    #>


    param (
        [int]$sleepDuration=20,
        [int]$timeout=1800 #seconds in 30min
    )

    $timer=[Diagnostics.Stopwatch]::StartNew()
    while(($timer.Elapsed.TotalSeconds -lt $timeout))
    {
        try
        {
            Test-CloudDnsFromNodes
        } catch {
            $err = $_
            Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_warning, $err.Exception.Message))
            Start-Sleep $sleepDuration
            continue
        }
        return
    }
    if ($null -ne $err)
    {
        throw $err
    }
}

function Test-CloudDnsFromNodes
{
    <#
    .DESCRIPTION
        Ensure that all the nodes can resolve the dns name of cloudagent.
    #>


    if (Test-MultiNodeDeployment)
    {
        $nodes = Get-ClusterNode -ErrorAction SilentlyContinue
        $cloudFqdn = $(Get-CloudFqdn)
        $cloudPorts = ($global:config[$modulename]["cloudAgentPort"], $global:config[$modulename]["cloudAgentAuthorizerPort"])
        $nodes | ForEach-Object {
            $nodeName = $_.Name
            try
            {
                Test-MocApiServer -cloudFqdn $cloudFqdn -ports $cloudPorts -nodeName $nodeName
            }
            catch [Exception]
            {                
                throw $([System.Exception]::new([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_node_agent_cannot_reach_apiserver , $nodeName, $cloudFqdn), $_.Exception))
            }
        }
    }
}

function Wait-ForCACertificateUpdate
{
    <#
    .DESCRIPTION
        Waits for all nodes to be able to resolve the dns name of cloudagent and connect to it.
    #>


    param (
        [int]$sleepDuration=20,
        [int]$timeout=1800, #seconds in 30min
        [parameter(Mandatory=$true)]
        [DateTime]$currentDate
    )

    $timer=[Diagnostics.Stopwatch]::StartNew()
    while(($timer.Elapsed.TotalSeconds -lt $timeout))
    {
        Write-Status -moduleName $moduleName $($MocLocMessage.moc_waiting_for_ca_certificate_update)
        $result = Get-MocCertificate -name $global:cloudAgentCACertName | ConvertFrom-Json
        $notBeforeDate = (([System.DateTimeOffset]::FromUnixTimeSeconds($result.tags[0].NotBeforeEpoch)).DateTime).ToUniversalTime()
        # Add 5 minutes to Offset certificate 5 minutes during creation
        $notBeforeDate = $notBeforeDate.AddMinutes(5)
        if ($notBeforeDate -lt $currentDate) {
            Start-Sleep $sleepDuration
            continue
        }
        return
    }
    throw $([System.Exception]::new([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_cloudagent_ca_certificate_failed), $_.Exception))
}

function Wait-ForServerCertificateUpdate
{
    <#
    .DESCRIPTION
        Waits for all nodes to be able to resolve the dns name of cloudagent and connect to it.
    #>


    param (
        [int]$sleepDuration=20,
        [int]$timeout=1800, #seconds in 30min
        [parameter(Mandatory=$true)]
        [DateTime]$currentDate
    )

    $timer=[Diagnostics.Stopwatch]::StartNew()
    while(($timer.Elapsed.TotalSeconds -lt $timeout))
    {
        Write-Status -moduleName $moduleName $($MocLocMessage.moc_waiting_for_server_certificate_update)
        $result = Get-MocCertificate -name $global:cloudAgentServerCertName | ConvertFrom-Json
        $notBeforeDate = (([System.DateTimeOffset]::FromUnixTimeSeconds($result.tags[0].NotBeforeEpoch)).DateTime).ToUniversalTime()

        if ($notBeforeDate -lt $currentDate) {
            Start-Sleep $sleepDuration
            continue
        }
        return
    }
    throw $([System.Exception]::new([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_cloudagent_server_certificate_failed), $_.Exception))

}


#endregion

#region Configuration Helper Functions

function Initialize-HostOs
{
    <#
    .DESCRIPTION
        Configures the host operating system
 
    .PARAMETER nodeName
        The node to execute on.
    #>

    param (
        [String]$NodeName
    )

    Test-ForWindowsFeatures -NodeName $NodeName

    $vnet = Get-VNetConfiguration -module $moduleName

    if ($vnet) {
        Test-HostNetworking -NodeName $NodeName -vswitchName $vnet.VswitchName
    }

    Initialize-Firewall -NodeName $NodeName
}

function Initialize-Firewall
{
    <#
    .DESCRIPTION
        Adds Windows Firewall exceptions to allow inbound network connections to the agents.
 
    .PARAMETER nodeName
        The node to execute on.
    #>

    param (
        [String]$nodeName
    )

    Write-Status -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_configuring_windows_firewall, $nodeName))

    $firewallRules = Get-FirewallRules

    Invoke-Command -ComputerName $nodeName -ScriptBlock {
        $rules = $args[0]
        foreach($rule in $rules)
        {
            $fw = Get-NetFirewallRule -DisplayName $($rule[0]) -ErrorAction Ignore
            if (!$fw)
            {
                New-NetFirewallRule -DisplayName $($rule[0]) -Protocol $($rule[1]) -LocalPort $($rule[2]) | Out-Null
            }
        }
    } -ArgumentList (, $firewallRules)
}

function Initialize-ImageDirectory
{
    <#
    .DESCRIPTION
        Creates a directory to store images and configures the required directory permissions.
 
    .PARAMETER nodeName
        The node to execute on.
    #>

    param (
        [String] $nodeName
    )

    Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_configuring_image_directory_permissions, $nodeName))

    # In order to avoid double-hop issues with network file share, we configure the
    # file share permission directly (instead of doing it from the remote powershell session)
    if ($global:config[$modulename]["imageDir"].StartsWith("\\"))
    {
        New-Item -ItemType Directory -Force -Path $global:config[$modulename]["imageDir"] | Out-Null
        $acl = get-acl $global:config[$modulename]["imageDir"]
        $AccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("BUILTIN\Users", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow")
        $acl.SetAccessRule($AccessRule)
        $acl | set-acl $global:config[$modulename]["imageDir"]
    }
    else
    {
        Invoke-Command -ComputerName $nodeName -ScriptBlock {
            $imageDir = $args[0]
            New-Item -ItemType Directory -Force -Path $imageDir | Out-Null
            $acl = get-acl $imageDir
            $AccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("BUILTIN\Users", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow")
            $acl.SetAccessRule($AccessRule)
            $acl | set-acl $imageDir
        } -ArgumentList $global:config[$modulename]["imageDir"]
    }
}

function Initialize-Directories
{
    <#
    .DESCRIPTION
        Creates work directories
    #>


    Write-Status -moduleName $moduleName $($MocLocMessage.moc_configuring_directories)

    New-Item -ItemType Directory -Force -Path $global:config[$modulename]["workingDir"]  | Out-Null
    New-Item -ItemType Directory -Force -Path $global:config[$modulename]["installationPackageDir"] | Out-Null
    New-Item -ItemType Directory -Force -Path $([io.Path]::Combine($global:config[$modulename]["installationPackageDir"], $global:yamlDirectoryName)) | Out-Null
    New-Item -ItemType Directory -Force -Path $global:config[$modulename]["imageDir"] | Out-Null
    New-Item -ItemType Directory -Force -Path $global:config[$modulename]["cloudConfigLocation"] | Out-Null

    if (-not (Test-MultiNodeDeployment))
    {
        Initialize-ImageDirectory -nodeName ($env:computername)
    }

    if ( !(Test-Path $global:config[$modulename]["sshPrivateKey"])) {
        Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_sshkey_missing, $global:config[$modulename]["sshPrivateKey"]))
        New-Item -ItemType Directory -Force -Path  ([IO.Path]::GetDirectoryName($global:config[$modulename]["sshPrivateKey"])) | Out-Null
        ssh-keygen -q -f $($global:config[$modulename]["sshPrivateKey"]) -t rsa -N '""'
    }
}

function Enable-MocOfflineDownload
{

    <#
    .DESCRIPTION
        Turns on offline downloading. Changes the -offlineDownload flag in Set-MocConfig to true.
     
    .PARAMETER stagingShare
        Path that the bits will be downloaded to.
         
    .PARAMETER activity
        Activity name to use when writing progress
    #>
   
    
    param(
        [Parameter()]
        [String] $stagingShare,

        [Parameter()]
        [String] $activity = $MyInvocation.MyCommand.Name        
    )
    
    $activity = $MyInvocation.MyCommand.Name
    
    Initialize-MocEnvironment -activity $activity
    
    if (-not [string]::IsNullOrWhiteSpace($stagingShare))
    {
        if ((-not [string]::IsNullOrWhiteSpace(($global:config[$modulename]["stagingShare"]))) -and ($global:config[$modulename]["stagingShare"] -ne $stagingShare))
        {
            $errorMsg = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.Moc_stagingshare_updated, $global:config[$modulename]["stagingShare"], $stagingShare))
            Write-Warning $errorMsg
        }

        $global:config[$modulename]["stagingShare"] = $stagingShare
    }

    if ([string]::IsNullOrWhiteSpace($global:config[$modulename]["stagingShare"]))
    {
        throw $($GenericLocMessage.generic_staging_share_unspecified)
    }
    
    Set-MocConfigValue -name "stagingShare" -value $global:config[$modulename]["stagingShare"]
    Set-MocConfigValue -name "offlineDownload" -value $True

}

function Disable-MocOfflineDownload
{

    <#
    .DESCRIPTION
        Turns off offline downloading. Changes the -offlineDownload flag in Set-MocConfig to false
     
    .PARAMETER activity
        Activity name to use when writing progress
    #>
 
    $activity = $MyInvocation.MyCommand.Name
    
    Initialize-MocEnvironment -activity $activity
    
    Set-MocConfigValue -name "offlineDownload" -value $False

}

function Get-MocRelease
{
    <#
    .DESCRIPTION
        Download the Moc release content
 
    .PARAMETER Version
        Version
 
    .PARAMETER path
        Path that the bits will be downloaded to if provided
 
    .PARAMETER activity
        Activity name to use when writing progress
    #>


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

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

    $activity = $MyInvocation.MyCommand.Name
    
    try
    {
        Initialize-MocEnvironment -activity $activity
    }
    catch
    {
        $offlineDownload = $true
    }

    if ($global:config[$modulename]["offlineDownload"] -eq $true) 
    {
        $offlineDownload = $true
    }
    
    if (-not [string]::IsNullOrWhiteSpace($path))
    {
        if ((-not [string]::IsNullOrWhiteSpace(($global:config[$modulename]["stagingShare"]))) -and ($global:config[$modulename]["stagingShare"] -ne $path))
        {
            $errorMsg = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $AksHciLocMessage.Moc_stagingshare_updated, $global:config[$modulename]["stagingShare"], $stagingShare))
            Write-Warning $errorMsg
        }

        $global:config[$modulename]["stagingShare"] = $path
    }

    if (($offlineDownload) -and [string]::IsNullOrWhiteSpace($global:config[$modulename]["stagingShare"]))
    {
        throw $($GenericLocMessage.generic_staging_share_unspecified)
    }

    if ($offlineDownload)
    {
        $wssdDir = $global:config[$moduleName]["stagingShare"]
        Get-MocReleaseContent -version $version -activity $activity -wssdDir $wssdDir
    }
    else
    {
        Get-MocReleaseContent -version $version -activity $activity
    }
    
    if (-not ($global:config[$modulename]["useStagingShare"]) -and (-not $offlineDownload))
    {
        Test-AuthenticodeBinaries -workingDir $global:config[$modulename]["installationPackageDir"] -binaries $script:mocBinaries
    }
}

function Get-MocReleaseContent
{
    <#
    .DESCRIPTION
        Download all required files and packages for the Moc release
 
    .PARAMETER version
        Version of Moc
 
    .PARAMETER wssdDir
        Directory for wssd installation
 
    .PARAMETER activity
        Activity name to use when writing progress
    #>


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

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

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

    )

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

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_discovering_release_content)
    $productRelease = Get-ProductRelease -version $version -moduleName $moduleName

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

                Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_download_release_content, $wssdDir))

                $downloadParams = Get-ReleaseDownloadParameters -name $subProductRelease.ProductStream -version $subProductRelease.Version -destination $wssdDir -parts $global:smallBinConcurrentDownloads -moduleName $moduleName
                $releaseInfo = Get-DownloadSdkRelease @downloadParams

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

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

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

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

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

                return
            }
        }
    }

    throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_invalid_release_content, $version))
}

function Get-MocLogs
{
    <#
    .DESCRIPTION
        Collects all the logs from the deployment
 
    .PARAMETER Path
        Path to store the logs
 
    .PARAMETER activity
        Activity name to use when writing progress
 
    .PARAMETER VirtualMachineLogs
        Switch to get only the logs from the vm's (LB vm if unstacked deployment and management-cluster vm)
 
    .PARAMETER AgentLogs
        Switch to get only logs of the wssdagent and wssdcloudagent on all nodes
 
    .PARAMETER EventLogs
        Switch to get only Windows Event Logson all nodes
     
    .PARAMETER ClusterLogTimeSpan
        Timespan value for cluster logs
    #>


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

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

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

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

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

        [Parameter(Mandatory=$false)]
        [Timespan]$ClusterLogTimeSpan = (New-TimeSpan -Days 1) # Default value of 1 day

    )

    Initialize-MocEnvironment -activity $activity
    if (!$path)
    {
        $path = [io.Path]::Combine([io.Path]::GetTempPath(), [io.Path]::GetRandomFileName())
    }

    $allswitch = $true
    if ($VirtualMachineLogs.IsPresent -or $AgentLogs.IsPresent -or $EventLogs.IsPresent)
    {
        $allswitch = $false
    }

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

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

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

    Write-Status -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_collecting_logs, $logDir))

    Wait-ForCloudAgentEndpoint -timeout 5 -activity $activity
    if ($allSwitch -or $VirtualMachineLogs.IsPresent)
    {
        try
        {
            Get-GuestVirtualMachineLogs -logDirectoryName $logDir -activity $activity
        }
        catch [Exception]
        {
            Write-Status -moduleName $moduleName  -msg $($GenericLocMessage.generic_exception)
            Write-SubStatus -moduleName $moduleName  -msg $_.Exception.Message.ToString()
        }
    }
    if ($allSwitch)
    {
        Invoke-MocBackup -path ([Io.Path]::Combine($logDir, "store"))
    }

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_gathering_cloud_logs)
    if ($allSwitch -or $AgentLogs.IsPresent)
    {
        try
        {
            $log_cloud_agent_dir = [io.Path]::Combine($logDir, "cloudlogs")
            New-Item -ItemType Directory -Force -Path $log_cloud_agent_dir | Out-Null

            $fPath = $global:config[$moduleName]["cloudConfigLocation"]+"\log\*"
            if ((Test-Path $fPath))
            {
                Copy-FileLocal -Source $fPath -Destination $log_cloud_agent_dir
            }
        }
        catch [Exception]
        {
            Write-Status -moduleName $moduleName  -msg $($MocLocMessage.moc_failed_to_capture_cloudagent_logs)
            Write-SubStatus -moduleName $moduleName  -msg $_.Exception.Message.ToString()
            if (($global:config[$moduleName]["cloudConfigLocation"] -ieq $global:defaultCloudConfigLocation) -and (Test-MultiNodeDeployment))
            {
                Write-SubStatus -moduleName $moduleName  -msg $($MocLocMessage.moc_set_cloudconfiglocation_param)
            }
        }
    }

    if (Test-MultiNodeDeployment)
    {
        Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_gathering_cluster_logs)
        Get-Cluster -ErrorAction Ignore | Select-Object -Property * >> $log_cloud_agent_dir"\failover_cluster.txt"
        Get-ClusterGroup -ErrorAction Ignore >> $log_cloud_agent_dir"\failover_clustergroup.txt"
        Get-ClusterResource -ErrorAction Ignore >> $log_cloud_agent_dir"\failover_clusterresource.txt"
        Get-ClusterNetwork -ErrorAction Ignore | Select-Object -Property * >> $log_cloud_agent_dir"\failover_clusternetwork.txt"
        Get-ClusterSharedVolume -ErrorAction Ignore >> $log_cloud_agent_dir"\failover_clustercsv.txt"
        Get-ClusterLog -UseLocalTime -Destination $log_cloud_agent_dir -TimeSpan $ClusterLogTimeSpan.TotalMinutes | Out-Null

        $nodes = Get-ClusterNode -ErrorAction Ignore
        foreach ($node in $nodes)
        {
            try
            {
                $nodeName = $node.Name
                Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_gathering_logs_for_node, $nodeName))

                $sourcesuffix = $($global:config[$moduleName]["nodeConfigLocation"]+"\log\*")
                $remotePath = "\\$nodeName\" + ($sourcesuffix).Replace(":", "$")
                $log_node_agent_dir = [io.Path]::Combine($logDir, "nodelogs_${nodeName}")
                New-Item -ItemType Directory -Force -Path $log_node_agent_dir | Out-Null

                if ($allSwitch -or $AgentLogs.IsPresent)
                {
                    if ((Test-Path $remotePath))
                    {
                        Copy-FileLocal -source $remotePath -destination $log_node_agent_dir -recurse
                    }
                }

                $nodeLoginYaml = $(Get-MocConfigValue -name "nodeLoginYAML")

                $nodeDir = Invoke-Command -ComputerName $nodeName -ScriptBlock {
                    $nodeAgentRegistryPath = $args[0]
                    $allSwitch = $args[1]
                    $eventSwitch = $args[2]
                    $nodeAgentPath = $args[3]
                    $nodeLoginYaml = $args[4]

                    $parent = [System.IO.Path]::GetTempPath()
                    [string] $guidString = [System.Guid]::NewGuid()
                    $tempDir = [io.Path]::Combine($parent, $guidString)
                    $vhdFile = [io.Path]::Combine($tempDir, "node_hyperv_vhd.txt")
                    $storeDir = [io.Path]::Combine($tempDir, "store")
                    New-Item -ItemType Directory -Path $tempDir | Out-Null
                    New-Item -ItemType Directory -Path $storeDir -Force | Out-Null
                    New-Item -ItemType File -Path $vhdFile -Force | Out-Null
                    if ($allSwitch)
                    {

                        # Complete Registry dump
                        if ((Test-Path $nodeAgentRegistryPath))
                        {
                            Get-ItemProperty "$nodeAgentRegistryPath\*" > $tempDir"\nodeagent_registry.txt"
                        }

                        # System
                        ipconfig /all >> $tempDir"\ipconfig.txt"
                        route print >> $tempDir"\route.txt"
                        arp -av >> $tempDir"\arp.txt"
                        netstat -afio  >>$tempDir"\netstat-afio.txt"
                        netstat -b >>$tempDir"\netstat-b.txt"
                        get-netadapter  | ForEach-Object {$ifindex=$_.IfIndex; $ifName=$_.Name; netsh int ipv4 sh int $ifindex | Out-File  -FilePath "$tempDir/${ifName}_int.txt" -Encoding ascii}
                        Get-ComputerInfo >> $tempDir"\node_system_info.txt"
                        Get-VM | Format-List * >> $tempDir"\node_hyperv_vm.txt"
                        Get-VM | Get-VMNetworkAdapter | Format-List * >> $tempDir"\node_hyperv_vnic.txt"
                        Get-VMSwitch | Format-List * >> $tempDir"\node_hyperv_vswitch.txt"
                        foreach ($vm in Get-VM) {
                            $vm.Name | Out-File -FilePath $vhdFile -Append
                            $vm.VMId  | Out-File -FilePath $vhdFile -Append
                            $vm.VMId | Get-VHD | Format-List *  | Out-File -FilePath $vhdFile -Append
                        }

                        Invoke-Expression "& '$nodeAgentPath' security login --loginpath ""$nodeLoginYaml"" --identity"
                        Invoke-Expression "& '$nodeAgentPath' admin recovery backup --path ""$storeDir"" "
                        try
                        {
                            # Expected to fail if nodeagent is not started with debug flag
                            Invoke-WebRequest http://localhost:6060/debug/pprof/goroutine?debug=1 -OutFile $tempDir/"nodeagent-routine.txt" -ErrorAction Ignore
                        }
                        catch {}
                        try
                        {
                            # CloudAgent will only be active on one node, so we expect it to fail in most cases
                            # Expected to fail if cloudagent is not started with debug flag
                            Invoke-WebRequest http://localhost:8080/debug/pprof/goroutine?debug=1 -OutFile $tempDir/"cloudagent-routine.txt" -ErrorAction Ignore | Out-Null
                        }
                        catch {}

                    }

                    if ($allSwitch -or $eventSwitch)
                    {
                        # Event Logs
                        Copy-Item "$env:SystemDrive\Windows\System32\Winevt\Logs\Application.evtx" -destination $tempDir
                        Copy-Item "$env:SystemDrive\Windows\System32\Winevt\Logs\System.evtx" -destination $tempDir
                        Copy-Item "$env:SystemDrive\Windows\System32\Winevt\Logs\\Microsoft-Windows-Hyper-V*.evtx" -destination $tempDir
                    }

                    return $tempDir
                } -ArgumentList @($global:nodeAgentRegistryPath, $allSwitch, $EventLogs.IsPresent, $global:nodeCtlFullPath, $nodeLoginYaml)

                $remotePath = "\\$nodeName\" + ($nodeDir).Replace(":", "$")
                if ((Test-Path $remotePath))
                {
                    Copy-FileLocal -source $remotePath"\*" -destination $log_node_agent_dir -recurse
                } 

                Invoke-Command -ComputerName $nodeName -ScriptBlock {
                    Remove-Item $args[0] -Recurse
                } -ArgumentList $nodeDir
            }
            catch [Exception]
            {
                # An exception was thrown, write it out and exit
                Write-Status -moduleName $moduleName  -msg $($GenericLocMessage.generic_exception)
                Write-SubStatus -moduleName $moduleName  -msg $_.Exception.Message.ToString()
            }
        }
    }
    else
    {
        Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_gathering_local_node_logs)

        $log_node_agent_dir = [io.Path]::Combine($logDir, "nodelogs")
        New-Item -ItemType Directory -Force -Path $log_node_agent_dir | Out-Null
        $vhdFile = [io.Path]::Combine($log_node_agent_dir, "node_hyperv_vhd.txt")
        $storeDir = [io.Path]::Combine($log_node_agent_dir, "store")
        New-Item -ItemType Directory -Path $storeDir | Out-Null
        New-Item -ItemType File -Path $vhdFile -Force | Out-Null
        if ($allSwitch -or $AgentLogs.IsPresent)
        {
            $tPath = $global:config[$moduleName]["nodeConfigLocation"]+"\log\*"
            if ((Test-Path $tPath))
            {
                Copy-FileLocal -source $tPath -destination $log_node_agent_dir -recurse
            }
        }

        if ($allSwitch)
        {
            # Registry dump
            if ((Test-Path $global:nodeAgentRegistryPath))
            {
                Get-ItemProperty "$global:nodeAgentRegistryPath\*" > $log_node_agent_dir"\nodeagent_registry.txt" -ErrorAction Ignore
            }

            # System
            ipconfig /all >> $log_node_agent_dir"\ipconfig.txt"
            get-netadapter  | ForEach-Object {$ifindex=$_.IfIndex; $ifName=$_.Name; netsh int ipv4 sh int $ifindex | Out-File  -FilePath "$log_node_agent_dir/${ifName}_int.txt" -Encoding ascii}
            Get-ComputerInfo >> $log_node_agent_dir"\node_system_info.txt"
            Get-VM | Format-List * >> $log_node_agent_dir"\node_hyperv_vm.txt"
            Get-VM | Get-VMNetworkAdapter | Format-List * > $log_node_agent_dir"\node_hyperv_vnic.txt"
            Get-VMSwitch | Format-List * >> $log_node_agent_dir"\node_hyperv_vswitch.txt"
            foreach ($vm in Get-VM) {
                $vm.Name | Out-File -FilePath $vhdFile -Append
                $vm.VMId  | Out-File -FilePath $vhdFile -Append
                $vm.VMId | Get-VHD | Format-List *  | Out-File -FilePath $vhdFile -Append
            }
            Invoke-NodeCommand $(" admin recovery backup --path ""$storeDir"" " + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}) )
            try
            {
                # Expected to fail if nodeagent is not started with debug flag
                Invoke-WebRequest http://localhost:6060/debug/pprof/goroutine?debug=1 -OutFile $log_node_agent_dir/"nodeagent-routine.txt" -ErrorAction Ignore
            }
            catch {}
            try
            {
                # Expected to fail if cloudagent is not started with debug flag
                Invoke-WebRequest http://localhost:8080/debug/pprof/goroutine?debug=1 -OutFile $log_node_agent_dir/"cloudagent-routine.txt" -ErrorAction Ignore
            }
            catch {}
        }

        if ($allSwitch -or $EventLogs.IsPresent)
        {
            # Event Logs
            Copy-Item "$env:SystemDrive\Windows\System32\Winevt\Logs\Application.evtx" -destination $log_node_agent_dir
            Copy-Item "$env:SystemDrive\Windows\System32\Winevt\Logs\System.evtx" -destination $log_node_agent_dir
            Copy-Item "$env:SystemDrive\Windows\System32\Winevt\Logs\\Microsoft-Windows-Hyper-V*.evtx" -destination $log_node_agent_dir
        }
    }

    return $path
}

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


    Get-WinEvent -ProviderName $moduleName -ErrorAction Ignore
}


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


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

    $activity = $MyInvocation.MyCommand.Name

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

    $catalog = Get-LatestCatalog -moduleName $moduleName
    $productRelease = $catalog.ProductStreamRefs[0].ProductReleases[0]

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

    throw $($MocLocMessage.moc_no_latest_version)
}

function Reset-Firewall
{
    <#
    .DESCRIPTION
        Removes Windows Firewall exceptions that were previously added for agent communication.
 
    .PARAMETER nodeName
        The node to execute on.
    #>

    param (
        [String]$nodeName
    )

    Write-Status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_cleanup_windows_firewall, $nodeName)) -moduleName $moduleName

    $firewallRules = Get-FirewallRules

    Invoke-Command -ComputerName $nodeName -ScriptBlock {
        $rules = $args[0]
        foreach($rule in $rules)
        {
            $fw = Get-NetFirewallRule -DisplayName $($rule[0]) -ErrorAction Ignore
            if ($fw)
            {
                $fw | Remove-NetFirewallRule
            }
        }
    } -ArgumentList (, $firewallRules)
}

function Reset-Host
{
    <#
    .DESCRIPTION
        Cleans up the host by removing resources from nodeagent and cloudagent and stopping the processes.
 
    .PARAMETER removeAll
        Removes all artifacts creates by the deployment script.
 
    .PARAMETER skipImageDeletion
        This parameter skips deletion of downloaded VHD images and leaves them present in the configured image storage location.
        This is intended to make redeployment faster and avoid unnecessary downloads of the VHDs.
 
    .PARAMETER skipConfigDeletion
        This parameter skips deletion of the module configuration.
 
    .PARAMETER activity
        Activity name to use when writing progress
    #>


    param (
        [Switch]$removeAll,
        [Switch]$skipImageDeletion,
        [Switch]$skipConfigDeletion,
        [String]$activity = $MyInvocation.MyCommand.Name
    )

    Uninstall-Cloud -activity $activity

    if ([string]::IsNullOrWhiteSpace($global:config[$moduleName]["cloudConfigLocation"]) -or [string]::IsNullOrWhiteSpace($global:config[$moduleName]["imageDir"]))
    {
        # This case happens during Initialize Node where the cloud config location and image dir paths don't exist
        return
    }

    $hosts = $env:computername
    if (Test-MultiNodeDeployment)
    {
        $hosts = (Get-ClusterNode -ErrorAction Ignore).Name
    }

    # In order to avoid double-hop issues with network file share, we remove the
    # files directly (instead of doing it from the remote powershell session)
    if ($removeAll.IsPresent -And $global:config[$moduleName]["cloudConfigLocation"].StartsWith("\\"))
    {
        Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_removing_cloudagent_directory)
        Remove-Item -Path $global:config[$moduleName]["cloudConfigLocation"] -Force -Recurse -ErrorAction Ignore
    }

    if ($removeAll.IsPresent -And $global:config[$moduleName]["imageDir"].StartsWith("\\"))
    {
        Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_removing_image_directory)
        if ($skipImageDeletion.IsPresent)
        {
            Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_downloaded_images_preserved)
            Get-ChildItem -Path $global:config[$moduleName]["imageDir"] -Directory -ErrorAction Ignore | Remove-Item -Force -Recurse -ErrorAction Ignore
        }
        else
        {
            Remove-Item -Path $global:config[$moduleName]["imageDir"] -Force -Recurse -ErrorAction Ignore
        }
    }

    # Before we remove workingDir, ensure that this deployment machine is not using that directory as the current working dir. Deleting it while it is the working directory
    # can cause unexpected behavior later on (e.g. download agent failure in redeployment)
    $cwd = Get-Location | Select-Object -ExpandProperty Path
    if ($cwd -ieq $global:config[$moduleName]["installationPackageDir"])
    {
        Set-Location -Path ..
    }

    $inputArgs = @(
        $([io.Path]::Combine($global:config[$moduleName]["installationPackageDir"], $global:yamlDirectoryName)),
        $global:config[$moduleName]["workingDir"],
        $removeAll.IsPresent,
        $global:config[$moduleName]["imageDir"],
        $skipImageDeletion.IsPresent,
        $global:installDirectory,
        $skipConfigDeletion.IsPresent,
        $GenericLocMessage
    )

    $hosts | ForEach-Object {
        Write-StatusWithProgress -activity $activity -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_cleaning_up_files, $_))

        $inputArgsIter = @($_)
        $inputArgsIter += $inputArgs

        Invoke-Command -ComputerName $_ -ScriptBlock {
            $hostname = $args[0]
            $yamlLocation = $args[1]
            $workingDir = $args[2]
            $removeAll = $args[3]
            $imageDir = $args[4]
            $skipImageDeletion = $args[5]
            $installDir= $args[6]
            $skipConfigDeletion = $args[7]
            $GenericLocMessage = $args[8]

            write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_removing_yaml_on_hostname, $hostname))
            Remove-Item -Path $yamlLocation -Force -Recurse -ErrorAction Ignore

            if ($removeAll)
            {
                write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_removing_image_directory_on_hostname, $hostname))
                if ($skipImageDeletion)
                {
                    write-verbose $($GenericLocMessage.generic_downloaded_images_preserved)
                    Get-ChildItem -Path $imageDir -Directory -ErrorAction Ignore | Remove-Item -Force -Recurse -ErrorAction Ignore
                }
                else
                {
                    if ($imageDir)
                    {
                        Remove-Item -Path $imageDir -Force -Recurse -ErrorAction Ignore
                    }
                }

                if (-not $skipConfigDeletion)
                {
                    if ($workingDir)
                    {
                        # Keeping the working dir as part of config deletion, since config is now part of workingDir
                        Remove-Item -Path $workingDir -Force -Recurse -ErrorAction Ignore
                    }
                }

                write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_removing_installation_directory_contents, $hostname))
                Remove-Item -Path $installDir -Force -Recurse -ErrorAction Ignore
            }
        } -ArgumentList $inputArgsIter
    }

    if (-not $skipConfigDeletion.IsPresent)
    {
        Reset-Configuration -moduleName $moduleName
    }
}

function Uninstall-Cloud
{
    <#
    .DESCRIPTION
        Deprovision an onPremise cloud
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


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

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_deprovisioning_cloud)

    $deprovisionCloudResources = $true
    if (Test-MultiNodeDeployment)
    {
        if ($global:config[$moduleName]["clusterRoleName"])
        {
            $cg = Get-ClusterGroup -name $global:config[$moduleName]["clusterRoleName"].ToString() -ErrorAction Ignore
            if ($null -eq $cg -or $cg.State -ieq "Failed")
            {
                # Skip clean-up of cloud resources if the cluster is in a failed state
                $deprovisionCloudResources = $false
            }
        }
    }

    if (!(Test-Path $global:cloudCtlFullPath))
    {
        $deprovisionCloudResources = $false
    }

    # 1. Cleanup Cloud Resources
    if ($deprovisionCloudResources)
    {
        $mocLocation = $($global:config[$modulename]["cloudLocation"])
        Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_cleanup_cloud_groups)
        try {
            Reset-MocGroup -location $($mocLocation)
        } catch {
            if (-not ($_.Exception.Message -like "*connection closed*")) {
                Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_warning, $_.Exception.Message))
            }
        }

        Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_cleanup_cloud_gallery)
        try
        {
            Reset-MocGalleryImage -location $mocLocation
        }
        catch
        {
            if (-not ($_.Exception.Message -like "*connection closed*"))
            {
                Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_warning, $_.Exception.Message))
            }
        }

        Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_cleanup_cloud_container)
        try
        {
            Reset-MocContainer -location $($mocLocation)
        }
        catch
        {
            if (-not ($_.Exception.Message -like "*connection closed*"))
            {
                Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_warning, $_.Exception.Message))
            }
        }

        Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_cleanup_cloud_vippool)
        try
        {
            Reset-MocVipPool -location $($mocLocation)
        }
        catch
        {
            if (-not ($_.Exception.Message -like "*connection closed*"))
            {
                Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_warning, $_.Exception.Message))
            }
        }

        if (Test-MultiNodeDeployment)
        {
            try
            {
                Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_cleanup_cloud_cluster)
                Reset-MocCluster -location $($mocLocation)
            }
            catch
            {
                if (-not ($_.Exception.Message -like "*connection closed*"))
                {
                    Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_warning, $_.Exception.Message))
                }
            }
        }
        else
        {
            try
            {
                Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_cleanup_cloud_node)
                Reset-MocNode -location $($mocLocation)
            }
            catch
            {
                if (-not ($_.Exception.Message -like "*No connection could be made because the target machine actively refused it*"))
                {
                    Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_warning, $_.Exception.Message))
                }
            }
        }

        Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_cleanup_mac_pools)
        try
        {
            Reset-MocMacPool -location $($mocLocation)
        }
        catch
        {
            if (-not ($_.Exception.Message -like "*connection closed*"))
            {
                Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_warning, $_.Exception.Message))
            }
        }

        Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_cleanup_cloud_locations)
        try
        {
            Reset-MocLocation
        }
        catch
        {
            if (-not ($_.Exception.Message -like "*connection closed*"))
            {
                Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_warning, $_.Exception.Message))
            }
        }
    }

    # 2. Deprovision Cloud Agents
    Uninstall-CloudAgent -cloudAgentName $global:cloudAgentAppName -activity $activity

    # 3. DeProvision Node Agent
    if (Test-MultiNodeDeployment)
    {
        # Try to get the nodes, but don't fail if the cluster is in a bad state.
        # Since we don't want Get-ClusterNode to swallow any error from the ForEach loop.
        # So, we have to keep the Get-ClusterNode separate from the pipe/foreach loop.
        $nodes = Get-ClusterNode -ErrorAction Ignore
        $nodes | ForEach-Object { 
            Uninstall-Node -activity $activity -nodeName $_.Name
        }
    } else {
        Uninstall-Node -activity $activity -nodeName ($env:computername)
    }
}

function Uninstall-CloudAgent
{
    <#
    .DESCRIPTION
        Deprovision a Cloud Agent
        1. Stop the Service
        2. Remove Cluster Service
 
    .PARAMETER cloudAgentName
        Cloud agent name
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


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

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_deprovisioning_cloudagent, $cloudAgentName))

    Stop-AllProcesses -processName "wssdcloudagent"

    # Remove the Cluster Gen Service
    if (Test-MultiNodeDeployment)
    {
        if ($global:config[$moduleName]["clusterRoleName"])
        {
            # Try to get the groups, but don't fail if the cluster is in a bad state.
            # Since we don't want Get-ClusterGroup to swallow any error from the ForEach loop.
            # So, we have to keep the Get-ClusterGroup separate from the pipe/foreach loop.
            $nodes = Get-ClusterGroup $global:config[$moduleName]["clusterRoleName"].ToString()  -ErrorAction Ignore 
            $nodes | ForEach-Object { 
                Remove-ClusterGroup -InputObject $_ -Force -RemoveResources -ErrorAction Ignore
            }
        }
        else 
        {
            # The config is lost, try to cleanup orphaned resource
            Get-ClusterGroup -Name "ca-*" | Remove-ClusterGroup -RemoveResources -Force -ErrorAction Ignore -Verbose
        }

        $nodes = Get-ClusterNode -ErrorAction Ignore

        $nodes.Name | ForEach-Object {
            Invoke-Command -ComputerName $_ -ScriptBlock {
                $cloudConfigLocation = $args[0]
                $registryLocation = $args[1]
                $GenericLocMessage = $args[2]
                try
                {
                    Get-Service wssdcloudagent -ErrorAction Ignore | ForEach-Object {
                        $service = Get-WmiObject -Class Win32_Service -Filter "Name='wssdcloudagent'"
                        if ($null -ne $service) {
                            Stop-Service wssdcloudagent -ErrorAction:SilentlyContinue
                            $service.delete() | Out-Null
                        }
                    }

                    # Flush dns on all nodes to avoid issues with stale entries when re-installing cloudagent.
                    # The server flushes dns during installation, so not needed for single node.
                    Clear-DnsClientCache
                }
                catch [Exception]
                {
                    # Write-ModuleEventLog -moduleName $moduleName -entryType Error -eventId 100 -message "$activity - $_"
                }

                write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_remove_cloudagent_directory, $(hostname)))
                if ($cloudConfigLocation)
                {
                    Remove-Item -Path $cloudConfigLocation -Force -Recurse -ErrorAction Ignore
                }
                write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_remove_cloudagent_registry, $(hostname)))
                Remove-Item -Path $registryLocation -Recurse -Force -ErrorAction Ignore
            } -ArgumentList @($global:config[$moduleName]["cloudConfigLocation"], $global:cloudAgentRegistryPath, $GenericLocMessage)
        }
    }
    else
    {
        $service = Get-WmiObject -Class Win32_Service -Filter "Name='wssdcloudagent'"
        if ($null -ne $service) {
            Stop-Service WssdCloudAgent -ErrorAction:SilentlyContinue
            $service.delete() | Out-Null
        }

        try {
            write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_remove_cloudagent_directory, $(hostname)))
            Remove-Item -Path $global:config[$moduleName]["cloudConfigLocation"] -Force -Recurse -ErrorAction Ignore
            write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_remove_cloudagent_registry, $(hostname)))
            Remove-Item -Path $global:cloudAgentRegistryPath -Recurse -Force -ErrorAction Ignore
        }
        catch
        {
            Write-ModuleEventLog -moduleName $moduleName -entryType Error -eventId 100 -message "$activity - $_"
        }
    }
}

function Uninstall-NodeAgent
{
    <#
    .DESCRIPTION
        Provision a Node Agent
        1. Download Node Agent Binary to the Node
        2. Register as Windows service
        3. Configure Service
        4. Start the Service
 
    .PARAMETER nodeName
        The node to execute on.
    #>


    param (
        [String]$nodeName
    )

    Write-Status -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_deprovisioning_nodeagent, $nodeName))

    $nodeConfigLocation = Get-ConfigurationValue -module $moduleName -name "nodeConfigLocation"
    if ([string]::IsNullOrWhiteSpace($nodeConfigLocation))
    {
        # This might happen in cases where the configuration is lost.
        # We make best effort to cleanup the default location
        $nodeConfigLocation = $global:defaultNodeConfigLocation
    }

    Invoke-Command -ComputerName $nodeName -ScriptBlock {
        $nodeConfigLocation = $args[0]
        $registryLocation = $args[1]
        $GenericLocMessage = $args[2]
        Get-Service WssdAgent -ErrorAction Ignore | ForEach-Object {
            $service = Get-WmiObject -Class Win32_Service -Filter "Name='wssdagent'"
            if ($null -ne $service) {
                Stop-Service WssdAgent -ErrorAction:SilentlyContinue
                $service.delete() | Out-Null
            }
        }
        write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_remove_nodeagent_directory, $(hostname)))
        Remove-Item -Path $nodeConfigLocation -Force -Recurse -ErrorAction Ignore
        write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_remove_nodeagent_registry, $(hostname)))
        Remove-Item -Path $registryLocation -Recurse -Force -ErrorAction Ignore
    } -ArgumentList @($nodeConfigLocation, $global:nodeAgentRegistryPath, $GenericLocMessage)
}

function Uninstall-Node
{
    <#
    .DESCRIPTION
        Provision a Node Agent
        1. Download Node Agent Binary to the Node
        2. Register as Windows service
        3. Configure Service
        4. Start the Service
 
    .PARAMETER nodeName
        The node to execute on.
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


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

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_deprovisioning_node, $nodeName))

    Reset-Firewall -NodeName $nodeName
    Uninstall-NodeAgent -nodeName $nodeName
    Uninstall-MocBinaries -nodeName $nodeName
    Invoke-Command -ComputerName $nodeName -ScriptBlock {
        $tmp = $args[0]
        $GenericLocMessage = $args[1]

        write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_remove, $tmp, $(hostname)))
        Remove-Item -Path $tmp -Force -Recurse -ErrorAction Ignore
    } -ArgumentList @($global:mocMetadataRoot, $GenericLocMessage)
}

#region moc operations


#region moc macpool

function Set-MocYaml
{
    <#
    .DESCRIPTION
        Sets the content of the input to a file and return the path
 
    .PARAMETER yamlData
        input string
    #>


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

    if ([string]::IsNullOrWhiteSpace($yamlData))
    {
        throw $($GenericLocMessage.generic_invalid_yaml_input)
    }

    $yamlFile = [io.Path]::GetTempFileName() + ".yaml"
    if (
        $global:config -and
        $global:config[$moduleName] -and
        (-not [string]::IsNullOrWhiteSpace($global:config[$moduleName]["installationPackageDir"]))
    )
    {
        $yamlFile = [io.Path]::Combine($global:config[$moduleName]["installationPackageDir"],  $global:yamlDirectoryName,  ([io.Path]::GetRandomFileName() + ".yaml"))
    }

    Set-Content -Path $yamlFile -Value $yamlData -ErrorVariable err
    if (!$err -and $err.count -gt 0)
    {
        throw $err
    }
    Write-SubStatus -moduleName $moduleName -msg $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_set_yaml, $yamlFile, $yamlData))
    return $yamlFile
}
function New-MocMacPool
{
    <#
    .DESCRIPTION
        Adds a macpool resource to moc .
 
    .PARAMETER name
        name of macpool
 
    .PARAMETER location
        location
 
    .PARAMETER macPoolStart
        First MAC address in the pool
 
    .PARAMETER macPoolEnd
        Last MAC address in the pool
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [string]$name,
        [Parameter(Mandatory=$true)]
        [string]$macPoolStart,
        [Parameter(Mandatory=$true)]
        [string]$macPoolEnd,
        [Parameter(Mandatory=$true)]
        [string]$location,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    $argsDict = @{"--name" = $name; "--start-mac"= $macPoolStart; "--end-mac" = $macPoolEnd; "--location" = $location }

    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    Invoke-MocCommand " network macpool create" -argDictionary $argsDict
}

function Get-MocMacPool
{
    <#
    .DESCRIPTION
        Removes a macpool resource from moc.
 
    .PARAMETER name
        Name of the macpool
 
    .PARAMETER location
        Location for the macpool
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$location,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    if ([string]::IsNullOrWhiteSpace($name))
    {
        Invoke-MocListCommand $(" network macpool list --location "+$location + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
    else
    {
        Invoke-MocShowCommand $(" network macpool show --name ""$name"" --location "+$location + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
}

function Remove-MocMacPool
{
    <#
    .DESCRIPTION
        Removes a macpool resource from moc.
 
    .PARAMETER name
        Name of the macpool
 
    .PARAMETER location
        Location for the macpool
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$location,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" network macpool delete --name ""$name"" --location "+$location + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}

function Reset-MocMacPool
{
    <#
    .DESCRIPTION
        Removes the macpool resource from moc.
 
    .PARAMETER location
        The location to reset the entity
 
    #>


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

    Invoke-MocCommand " network macpool list --output tsv --query ""[*].name"" --location $location" | ForEach-Object {
        $entityName = $_
        if ($entityName -ine "Default")
        {
            if($timeoutSeconds) {
                Remove-MocMacPool -name $entityName -location $location -timeout $timeoutSeconds
            }
            else {
                Remove-MocMacPool -name $entityName -location $location
            }
        }
    }
}
#endregion moc macpool

#region moc location

function New-MocLocation
{
    <#
    .DESCRIPTION
        Adds a location resource to moc .
 
    .PARAMETER name
        Name of the location
 
    .PARAMETER timeoutSeconds
        timeout in seconds
     #>


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

    Write-Status -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_creating_cloud_location, $name))

    $argsDict = @{ "--name" = $name }

    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    Invoke-MocCommand " cloud location create" -argDictionary $argsDict
}

function Get-MocLocation
{
    <#
    .DESCRIPTION
        Removes a Location resource from moc.
 
    .PARAMETER name
        Name of the Location
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [String]$name,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    if ([string]::IsNullOrWhiteSpace($name))
    {
        Invoke-MocListCommand $(" cloud location list" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
    else
    {
        Invoke-MocShowCommand $(" cloud location show --name ""$name"" " + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
}

function Remove-MocLocation
{
    <#
    .DESCRIPTION
        Removes a Location resource from moc.
 
    .PARAMETER name
        Name of the Location
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


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

    Invoke-MocCommand $(" cloud location delete --name ""$name"" " + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}

function Reset-MocLocation
{
    <#
    .DESCRIPTION
        Remove all moc locations.
    #>


    Invoke-MocCommand " cloud location list --output tsv --query ""[*].name""" | ForEach-Object {
        $entityName = $_
        if ($entityName -ine "Reserved")
        {
            if($timeoutSeconds) {
                Remove-MocLocation -name $entityName -timeout $timeoutSeconds
            }
            else {
                Remove-MocLocation -name $entityName
            }
        }
    }
}

#endregion moc location

#region moc group


function New-MocGroup
{
    <#
    .DESCRIPTION
        Adds a group resource to moc .
 
    .PARAMETER name
        Name of the group
 
    .PARAMETER location
        Location for the group
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$location,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    $argsDict = @{ "--name" = $name; "--location" = $location}

    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    Invoke-MocCommand " cloud group create" -argDictionary $argsDict
}

function Get-MocGroup
{
    <#
    .DESCRIPTION
        Removes a Group resource from moc.
 
    .PARAMETER name
        Name of the Group
 
    .PARAMETER location
        Location for the Group
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$location,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    if ([string]::IsNullOrWhiteSpace($name))
    {
        Invoke-MocListCommand $(" cloud group list --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}) )
    }
    else
    {
        Invoke-MocShowCommand $(" cloud group show --name ""$name"" --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
}

function Remove-MocGroup
{
    <#
    .DESCRIPTION
        Removes a Group resource from moc.
 
    .PARAMETER name
        Name of the Group
 
    .PARAMETER location
        Location for the Group
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$location,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" cloud group delete --name ""$name"" --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}
function Reset-MocGroup
{
    <#
    .DESCRIPTION
        Remove all moc groups.
 
    .PARAMETER location
        The location to reset the entity
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$location,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" cloud group list --output tsv --query ""[*].name"" --location $location") | ForEach-Object {
        $entityName = $_
        if ($entityName -notlike "No *")
        {
            if($timeoutSeconds) {
                Remove-MocGroup -name $entityName -location $location -timeout $timeoutSeconds
            }
            else {
                Remove-MocGroup -name $entityName -location $location
            }
        }
    }
}

#endregion moc group

#region moc container

function New-MocContainer
{
    <#
    .DESCRIPTION
        Adds a container resource to moc .
 
    .PARAMETER name
        Name of the container
 
    .PARAMETER path
        path to create the container
 
    .PARAMETER location
        Location of the container
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$path,
        [Parameter(Mandatory=$true)]
        [String]$location,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    $argsDict = @{ "--name" = $name; "--path" = $path; "--location" = $location }
    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    Invoke-MocCommand " storage container create" -argDictionary $argsDict
}

function Get-MocContainer
{
    <#
    .DESCRIPTION
        Removes a Container resource from moc.
 
    .PARAMETER name
        Name of the Container
 
    .PARAMETER location
        Location for the Container
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$location,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    if ([string]::IsNullOrWhiteSpace($name))
    {
        Invoke-MocListCommand $(" storage container list --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
    else
    {
        Invoke-MocShowCommand $(" storage container show --name ""$name"" --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
}

function Remove-MocContainer
{
    <#
    .DESCRIPTION
        Removes a Container resource from moc.
 
    .PARAMETER name
        Name of the Container
 
    .PARAMETER location
        Location for the Container
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$location,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" storage container delete --name ""$name"" --location "+$location + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}
function Reset-MocContainer
{
    <#
    .DESCRIPTION
        Remove all moc storage containers.
 
    .PARAMETER location
        The location to reset the entity
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$location,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" storage container list --output tsv --query ""[*].name"" --location $location") | ForEach-Object {
        $entityName = $_
        if ($entityName -notlike "No *") {
        if ($timeoutSeconds) {
                Invoke-MocCommand $(" storage container delete --name ""$entityName"" --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
        }
        else {
                Invoke-MocCommand $(" storage container delete --name ""$entityName"" --location $location")
        }
        }
    }
}

#endregion moc container

#region moc vippool

function New-MocVipPool
{
    <#
    .DESCRIPTION
        Adds a vip pool resource to moc .
 
    .PARAMETER name
        name of vippool
 
    .PARAMETER location
        location
 
    .PARAMETER vipPoolStart
        First vip address in the pool
 
    .PARAMETER vipPoolEnd
        Last vip address in the pool
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [string]$name,
        [Parameter(Mandatory=$true)]
        [string]$vipPoolStart,
        [Parameter(Mandatory=$true)]
        [string]$vipPoolEnd,
        [Parameter(Mandatory=$true)]
        [string]$location,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    $argsDict = @{ "--name" = $name; "--start-ip" = $vipPoolStart; "--end-ip" = $vipPoolEnd; "--location" = $location }

    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    Invoke-MocCommand " network vippool create" -argDictionary $argsDict
}

function Get-MocVipPool
{
    <#
    .DESCRIPTION
        Get a VipPool resource from moc.
 
    .PARAMETER name
        Name of the VipPool
 
    .PARAMETER location
        Location for the VipPool
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$location,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    if ([string]::IsNullOrWhiteSpace($name))
    {
        Invoke-MocListCommand $(" network vippool list --location "+$location + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
    else
    {
        Invoke-MocShowCommand $(" network vippool show --name ""$name"" --location "+$location + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
}

function Remove-MocVipPool
{
    <#
    .DESCRIPTION
        Removes a VipPool resource from moc.
 
    .PARAMETER name
        Name of the VipPool
 
    .PARAMETER location
        Location for the VipPool
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$location,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" network vippool delete --name ""$name"" --location "+$location + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}

function Reset-MocVipPool
{
    <#
    .DESCRIPTION
        Remove all moc network vippools.
 
    .PARAMETER location
        The location to reset the entity
 
    .PARAMETER timeoutSeconds
        timeout in seconds
 
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$location,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" network vippool list --output tsv --query ""[*].name"" --location "+$location) | ForEach-Object {
        $entityName = $_
        if ($entityName -notlike "No *") {
            Invoke-MocCommand $(" network vippool delete --name ""$entityName"" --location "+$location + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
        }
    }
}

#endregion moc vippool

#region moc node

function New-MocNode
{
    <#
    .DESCRIPTION
        Adds a node resource to moc .
 
    .PARAMETER name
        Name of the node
 
    .PARAMETER location
        Location for the node
 
    .PARAMETER fqdn
        fqdn of the node
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$location,
        [Parameter(Mandatory=$true)]
        [String]$fqdn,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    $argsDict = @{ "--name" = $name; "--fqdn" = $fqdn; "--port" = 45000; "--authorizer-port" = 45001; "--location" = $location }

    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    Invoke-MocCommand " cloud node create" -argDictionary $argsDict
}

function Get-MocNode
{
    <#
    .DESCRIPTION
        Get a Node(s) resource from moc.
 
    .PARAMETER name
        Name of the Node
 
    .PARAMETER location
        Location for the Node
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$location,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    if ([string]::IsNullOrWhiteSpace($name))
    {
        Invoke-MocListCommand $(" cloud node list --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}) )
    }
    else
    {
        Invoke-MocShowCommand $(" cloud node show --name ""$name"" --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}) )
    }
}

function Remove-MocNode
{
    <#
    .DESCRIPTION
        Removes a Node resource from moc.
 
    .PARAMETER name
        Name of the Node
 
    .PARAMETER location
        Location for the Node
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$location,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" cloud node delete --name ""$name"" --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}
function Reset-MocNode
{
    <#
    .DESCRIPTION
        Remove all moc nodes.
 
    .PARAMETER location
        The location to reset the entity
 
    #>


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

    Invoke-MocCommand $(" cloud node list --output tsv --query ""[*].name"" --location $location") | ForEach-Object {
        $entityName = $_
        if ($entityName -notlike "No *")
        {
        if($timeoutSeconds) {
        Remove-MocNode -name $entityName -location $location -timeout $timeoutSeconds
        }
        else {
                Remove-MocNode -name $entityName -location $location
        }
        }
    }
}

#endregion moc node

#region moc physical node
function Confirm-NodeStatus
{

    <#
    .DESCRIPTION
        sanity check before New-MocPhysicalNode and Remove-MocPhysicalNode
    .PARAMETER nodeName
        The name of the node in the FC
    .PARAMETER checkConfiguration
        To check if the node is configured and running
    #>


    param (
        [String]$nodeName,
        [switch]$checkConfiguration 
    )

    # check if node in FC
    $node = Get-ClusterNode -ErrorAction Ignore | Where-Object { $_.Name -eq $nodeName -and $_.State -ieq 'Up'}
    if (($null -eq $node))
    {
        throw [CustomException]::new( $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_node_not_in_cluster, $nodeName)), $true)
    }

    if (!($checkConfiguration.IsPresent))
    { 
        return
    }

    # check if node is configured
    Test-Binary -nodeName $nodeName -binaryName $global:nodeAgentFullPath
    Test-NodeConfiguration -nodeName $nodeName
}

function New-MocPhysicalNode
{
    <#
    .DESCRIPTION
        FRU scenario, orchestrates the onboarding of a new node
    .PARAMETER nodeName
        The name of the node in the FC
    .PARAMETER activity
        Activity name to use when updating progress
    #>


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

    if (!(Test-MultiNodeDeployment))
    {
        throw [CustomException]::new( $($MocLocMessage.moc_new_node_support), $true)
    }

    try 
    {
        $nodeTmp = Get-MocNode -name $nodeName -location $global:config[$modulename]["cloudLocation"] 
    } 
    catch 
    {
        # Ignore the exception
    }

    if ($nodeTmp)
    {
        # Already part of the deployment
        throw [CustomException]::new( $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_node_already_present, $nodeName)), $true)
    }

    Confirm-NodeStatus -nodeName $nodeName

    # node setup
    Initialize-Node -nodeName $nodeName
    
    $nodeLoginYaml = $(Get-MocConfigValue -name "nodeLoginYAML")
    Install-NodeAgent -nodeName $nodeName -activity $activity
    Invoke-NodeLogin -nodeName $nodeName -loginYaml $nodeLoginYaml

    # check if agent is installed successfully and registered with CA
    Confirm-NodeStatus -nodeName $nodeName -checkConfiguration
    # wait for all nodes to become active
    $mocLocation = $global:config[$modulename]["cloudLocation"]
    Wait-ForActiveNodes -location $mocLocation -activity $activity
}

function Remove-MocPhysicalNode
{
    <#
    .DESCRIPTION
        FRU scenario, orchestrates the offboarding of a node that has failed
    .PARAMETER nodeName
        The name of the node in the FC
    .PARAMETER activity
        Activity name to use when updating progress
    #>


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

    if (!(Test-MultiNodeDeployment))
    {
        throw [CustomException]::new( $($MocLocMessage.moc_remove_node_support), $true)
    }

    if ($nodeName -ieq (hostname))
    {
        throw [CustomException]::new( $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_node_removal_not_allowed, $(hostname))), $true)
    }

    # Check if cloudagent is running in the node
    $clusterName = $global:config[$moduleName]["clusterRoleName"]
    $resourceGroup = Get-ClusterGroup -Name $clusterName
    if ($nodeName -ieq $resourceGroup.OwnerNode)
    {
        # Move cloudagent to another node in the cluster
        Move-ClusterGroup -Name $clusterName
        Wait-ForCloudAgentEndpoint -timeout 60 -activity $activity
        $resourceGroup = Get-ClusterGroup -Name $clusterName
        # cloudagent is still running in the same node would mean it is a cluster comprising of single node
        if ($nodeName -ieq $resourceGroup.OwnerNode)
        {
            throw [CustomException]::new( $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_node_removal_not_allowed, $(hostname))), $true)
        }
    }

    Remove-MocIdentity -name $nodeName

    # check if resources have migrated
    $mocLocation = $($global:config[$modulename]["cloudLocation"])
    try
    {
        Remove-MocNode -name $nodeName -location $mocLocation
    }
    catch
    {
        Write-ModuleEventLog -moduleName $moduleName -entryType Warning -eventId 2 -message "$activity - $_"
    }

    # No need to validate the node.
    # We can let the user call it to cleanup any residue
    try
    {
        Uninstall-Node -nodeName $nodeName -activity $activity
    }
    catch
    {
        # swallow error if node is unreachable and uninstall is not successful
        Write-ModuleEventLog -moduleName $moduleName -entryType Warning -eventId 2 -message "$activity - $_"
    }
}
#endregion moc physical node


#region moc cluster
function New-MocCluster
{
    <#
    .DESCRIPTION
        Adds a cluster resource to moc .
 
    .PARAMETER name
        Name of the cluster
 
    .PARAMETER location
        Location for the cluster
 
    .PARAMETER fqdn
        Fqdn of the cluster
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$location,
        [Parameter(Mandatory=$true)]
        [String]$fqdn,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    $argsDict = @{"--name" = $name; "--fqdn" = $fqdn; "--location" = $location }

    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    Invoke-MocCommand " cloud cluster create" -argDictionary $argsDict
}

function Get-MocCluster
{
    <#
    .DESCRIPTION
        Get a Cluster(s) resource from moc.
 
    .PARAMETER name
        Name of the Cluster
 
    .PARAMETER location
        Location for the Cluster
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$location,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    if ([string]::IsNullOrWhiteSpace($name))
    {
        Invoke-MocListCommand $(" cloud cluster list --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
    else
    {
        Invoke-MocShowCommand $(" cloud cluster show --name ""$name"" --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}) )
    }
}

function Remove-MocCluster
{
    <#
    .DESCRIPTION
        Removes a Cluster resource from moc.
 
    .PARAMETER name
        Name of the Cluster
 
    .PARAMETER location
        Location for the Cluster
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$location,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" cloud cluster delete --name ""$name"" --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}

function Reset-MocCluster
{
    <#
    .DESCRIPTION
        Remove all moc clusters.
 
    .PARAMETER location
        The location to reset the entity
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$location,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" cloud cluster list --output tsv --query ""[*].name"" --location $location") | ForEach-Object {
        $entityName = $_
        if ($entityName -notlike "No *")
        {
            Remove-MocCluster -name $entityName -location $location -timeout $timeoutSeconds
        }
    }
}

#endregion moc cluster

#region moc galleryimage

function New-MocGalleryImage
{
    <#
    .DESCRIPTION
        Adds a galleryimage resource to cloudagent.
 
    .PARAMETER name
        Name of the galleryimage.
 
    .PARAMETER location
        Location for the galleryimage.
 
    .PARAMETER imagePath
        Hostname or IP of the galleryimage.
 
    .PARAMETER container
        Container that galleryimage will use
 
    .PARAMETER timeoutSeconds
        timeout in seconds
 
    .PARAMETER imageSourceSfs
    Switch indicating image source if sfs
 
    .PARAMETER release
        Release name of the image if imageSource is sfs
 
    .PARAMETER version
        Version of the image if imageSource is sfs
 
    .PARAMETER numDownloads
        Number of parallel downloads of the image if imageSource is sfs
 
    .PARAMETER destinationDir
       Destination Dir of the image if imageSource is sfs
 
    .PARAMETER hyperVGeneration
        hyperv generation of the image
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$location,
        [Parameter(Mandatory=$true)]
        [String]$container,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds,
        [Parameter(Mandatory=$false)]
        [System.IO.FileInfo]$imagePath, 
        [Parameter(Mandatory=$false)]

        [Parameter(ParameterSetName = 'sfs')]
        [switch]$imageSourceSfs,

        [Parameter(ParameterSetName = 'sfs')]
        [String]$release,

        [Parameter(ParameterSetName = 'sfs')]
        [String]$version,

        [Parameter(ParameterSetName = 'sfs')]
        [ValidateRange(1,10)]
        [Int]$numDownloads,
        [Parameter(Mandatory=$false)]
        [ValidateSet('HyperVGeneration_V1', 'HyperVGeneration_V2')]
        [string]$hyperVGeneration

    )

    $argsDict = @{"--name" = $name; "--container-name" = $container; "--location" = $location}
    if ($imagePath -and ($imageSourceSfs -or $PSCmdlet.ParameterSetName -ieq "sfs"))
    {
        throw $($MocLocMessage.moc_imgpath_imgsourcesfs_flags_present)        
    }
    if ($PSCmdlet.ParameterSetName -ieq "sfs")
    {
        $argsDict += @{"--image-source" =  "sfs"; "--release" =  $release; "--version" = $version; "--num-downloads" = $numDownloads}
    }
    else
    {
        if ($imagePath)
        {
           $argsDict["--image-path"] = $imagePath
        }
    }
    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    if (-Not [string]::IsNullOrWhiteSpace($hyperVGeneration)) {
        $argsDict["--hyperv-generation"] = $hyperVGeneration
    }

    Invoke-MocCommand " compute galleryimage create" -argDictionary $argsDict
}

function Get-MocGalleryImage
{
    <#
    .DESCRIPTION
        Removes a GalleryImage resource from cloudagent.
 
    .PARAMETER name
        Name of the GalleryImage
 
    .PARAMETER location
        Location for the GalleryImage
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$location,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    if ([string]::IsNullOrWhiteSpace($name))
    {
        Invoke-MocListCommand $(" compute galleryimage list --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}) )
    }
    else
    {
        Invoke-MocShowCommand $(" compute galleryimage show --name ""$name"" --location $location" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
}

function Remove-MocGalleryImage
{
    <#
    .DESCRIPTION
        Removes a GalleryImage resource from cloudagent.
 
    .PARAMETER name
        Name of the GalleryImage
 
    .PARAMETER location
        Location for the GalleryImage
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$location,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" compute galleryimage delete --name ""$name"" --location "+$location + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}
function Reset-MocGalleryImage
{
    <#
    .DESCRIPTION
        Remove all compute galleryimages.
 
    .PARAMETER location
        The location to reset the entity
 
    #>


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

    Invoke-MocCommand $(" compute galleryimage list --output tsv --query ""[*].name"" --location $location ") | ForEach-Object {
        $entityName = $_
        if ($entityName -ine "No GalleryImage Resources")
        {
            if($timeoutSeconds) {
                Remove-MocGalleryImage -name $entityName -location $location -timeout $timeoutSeconds
            }
            else {
                Remove-MocGalleryImage -name $entityName -location $location
            }
        }
    }
}

#endregion moc galleryimage

#region moc network interface
function New-MocNetworkInterface
{
    <#
    .DESCRIPTION
        Adds a vnic resource to moc .
 
    .PARAMETER name
        Name of the vnic
 
    .PARAMETER virtualNetworkName
        The vnet that the vnic should be connected to
 
    .PARAMETER group
        The name of the group in which the vnic resides
 
    .PARAMETER ipAddress
        The ipAddress of the networkInterface
 
    .PARAMETER loadBalancerBackend
        The loadBalancer Backend to which this network interface should be associated to
 
    .PARAMETER macAddress
        The macAddress of the networkInterface
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$virtualNetworkName,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$false)]
        [String]$ipAddress,
        [Parameter(Mandatory=$false)]
        [String]$loadBalancerBackend,
        [Parameter(Mandatory=$false)]
        [String]$macAddress,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    $argsDict = @{ "--name" = $name; "--vnet-name" = $virtualNetworkName; "--group" = $group }

    if ($macAddress) {
        $argsDict["--mac-address"] = $macAddress
    }

    if ($ipAddress) {
        $argsDict["--private-ip-address"] = $ipAddress
    }

    if ($loadBalancerBackend) {
        $argsDict["--lb-name"] = $loadBalancerBackend
    }

    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    Invoke-MocCommand " network vnic create" -argDictionary $argsDict
}

function Get-MocNetworkInterface
{
    <#
    .DESCRIPTION
        Removes a NetworkInterface resource from moc.
 
    .PARAMETER name
        Name of the NetworkInterface
 
    .PARAMETER group
        group for the NetworkInterface
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


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

    if ([string]::IsNullOrWhiteSpace($name))
    {
        Invoke-MocListCommand $(" network vnic list --group $group" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
    else
    {
        Invoke-MocShowCommand $(" network vnic show --name ""$name"" --group $group" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
}

function Remove-MocNetworkInterface
{
    <#
    .DESCRIPTION
        Removes a NetworkInterface resource from moc.
 
    .PARAMETER name
        Name of the NetworkInterface
 
    .PARAMETER group
        group for the NetworkInterface
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" network vnic delete --name ""$name"" --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}

function Reset-MocNetworkInterface
{
    <#
    .DESCRIPTION
        Remove all moc networkinterface.
 
     .PARAMETER group
        group for the NetworkInterface
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" network vnic list --output tsv --query ""[*].name"" --group $group") | ForEach-Object {
        $entityName = $_
        if ($entityName -notlike "No *")
        {
            if($timeoutSeconds) {
                Remove-MocNetworkInterface -name $entityName -group $group -timeout $timeoutSeconds
            }
            else {
                Remove-MocNetworkInterface -name $entityName -group $group
            }
        }
    }
}

#endregion moc network interface

#region moc loadbalancer
function New-MocLoadBalancer
{
    <#
    .DESCRIPTION
        Adds a loadbalancer resource to moc .
 
    .PARAMETER name
        Name of the loadbalancer
 
    .PARAMETER virtualNetworkName
        The vnet that the loadbalancer should be connected to
 
    .PARAMETER group
        The name of the group in which the loadbalancer resides
 
    .PARAMETER backendPoolName
        The backendPoolName
 
    .PARAMETER frontendPort
        The frontendPort for the loadbalancer
 
    .PARAMETER backendPort
        The backendPort for the loadbalancer
 
    .PARAMETER protocol
        The protocol for the loadbalancer
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$virtualNetworkName,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$true)]
        [String]$backendPoolName,
        [Parameter(Mandatory=$true)]
        [int]$frontendPort,
        [Parameter(Mandatory=$true)]
        [int]$backendPort,
        [Parameter(Mandatory=$true)]
        [ValidateSet('TCP', 'UDP')]
        [string]$protocol,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    $argsDict = @{"--name" = $name; "--vnet-name" = $virtualNetworkName; "--backend-pool-name" = $backendPoolName; "--frontend-port" = $frontendPort;
        "--backend-port" = $backendPort; "--protocol" = $protocol; "--group" = $group
    }

    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    Invoke-MocCommand " network loadbalancer create" -argDictionary $argsDict
}

function Get-MocLoadBalancer
{
    <#
    .DESCRIPTION
        Get a LoadBalancer resource from moc.
 
    .PARAMETER name
        Name of the LoadBalancer
 
    .PARAMETER group
        group for the LoadBalancer
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    if ([string]::IsNullOrWhiteSpace($name))
    {
        Invoke-MocListCommand $(" network loadbalancer list --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
    else
    {
        Invoke-MocShowCommand $(" network loadbalancer show --name ""$name"" --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
}

function Remove-MocLoadBalancer
{
    <#
    .DESCRIPTION
        Removes a LoadBalancer resource from moc.
 
    .PARAMETER name
        Name of the LoadBalancer
 
    .PARAMETER group
        group for the LoadBalancer
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" network loadbalancer delete --name ""$name"" --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}

function Reset-MocLoadBalancer
{
    <#
    .DESCRIPTION
        Remove all moc loadbalancer.
 
     .PARAMETER group
        group for the LoadBalancer
    #>


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

    Invoke-MocCommand $(" network loadbalancer list --output tsv --query ""[*].name"" --group $group") | ForEach-Object {
        $entityName = $_
        if ($entityName -notlike "No *")
        {
            if($timeoutSeconds) {
                Remove-MocLoadBalancer -name $entityName -group $group -timeout $timeoutSeconds
            }
            else {
                Remove-MocLoadBalancer -name $entityName -group $group
            }
        }
    }
}

#endregion moc network interface

#region moc virtual network

function New-MocIPPool
{
    <#
    .DESCRIPTION
        Adds a vnet resource to moc .
 
    .PARAMETER name
        Name of the vnet
 
    .PARAMETER type
        Type of the vnet
 
    .PARAMETER startIpAddress
        The name of the group in which the vnet resides
 
    .PARAMETER endIPAddress
        name of the macpool
 
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [ValidateSet('vm', 'vippool')]
        [string]$type,
        [Parameter(Mandatory=$false)]
        [string]$startIpAddress,
        [Parameter(Mandatory=$false)]
        [string]$endIPAddress
    )

    return @"
      - name: $name
        type: $type
        start: $startIpAddress
        end: $endIPAddress
"@

}

function New-MocVirtualNetwork
{
    <#
    .DESCRIPTION
        Adds a vnet resource to moc .
 
    .PARAMETER name
        Name of the vnet
 
    .PARAMETER type
        Type of the vnet
 
    .PARAMETER group
        The name of the group in which the vnet resides
 
    .PARAMETER macPool
        name of the macpool
 
    .PARAMETER ipPools
        list of IPPools
 
    .PARAMETER vlanID
        Non Zero value, if vlan is required
 
    .PARAMETER tags
        Additional tags for vnet creation
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [string]$group,
        [Parameter(Mandatory=$false)]
        [string]$type,
        [Parameter(Mandatory=$false)]
        [string]$macPool,
        [Parameter(Mandatory=$false)]
        [string[]]$ipPools,
        [Parameter(Mandatory=$false)]
        [uint32]$vlanID,
        [Parameter(Mandatory=$false)]
        [Hashtable]$tags,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    $argsDict = @{ "--name" = $name; "--type" = $type; "--group" = $group }

    if ([string]::IsNullOrWhiteSpace($type)) {
        $type = "Transparent"
        if ($name -eq "Default Switch") {
            $type = "ICS"
        }

        $argsDict["--type"] = $type
    }

    if (-not [string]::IsNullOrWhiteSpace($macPool)) {
        $argsDict["--mac-pool"] = $macPool
    }

    if ($vlanID -gt 0) {
        $argsDict["--vlan-id"] = $vlanID
    }

    if ($ipPools -and $ipPools.Count -gt 0) {
        $argsDict["--ip-pools"] = $ipPools
    }

    if ($tags -and $tags.Count -gt 0) {
        [string[]] $tagsVal = @()

        foreach ($key in $tags.Keys) {
            $val = $tags[$key]
            $tagsVal += ("{0}={1}" -f $key, $val)
        }
        $argsDict["--tags"] = $tagsVal
    }

    if($timeoutSeconds) {
    $argsDict["--timeout"] = $timeoutSeconds
    }

    Invoke-MocCommand " network vnet create" -argDictionary $argsDict
}

function Get-MocVirtualNetwork
{
    <#
    .DESCRIPTION
        Get a VirtualNetwork resource from moc.
 
    .PARAMETER name
        Name of the VirtualNetwork
 
    .PARAMETER group
        group for the VirtualNetwork
    #>


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

    if ([string]::IsNullOrWhiteSpace($name))
    {
        Invoke-MocListCommand $(" network virtualnetwork list --group "+$group)
    }
    else
    {
        Invoke-MocShowCommand $(" network virtualnetwork show --name ""$name"" --group "+$group)
    }
}

function Remove-MocVirtualNetwork
{
    <#
    .DESCRIPTION
        Removes a VirtualNetwork resource from moc.
 
    .PARAMETER name
        Name of the VirtualNetwork
 
    .PARAMETER group
        group for the VirtualNetwork
    #>


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

    Invoke-MocCommand $(" network virtualnetwork delete --name ""$name"" --group $group")
}

function Reset-MocVirtualNetwork
{
    <#
    .DESCRIPTION
        Remove all moc virtualnetwork.
 
     .PARAMETER group
        group for the virtualnetwork
    #>


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

    Invoke-MocCommand $(" network virtualnetwork list --output tsv --query ""[*].name"" --group $group") | ForEach-Object {
        $entityName = $_
        if ($entityName -notlike "No *")
        {
            Remove-MocVirtualNetwork -name $entityName -group $group
        }
    }
}
#endregion moc network interface

#region moc virtualmachine

function New-MocVirtualMachine
{
    <#
    .DESCRIPTION
        Create a moc virtualmachine
 
    .PARAMETER name
        Name of the virtualmachine
 
    .PARAMETER group
        The name of the group in which the virtualmachine resides
 
    .PARAMETER galleryImageName
        The name of the galleryImageName to use for image
 
    .PARAMETER diskName
        The name of the VHD to use as OsDisk for the VM, if galleryImageName is empty
 
    .PARAMETER osType
        The type of operating system
 
    .PARAMETER networkInterfaces
        List of networkinterfaces to connect the virtualmachine
 
    .PARAMETER virtualHardDisks
        List of virtualHardDisks to connect the virtualmachine
 
    .PARAMETER storageContainerName
        The name of the storageContainerName to store the virtualmachine data
 
    .PARAMETER disableHA
        switch to disable high availability
     
    .PARAMETER computerName
        name of the computer to set on the VM.
 
    .PARAMETER adminUser
        administrator username
 
    .PARAMETER adminPass
        administrator Password in Clear text
 
    .PARAMETER bootstrapType
        bootstrap type [CloudInit/WindowsAnswerFile]
 
    .PARAMETER tags
        hashtable of tags
 
    .PARAMETER timeoutSeconds
        timeout in seconds
     
    .PARAMETER disableSecureBoot
        switch to disable secure boot
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [string]$group,
        [Parameter(Mandatory=$false, ParameterSetName = 'UsingGalleryImageName')]
        [string]$galleryImageName,
        [Parameter(Mandatory=$false, ParameterSetName = 'UsingDiskName')]
        [string]$diskName,
        [Parameter(Mandatory=$true)]
        [ValidateSet('Windows', 'Linux')]
        [string]$osType,
        [Parameter(Mandatory=$false)]
        [string[]]$networkInterfaces,
        [Parameter(Mandatory=$false)]
        [string[]]$virtualHardDisks,
        [Parameter(Mandatory=$false)]
        [string]$storageContainerName = "",
        [Parameter(Mandatory=$false)]
        [switch]$disableHA,
        [Parameter(Mandatory=$true)]
        [string]$computerName,
        [Parameter(Mandatory=$true)]
        [string]$adminUser,
        [Parameter(Mandatory=$true)]
        [string]$adminPass,
        [Parameter(Mandatory=$true)]
        [ValidateSet('CloudInit', 'WindowsAnswerFiles')]
        [string]$bootstrapType,
        [Parameter(Mandatory=$false)]
        [Hashtable]$tags,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds,
        [Parameter(Mandatory=$false)]
        [switch]$disableSecureBoot
        
    )

    [string[]] $boolFlags = @()

    # Construct OsProfile related Info
    switch ($osType)
    {
        'Windows' {
            $boolFlags += "--enable-auto-update"
        }
        'Linux' {
            $boolFlags += "--disable-password-auth"
        }
    }

    $argsDict = @{ "--name" = $name; "--computer-name" = $computerName; "--admin-username" = $adminUser; "--admin-password" = $adminPass;
        "--os-bootstrap-engine" = $bootstrapType; "--os-type" = $osType; "--group" = $group 
    }

    if (-not [string]::IsNullOrEmpty($galleryImageName))
    {
        $argsDict["--image-name"] = $galleryImageName
    }
    elseif (-not [string]::IsNullOrEmpty($diskName))
    {
        $argsDict["--os-disk-uri"] = $diskName
    }

    if ($networkInterfaces)
    {
        $argsDict["--network-interfaces"] = $networkInterfaces
    }

    if ($virtualHardDisks -and $virtualHardDisks.Count -gt 0)
    {
        $argsDict["--data-disk-uris"] = $virtualHardDisks
    }

    if (-not [string]::IsNullOrEmpty($storageContainerName))
    {
        $argsDict["--storage-container-name"] = $storageContainerName
    }

    if ($disableHA.IsPresent)
    {
        $boolFlags += "--disable-high-availability"
    }
    if ($disableSecureBoot.IsPresent)
    {
        $boolFlags += "--disable-secure-boot"
    }

    if ($tags -and $tags.Count -gt 0) {
        [string[]] $tagsVal = @()

        foreach ($key in $tags.Keys) {
            $val = $tags[$key]
            $tagsVal += ("{0}={1}" -f $key, $val)
        }
        $argsDict["--tags"] = $tagsVal
    }

    if($timeoutSeconds) {
    $argsDict["--timeout"] = $timeoutSeconds
    }

    Invoke-MocCommand " compute vm create" -argDictionary $argsDict -boolFlags $boolFlags
}

function Get-MocVirtualMachine
{
    <#
    .DESCRIPTION
        Get VirtualMachine(s) from moc.
 
    .PARAMETER name
        Name of the VirtualMachine
 
    .PARAMETER group
        group for the VirtualMachine
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    if ([string]::IsNullOrWhiteSpace($name))
    {
        Invoke-MocListCommand $(" compute vm list --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
    else
    {
        Invoke-MocShowCommand $(" compute vm show --name ""$name"" --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
}

function Remove-MocVirtualMachine
{
    <#
    .DESCRIPTION
        Removes a VirtualMachine resource from moc.
 
    .PARAMETER name
        Name of the VirtualMachine
 
    .PARAMETER group
        group for the VirtualMachine
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" compute vm delete --name ""$name"" --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}

function Start-MocVirtualMachine
{
    <#
    .DESCRIPTION
        Removes a VirtualMachine resource from moc.
 
    .PARAMETER name
        Name of the VirtualMachine
 
    .PARAMETER group
        group for the VirtualMachine
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" compute vm start --name ""$name"" --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}

function Stop-MocVirtualMachine
{
    <#
    .DESCRIPTION
        Removes a VirtualMachine resource from moc.
 
    .PARAMETER name
        Name of the VirtualMachine
 
    .PARAMETER group
        group for the VirtualMachine
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" compute vm stop --name ""$name"" --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}

function Restart-MocVirtualMachine
{
    <#
    .DESCRIPTION
        Restart a VirtualMachine
 
    .PARAMETER name
        Name of the VirtualMachine
 
    .PARAMETER group
        group for the VirtualMachine
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" compute vm restart --name ""$name"" --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}

function Get-MocVirtualMachineSizes
{
    <#
    .DESCRIPTION
        List sizes for a moc VirtualMachine
    #>


    Invoke-MocCommand $(" compute vm list-sizes" )
}

function Resize-MocVirtualMachine
{
    <#
    .DESCRIPTION
        Resize a VirtualMachine
 
    .PARAMETER name
        Name of the VirtualMachine
 
    .PARAMETER group
        group for the VirtualMachine
 
    .PARAMETER newSize
        newSize for the VirtualMachine
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$true)]
        [String]$newSize,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" compute vm resize --name ""$name"" --group $group --size $newSize" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}

function Resize-MocVirtualMachineCustom
{
    <#
    .DESCRIPTION
        Resize a VirtualMachine with Custom Size
 
    .PARAMETER name
        Name of the VirtualMachine
 
    .PARAMETER group
        group for the VirtualMachine
 
     .PARAMETER cpuCount
        new cpuCount for the VirtualMachine
 
     .PARAMETER memoryMB
        new memoryMB for the VirtualMachine
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$true)]
        [int]$cpuCount,
        [Parameter(Mandatory=$true)]
        [int]$memoryMB,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" compute vm resize --name ""$name"" --group $group --size Custom --cpucount $cpuCount --memorymb $memoryMB" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}

function Connect-MocVirtualHardDisk
{
    <#
    .DESCRIPTION
        Connect a VirtualMachine to VirtualHardDisk
 
    .PARAMETER name
        Name of the VirtualMachine
 
    .PARAMETER group
        group for the VirtualMachine
 
    .PARAMETER virtualMachineName
        virtualMachineName
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$true)]
        [String]$virtualMachineName,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" compute vm disk attach --name ""$name"" --vm-name $virtualMachineName --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}

function Disconnect-MocVirtualHardDisk
{
    <#
    .DESCRIPTION
        Disconnect a VirtualMachine from VirtualHardDisk
 
    .PARAMETER name
        Name of the VirtualMachine
 
    .PARAMETER group
        group for the VirtualMachine
 
    .PARAMETER virtualMachineName
        virtualMachineName
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$true)]
        [String]$virtualMachineName,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" compute vm disk detach --name ""$name"" --vm-name $virtualMachineName --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}

function Connect-MocNetworkInterface
{
    <#
    .DESCRIPTION
        Connect a VirtualMachine to NetworkInterface
 
    .PARAMETER name
        Name of the VirtualMachine
 
    .PARAMETER group
        group for the VirtualMachine
 
    .PARAMETER virtualMachineName
        virtualMachineName
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$true)]
        [String]$virtualMachineName,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" compute vm nic add --name ""$name"" --vm-name $virtualMachineName --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}

function Disconnect-MocNetworkInterface
{
    <#
    .DESCRIPTION
        Disconnect a VirtualMachine from NetworkInterface
 
    .PARAMETER name
        Name of the VirtualMachine
 
    .PARAMETER group
        group for the VirtualMachine
 
    .PARAMETER virtualMachineName
        virtualMachineName
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$true)]
        [String]$virtualMachineName,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" compute vm nic remove --name ""$name"" --vm-name $virtualMachineName --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}
function Reset-MocVirtualMachine
{
    <#
    .DESCRIPTION
        Remove all moc virtual machines.
 
     .PARAMETER group
        group for the VirtualMachine
    #>


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

    Invoke-MocCommand $(" compute vm list --output tsv --query ""[*].name"" --group $group") | ForEach-Object {
        $entityName = $_
        if ($entityName -notlike "No *")
        {
            Remove-MocVirtualMachine -name $entityName -group $group
        }
    }
}
#endregion moc virtualmachine

#region moc virtualmachine

function New-MocVMSS
{
    <#
    .DESCRIPTION
        Adds a vmss resource to moc .
 
    .PARAMETER name
        Name of the vmss
 
    .PARAMETER galleryImageName
        name of the image to use for vmss
 
    .PARAMETER replicaCount
        replica count of vmss
 
    .PARAMETER osType
        OperatingSystem type of the vmss
 
    .PARAMETER group
        The name of the group in which the vmss resides
 
    .PARAMETER virtualNetworkName
        The name of the virtualNetworkName to connect the vmss to
 
    .PARAMETER storageContainerName
        The name of the storageContainerName to store the vmss data
 
    .PARAMETER disableHA
        switch to disable high availability
 
    .PARAMETER computerNamePrefix
        name of the computer to set on the VM.
 
    .PARAMETER adminUser
        administrator username
 
    .PARAMETER adminPass
        administrator Password in Clear text
 
    .PARAMETER sshPublicKey
        sshPublicKey to use to set certificate based ssh auth
 
    .PARAMETER bootstrapType
        bootstrap type [CloudInit/WindowsAnswerFile]
 
    .PARAMETER tags
        hashtable of tags
 
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [string]$group,
        [Parameter(Mandatory=$true)]
        [string]$galleryImageName,
        [Parameter(Mandatory=$true)]
        [int]$replicaCount,
        [Parameter(Mandatory=$true)]
        [ValidateSet('Windows', 'Linux')]
        [string]$osType,
        [Parameter(Mandatory=$false)]
        [string]$virtualNetworkName,
        [Parameter(Mandatory=$false)]
        [string]$storageContainerName = "",
        [Parameter(Mandatory=$false)]
        [switch]$disableHA,
        [Parameter(Mandatory=$true)]
        [string]$computerNamePrefix,
        [Parameter(Mandatory=$true)]
        [string]$adminUser,
        [Parameter(Mandatory=$true)]
        [string]$adminPass,
        [Parameter(Mandatory=$false)]
        [string]$sshPublicKey,
        [Parameter(Mandatory=$true)]
        [ValidateSet('CloudInit', 'WindowsAnswerFiles')]
        [string]$bootstrapType,
        [Parameter(Mandatory=$false)]
        [Hashtable]$tags,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    $argsDict = @{ "--name" = $name; "--replicas" = $replicaCount; "--image-name" = $galleryImageName; "--os-type" = $osType;
        "--computer-name-prefix" = $computerNamePrefix; "--admin-username" = $adminUser; "--admin-password" = $adminPass; "--bootstrap-type" = $bootstrapType;
        "--group" = $group 
    }

    if (-Not [string]::IsNullOrEmpty($sshPublicKey))
    {
        $argsDict["--ssh-public-key"] = $sshPublicKey

    }
     if ($virtualNetworkName)
     {
         $argsDict["--vnet-name"] = $virtualNetworkName
     }

    if (-not [string]::IsNullOrEmpty($storageContainerName))
    {
        $argsDict["--storage-container-name"] = $storageContainerName
    }

    [string[]] $boolFlags = @()

    if ($disableHA.IsPresent) {
        $boolFlags += "--disable-high-availability"
    }

    if ($tags -and $tags.Count -gt 0) {
        [string[]] $tagsVal = @()

        foreach ($key in $tags.Keys) {
            $val = $tags[$key]
            $tagsVal += ("{0}={1}" -f $key, $val)
        }
        $argsDict["--tags"] = $tagsVal
    }

    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }


    Invoke-MocCommand " compute vmss create" -argDictionary $argsDict -boolFlags $boolFlags
}

function Get-MocVMSS
{
    <#
    .DESCRIPTION
        Get a VMSS resource from moc.
 
    .PARAMETER name
        Name of the VMSS
 
    .PARAMETER group
        group for the VMSS
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    if ([string]::IsNullOrWhiteSpace($name))
    {
        Invoke-MocListCommand $(" compute vmss list --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
    else
    {
        Invoke-MocShowCommand $(" compute vmss show --name ""$name"" --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
}

function Set-MocVMSS
{
    <#
    .DESCRIPTION
        Get a VMSS resource from moc.
 
    .PARAMETER name
        Name of the VMSS
 
    .PARAMETER group
        group for the VMSS
 
    .PARAMETER replicaCount
        replicaCount for the VMSS
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [int]$replicaCount,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" compute vmss scale --count $replicaCount --name ""$name"" --group $group" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}) )
}


function Remove-MocVMSS
{
    <#
    .DESCRIPTION
        Removes a VMSS resource from moc.
 
    .PARAMETER name
        Name of the VMSS
 
    .PARAMETER group
        group for the VMSS
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" compute vmss delete --name ""$name"" --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}

function Reset-MocVMSS
{
    <#
    .DESCRIPTION
        Remove all moc vmss
 
     .PARAMETER group
        group for the VMSS
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" compute vmss list --output tsv --query ""[*].name"" --group $group") | ForEach-Object {
        $entityName = $_
        if ($entityName -notlike "No *")
        {
            if($timeoutSeconds) {
                Remove-MocVMSS -name $entityName -group $group -timeout $timeoutSeconds
            }
            else {
                Remove-MocVMSS -name $entityName -group $group
            }
        }
    }
}
#endregion moc virtualmachine

#region moc virtual harddisk
function New-MocVirtualHardDisk
{
    <#
    .DESCRIPTION
        Adds a virtualharddisk resource to moc .
 
    .PARAMETER name
        Name of the virtualharddisk
 
    .PARAMETER sizeBytes
        size in bytes for the virtualharddisk
 
    .PARAMETER group
        The name of the group in which the virtualharddisk resides
 
    .PARAMETER dynamic
        flag to specify if we want
        * dynamic expanding disk -> overprovisioning
        * static disks
 
    .PARAMETER containerName
        name of the storage container to use
 
    .PARAMETER timeoutSeconds
        timeout in seconds
 
    .PARAMETER hyperVGeneration
        hyperv generation of the disk
    .PARAMETER diskFileFormat
        hyperv generation of the disk
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$true)]
        [int64]$sizeBytes,
        [Parameter(Mandatory=$false)]
        [bool]$dynamic = $true,
        [Parameter(Mandatory=$false)]
        [string]$containerName,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds,
        [Parameter(Mandatory=$false)]
        [ValidateSet('HyperVGeneration_V1', 'HyperVGeneration_V2')]
        [string]$hyperVGeneration, 
        [Parameter(Mandatory=$false)]
        [ValidateSet('VHD', 'VHDX')]
        [string]$diskFileFormat

    )

    [string[]] $boolFlags = @()
    $argsDict = @{ "--name" = $name; "--disk-size-bytes" = $sizeBytes; "--group" = $group }

    if (-Not [string]::IsNullOrWhiteSpace($containerName)) {
        $argsDict["--container"] = $containerName
    }

    if (-Not [string]::IsNullOrWhiteSpace($hyperVGeneration)) {
        $argsDict["--hyperv-generation"] = $hyperVGeneration
    }

    if (-Not [string]::IsNullOrWhiteSpace($diskFileFormat)) {
        $argsDict["--disk-file-format"] = $diskFileFormat
    }

    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    if ($dynamic) {
        $boolFlags += "--dynamic"
    }

    Invoke-MocCommand " storage vhd create" -argDictionary $argsDict -boolFlags $boolFlags
}

function Import-MocVirtualHardDisk
{
    <#
    .DESCRIPTION
        Impoirts an existing virtualharddisk resource to moc .
 
    .PARAMETER name
        Name of the virtualharddisk
 
    .PARAMETER sourcePath
        sourcePath of the virtualharddisk to import
 
    .PARAMETER group
        The name of the group in which the virtualharddisk resides
 
    .PARAMETER containerName
        name of the storage container to use
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$true)]
        [string]$sourcePath,
        [Parameter(Mandatory=$false)]
        [string]$containerName,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    $yaml = @"
name: $name
virtualharddiskproperties:
  source: $sourcePath
"@


    $yamlFile = Set-MocYaml -yamlData $yaml

    $cmd = ""
    if (-Not [string]::IsNullOrWhiteSpace($containerName))
    {
        $cmd +=  " --container $containerName"
    }


    Invoke-MocCommand $(" storage vhd create --config ""$yamlFile"" --group $group $cmd" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}) )
}

function Get-MocVirtualHardDisk
{
    <#
    .DESCRIPTION
        Get a VirtualHardDisk resource from moc.
 
    .PARAMETER name
        Name of the VirtualHardDisk
 
    .PARAMETER group
        group for the VirtualHardDisk
 
    .PARAMETER containerName
        name of the storage container to use
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$false)]
        [String]$containerName,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    $cmd = ""
    if (-Not [string]::IsNullOrWhiteSpace($containerName))
    {
        $cmd +=  " --container $containerName"
    }

    if ([string]::IsNullOrWhiteSpace($name))
    {
        Invoke-MocListCommand $(" storage vhd list --group "+$group + $cmd + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
    else
    {
        Invoke-MocShowCommand $(" storage vhd show --name ""$name"" --group "+$group + $cmd + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
}

function Resize-MocVirtualHardDisk
{
    <#
    .DESCRIPTION
        Resize a VirtualHardDisk resource from moc.
 
    .PARAMETER name
        Name of the VirtualHardDisk
 
    .PARAMETER group
        group for the VirtualHardDisk
 
    .PARAMETER newSizeBytes
        newSizeBytes for the VirtualHardDisk
 
    .PARAMETER containerName
        name of the storage container to use
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$true)]
        [int64]$newSizeBytes,
        [Parameter(Mandatory=$true)]
        [string]$containerName,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    $cmdDict = @{ "--name" = $name; "--size-bytes" = $newSizeBytes; "--group" = $group } 

    if (-Not [string]::IsNullOrWhiteSpace($containerName))
    {
        $cmdDict["--container"] = $containerName
    }
    if($timeoutSeconds) {
        $cmdDict["--timeout"] = $timeoutSeconds
    }

    Invoke-MocCommand $( "storage vhd resize") -argDictionary $cmdDict
}

function Remove-MocVirtualHardDisk
{
    <#
    .DESCRIPTION
        Removes a VirtualHardDisk resource from moc.
 
    .PARAMETER name
        Name of the VirtualHardDisk
 
    .PARAMETER group
        group for the VirtualHardDisk
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$true)]
        [string]$containerName,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" storage vhd delete --name ""$name"" --group $group --container $containerName" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}

function Reset-MocVirtualHardDisk
{
    <#
    .DESCRIPTION
        Remove all moc virtual harddisks.
 
     .PARAMETER group
        group for the VirtualHardDisk
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$true)]
        [string]$containerName,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    $cmd = ""
    if (-Not [string]::IsNullOrWhiteSpace($containerName))
    {
        $cmd +=  " --container $containerName"
    }

    Invoke-MocCommand $(" storage vhd list --output tsv --query ""[*].name"" --group $group $cmd") | ForEach-Object {
        $entityName = $_
        if ($entityName -notlike "No *")
        {
            if($timeoutSeconds) {
                Remove-MocVirtualHardDisk -name $entityName -group $group -containerName $containerName -timeout $timeoutSeconds
            }
            else {
                Remove-MocVirtualHardDisk -name $entityName -group $group -containerName $containerName
            }
        }
    }
}
#endregion moc virtual harddisk


#region moc certificate
function Update-MocCertificate
{
    <#
    .DESCRIPTION
        Updates a certificate resource to moc .
 
    .PARAMETER name
        Name of the certificate.
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


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

    [string[]] $boolFlags = @()

    $argsDict = @{ "--name" = $name;}

    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    return Invoke-MocCommand " security certificate update" -argDictionary $argsDict
}

function Get-MocCertificate
{
    <#
    .DESCRIPTION
        Updates a certificate resource to moc .
 
    .PARAMETER name
        Name of the certificate.
 
    .PARAMETER fqdn
        Hostname or IP of the cloud agent.
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds,
        [Parameter(Mandatory=$false)]
        [String]$format = "json",
        [Parameter(Mandatory=$false)]
        [int32]$expiryDays = 90
    )


    [string[]] $boolFlags = @()

    $argsDict = @{ "--name" = $name; "--output" = $format; "--expirydays" = $expiryDays }

    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    return Invoke-MocCommand " security certificate list " -argDictionary $argsDict
}
#endregion moc certificate

#region moc identity
function New-MocIdentity
{
    <#
    .DESCRIPTION
        Adds a identity resource to moc .
 
    .PARAMETER name
        Name of the identity identity.
 
    .PARAMETER validityDays
        Time before expiry of token for identity in days.
 
    .PARAMETER location
        Location for the identity.
 
    .PARAMETER fqdn
        Hostname or IP of the cloud agent.
 
    .PARAMETER clienttype
        Type of client for which identity is created.
 
    .PARAMETER port
        Port that cloud agent is listening on.
 
    .PARAMETER authport
        Authorizer Port that cloud agent is listening on.
     
    .PARAMETER encode
        output to be base64 encoded.
     
    .PARAMETER outFile
        write the identity to the given file name.
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$validityDays,
        [String]$fqdn = "localhost",
        [Parameter(Mandatory=$true)]
        [String]$location,
        [string]$clienttype = "Node",
        [int]$port = $global:defaultCloudAgentPort,
        [int]$authport = $global:defaultCloudAuthorizerPort,
        [switch]$encode,
        [string]$outFile = "",
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Write-Status -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_creating_identity, $name))

    [string[]] $boolFlags = @()

    $argsDict = @{ "--name" = $name; "--validity-days" = $validityDays; "--fqdn" = $fqdn; "--client-type" = $clienttype;
        "--port" = $port; "--auth-port" = $authport; "--location" = $location; "--outfile" = $outFile}

    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    $boolFlags += "--encode=$encode"

    return Invoke-MocCommand " security identity create" -argDictionary $argsDict -boolFlags $boolFlags
}

function Update-MocIdentity
{
    <#
    .DESCRIPTION
        Updates a identity resource to moc .
 
    .PARAMETER name
        Name of the identity identity.
 
    .PARAMETER validityDays
        Time before expiry of token for identity in days.
 
    .PARAMETER location
        Location for the identity.
 
    .PARAMETER fqdn
        Hostname or IP of the cloud agent.
 
    .PARAMETER clienttype
        Type of client for which identity is created.
 
    .PARAMETER port
        Port that cloud agent is listening on.
 
    .PARAMETER authport
        Authorizer Port that cloud agent is listening on.
 
    .PARAMETER encode
        output to be base64 encoded.
 
    .PARAMETER outFile
        write the identity to the given file name.
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$validityDays,
        [String]$fqdn = "localhost",
        [Parameter(Mandatory=$true)]
        [String]$location,
        [string]$clienttype = "Node",
        [int]$port = $global:defaultCloudAgentPort,
        [int]$authport = $global:defaultCloudAuthorizerPort,
        [switch]$encode,
        [string]$outFile = "",
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Write-Status -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_creating_identity, $name))

    [string[]] $boolFlags = @()

    $argsDict = @{ "--name" = $name; "--validity-days" = $validityDays; "--fqdn" = $fqdn; "--client-type" = $clienttype;
        "--port" = $port; "--auth-port" = $authport; "--location" = $location; "--outfile" = $outFile}

    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    $boolFlags += "--encode=$encode"

    return Invoke-MocCommand " security identity update" -argDictionary $argsDict -boolFlags $boolFlags
}

function Remove-MocIdentity
{
    <#
    .DESCRIPTION
        Deletes a identity resource from moc .
 
    .PARAMETER name
        Name of the identity identity.
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


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

    Write-Status -moduleName $moduleName $($MocLocMessage.moc_deleting_identity)

    return Invoke-MocCommand $(" security identity delete --name $name" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}

function Get-MocIdentity
{
    <#
    .DESCRIPTION
        Get a identity resource from moc .
 
    .PARAMETER name
        Name of the identity identity.
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$false)]
        [String]$format = "json",
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    $argsDict = @{ "--name" = $name; "--output" = $format }

    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    return Invoke-MocCommand " security identity list "  -argDictionary $argsDict
}

function Invoke-MocIdentityRotate
{
    <#
    .DESCRIPTION
        Adds a identity resource to moc .
 
    .PARAMETER name
        Name of the identity identity.
 
    .PARAMETER encode
        output to be base64 encoded.
    #>


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

    Write-Status -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_rotating_identity_tokens_for, $name))
    return Invoke-MocCommand " security identity rotate --name $name --encode=$encode"
}

function New-MocAdminIdentity
{
   <#
    .DESCRIPTION
        Creates a new Admin identity resource in moc .
 
    .PARAMETER name
        Name of the identity identity.
 
    .PARAMETER validityDays
        Time before expiry of token for identity in days.
    #>


    param (
        [String]$name = "Appliance",
        [String]$validityDays = 90
    )

    $mocConfig = Get-MocConfig
    $adminIdentity  = New-MocIdentity -name $name -validityDays $validityDays -fqdn $mocConfig.cloudFqdn -location $mocConfig.cloudLocation -port $mocConfig.cloudAgentPort -authport $mocConfig.cloudAgentAuthorizerPort -encode
    New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "LocationContributor" | Out-Null
    New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "IdentityContributor" | Out-Null
    New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "RoleContributor" | Out-Null
    New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "CertificateReader" | Out-Null
    New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "VipPoolReader" -location $mocConfig.cloudLocation | Out-Null
    New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "GroupContributor" -location $mocConfig.cloudLocation | Out-Null
    New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "KeyVaultContributor" -location $mocConfig.cloudLocation | Out-Null
    New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "VirtualNetworkContributor" $mocConfig.cloudLocation | Out-Null
    New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "LBContributor" -location $mocConfig.cloudLocation | Out-Null
    New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "NetworkInterfaceContributor" -location $mocConfig.cloudLocation | Out-Null
    New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "VMContributor" -location $mocConfig.cloudLocation | Out-Null
    New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "SecretContributor" -location $mocConfig.cloudLocation | Out-Null
    New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "GalleryImageContributor" -location $mocConfig.cloudLocation | Out-Null
    New-MocRoleAssignmentWhenAvailable -identityName $name -roleName "StorageContainerContributor" -location $mocConfig.cloudLocation | Out-Null
    return $adminIdentity
}

#endregion moc identity

#region moc role assignment
function New-MocRoleAssignment
{
    <#
    .DESCRIPTION
        Assigns a moc role to a moc identity
 
    .PARAMETER identityName
        Name of the identity.
 
    .PARAMETER roleName
        Name of the role to assign.
 
    .PARAMETER location
        Location to which the role is scoped to.
 
    .PARAMETER group
        Group to which the role is scoped to.
 
    .PARAMETER providerType
        ProviderType to which the role is scoped to.
 
    .PARAMETER resourceName
        Name of resource to which the role is scoped to.
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$identityName,
        [Parameter(Mandatory=$true)]
        [String]$roleName,
        [String]$location,
        [String]$group,
        [string]$providerType,
        [string]$resourceName,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Write-Status -moduleName $moduleName $($MocLocMessage.moc_creating_role_assignment)

    $argsDict = @{ "--identity" = $identityName; "--role" = $roleName }

    if (-not [string]::IsNullOrWhiteSpace($location))
    {
        $argsDict["--location"] = $location
    }
    if (-not [string]::IsNullOrWhiteSpace($group))
    {
        $argsDict["--group"] = $group
    }
    if (-not [string]::IsNullOrWhiteSpace($providerType))
    {
        $argsDict["--provider-type"] = $providerType
    }
    if (-not [string]::IsNullOrWhiteSpace($resourceName))
    {
        $argsDict["--resource"] = $resourceName
    }
    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    return Invoke-MocCommand " security roleassignment create" -argDictionary $argsDict
}

function New-MocRoleAssignmentWhenAvailable
{
    <#
    .DESCRIPTION
        Waits for a moc role to be available, then assigns it to a moc identity
 
    .PARAMETER identityName
        Name of the identity.
 
    .PARAMETER roleName
        Name of the role to assign.
 
    .PARAMETER location
        Location to which the role is scoped to.
 
    .PARAMETER group
        Group to which the role is scoped to.
 
    .PARAMETER providerType
        ProviderType to which the role is scoped to.
 
    .PARAMETER resourceName
        Name of resource to which the role is scoped to.
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$identityName,
        [Parameter(Mandatory=$true)]
        [String]$roleName,
        [String]$location,
        [String]$group,
        [string]$providerType,
        [string]$resourceName
    )

    Write-Status -moduleName $moduleName $($MocLocMessage.moc_creating_role_assignment)

    $isRoleAvailable = Wait-ForMocRole -roleName $roleName
    if (-not $isRoleAvailable)
    {
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_role_unavailable, $roleName))
    }

    return New-MocRoleAssignment -identityName $identityName -roleName $roleName -location $location -group $group -providerType $providerType -resourceName $resourceName
}

function Get-MocRoleAssignment
{
    <#
    .DESCRIPTION
        Get a RoleAssignment resource from moc.
 
    .PARAMETER name
        Name of the RoleAssignment.
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [String]$name,
        [String]$identityName,
        [String]$roleName,
        [String]$location,
        [String]$group,
        [string]$providerType,
        [string]$resourceName,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )


    if ([string]::IsNullOrWhiteSpace($name))
    {
        $cmd = ""
        if (-not [string]::IsNullOrWhiteSpace($identityName))
        {
            $cmd += " --identity $identityName"
        }
        if (-not [string]::IsNullOrWhiteSpace($roleName))
        {
            $cmd += " --role $roleName"
        }
        if (-not [string]::IsNullOrWhiteSpace($location))
        {
            $cmd += " --location $location"
        }
        if (-not [string]::IsNullOrWhiteSpace($group))
        {
            $cmd += " --group $group"
        }
        if (-not [string]::IsNullOrWhiteSpace($providerType))
        {
            $cmd += " --provider-type $providerType"
        }
        if (-not [string]::IsNullOrWhiteSpace($resourceName))
        {
            $cmd += " --resource $resourceName"
        }
        Invoke-MocListCommand $(" security roleassignment list $cmd" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
    else
    {
        Invoke-MocShowCommand $(" security roleassignment show --name ""$name""" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
}

function Remove-MocRoleAssignmentByName
{
    <#
    .DESCRIPTION
        Removes a role assignment to an identity from moc using a generated role assignment name.
 
    .PARAMETER name
        Name of the role assignment.
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


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

    return Invoke-MocCommand $(" security roleassignment delete --name $name" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}

function Remove-MocRoleAssignment
{
    <#
    .DESCRIPTION
        Removes a role assignment to an identity from moc.
 
    .PARAMETER name
        Name of the role assignment.
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$identityName,
        [Parameter(Mandatory=$true)]
        [String]$roleName,
        [String]$location,
        [String]$group,
        [string]$providerType,
        [string]$resourceName,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    $cmdDict = @{ "--identity" = $identityName; "--role" = $roleName }
    if (-not [string]::IsNullOrWhiteSpace($location))
    {
        $cmdDict[" --location"] = $location
    }
    if (-not [string]::IsNullOrWhiteSpace($group))
    {
        $cmdDict["--group"] = $group  
    }
    if (-not [string]::IsNullOrWhiteSpace($providerType))
    {
        $cmdDict["--provider-type"] = $providerType
    }
    if (-not [string]::IsNullOrWhiteSpace($resourceName))
    {
        $cmdDict["--resource"] = $resourceName
    }

    if($timeoutSeconds) {
        $cmdDict['--timeout'] = $timeoutSeconds
    }

    return Invoke-MocCommand " security roleassignment delete" -argDictionary $cmdDict
}



#endregion moc role assignment


#region moc earlyaccess preview
function Enable-MocPreview
{
    <#
    .SYNOPSIS
        Enable AKSHCI catalog and ring configuration to expose early access preview builds.
 
    .DESCRIPTION
        Enable AKSHCI catalog and ring configuration to expose early access preview builds.
 
    .PARAMETER activity
        Activity name to use when updating progress
 
    .PARAMETER catalog
        Release catalog for AKS HCI. Reserved for internal use. We do not recommend using this parameter.
 
    .PARAMETER ring
        Audience (aka ring) type of each catalog. Reserved for internal use. We do not recommend using this parameter.
    #>


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

        [parameter()]
        [String] $catalog,

        [parameter()]
        [String] $ring

    )

    #Set MocConfig for early access preview
    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_enabling_preview, $moduleName))
    Set-MocConfigValue -name "catalog" -value $catalog
    Set-MocConfigValue -name "ring" -value $ring
    Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_configuration_for_module_updated, $moduleName))
    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
}

function Disable-MocPreview
{

    <#
    .SYNOPSIS
        Disable AKSHCI catalog and ring configuration which exposes early access preview builds and revert to a stable build.
 
    .DESCRIPTION
        Disable AKSHCI catalog and ring configuration which exposes early access preview builds and revert to a stable build.
 
    .PARAMETER activity
        Activity name to use when updating progress.
 
    .PARAMETER catalog
        Release catalog for AKS HCI. Reserved for internal use. We do not recommend using this parameter.
 
    .PARAMETER ring
        Audience (aka ring) type of each catalog. Reserved for internal use. We do not recommend using this parameter.
    #>


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

        [parameter()]
        [String] $catalog,

        [parameter()]
        [String] $ring
    )

    #Revert MocConfig from early access preview to stable build
    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_disabling_preview, $moduleName))
    Set-MocConfigValue -name "catalog" -value $catalog
    Set-MocConfigValue -name "ring" -value $ring
    Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_configuration_for_module_updated, $moduleName))
    Write-Status -moduleName $moduleName $($GenericLocMessage.generic_done)
}
#end region earlyaccess preview

#region moc role
function New-MocRole
{
    <#
    .DESCRIPTION
        Add new role resource to moc.
 
    .PARAMETER name
        Name of the role.
 
    .PARAMETER actionOperations
        List of actions to be allowed in role.
 
    .PARAMETER actionProviders
        List of providers to which the corresponding actions are applied to.
 
    .PARAMETER notActionOperations
        List of actions to be excluded in role.
 
    .PARAMETER notActionProviders
        List of providers to which the corresponding excluded actions are applied to.
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [string[]]$actionOperations,
        [Parameter(Mandatory=$true)]
        [string[]]$actionProviders,
        [Parameter(Mandatory=$false)]
        [string[]]$notActionOperations,
        [Parameter(Mandatory=$false)]
        [string[]]$notActionProviders,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Write-Status -moduleName $moduleName  $($MocLocMessage.moc_creating_role)

    if ($actionOperations.Count -eq 0)
    {
        throw $($MocLocMessage.moc_no_action_operation)
    }

    if ($actionOperations.Length -ne $actionProviders.Length)
    {
        throw $($MocLocMessage.moc_unequal_action_operation)
    }

    if ($notActionOperations -and $notActionOperations.Length -ne $notActionProviders.Length)
    {
        throw $($MocLocMessage.moc_unequal_not_action_operations)
    }

    $argsDict = @{ "--name" = $name }

    if ($actionOperations -and $actionOperations.Count -gt 0)
    {
        $argsDict["--actions"] = $actionOperations
        $argsDict["--action-providers"] = $actionProviders
    }

    if ($notActionOperations -and $notActionOperations.Count -gt 0)
    {
        $argsDict["--not-actions"] = $notActionOperations
        $argsDict["--not-action-providers"] = $notActionProviders
    }
    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    Invoke-MocCommand " security role create" -argDictionary $argsDict
}

function Get-MocRole
{
    <#
    .DESCRIPTION
        Get a Role resource from moc.
 
    .PARAMETER name
        Name of the Role.
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [String]$name,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    if ([string]::IsNullOrWhiteSpace($name))
    {
        Invoke-MocListCommand $(" security role list" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
    else
    {
        Invoke-MocShowCommand $(" security role show --name ""$name""" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
}

function Remove-MocRole
{
    <#
    .DESCRIPTION
        Removes a role resource from moc.
 
    .PARAMETER name
        Name of the role.
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


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

    return Invoke-MocCommand $(" security role delete --name $name" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}) )
}

#endregion moc role

#region moc login

function Invoke-NodeLogin
{
    <#
    .DESCRIPTION
        Provisions the Script to have access to node ctl
 
    .PARAMETER nodeName
        The node to execute on.
    #>

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

    Invoke-Command -ComputerName $nodeName -ScriptBlock {
        $nodeloginYAMLLocation = $args[0]
        $nodectlPath = $args[1]
        Invoke-Expression "& '$nodectlPath' security login --loginpath ""$nodeloginYAMLLocation"" --identity"
    } -ArgumentList @($loginYaml, $global:nodeCtlFullPath)
}

#endregion moc login


#region moc key vault
function New-MocKeyVault
{
    <#
    .DESCRIPTION
        Adds a keyvault resource to moc .
 
    .PARAMETER name
        Name of the keyvault
 
    .PARAMETER group
        The name of the group in which the keyvault resides
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [string]$group,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    $argsDict = @{ "--name" = $name; "--group" = $group }

    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    Invoke-MocCommand " security keyvault create" -argDictionary $argsDict
}

function Get-MocKeyVault
{
    <#
    .DESCRIPTION
        Get a KeyVault resource from moc.
 
    .PARAMETER name
        Name of the KeyVault
 
    .PARAMETER group
        group for the KeyVault
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    if ([string]::IsNullOrWhiteSpace($name))
    {
        Invoke-MocListCommand $(" security keyvault list --group $group" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
    else
    {
        Invoke-MocShowCommand $(" security keyvault show --name ""$name"" --group $group" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
}

function Remove-MocKeyVault
{
    <#
    .DESCRIPTION
        Removes a KeyVault resource from moc.
 
    .PARAMETER name
        Name of the KeyVault
 
    .PARAMETER group
        group for the KeyVault
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" security keyvault delete --name ""$name"" --group "+$group + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}

function Reset-MocKeyVault
{
    <#
    .DESCRIPTION
        Remove all moc keyvault.
 
     .PARAMETER group
        group for the NetworkInterface
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" security keyvault list --output tsv --query ""[*].name"" --group $group") | ForEach-Object {
        $entityName = $_
        if ($entityName -notlike "No *")
        {
            if($timeoutSeconds) {
                Remove-MocKeyVault -name $entityName -group $group -timeout $timeoutSeconds
            }
            else {
                Remove-MocKeyVault -name $entityName -group $group
            }
        }
    }
}
#endregion moc key vault

#region moc secret
function New-MocSecret
{
    <#
    .DESCRIPTION
        Adds a secret resource to moc .
 
    .PARAMETER name
        Name of the secret
 
    .PARAMETER group
        The name of the group in which the secret resides
 
    .PARAMETER keyvaultName
        name of the keyvault
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [string]$group,
        [Parameter(Mandatory=$true)]
        [string]$value,
        [Parameter(Mandatory=$true)]
        [string]$keyvaultName,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    $argsDict = @{ "--name" = $name; "--value" = $value; "--vault-name" = $keyvaultName; "--group" = $group}

    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    Invoke-MocCommand " security keyvault secret set" -argDictionary $argsDict
}

function Get-MocSecret
{
    <#
    .DESCRIPTION
        Get a Secret resource from moc.
 
    .PARAMETER name
        Name of the Secret
 
    .PARAMETER group
        group for the Secret
 
    .PARAMETER keyvaultName
        name of the keyvault
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$true)]
        [string]$keyvaultName,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    if ([string]::IsNullOrWhiteSpace($name))
    {
        Invoke-MocListCommand $(" security keyvault secret list --group $group --vault-name $keyvaultName" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
    else
    {
        Invoke-MocShowCommand $(" security keyvault secret show --name ""$name"" --group $group --vault-name $keyvaultName" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
}

function Remove-MocSecret
{
    <#
    .DESCRIPTION
        Removes a Secret resource from moc.
 
    .PARAMETER name
        Name of the Secret
 
    .PARAMETER group
        group for the Secret
 
    .PARAMETER keyvaultName
        name of the keyvault
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$true)]
        [string]$keyvaultName,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" security keyvault secret delete --name ""$name"" --group $group --vault-name $keyvaultName" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}

function Reset-MocSecret
{
    <#
    .DESCRIPTION
        Remove all moc secret.
 
     .PARAMETER group
        group for the NetworkInterface
 
    .PARAMETER keyvaultName
        name of the keyvault
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$true)]
        [string]$keyvaultName,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" security keyvault secret list --output tsv --query ""[*].name"" --group $group --vault-name $keyvaultName") | ForEach-Object {
        $entityName = $_
        if ($entityName -notlike "No *")
        {
        if($timeoutSeconds) {
                Remove-MocSecret -name $entityName -group $group -keyvaultName $keyvaultName -timeout $timeoutSeconds
        }
        else {
                Remove-MocSecret -name $entityName -group $group -keyvaultName $keyvaultName 
        }
        }
    }
}
#endregion moc secret

#region moc key
function New-MocKey
{
    <#
    .DESCRIPTION
        Adds a key resource to moc .
 
    .PARAMETER name
        Name of the key
 
    .PARAMETER group
        The name of the group in which the key resides
 
    .PARAMETER keyvaultName
        name of the keyvault
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [string]$group,
        [Parameter(Mandatory=$true)]
        [ValidateSet('RSA', 'AES')]
        [string]$type,
        [Parameter(Mandatory=$true)]
        [ValidateSet(256, 2048)]
        [string]$size,
        [Parameter(Mandatory=$true)]
        [string]$keyvaultName,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    $argsDict = @{ "--name" = $name; "--vault-name" = $keyvaultName; "--key-size" = $size; "--key-type" = $type; "--group" = $group }

    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    Invoke-MocCommand " security keyvault key create" -argDictionary $argsDict -boolFlags $boolFlags
}

function Get-MocKey
{
    <#
    .DESCRIPTION
        Get a Key resource from moc.
 
    .PARAMETER name
        Name of the Key
 
    .PARAMETER group
        group for the Key
 
    .PARAMETER keyvaultName
        name of the keyvault
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$true)]
        [string]$keyvaultName,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    if ([string]::IsNullOrWhiteSpace($name))
    {
        Invoke-MocListCommand $(" security keyvault key list --group $group --vault-name $keyvaultName" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
    else
    {
        Invoke-MocShowCommand $(" security keyvault key show --name ""$name"" --group $group --vault-name $keyvaultName" + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
    }
}

function Import-MocKey
{
    <#
    .DESCRIPTION
        Imports a key resource to moc .
 
    .PARAMETER name
        Name of the key
 
    .PARAMETER group
        The name of the group in which the key resides
 
    .PARAMETER importKeyFile
        The path of the import key file previously exported from moc
 
    .PARAMETER type
        The type of key. Can be either RSA or AES
 
    .PARAMETER size
        The size of key. Can be either 256 for AES or 2048 for RSA
 
    .PARAMETER keyvaultName
        name of the keyvault
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [string]$group,
        [Parameter(Mandatory=$true)]
        [string]$importKeyFile,
        [Parameter(Mandatory=$true)]
        [ValidateSet('RSA', 'AES')]
        [string]$type,
        [Parameter(Mandatory=$true)]
        [ValidateSet(256, 2048)]
        [string]$size,
        [Parameter(Mandatory=$true)]
        [string]$keyvaultName,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    $argsDict = @{ "--name" = $name; "--vault-name" = $keyvaultName; "--key-size" = $size; "--key-type" = $type; "--group" = $group; "--key-file-path" = $importKeyFile }

    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    Invoke-MocCommand " security keyvault key import" -argDictionary $argsDict -boolFlags $boolFlags
}


function Export-MocKey
{
    <#
    .DESCRIPTION
        Exports a key resource from moc .
 
    .PARAMETER name
        Name of the key
 
    .PARAMETER group
        The name of the group in which the key resides
 
    .PARAMETER wrappingKeyName
        The name of the key that will be used to wrap the key
 
    .PARAMETER wrappingKeyFile
        The key file path of the key that will be used to wrap the key
 
    .PARAMETER outFile
        The file path where to store the export key
 
    .PARAMETER algorithm
        The wrapping key algorithm
 
    .PARAMETER size
        The size of key. Can be either 256 for AES or 2048 for RSA
 
    .PARAMETER keyvaultName
        name of the keyvault
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [string]$group,
        [Parameter(Mandatory=$false)]
        [string]$wrappingKeyName,
        [Parameter(Mandatory=$false)]
        [string]$wrappingPubKeyFile,
        [Parameter(Mandatory=$false)]
        [ValidateSet('CKM_RSA_AES_KEY_WRAP')]
        [string]$algorithm = 'CKM_RSA_AES_KEY_WRAP',
        [Parameter(Mandatory=$true)]
        [ValidateSet(256, 2048)]
        [string]$size,
        [Parameter(Mandatory=$false)]
        [string]$outFile,
        [Parameter(Mandatory=$true)]
        [string]$keyvaultName,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    $argsDict = @{ "--name" = $name; "--vault-name" = $keyvaultName; "--algorithm" = $algorithm; "--group" = $group; "--out-file" = $outFile; "--key-size" = $size; }

    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    if (-not ([string]::IsNullOrEmpty($wrappingKeyName))) {
        $argsDict["--wrapping-key-name"] = $wrappingKeyName
    }

    if (-not ([string]::IsNullOrEmpty($wrappingPubKeyFile))) {
        $argsDict["--wrapping-pub-key-file"] = $wrappingPubKeyFile
    }

    Invoke-MocCommand " security keyvault key export" -argDictionary $argsDict -boolFlags $boolFlags
}

function Invoke-MocKeyEncrypt
{
    <#
    .DESCRIPTION
        Encrypt input data using key
 
    .PARAMETER name
        Name of the Key
 
    .PARAMETER group
        group for the Key
 
    .PARAMETER keyvaultName
        name of the keyvault
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$true)]
        [string]$keyvaultName,
        [Parameter(Mandatory=$true)]
        [string]$inputDataFile,
        [Parameter(Mandatory=$true)]
        [string]$outputDataFile,
        [Parameter(Mandatory=$false)]
        [ValidateSet('plaintext','base64')]
        [string]$inputType = 'base64',
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    $argsdict = @{
        "--name"       = $name;
        "--group"      = $group;
        "--vault-name" = $keyvaultName;
        "--file"       = $inputDataFile;
        "--out-file"   = $outputDataFile;
        "--data-type"  = $inputType
    }

    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    Invoke-MocCommand "security keyvault key encrypt" -argDictionary $argsDict
}

function Invoke-MocKeyDecrypt
{
    <#
    .DESCRIPTION
        Decrypt input data using key
 
    .PARAMETER name
        Name of the Key
 
    .PARAMETER group
        group for the Key
 
    .PARAMETER keyvaultName
        name of the keyvault
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$true)]
        [string]$keyvaultName,
        [Parameter(Mandatory=$true)]
        [string]$inputDataFile,
        [Parameter(Mandatory=$true)]
        [string]$outputDataFile,
        [Parameter(Mandatory=$false)]
        [ValidateSet('plaintext','base64')]
        [string]$inputType = 'base64',
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    $argsdict = @{
        "--name"       = $name;
        "--group"      = $group;
        "--vault-name" = $keyvaultName;
        "--file"       = $inputDataFile;
        "--out-file"   = $outputDataFile;
        "--data-type"  = $inputType
    }

    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    Invoke-MocCommand "security keyvault key decrypt" -argDictionary $argsDict
}

function Invoke-MocKeyWrap
{
    <#
    .DESCRIPTION
        Wrap input data using key
 
    .PARAMETER name
        Name of the Key
 
    .PARAMETER group
        group for the Key
 
    .PARAMETER keyvaultName
        name of the keyvault
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$true)]
        [string]$keyvaultName,
        [Parameter(Mandatory=$true)]
        [string]$inputDataFile,
        [Parameter(Mandatory=$true)]
        [string]$outputDataFile,
        [Parameter(Mandatory=$false)]
        [ValidateSet('plaintext','base64')]
        [string]$inputType = 'base64',
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    $argsdict = @{
        "--name"       = $name;
        "--group"      = $group;
        "--vault-name" = $keyvaultName;
        "--file"       = $inputDataFile;
        "--out-file"   = $outputDataFile;
        "--data-type"  = $inputType
    }

    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    Invoke-MocCommand "security keyvault key wrap" -argDictionary $argsDict
}

function Invoke-MocKeyUnwrap
{
    <#
    .DESCRIPTION
       Unwrap input data using key
 
    .PARAMETER name
        Name of the Key
 
    .PARAMETER group
        group for the Key
 
    .PARAMETER keyvaultName
        name of the keyvault
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$true)]
        [string]$keyvaultName,
        [Parameter(Mandatory=$true)]
        [string]$inputDataFile,
        [Parameter(Mandatory=$true)]
        [string]$outputDataFile,
        [Parameter(Mandatory=$false)]
        [ValidateSet('plaintext','base64')]
        [string]$inputType = 'base64',
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    $argsdict = @{
        "--name"       = $name;
        "--group"      = $group;
        "--vault-name" = $keyvaultName;
        "--file"       = $inputDataFile;
        "--out-file"   = $outputDataFile;
        "--data-type"  = $inputType
    }

    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    Invoke-MocCommand "security keyvault key unwrap" -argDictionary $argsDict
}

function Get-MocKeyPublicKey
{
    <#
    .DESCRIPTION
       Downloads Public Key if keytype is RSA
 
    .PARAMETER name
        Name of the Key
 
    .PARAMETER group
        group for the Key
 
    .PARAMETER keyvaultName
        name of the keyvault
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$true)]
        [string]$keyvaultName,
        [Parameter(Mandatory=$true)]
        [string]$outputFile,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $("security keyvault key download --name $name --group $group --vault-name $keyvaultName --file-path ""$outputFile"" " + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}))
}

function Remove-MocKey
{
    <#
    .DESCRIPTION
        Get a Key resource from moc.
 
    .PARAMETER name
        Name of the Key
 
    .PARAMETER group
        group for the Key
 
    .PARAMETER keyvaultName
        name of the keyvault
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$name,
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$true)]
        [string]$keyvaultName,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    if($timeoutSeconds) {
        $argsDict["--timeout"] = $timeoutSeconds
    }

    Invoke-MocCommand $(" security keyvault key delete --name ""$name"" --group $group --vault-name $keyvaultName")
}

function Reset-MocKey
{
    <#
    .DESCRIPTION
        Remove all moc key
 
     .PARAMETER group
        group for the key
 
    .PARAMETER keyvaultName
        name of the keyvault
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$group,
        [Parameter(Mandatory=$true)]
        [string]$keyvaultName,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" security keyvault key list --output tsv --query ""[*].name"" --group $group --vault-name $keyvaultName") | ForEach-Object {
        $entityName = $_
        if ($entityName -notlike "No *")
        {
            if ($timeoutSeconds) {
                Remove-MocKey -name $entityName -group $group -keyvaultName $keyvaultName -timeout $timeoutSeconds
            }
            else {
                Remove-MocKey -name $entityName -group $group -keyvaultName $keyvaultName
            }
        }
    }
}
#endregion moc key

#region moc backup

function Invoke-MocBackup
{
    <#
    .DESCRIPTION
        Removes a group resource from moc.
 
    .PARAMETER path
        Path to backup to
 
    .PARAMETER timeoutSeconds
        timeout in seconds
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String]$path,
        [Parameter(Mandatory=$false)]
        [int32]$timeoutSeconds
    )

    Invoke-MocCommand $(" admin recovery backup --path ""$path"" " + (&{If($timeoutSeconds) {" --timeout $timeoutSeconds"}}) )
}

#endregion moc backup

#end region
function Get-NodeAgentVersion
{
    <#
    .DESCRIPTION
        Executes a nodeagent command.
    #>


    $cmdArgs = " version"
    $tmp = Invoke-CommandLine -Command $global:nodeAgentBinary -Arguments $cmdArgs -moduleName $moduleName
    return $tmp[2]
}

function Get-MocAgentVersion
{
    <#
    .DESCRIPTION
        Executes a nodeagent command.
    #>


    $cmdArgs = " version"
    $tmp = Invoke-CommandLine -Command $global:cloudAgentBinary -Arguments $cmdArgs -moduleName $moduleName
    return $tmp[2]
}

function Set-DeploymentType
{
    <#
    .DESCRIPTION
        Determine what type of deployment is being performed (single node or multi-node).
    #>


    Write-Status -moduleName $moduleName $($MocLocMessage.moc_determining_deployment_type)

    # Important to save the cluster information globally for subsequent function calls to use
    $failoverCluster = Get-FailoverCluster
    if ($failoverCluster)
    {
        Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_multinode_deployment_using_failover_cluster, $failoverCluster.Name))
        # Set additional cluster related globals for later use
        Set-MocConfigValue -name "cloudFqdn" -value ($global:config[$moduleName]["clusterRoleName"] + "." + $failoverCluster.Domain)
        Set-MocConfigValue -name "deploymentType" -value ([DeploymentType]::MultiNode)
        Set-MocConfigValue -name "nodeCount" -value (Get-ClusterNode).Count
    }
    else
    {
        Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_singlenode_deployment)
        $hostname = ([System.Net.Dns]::GetHostByName(($env:computerName))).Hostname

        # If this single node hostname is a short name, we use the host ip to avoid DNS resolution issues
        if ($hostname -notlike '*.*')
        {
            $hostname = Get-HostIp -nodeName localhost
        }

        Set-MocConfigValue -name "cloudFqdn" -value $hostname
        Set-MocConfigValue -name "deploymentType" -value ([DeploymentType]::SingleNode)
    }
}

function Test-Remoting
{
    <#
    .DESCRIPTION
        Validate that powershell remoting to a node is working.
 
    .PARAMETER nodeName
        The node to remote to.
    #>


    param (
        [String]$nodeName
    )

    try
    {
        Invoke-Command -ComputerName $nodeName -ScriptBlock { return $null } -ErrorAction Stop
        return $true
    } catch {}

    return $false
}


function Test-CloudLimits
{
    <#
    .DESCRIPTION
        Verify the resource limits for all nodes in the cloud.
 
    .PARAMETER path
        Path to WSSD Image directory.
 
    .PARAMETER minRequiredDisk
        Minimimum required disk space (GB).
 
    .PARAMETER minRequiredMemory
        Minimimum required memory (GB).
 
    .PARAMETER minRequiredLp
        Minimimum required logical processor.
    #>


    param (
        [string]$path,
        [int]$minRequiredDisk,
        [int]$minRequiredMemory,
        [int]$minRequiredLp
    )

    Write-Status $($MocLocMessage.moc_verifying_cloud_limits) -moduleName $moduleName

    if (Test-MultiNodeDeployment)
    {
        if($path.ToLower().IndexOf("$env:SystemDrive\clusterstorage".ToLower()) -eq 0)
        {
            Write-SubStatus $($MocLocMessage.moc_checking_available_space) -moduleName $moduleName
            $space = Get-ClusterSharedVolume -ErrorAction Stop | Where-Object {$path.ToLower().IndexOf($_.SharedVolumeInfo.FriendlyVolumeName.ToLower()) -eq 0}
            if ($null -ne $space)
            {

                $freespaceGb = [Math]::Round($space.SharedVolumeInfo.Partition.FreeSpace / 1GB)
            }
            else
            {
                throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_missing_drive, $path))
            }

            Write-SubStatus $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_shared_drive_has_free_space, $freespaceGb)) -moduleName $moduleName
            Write-SubStatus $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_shared_drive_disk_space_required, $minRequiredDisk)) -moduleName $moduleName

            if ($freespaceGb -lt $minRequiredDisk)
            {
                Write-Output $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_shared_drive_has_free_space, $freespaceGb))
                Write-Output $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_shared_drive_disk_space_required, $minRequiredDisk))
                throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_insufficient_shared_drive_space, $drive))
            }
        }
        Get-ClusterNode -ErrorAction Stop | ForEach-Object {
            Test-HostLimits -nodeName $_.Name -minRequiredMemory $minRequiredMemory -minRequiredLp $minRequiredLp
        }
    }
    else
    {
        $drive = Split-Path -Path $path -Qualifier
        $disk = Get-WmiObject Win32_LogicalDisk -Filter $("DeviceID='$drive'") | Select-Object Size,FreeSpace
        $freespaceGb = [Math]::Round($Disk.Freespace / 1GB)
        Write-SubStatus $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_drive_free_space, $drive, $freespaceGb)) -moduleName $moduleName
        Write-SubStatus $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_minimum_disk_space_required_on_drive, $minRequiredDisk, $drive)) -moduleName $moduleName

        if ($freespaceGb -lt $minRequiredDisk)
        {
            Write-Output $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_drive_free_space, $drive, $freespaceGb))
            Write-Output $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_minimum_disk_space_required_on_drive, $minRequiredDisk, $drive))
            throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_insufficient_drive_space, $drive))
        }
        Test-HostLimits -nodeName ($env:computername) -minRequiredMemory $minRequiredMemory -minRequiredLp $minRequiredLp
    }
}

function Test-HostLimits
{
    <#
    .DESCRIPTION
        Verify the resource limits for a host node.
 
    .PARAMETER nodeName
        The node to execute on.
 
    .PARAMETER minRequiredMemory
        Minimimum required memory (GB).
 
    .PARAMETER minRequiredLp
        Minimimum required LogicalProcessor.
    #>


    param (
        [string]$nodeName,
        [int]$minRequiredMemory,
        [int]$minRequiredLp
    )

    Write-Status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_verifying_host_limits, $nodeName)) -moduleName $moduleName
    Invoke-Command -ComputerName $nodeName -ScriptBlock {
        $minRequiredMemory = $args[0]
        $minRequiredLp = $args[1]
        $MocLocMessage = $args[2]
        $GenericLocMessage = $args[3]

        $freemem = Get-WmiObject -Class Win32_OperatingSystem
        $freemem = [Math]::Round($freemem.FreePhysicalMemory / 1MB)

        write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_free_memory_left, $freemem))
        write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_minimum_required_memory, $minRequiredMemory))

        if ($freemem -lt $minRequiredMemory)
        {
            throw $($MocLocMessage.moc_insufficient_memory)
        }

        $lpCount = (Get-ComputerInfo -Property CsNumberOfLogicalProcessors).CsNumberOfLogicalProcessors

        write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_logical_processors_count, $lpCount))
        write-verbose $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_minimum_required_logical_processors, $minRequiredLp))

        if ($lpCount -lt $minRequiredLp)
        {
            throw $($MocLocMessage.moc_insufficient_logical_procesors)
        }
    } -ArgumentList $minRequiredMemory, $minRequiredLp, $MocLocMessage, $GenericLocMessage
}


function Confirm-Remoting
{
    <#
    .DESCRIPTION
        Confirm that powershell remoting is enabled and working for the cloud.
 
    .PARAMETER localhostOnly
        Limit the validation to only the local node.
    #>


    param (
        [Switch]$localhostOnly
    )

    if ((-not $localhostOnly.IsPresent) -and (Test-MultiNodeDeployment))
    {
        # Multi-node
        Get-ClusterNode -ErrorAction Stop | ForEach-Object {
            $hostname = $_.Name
            Write-Status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_testing_powershell, $hostname)) -moduleName $moduleName
            if (-not (Test-Remoting -nodeName $hostname))
            {
                throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_disabled_powershell, $hostname))
            }
            Write-SubStatus $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_remoting_successful, $hostname)) -moduleName $moduleName
        }
    }
    else
    {
        # Single node or localhostOnly
        Write-Status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_testing_powershell, $env:computername)) -moduleName $moduleName

        if (-not (Test-Remoting -nodeName $env:computername))
        {
            Write-SubStatus $($MocLocMessage.moc_remoting_not_enabled) -moduleName $moduleName

            Enable-Remoting

            if (-not (Test-Remoting -nodeName $env:computername))
            {
                throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_ps_remoting_unsuccessful, $env:computername))
            }
        }

        Write-SubStatus $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_remoting_successful, $env:computername)) -moduleName $moduleName
    }
}

function Confirm-NetworkControllerConfiguration
{
    <#
    .DESCRIPTION
        Does basic validation of NetworkController Data
    #>


    param(
        [string] $networkControllerFqdnOrIpAddress,
        [string] $networkControllerClientCertificateName,
        [string] $networkControllerLbSubnetRef,
        [string] $networkControllerLnetRef,
        [VipPoolSettings] $vipPool
        )

    if ([string]::IsNullOrWhiteSpace($networkControllerFqdnOrIpAddress))
    {
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_empty, "networkControllerFqdnOrIpAddress"))
    }

    if ([string]::IsNullOrWhiteSpace($networkControllerLbSubnetRef))
    {
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_empty, "networkControllerLbSubnetRef"))
    }

    if ([string]::IsNullOrWhiteSpace($networkControllerLnetRef))
    {
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_empty, "networkControllerLnetRef"))
    }

    if (-not (Test-NetConnection -ComputerName $networkControllerFqdnOrIpAddress -Port 443 -InformationLevel Quiet))
    {                
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_conn_test_failed, $networkControllerFqdnOrIpAddress))
    }

    # TODO We need to add validation for various NetworkController parameters. Skipping it for now until its confirmed NC REST endpoint is accessible at this point.
}
function Confirm-Configuration
{
    <#
    .DESCRIPTION
        Validate if the configuration can be used for the deployment
    #>


    param (
        [String] $workingDir,
        [String] $cloudConfigLocation,
        [String] $imageDir,
        [Switch] $skipHostLimitChecks,
        [Switch] $skipRemotingChecks,
        [Switch] $useStagingShare,
        [String] $stagingShare,
        [VirtualNetwork] $vnet,
        [string] $cloudServiceCidr,
        [Switch] $useNetWorkController,
        [string] $networkControllerFqdnOrIpAddress,
        [string] $networkControllerClientCertificateName,
        [string] $networkControllerLbSubnetRef,
        [string] $networkControllerLnetRef,
        [VipPoolSettings] $vipPool,
        [string] $clusterRoleName
    )

    if (!$skipRemotingChecks.IsPresent)
    {
        Confirm-Remoting
    }

    if ($useNetworkController.IsPresent)
    {
        Confirm-NetworkControllerConfiguration -networkControllerFqdnOrIpAddress $networkControllerFqdnOrIpAddress `
        -networkControllerLbSubnetRef $networkControllerLbSubnetRef `
        -networkControllerLnetRef $networkControllerLnetRef `
        -networkControllerClientCertificateName $networkControllerClientCertificateName `
        -vipPool $vipPool
    }

    if ([string]::IsNullOrWhiteSpace($workingDir))
    {
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_empty, "workingDir"))
    }

    if ([string]::IsNullOrWhiteSpace($cloudConfigLocation))
    {
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_empty, "cloudConfigLocation"))
    }

    # cloudConfigLocation cannot be a parent path for workingDir
    # for example, workingDir = c:\test\123 and cloudConfigLocation = c:\test is invalid
    # During install of Moc, the system is reset including cloudConfigLocation, that would wipe of workingDir, that
    # contains critical PS configuration
    if ($workingDir.StartsWith($cloudConfigLocation.TrimEnd('\\'), $true, [System.Globalization.CultureInfo]::InvariantCulture))
    {
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, 
                $GenericLocMessage.generic_invalid_working_dir_cloudconfig, $workingDir, $cloudConfigLocation)
                )
    }

    $multiNode = Test-MultiNodeDeployment
    if ($multiNode) {
        #Check if the working directory corresponds to CSV root.
        $volumeList = Get-ClusterSharedVolume | Select-Object SharedVolumeInfo
        foreach ($volume in $volumeList) {
            if (!$volume.SharedVolumeInfo.FriendlyVolumeName)
            {
                # If volume is not online, we will not find get the friendly volume name
                continue
            }
            $rootPath = $volume.SharedVolumeInfo.FriendlyVolumeName.ToLower()
            if (([System.IO.Path]::GetFullPath($WorkingDir)).ToLower() -eq $rootPath) {
                throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_invalid_workingDir_system_root))
            }
        }
   } else {
        #Static check to ensure working directory is not System Drive or Root
        if (($WorkingDir -eq $env:SystemDrive) -or ($WorkingDir.TrimEnd('\\') -eq $env:SystemDrive) -or
            ($WorkingDir -eq $env:SystemRoot) -or ($WorkingDir.TrimEnd('\\') -eq $env:SystemRoot) -or
            ($WorkingDir -eq [System.IO.Path]::GetFullPath($env:SystemDrive))) {
            throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_invalid_workingDir_system_root))
        }
    }

    #Check HCI node registration status
    if ($multiNode)
    {
       Test-ClusterHealth
       Test-HCIRegistration

       # Sanity check.
       if ((Test-LocalFilePath -path $workingDir) )
       {
            throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_dir_not_set, "-workingDir"))
       }

       if ((Test-LocalFilePath -path $cloudConfigLocation))
       {
            throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_dir_not_set, "-cloudConfigLocation"))
       }

       if ((Test-LocalFilePath -path $imageDir))
       {           
           throw $($MocLocMessage.moc_no_moc_config)
       }
    }

    if (!$skipHostLimitChecks.IsPresent)
    {
        $tmpDir =  $workingDir
        # Skip SMB path
        if (!$tmpDir.StartsWith("\\"))
        {
            if ($multiNode)
            {
                Test-CloudLimits -path $tmpDir -minRequiredDisk 40 -minRequiredMemory 10 -minRequiredLp 4
            }
            else
            {
                Test-CloudLimits -path $tmpDir -minRequiredDisk 50 -minRequiredMemory 20 -minRequiredLp 4
            }
        }
    }

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

    #Netowrk Validations :
    #1. whether vSwitch provided in the vnet configuration exists.
    #2. CloudCIDR parameter is provided in case of static networks.
    if ($multiNode) {
        Get-ClusterNode -ErrorAction Stop | ForEach-Object {
            if ($vnet) {
                Test-HostNetworking -nodeName $_.Name -vswitchName $vnet.VswitchName
            }
        }
        Test-ClusterNetworkProperties -cloudServiceCidr $cloudServiceCidr -vnet $vnet -clusterRoleName $clusterRoleName
    }
    else
    {
        if ($vnet) {
             Test-HostNetworking -nodeName ($env:computername) -vswitchName $vnet.VswitchName
        }
        # There is no cloud service cidr for standalone
        # We use the host ip for cloud service ip
        if (-not [string]::IsNullOrWhiteSpace($cloudServiceCidr))
        {
            Write-Warning $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_ignore_csip , $cloudServiceCidr))   
        }
    }
}

function Get-GuestVirtualMachineLogs
{
    <#
    .DESCRIPTION
        Collects logs from Virtual Machines via scp
 
    .PARAMETER logDirectoryName
      Output directory for the logs
 
    .PARAMETER activity
        Activity name to use when writing progress
    #>


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

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

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_discovering_cloud_groups)

    $groups = Invoke-MocCommand ("cloud group list --output tsv --query ""[*].name"" --location " + $global:config[$modulename]["cloudLocation"])
    foreach ($grpName in $groups)
    {
        if ($grpName -ieq "No Group Resources")
        {
            continue
        }

        # Make Resource Group directory
        $logGroupDir = [io.Path]::Combine($logDirectoryName, $grpName)
        New-Item -ItemType Directory -Force -Path $logGroupDir | Out-Null

        try
        {
            $virtualMachines = Invoke-MocCommand "compute vm list -o tsv --query ""[*].name"" --group ""$grpName"""
            foreach ($vmName in $virtualMachines)
            {
                if ($vmName -ieq "No VirtualMachine Resources")
                {
                    continue
                }

                Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_gather_logs_for_vm, $vmName))

                try
                {
                    $nicName = Invoke-MocCommand "compute vm show --name ""$vmName"" -o tsv --query virtualmachineproperties.networkprofile.networkinterfaces[0] --group ""$grpName"""
                    $cleanNicName = $nicName.Split('', [System.StringSplitOptions]::RemoveEmptyEntries) -join ''
                    $ipAddress = Invoke-MocCommand "network vnic show --name ""$cleanNicName"" -o tsv --query properties.ipConfigurations[0].properties.privateIPAddress --group ""$grpName"""
                    if ([string]::IsNullOrWhiteSpace($ipAddress) -or $ipAddress -ieq "no virtual network interface resources")
                    {
                        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_log_collection_failed, $vmName))
                    }

                    # Remove cached keys belonging to this ip from the known_hosts file.
                    # This will avoid scp/ssh warnings being shown in redeployment scenarios where stale data may exist for a host/ip in the known_hosts file.
                    (& ssh-keygen -R ${ipAddress}) 2>&1>$null
                    $userSshPrivateKey = Get-UserSSHPrivateKey

                    # Make VM directory
                    $logVMDir = [io.Path]::Combine($logDirectoryName, $grpName)
                    New-Item -ItemType Directory -Force -Path $logVMDir | Out-Null

                    Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_grabbing_cloudinit_logs_from_vm, $vmName))
                    $osType = Invoke-MocCommand "compute vm show --name ""$vmName"" -o yaml --query virtualmachineproperties.osprofile.osType --group ""$grpName"""
                    if ($osType -like 'Windows')
                    {
                        $logDirFile = [io.Path]::Combine($logVMDir, "${vmName}_logs.zip")
                        $randomFolder=$(New-Guid)
                        (& ssh -oBatchMode=yes -i $userSshPrivateKey -o StrictHostKeyChecking=no  "Administrator@${ipAddress}" "mkdir C:\tmp\$randomFolder && pushd C:\tmp\$randomFolder && powershell.exe C:\Packages\collect-windows-logs.ps1") 2>&1>$null
                        if ($?)
                        {
                            (& scp -oBatchMode=yes -i $userSshPrivateKey -o StrictHostKeyChecking=no  "Administrator@${ipAddress}:/tmp/$randomFolder/*.zip" $logDirFile) 2>$null
                            (& ssh -oBatchMode=yes -i $userSshPrivateKey -o StrictHostKeyChecking=no  "Administrator@${ipAddress}" "powershell.exe Remove-Item -Recurse -Force C:\tmp\$randomFolder") 2>$null
                        }
                        else
                        {
                            Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_failed_to_get_logs_from_vm, $vmName))
                        }
                        # Windows VM log collection is over
                        continue
                    }

                    $logDirFile = [io.Path]::Combine($logVMDir, "${vmName}_cloudinit.log")
                    (& scp -i $userSshPrivateKey -o StrictHostKeyChecking=no "clouduser@${ipAddress}:/var/log/cloud-init-output.log"  $logDirFile) 2>$null
                    Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_grabbing_systemd_logs, $vmName))
                    $systemdLogFile = [io.Path]::Combine($logVMDir, "${vmName}_systemd.log")
                    (& ssh -oBatchMode=yes -i $userSshPrivateKey -o StrictHostKeyChecking=no  "clouduser@${ipAddress}" sudo journalctl --output=short-precise -n $global:defaultLogLineCount) > $systemdLogFile

                    # Check if LB VM Type
                    $vmType = Invoke-MocCommand "compute vm show --name ""$vmName"" -o tsv --query virtualmachineproperties.vmType --group ""$grpName"""
                    $cleanVmType = $vmType.Split('', [System.StringSplitOptions]::RemoveEmptyEntries) -join ''
                    if ($cleanVmType -ieq "LoadBalancer")
                    {
                        if ($ipAddress -ine "no virtual network interface resources")
                        {
                            Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_grabbing_haproxy_configuration, $vmName))
                            $lbLogDirFile = [io.Path]::Combine($logVMDir, "${vmName}_haproxy.cfg")
                            (& scp -i $userSshPrivateKey -o StrictHostKeyChecking=no  "clouduser@${ipAddress}:/etc/haproxy/haproxy.cfg"  $lbLogDirFile) 2>$null

                            Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_grabbing_keepalived_configuration, $vmName))
                            $lbLogDirFile = [io.Path]::Combine($logVMDir, "${vmName}_keepalived.conf")
                            (& scp -i $userSshPrivateKey -o StrictHostKeyChecking=no  "clouduser@${ipAddress}:/etc/keepalived/keepalived.conf"  $lbLogDirFile) 2>$null

                            Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_grabbing_haproxy_logs, $vmName))
                            $lbLogDirFile = [io.Path]::Combine($logVMDir, "${vmName}_haproxy.log")
                            (& ssh -oBatchMode=yes -i $userSshPrivateKey -o StrictHostKeyChecking=no  "clouduser@${ipAddress}" sudo journalctl -u haproxy -n $global:defaultLogLineCount) > $lbLogDirFile

                            Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_grabbing_keepalived_logs, $vmName))
                            $lbLogDirFile = [io.Path]::Combine($logVMDir, "${vmName}_keepalived.log")
                            (& ssh -oBatchMode=yes -i $userSshPrivateKey -o StrictHostKeyChecking=no  "clouduser@${ipAddress}" sudo journalctl -u keepalived -n $global:defaultLogLineCount) > $lbLogDirFile

                            Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_grabbing_lbagent_logs, $vmName))
                            $lbLogDirFile = [io.Path]::Combine($logVMDir, "${vmName}_lbagent.log")
                            (& ssh -oBatchMode=yes -i $userSshPrivateKey -o StrictHostKeyChecking=no  "clouduser@${ipAddress}" sudo journalctl -u lbagent -n $global:defaultLogLineCount) > $lbLogDirFile
                        }
                    }
                    else
                    {
                        Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_grabbing_docker_logs, $vmName))
                        $dockerLogFile = [io.Path]::Combine($logVMDir, "${vmName}_docker.log")
                        (& ssh -oBatchMode=yes -i $userSshPrivateKey -o StrictHostKeyChecking=no  "clouduser@${ipAddress}" sudo journalctl  --no-pager -u docker -n $global:defaultLogLineCount) > $dockerLogFile

                        Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_grabbing_kubelet_logs, $vmName))
                        $kubeletLogFile = [io.Path]::Combine($logVMDir, "${vmName}_kubelet.log")
                        (& ssh -oBatchMode=yes -i $userSshPrivateKey -o StrictHostKeyChecking=no  "clouduser@${ipAddress}" sudo journalctl  --no-pager -u kubelet -n $global:defaultLogLineCount) > $kubeletLogFile
                    }
                }
                catch [Exception]
                {
                    Write-Status -msg $($GenericLocMessage.generic_exception) -moduleName $moduleName
                    Write-SubStatus -msg $_.Exception.Message.ToString()  -moduleName $moduleName
                    Write-SubStatus -msg $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_could_not_get_cloudinit_logs, $vmName)) -moduleName $moduleName
                }
            }
        }
        catch [Exception]
        {
            Write-Status -msg $($GenericLocMessage.generic_exception) -moduleName $moduleName
            Write-SubStatus -msg $_.Exception.Message.ToString()  -moduleName $moduleName
        }
    }
}

function Wait-ForActiveNodes
{
    <#
    .DESCRIPTION
        Waits for all nodes in the cloud to be in Active state.
 
    .PARAMETER sleepDuration
        Duration to sleep for between attempts
 
    .PARAMETER timeout
        Duration until timeout of waiting
 
    .PARAMETER location
        Location for the nodes.
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    param (
        [int]$sleepDuration=20,
        [int]$timeout=1800, #seconds in 30min
        [string]$location = $global:defaultCloudLocation,
        [String]$activity = $MyInvocation.MyCommand.Name
    )

    trap [System.Exception]
    {
        throw $($MocLocMessage.moc_nodes_not_active, $_.Exception.Message)
    }

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $($MocLocMessage.moc_waiting_for_cloud_nodes_to_be_active)

    ## Start the timer
    $timer = [Diagnostics.Stopwatch]::StartNew()

    $nodeCount = 1
    if (Test-MultiNodeDeployment)
    {
        $nodeCount = (Get-ClusterNode -ErrorAction Ignore).Count
        Wait-ForConnectionFromNodes -timeout $timeout
    }

    while(($timer.Elapsed.TotalSeconds -lt $timeout))
    {
        $activeNodes = $null

        try {
            $activeNodes = Invoke-MocCommand "cloud node list -o json --query ""[?properties.statuses.RunningState=='Active']"" --location $location" | ConvertFrom-Json
        } catch {
            # When no nodes are Active, ConvertFrom-Json will throw an exception
            if (-not ($_.Exception.Message -like "*Invalid JSON primitive: No.*")) {
                Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_warning, $_.Exception.Message))
            }
        }

        if ($null -ne $activeNodes)
        {
            $numActive = ($activeNodes).Count
            if ($nodeCount -eq $numActive)
            {
                Write-SubStatus -moduleName $moduleName $($MocLocMessage.moc_all_nodes_active)
                return $true
            }
        }

        Start-Sleep $sleepDuration

    }

    return $false
}

#endregion

#region Wait for resource functions

function Test-MocApiServer
{
     <#
    .DESCRIPTION
        Waits for the cloudagent generic service VIP/FQDN to be functional (i.e. wait for DNS to propogate).
 
    .PARAMETER cloudFqdn
        Fqdn to attemp to resolve and connect
     
    .PARAMETER ports
        List of ports to test
 
    .PARAMETER nodeName
        Name of the node to run the test from
    #>


    param (
        [Parameter(Mandatory=$true)]
        [String] $cloudFqdn,
        [Parameter(Mandatory=$true)]
        [String[]] $ports,
        [Parameter(Mandatory=$true)]
        [string] $nodeName
    )

    Invoke-Command -ComputerName $nodeName -ErrorAction Stop -ScriptBlock {
        $cloudFqdn = $args[0]
        $ports = $args[1]
        $MocLocMessage = $args[2]
        Clear-DnsClientCache
        Resolve-DnsName $cloudFqdn | Out-Null

        foreach ($port in $ports)
        {
            if (-not (Test-NetConnection -ComputerName $cloudFqdn -Port $port -InformationLevel Quiet))
            {                
                throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_testnetconnection_failed_to_contact_ca, $cloudFqdn, $port))                
            }
        }
    } -ArgumentList @($cloudFqdn, $ports, $MocLocMessage)
}

function Wait-ForCloudAgentEndpoint
{
    <#
    .DESCRIPTION
        Waits for the cloudagent generic service VIP/FQDN to be functional (i.e. wait for DNS to propogate).
 
        On failure, error is thrown back with appropriate message conveying why this failed.
 
    .PARAMETER sleepDuration
        Duration to sleep for between attempts
     
    .PARAMETER timeout
        Duration until timeout of waiting
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    param (
        [int]$sleepDuration=20,
        [int]$timeout=3600, #seconds in a hour
        [String]$activity = $MyInvocation.MyCommand.Name
    )

    trap [System.Exception]
    {
        throw $($MocLocMessage.moc_cloudagent_unreachable, $_.Exception.Message)
    }

    Test-MocService -activity $activity

    Write-StatusWithProgress -activity $activity -status $($MocLocMessage.moc_waiting_for_cloudagent_api_endpoint) -moduleName $moduleName
    Write-SubStatus $($MocLocMessage.moc_dns_propogation_warning) -moduleName $moduleName

    $endpoint = Get-CloudFqdn
    $cloudPorts = ($global:config[$modulename]["cloudAgentPort"], $global:config[$modulename]["cloudAgentAuthorizerPort"])

    $lastException = $null
    ## Start the timer
    $timer = [Diagnostics.Stopwatch]::StartNew()
    while(($timer.Elapsed.TotalSeconds -lt $timeout))
    {
        Write-SubStatus $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_testing_cloudagent_endpoint, $endpoint)) -moduleName $moduleName
        $location = $null

        try {

            # Test using existing APIs
            # This could fail if DNS resolution fails
            #
            Test-MocApiServer -cloudFqdn $endpoint -ports $cloudPorts -nodeName (hostname)

            Invoke-MocLogin -loginYaml $($global:config[$moduleName]["mocLoginYAML"]) | Out-Null
        
            $location = Get-MocLocation
            if ($null -ne $location)
            {
                Write-SubStatus $($MocLocMessage.moc_cloudagent_vip_is_working) -moduleName $moduleName
                # MOC is completely functional
                return
            }
        } catch {
            $lastException = $_
            Write-Verbose -Message $_
        }
        Sleep $sleepDuration
    }

    throw $lastException
}

function Test-MocAgents
{
    param (
        [String]$activity = $MyInvocation.MyCommand.Name
    )
    <#
    .DESCRIPTION
        Test if cloud agent service is running.
        Test if node agent service is running.
 
        If Service exists and is running, would return
        If Service exists and is not running, would throw exception
        If Service doesnt exist, an exception woud be thrown
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    trap [System.Exception]
    {
        throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_agents_not_healthy, $_.Exception.Message))
    }

    Test-MocService -activity $activity

    if (Test-MultiNodeDeployment)
    {
        Get-ClusterNode -ErrorAction Stop | ForEach-Object {
            $nodeName = ${_}.Name
            $tmpService = Get-Service wssdagent -ComputerName $nodeName
            if ($tmpService.Status -ne "Running")
            {
                throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_nodeagent_service_not_running, $nodeName))
            }
            $status = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_nodeagent_service_running, $nodeName))
            Write-StatusWithProgress -activity $activity -status $status -moduleName $moduleName
        }
    }
    else
    {
        $tmpService = Get-Service wssdagent
        if ($tmpService.Status -ne "Running")
        {
            throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_nodeagent_service_not_running, $nodeName))
        }
        $status = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_nodeagent_service_running, $nodeName))
        Write-StatusWithProgress -activity $activity -status $status -moduleName $moduleName
    }
}

function Test-MocService
{
    param (
        [String]$activity = $MyInvocation.MyCommand.Name
    )
    <#
    .DESCRIPTION
        Test if cloud agent service is running.
 
        If Service exists and is running, would return $true
        If Service exists and is not running, would return $false
        If Service doesnt exist, an exception woud be thrown
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    if (Test-MultiNodeDeployment)
    {
        $status = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_testing_for_cloudagent_service, (Get-Cluster).Name))
        Write-StatusWithProgress -activity $activity -status $status -moduleName $moduleName
        $tmpGroup = Get-ClusterGroup -Name ($global:config[$modulename]["clusterRoleName"].ToString()) -ErrorAction Ignore
        if ($tmpGroup.State -ne "Online")
        {
            throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_cloudagent_service_not_running, (Get-Cluster).Name))
        }

        $status = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_cloudagent_service_running, (Get-Cluster).Name))
        Write-StatusWithProgress -activity $activity -status $status -moduleName $moduleName
    }
    else
    {
        $nodeName = (hostname)
        $status = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_testing_for_cloudagent_service, $nodeName))
        Write-StatusWithProgress -activity $activity -status $status -moduleName $moduleName
        $tmpService = Get-Service wssdcloudagent  -ErrorAction Ignore
        if ($tmpService.Status -ne "Running")
        {
            throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_cloudagent_service_not_running, $nodeName))
        }
        
        $status = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_cloudagent_service_running, $nodeName))
        Write-StatusWithProgress -activity $activity -status $status -moduleName $moduleName
    }
}

function Test-Moc
{
    param (
        [String]$activity = $MyInvocation.MyCommand.Name
    )
    <#
    .DESCRIPTION
        Test if MOC installation is healthy
        Test if MOC Service is healthy
        Test if MOC endpoints are helathy
        Test if MOC Dns resolutions are healthy from nodes
 
        Throws if any tests fails
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    Initialize-MocEnvironment -activity $activity

    Write-StatusWithProgress -activity $activity -status $($MocLocMessage.moc_test_health) -moduleName $moduleName

    # Test-MocInstallation
    $currentState = Get-ConfigurationValue -module $moduleName -type ([Type][InstallState]) -name "installState"
    if ($currentState -ne $nil)
    {
        switch ($currentState) {
            ([InstallState]::NotInstalled) {
                throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_not_installed))
            }
            ([InstallState]::InstallFailed) {
                throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_not_installed))
            }
            Default {
                $status = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_installed))
                Write-StatusWithProgress -activity $activity -status $status -moduleName $moduleName
            }
        }
    }

    Test-MocAgents
    Wait-ForCloudAgentEndpoint
    Test-CloudDnsFromNodes

    Write-StatusWithProgress -activity $activity -status $($MocLocMessage.moc_healthy) -moduleName $moduleName
}

function Wait-ForMocRole
{
    <#
    .DESCRIPTION
        Waits for MOC role to be available.
 
    .PARAMETER roleName
        Name of role to wait on
 
    .PARAMETER sleepDuration
        Duration to sleep for between attempts
 
    .PARAMETER timeout
        Duration until timeout of waiting
 
    .PARAMETER activity
        Activity name to use when updating progress
    #>


    param (
        [Parameter(Mandatory=$true)]
        [string]$roleName = $global:defaultCloudLocation,
        [int]$sleepDuration=5,
        [int]$timeout=120, #seconds
        [String]$activity = $MyInvocation.MyCommand.Name
    )

    Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_waiting_for_role, $roleName))

    ## Start the timer
    $timer = [Diagnostics.Stopwatch]::StartNew()

    while(($timer.Elapsed.TotalSeconds -lt $timeout))
    {
        try {
            Get-MocRole -name $roleName | Out-Null
        } catch {
            # When role is not found, exception is thrown
            if (-not ($_.Exception.Message -like "*Not Found*")) {
                Write-SubStatus -moduleName $moduleName $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_warning, $_.Exception.Message))
            }
            Start-Sleep $sleepDuration
            continue
        }

        return $true
    }

    return $false
}
#endregion

#region
function Test-MocConfiguration
{
    <#
    .SYNOPSIS
        Runs validation tests against Moc and host configuration to make sure host is ready to install Moc successfully.
    .DESCRIPTION
        Runs validation tests against Moc and host configuration to make sure host is ready to install Moc successfully.
    .PARAMETER Ignore
        Specifies which tests or category of tests to ignore during the validation test run. All others tests or category of tests will run.
    .PARAMETER Include
        Specifies which tests or category of tests to include during the validation test run. Only the tests or category of tests specified here will run.
    .PARAMETER List
        Causes the cmdlet to list the tests and test categories. This command will not run any test.
    .PARAMETER Skip
        Causes the cmdlet not to generate report file.
    #>

    [CmdletBinding(PositionalBinding=$False, SupportsShouldProcess, ConfirmImpact = 'Low')]
    param (
        [Parameter()]
        [String[]] $Ignore,

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

        [Parameter()]
        [Switch] $List,

        [Parameter()]
        [Switch] $Skip
    )

    $mocTests = Get-MocValidationTests

    if ($List) {
        $mocTests | Format-Table -Autosize -Property Category, TestName, Description
        return
    }

    $mocTestsToRun = @()

    if ($Include.length -gt 0) {
        $includedTests = $mocTests | Where-Object {$_.Category -in $Include -or $_.TestName -in $Include}
        $mocTestsToRun += $includedTests
    } else {
        $mocTestsToRun = $mocTests
    }
    
    if ($Ignore.length -gt 0) {
        $mocTestsToRun = $mocTestsToRun | Where-Object {$_.Category -notin $Ignore -and $_.TestName -notin $Ignore}
    }

    if (-not(PSasAdmin)){
        Write-Host ''
        Write-Host $($MocLocMessage.moc_validation_not_admin) -ForegroundColor Red
        Write-Host ''
        Write-Host $($MocLocMessage.moc_validation_not_admin_recommendation) -ForegroundColor Yellow
        Write-Host ''
        exit
    }

    $dateTime = get-date -Format "dd.MM.yyyy HH:mm:ss"
    Write-Host  $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_validation_start, $dateTime))

    $testResults = @()
    $overallResult = $true

    $totalTests = ($mocTestsToRun | measure).Count
    $current = 1
    foreach($mocTest in $mocTestsToRun) {
        $startTime = Get-Date
        Write-Host '===============================================================================' -ForegroundColor DarkYellow
        $outputHeader = "Test ({0} of {1}): `"{2}`". Category: {3}" -f $current,$totalTests,$mocTest.TestName,$mocTest.Category
        Write-Host ''
        Write-Host $outputHeader -ForegroundColor DarkYellow

        $testFunction = $mocTest.TestFunction
        try {
            $testReport = &"$testFunction"
        } catch {
            $testReport = [pscustomobject]@{
                'TestName' = $mocTest.TestName;
                'Category' = $mocTest.Category;
                'TestResult' = "Failed";
                'Details' = $_.Exception.Message
            }
        }
        $testResults += $testReport
        
        if ($testReport.TestResult -eq "Failed") {
            $overallResult = $false
            Write-Host "Test Failed" -ForegroundColor Red
            Write-Host "Details:"$testReport.Details -ForegroundColor Red
            Write-Host "Recommendation:"$testReport.Recommendation -ForegroundColor Red
        } else {
            Write-Host "Test Succeeded" -ForegroundColor Green
            Write-Host "Details:"$testReport.Details -ForegroundColor Green
            Write-Host "Recommendation:"$testReport.Recommendation -ForegroundColor Green
        }
        $current++
        $testExecutionTime = ((Get-Date) - $startTime).TotalMilliseconds
        Write-Host $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_validation_test_time, $testExecutionTime)) -ForegroundColor DarkYellow
        Write-Host ''
    }

    Write-Host $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_validation_end, $dateTime))

    if ($overallResult) {
        Write-Host ''
        Write-Host '====================================================='
        Write-Host ' '$mocLocMessage.moc_validation_test_success_summary    -ForegroundColor Green 
        Write-Host '====================================================='
        Write-Host ''
    } else {
        Write-Host ''
        Write-Host '====================================================='
        Write-Host ' '$mocLocMessage.moc_validation_test_failure_summary    -ForegroundColor Red 
        Write-Host '====================================================='
        Write-Host ''
    }
    if (-not($Skip)) {
        $testResults | ConvertTo-Html -Title $mocLocMessage.moc_validation_report_title | Out-File -FilePath moc_validation_report.html
        $reportFileName = "moc_validation_report.html"
        Write-Host $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_validation_report_check, $reportFileName)) -ForegroundColor Yellow 
    }

    return $testResults
}  

function Get-MocValidationTests()
{
    $mocTests = @()
    # Test category "Moc Host"
    $mocHostNetworkConnectivityTest = [ordered]@{
        'TestName' = "Validate MOC Host Internet Connetivity";
        'Category' = "MOC Host";
        'Description' = "Validates MOC Host Internet Connetivity";
        'TestFunction' = "Test-MocHostInternetConnectivity"
    }
    $mocTests  += New-Object -TypeName PsObject -Property $mocHostNetworkConnectivityTest

    $mocHostLimitTest = [ordered]@{
        'TestName' = "Validate MOC Host Limits";
        'Category' = "MOC Host";
        'Description' = "Validates MOC Host Limits";
        'TestFunction' = "Test-MocHostLimits"
    }
    $mocTests  += New-Object -TypeName PsObject -Property $mocHostLimitTest

    $mocHostRemotingTest = [ordered]@{
        'TestName' = "Validate MOC Host Remoting";
        'Category' = "MOC Host";
        'Description' = "Validates MOC Host Remoting";
        'TestFunction' = "Test-MocHostRemoting"
    }
    $mocTests  += New-Object -TypeName PsObject -Property $mocHostRemotingTest

    # Test category "Moc Configuration"
    $mocNetworkConfigurationTest = [ordered]@{
        'TestName' = "Validate MOC Network Configuraiton";
        'Category' = "MOC Configuration";
        'Description' = "Validates MOC network configuration";
        'TestFunction' = "Test-MocNetworkConfiguration"
    }
    $mocTests  += New-Object -TypeName PsObject -Property $mocNetworkConfigurationTest

    $mocSDNConfigurationTest = [ordered]@{
        'TestName' = "Validate MOC SDN Configuraiton";
        'Category' = "MOC Configuration";
        'Description' = "Validates MOC SDN configuration";
        'TestFunction' = "Test-MocSDNConfiguration"
    }
    $mocTests  += New-Object -TypeName PsObject -Property $mocSDNConfigurationTest

    $mocValidDirectoryTest = [ordered]@{
        'TestName' = "Validate MOC directories";
        'Category' = "MOC Configuration";
        'Description' = "Validates MOC directories";
        'TestFunction' = "Test-MocDirectories"
    }
    $mocTests  += New-Object -TypeName PsObject -Property $mocValidDirectoryTest

    # Test category "Moc Failover Cluster"
    $mocFailOverClusterHealthTest = [ordered]@{
        'TestName' = "Validate Failover Cluster Health";
        'Category' = "MOC Failover Cluster";
        'Description' = "Validates Failover Cluster Health";
        'TestFunction' = "Test-FailOverClusterHealthForMoc"
    }
    $mocTests  += New-Object -TypeName PsObject -Property $mocFailOverClusterHealthTest

    # Test category "Moc Failover Cluster"
    $mocFailOverClusteHCIRegistrationTest = [ordered]@{
        'TestName' = "Validate Failover Cluster HCI Registration";
        'Category' = "MOC Failover Cluster";
        'Description' = "Validates Failover Cluster HCI Registration";
        'TestFunction' = "Test-FailOverClusterHCIRegistrationForMoc"
    }
    $mocTests  += New-Object -TypeName PsObject -Property $mocFailOverClusteHCIRegistrationTest
    
    # Test category "Moc HyperV"
    $mocHyperVMCreationTest = [ordered]@{
        'TestName' = "Validate VM Creation in Hyper-V";
        'Category' = "MOC HyperV";
        'Description' = "Validates VM Creation in Hyper-V";
        'TestFunction' = "Test-VMCreationInHyperV"
    }
    $mocTests  += New-Object -TypeName PsObject -Property $mocHyperVMCreationTest

    $mocHyperVSwitchTest = [ordered]@{
        'TestName' = "Validate switch in Hyper-V";
        'Category' = "MOC HyperV";
        'Description' = "Validates switch in Hyper-V";
        'TestFunction' = "Test-SwitchInHyperV"
    }
    $mocTests  += New-Object -TypeName PsObject -Property $mocHyperVSwitchTest

    return $mocTests
}

Function PSasAdmin{
    $currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
    $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}

Function RunPScript([String] $PSScript){
    $GUID=[guid]::NewGuid().Guid
    $Job = Register-ScheduledJob -Name $GUID -ScheduledJobOption (New-ScheduledJobOption -RunElevated) -ScriptBlock ([ScriptBlock]::Create($PSScript)) -ArgumentList ($PSScript) -ErrorAction Stop
    $Task = Register-ScheduledTask -TaskName $GUID -Action (New-ScheduledTaskAction -Execute $Job.PSExecutionPath -Argument $Job.PSExecutionArgs) -Principal (New-ScheduledTaskPrincipal -UserID "NT AUTHORITY\SYSTEM" -LogonType ServiceAccount -RunLevel Highest) -ErrorAction Stop
    $Task | Start-ScheduledTask -AsJob -ErrorAction Stop | Wait-Job | Remove-Job -Force -Confirm:$False
    While (($Task | Get-ScheduledTaskInfo).LastTaskResult -eq 267009) {Start-Sleep -Milliseconds 150}
    $Job1 = Get-Job -Name $GUID -ErrorAction SilentlyContinue | Wait-Job
    $Job1 | Receive-Job -Wait -AutoRemoveJob 
    Unregister-ScheduledJob -Id $Job.Id -Force -Confirm:$False
    Unregister-ScheduledTask -TaskName $GUID -Confirm:$false
}

Function checkProxy{
    # Check Proxy settings
    Write-Host "Checking winHTTP proxy settings..." -ForegroundColor Yellow
    $ProxyServer="NoProxy"
    $winHTTP = netsh winhttp show proxy
    $Proxy = $winHTTP | Select-String server
    $ProxyServer=$Proxy.ToString().TrimStart("Proxy Server(s) : ")
    $Bypass = $winHTTP | Select-String Bypass
    if ($Bypass){
        $Bypass=$Bypass.ToString().TrimStart("Bypass List : ")
    } else {
        $Bypass=""
    }  

    if ($ProxyServer -eq "Direct access (no proxy server)."){
        $ProxyServer="NoProxy"
        Write-Host " Access Type : DIRECT"
    }

    if ( ($ProxyServer -ne "NoProxy") -and (-not($ProxyServer.StartsWith("http://")))){
        Write-Host " Access Type : PROXY"
        Write-Host "Proxy Server List :" $ProxyServer
        Write-Host "Proxy Bypass List :" $Bypass
        $ProxyServer = "http://" + $ProxyServer
    }

    #CheckwinInet proxy
    Write-Host ''
    Write-Host "Checking winInet proxy settings..." -ForegroundColor Yellow
    $winInet=RunPScript -PSScript "Get-ItemProperty -Path 'Registry::HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings'"
    if($winInet.ProxyEnable){
        Write-Host " Proxy Enabled : Yes"
    }
    else{
        Write-Host " Proxy Enabled : No"
    }

    $winInetProxy="Proxy Server List : "+$winInet.ProxyServer
    Write-Host $winInetProxy
    $winInetBypass="Proxy Bypass List : "+$winInet.ProxyOverride
    Write-Host $winInetBypass
    $winInetAutoConfigURL=" AutoConfigURL : "+$winInet.AutoConfigURL
    Write-Host $winInetAutoConfigURL

    return [pscustomobject]@{
        'ProxyServer' = $ProxyServer;
        'Bypass' = $Bypass;
    }
    return $ProxyServer
}

function Test-MocHostInternetConnectivity() {
    $ProxySettings = checkProxy
    $ProxyServer = $ProxySettings.ProxyServer
    $Bypass = $ProxySettings.Bypass

    $bing= $Bypass.Contains("*.bing.com")
    $google= $Bypass.Contains("*.google.com") 

    $ProxyTestFailed=$false
    $ErrorActionPreference= 'silentlycontinue'
    $TestFailed=$false

    # TODO: localized proxy related msg when proxy is updated
    Write-Host
    Write-Host $mocLocMessage.moc_validation_internet_testing -ForegroundColor Yellow
    if ($ProxyServer -ne "NoProxy"){
        Write-Host "Testing connection via winHTTP proxy..." -ForegroundColor Yellow
        if ($bing){
            $PSScript = "(Invoke-WebRequest -uri 'www.bing.com' -UseBasicParsing).StatusCode"
            $TestResult = RunPScript -PSScript $PSScript
        }else{
            $PSScript = "(Invoke-WebRequest -uri 'www.bing.com' -UseBasicParsing -Proxy $ProxyServer).StatusCode"
            $TestResult = RunPScript -PSScript $PSScript
        }
        if ($TestResult -eq 200){
            Write-Host $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $mocLocMessage.moc_validation_connection_result, "www.bing.com", $mocLocMessage.moc_validataion_succeeded)) -ForegroundColor Green
        }else{
            $ProxyTestFailed=$true
        }

        if ($google){
            $PSScript = "(Invoke-WebRequest -uri 'www.google.com' -UseBasicParsing).StatusCode"
            $TestResult = RunPScript -PSScript $PSScript
        }else{
            $PSScript = "(Invoke-WebRequest -uri 'www.google.com' -UseBasicParsing -Proxy $ProxyServer).StatusCode"
            $TestResult = RunPScript -PSScript $PSScript
        }
        if ($TestResult -eq 200){
            Write-Host $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $mocLocMessage.moc_validation_connection_result, "www.google.com", $mocLocMessage.moc_validataion_succeeded)) -ForegroundColor Green
        }else{
            $ProxyTestFailed=$true
        }
    }
    
    if (($ProxyServer -eq "NoProxy") -or ($ProxyTestFailed -eq $true)){
        if($ProxyTestFailed -eq $true){
            Write-host "Connection failed via winHTTP, trying winInet..."
        }else{
            Write-host "Testing connection via winInet..." -ForegroundColor Yellow
        }
        $PSScript = "(Invoke-WebRequest -uri 'www.bing.com' -UseBasicParsing).StatusCode"
        $TestResult = RunPScript -PSScript $PSScript
        if ($TestResult -eq 200){
            Write-Host  $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $mocLocMessage.moc_validation_connection_result, "www.bing.com", $mocLocMessage.moc_validataion_succeeded)) -ForegroundColor Green
        }else{
            $TestFailed=$true
            Write-Host $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $mocLocMessage.moc_validation_connection_result, "www.bing.com", $mocLocMessage.moc_validataion_failed)) -ForegroundColor Red
        }
        $PSScript = "(Invoke-WebRequest -uri 'www.google.com' -UseBasicParsing).StatusCode"
        $TestResult = RunPScript -PSScript $PSScript
        if ($TestResult -eq 200){
            Write-Host  $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $mocLocMessage.moc_validation_connection_result, "www.google.com", $mocLocMessage.moc_validataion_succeeded)) -ForegroundColor Green
        }else{
            $TestFailed=$true
            Write-Host $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $mocLocMessage.moc_validation_connection_result, "www.google.com", $mocLocMessage.moc_validataion_failed)) -ForegroundColor Red
        }
    }

    if ($TestFailed){
        Write-Host ''
        Write-Host ''
        Write-Host $mocLocMessage.moc_validation_internet_failure -ForegroundColor red
        Write-Host ''

        Write-Host $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $mocLocMessage.moc_validation_recommend_actions_header , $mocLocMessage.moc_validation_internet_recommend_actions)) -ForegroundColor Yellow
        Write-Host $recommendation


        return [pscustomobject]@{
            'TestName' = "Validate MOC host network connectivity";
            'Category' = "MOC Host";
            'TestResult' = "Failed";
            'Details' = $mocLocMessage.moc_validation_internet_failure;
            'Recommendation' = $recommendation
        }
        
    }else{
        Write-Host ''
        Write-Host $mocLocMessage.moc_validation_internet_success -ForegroundColor Green
        Write-Host ''

        return [pscustomobject]@{
            'TestName' = "Validate MOC host network connectivity";
            'Category' = "MOC Host";
            'TestResult' = "Success";
            'Details' = $mocLocMessage.moc_validation_internet_success;
            'Recommendation' = ''
        }
    }
}

function Test-MocHostLimits() {
    Write-Host ''
    $skipHostLimitChecks = (Get-MocConfig)["skipHostLimitChecks"]
    $workingDir = Get-ConfigurationValue -name "workingDir" -module $moduleName
    $result = [pscustomobject]@{
        'TestName' = "Validate MOC Host Limits";
        'Category' = "MOC Host";
        'TestResult' = "Success";
        'Details' = "";
        'Recommendation' = ''
    }

    if (-not $skipHostLimitChecks)
    {
        $tmpDir =  $workingDir
        # Skip SMB path
        if (!$tmpDir.StartsWith("\\"))
        {
            $multiNode = Test-MultiNodeDeployment
            
            try{
                if ($multiNode)
                {
                    Test-CloudLimits -path $tmpDir -minRequiredDisk 40 -minRequiredMemory 10 -minRequiredLp 4
                }
                else
                {
                    Test-CloudLimits -path $tmpDir -minRequiredDisk 50 -minRequiredMemory 20 -minRequiredLp 4
                }
            }
            catch {
                $result.TestResult = "Failed"
                $result.Details = $_.Exception.Message
                return $result
            }
        }
    }

    $result.Details = $mocLocMessage.moc_validation_host_limit_success
    return $result
}

function Test-MocHostRemoting() {
    Write-Host ''
    $skipRemotingChecks = (Get-MocConfig)["skipRemotingChecks"]
    $result = [pscustomobject]@{
        'TestName' = "Validate MOC Host Remoting";
        'Category' = "MOC Host";
        'TestResult' = "Success";
        'Details' = "";
        'Recommendation' = ''
    }

    if (-not $skipRemotingChecks)
    {
        try {
            Confirm-Remoting
        } 
        catch {
            $result.TestResult = "Failed"
            $result.Details = $_.Exception.Message
            return $result
        }
    }

    $result.Details = $mocLocMessage.moc_validation_host_remoting_success
    return $result
}

function Test-MocNetworkConfiguration() {
    Write-Host ''
    $result = [pscustomobject]@{
        'TestName' = "Validate MOC Network Configuraiton";
        'Category' = "MOC Configuration";
        'TestResult' = "Failed";
        'Details' = "";
        'Recommendation' = ''
    }

    $multiNode = Test-MultiNodeDeployment
    $vnet = Get-VNetConfiguration -module $moduleName
    $cloudServiceCidr = (Get-MocConfig)["cloudServiceCidr"]

    #Netowrk Validations :
    #1. whether vSwitch provided in the vnet configuration exists.
    #2. CloudCIDR parameter is provided in case of static networks.
    if ($multiNode) {
        Get-ClusterNode -ErrorAction Stop | ForEach-Object {
            if ($vnet) {
                try {
                    Test-HostNetworking -nodeName $_.Name -vswitchName $vnet.VswitchName
                }
                catch {
                    $result.Details = $_.Exception.Message
                    return $result
                }
            }
        }
        try {
            $clusterRoleName = (Get-MocConfig)["clusterRoleName"]
            Test-ClusterNetworkProperties -cloudServiceCidr $cloudServiceCidr -vnet $vnet -clusterRoleName $clusterRoleName
        }
        catch {
            $result.Details = $_.Exception.Message
            return $result
        }
    }
    else
    {
        if ($vnet) {
            try {
                Test-HostNetworking -nodeName ($env:computername) -vswitchName $vnet.VswitchName
            }
            catch {
                $result.Details = $_.Exception.Message
                return $result
            }
        }
        # There is no cloud service cidr for standalone
        # We use the host ip for cloud service ip
        if (-not [string]::IsNullOrWhiteSpace($cloudServiceCidr))
        {
            Write-Warning $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_ignore_csip , $cloudServiceCidr))   
        }
    }

    $result.TestResult = "Success"
    $result.Details = "MOC network has been verified successfully"
    return $result
}

function Test-MocSDNConfiguration() {
    Write-Host ''
    $isSDNConfigured = (Get-MocConfig)["useNetworkController"]
    $result = [pscustomobject]@{
        'TestName' = "Validate MOC SDN Configuraiton";
        'Category' = "MOC Configuration";
        'TestResult' = "Success";
        'Details' = "";
        'Recommendation' = "";
    }

    if ($isSDNConfigured)
    {
        $networkControllerFqdnOrIpAddress = (Get-MocConfig)["networkControllerFqdnOrIpAddress"]
        $networkControllerLbSubnetRef = (Get-MocConfig)["networkControllerLbSubnetRef"]
        $networkControllerLnetRef = (Get-MocConfig)["networkControllerLnetRef"]
        $networkControllerClientCertificateName = (Get-MocConfig)["networkControllerClientCertificateName"]
        $vipPoolStart = (Get-MocConfig)["vipPoolStart"]
        $vipPoolEnd = (Get-MocConfig)["vipPoolEnd"]
        $vipPool = New-VipPoolSettings -name "testPool" -vipPoolStart $vipPoolStart -vipPoolEnd $vipPoolEnd

        try {
            Confirm-NetworkControllerConfiguration -networkControllerFqdnOrIpAddress $networkControllerFqdnOrIpAddress `
            -networkControllerLbSubnetRef $networkControllerLbSubnetRef `
            -networkControllerLnetRef $networkControllerLnetRef `
            -networkControllerClientCertificateName $networkControllerClientCertificateName `
            -vipPool $vipPool

            $result.Details = $mocLocMessage.moc_validation_sdn_configuration_success
        }
        catch {
            $result.TestResult = "Failed"
            $result.Details = $_.Exception.Message
        }

    } else {
        $result.TestResult = "Skipped"
        $result.Details = "SDN is not enabled"
    }

    return $result
}

function Test-MocDirectories() {
    Write-Host ''

    $result = [pscustomobject]@{
        'TestName' = "Validate MOC directories";
        'Category' = "MOC Configuration";
        'TestResult' = "Success";
        'Details' = "";
        'Recommendation' = ""
    }

    $workingDir = Get-ConfigurationValue -name "workingDir" -module $moduleName
    if ([string]::IsNullOrWhiteSpace($workingDir))
    {
        $result.TestResult = "Failed"
        $result.Details = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_empty, "workingDir"))
        return $result
    }

    
    $cloudConfigLocation = Get-ConfigurationValue -name "cloudConfigLocation" -module $moduleName
    if ([string]::IsNullOrWhiteSpace($cloudConfigLocation))
    {
        $result.TestResult = "Failed"
        $result.Details = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_empty, "cloudConfigLocation"))
        return $result
    }

    # cloudConfigLocation cannot be a parent path for workingDir
    # for example, workingDir = c:\test\123 and cloudConfigLocation = c:\test is invalid
    # During install of Moc, the system is reset including cloudConfigLocation, that would wipe of workingDir, that
    # contains critical PS configuration
    if ($workingDir.StartsWith($cloudConfigLocation.TrimEnd('\\'), $true, [System.Globalization.CultureInfo]::InvariantCulture))
    {
        $result.TestResult = "Failed"
        $result.Details = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.generic_invalid_working_dir_cloudconfig, $workingDir, $cloudConfigLocation))
        return $result        
    }

    $multiNode = Test-MultiNodeDeployment
    if ($multiNode) {
        #Check if the working directory corresponds to CSV root.
        $volumeList = Get-ClusterSharedVolume | Select-Object SharedVolumeInfo
        foreach ($volume in $volumeList) {
            if (!$volume.SharedVolumeInfo.FriendlyVolumeName)
            {
                # If volume is not online, we will not find get the friendly volume name
                continue
            }
            $rootPath = $volume.SharedVolumeInfo.FriendlyVolumeName.ToLower()
            if (([System.IO.Path]::GetFullPath($workingDir)).ToLower() -eq $rootPath) {
                $result.TestResult = "Failed"
                $result.Details = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_invalid_workingDir_system_root))
                return $result   
            }
        }
        if ((Test-LocalFilePath -path $workingDir) )
        {
            $result.TestResult = "Failed" 
            $result.Details = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_dir_not_set, "-workingDir"))
            return $result  
        }
    
        if ((Test-LocalFilePath -path $cloudConfigLocation))
        {
            $result.TestResult = "Failed"
            $result.Details = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_dir_not_set, "-cloudConfigLocation"))
            return $result  
        }
    
        $imageDir = Get-ConfigurationValue -name "imageDir" -module $moduleName
        if ((Test-LocalFilePath -path $imageDir))
        {           
            $result.TestResult = "Failed"
            $result.Details = $($MocLocMessage.moc_no_moc_config)
            return $result  
        }
   } else {
        #Static check to ensure working directory is not System Drive or Root
        if (($workingDir -eq $env:SystemDrive) -or ($workingDir.TrimEnd('\\') -eq $env:SystemDrive) -or
            ($workingDir -eq $env:SystemRoot) -or ($workingDir.TrimEnd('\\') -eq $env:SystemRoot) -or
            ($workingDir -eq [System.IO.Path]::GetFullPath($env:SystemDrive))) {
            $result.TestResult = "Failed"
            $result.Details = $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_invalid_workingDir_system_root))
            return $result   
        }
    }
    
    $useStagingShare = Get-ConfigurationValue -name "useStagingShare" -type "Boolean" -module $moduleName
    $stagingShare = Get-ConfigurationValue -name "stagingShare" -module $moduleName
    if ($useStagingShare -and [string]::IsNullOrWhiteSpace($stagingShare))
    {
        $result.TestResult = "Failed"
        $result.Details =  $($MocLocMessage.generic_staging_share_unspecified)
        return $result
    }

    $result.Details = $mocLocMessage.moc_validation_directories_success_details
    return $result
}

function Test-FailOverClusterHealthForMoc() {
    Write-Host ''

    $result =[pscustomobject]@{
        'TestName' = "Validate Failover Cluster Health";
        'Category' = "MOC Failover Cluster";
        'TestResult' = "Success";
        'Details' = "";
        'Recommendation' = ''
    }

    $failoverCluster = Get-FailoverCluster
    if ($nil -ne $failoverCluster) {
        try {
            Test-ClusterHealth
            $result.Details = $mocLocMessage.moc_validation_failover_cluster_healthy
        }
        catch {
            $result.TestResult = "Failed"
            $result.Details = $_.Exception.Message
        }
    } else {
        $result.TestResult = "Skipped"
        $result.Details = $mocLocMessage.moc_validation_failover_cluster_skip
    }

    return $result
}

function Test-FailOverClusterHCIRegistrationForMoc() {
    Write-Host ''

    $result = [pscustomobject]@{
        'TestName' = "Validate Failover Cluster HCI Registration";
        'Category' = "MOC Failover Cluster";
        'TestResult' = "Success";
        'Details' = "";
        'Recommendation' = ''
    }

    $failoverCluster = Get-FailoverCluster
    if ($nil -ne $failoverCluster) {
        try {
            Test-HCIRegistration
            $result.Details = $mocLocMessage.moc_validation_hci_registration_success
        }
        catch {
            $result.TestResult = "Failed"
            $result.Details = $_.Exception.Message
        }

    } else {
        $result.TestResult = "Skipped"
        $result.Details = $mocLocMessage.moc_validation_failover_cluster_skip
    }

    return $result
}

function Test-VMCreationInHyperV() {
    Write-Host ''

    return [pscustomobject]@{
        'TestName' = "Validate VM Creation in Hyper-V";
        'Category' = "MOC HyperV";
        'TestResult' = "Not Implemented";
        'Details' = "";
        'Recommendation' = ''
    }
}

function Test-SwitchInHyperV() {
    Write-Host ''

    return [pscustomobject]@{
        'TestName' = "Validate switch in Hyper-V";
        'Category' = "MOC HyperV";
        'TestResult' = "Not Implemented";
        'Details' = "";
        'Recommendation' = ''
    }
}

function Test-ClusterNetworkProperties
{
        <#
    .DESCRIPTION
        This function validates cloud service CIDR.
        We assume that this function is called only for multinode setups:
        Validations specific to Multinode setups :
            1. It is mandatory to have CIDR if any cluster network does not have DHCP enabled.
            2. CIDR should be part of a cluster network.
            3. The prefix length should match that of the cluster network.
            4. Should be able to create and start Failover Cluster resource.
            5. Cloud service CIDR should not overlap with VIP pool, or k8snodepool
           IP addresses provided during VNET configuration.
         
        Common Scenarios:
                                      | DHCP Enabled | Static network
        CloudServiceCIDR provided | Warning | Accepted
        CloudServiceCIDR not provided | Accepted | Error
 
    .PARAMETER CloudServiceCIDR
        The static IP/network prefix to be assigned to the MOC CloudAgent service.
    .PARAMETER vnet
        A VirtualNetwork object created using the New-AksHciNetworkSetting cmdlet.
    .PARAMETER clusterRoleName
        Name of the cluster group (Example: ca-2f87825b-a4af-473f-8a33-8e3bdd5f9b61)
    #>


    param (
        [string] $cloudServiceCidr,
        [virtualNetwork] $vnet,
        [string] $clusterRoleName
    )

    #Checks against cloudServiceIP
    if (-not [string]::IsNullOrWhiteSpace($cloudServiceCidr))
    {
        $cloudServiceCidrArray = $cloudServiceCidr.Split("/")
        $cloudServiceIP = $cloudServiceCidrArray[0]
        if ($cloudServiceCidrArray.Length -gt 1)
        {
            $prefixLength = $cloudServiceCidrArray[1] 
        }

        #Check if Cloud Service IP is in valid format.
        if (-Not [System.Net.IPAddress]::TryParse($cloudServiceIP, [ref]$null))
        {
            throw [CustomException]::new($([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, `
            $MocLocMessage.moc_invalid_csip, $cloudServiceIP)), $true)
        }
        
        #Check if cloud service IP is already in use
        if (Test-NetConnection $cloudServiceIP -InformationLevel "Quiet"  -ErrorAction Ignore -WarningAction SilentlyContinue)
        {
            throw [CustomException]::new($([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, `
            $MocLocMessage.moc_csip_in_use, $cloudServiceIP)), $true)
        }
    }


    $dhcpEnabledCluster = $True

    $clusterNetworks = Get-ClusterNetwork -ErrorAction SilentlyContinue | Where-Object { ($_.Role -eq "ClusterAndClient" ) `
                    -and ($_.Ipv4Addresses.Count -gt 0 ) } | Select-Object -property Name, IPv4Addresses, IPV4PrefixLengths
    
    $clusterNetworkList = ""
    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
                $clusterNetworkList += "$($clusterNetwork.IPv4Addresses[$i])/$($clusterNetwork.Ipv4PrefixLengths[$i]) " 
                #Static Network + Cloud Service CIDR not provided -> Throw error
                if ([string]::IsNullOrWhiteSpace($cloudServiceCidr))
                {
                    throw [CustomException]::new($([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, `
                    $MocLocMessage.moc_ca_cidr_required, $clusterNetwork.IPv4Addresses[$i])), $true)
                }
            }
        }
    }

    #Cloud Service CIDR is provided -> cluster network and prefix check.
    if(-not [string]::IsNullOrWhiteSpace($cloudServiceCidr))
    {
        $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!
                    $prefixLength = $clusterNetwork.Ipv4PrefixLengths[$i]
                    break
                }
            }
        }

        #DHCP Network + Cloud Service CIDR provided -> Give warning and ignore
        if($dhcpEnabledCluster -eq $True) 
        {
            Write-Warning $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_dhcp_network , $cloudServiceCidr))
        } #Not DHCP cluster, and not found in network.
        elseif ($foundInClusterNetwork -ne $True) 
        {
            throw [CustomException]::new($([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, `
            $MocLocMessage.moc_csip_not_in_network, $cloudServiceCidr, $clusterNetworkList)), $true)
        } #Not DHCP cluster, found in network, but not same prefix length.
        elseif ($cloudServiceCidrArray.Length -gt 1)
        {
            if ($cloudServiceCidrArray[1] -ne $prefixLength)
            {
                throw [CustomException]::new($([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_csip_invalid_prefix_len, $cloudServiceCidr, $cloudServiceCidrArray[1], $clusIpv4, $prefixLength)), $true)
            }
        }
    }

    Test-FailoverClusterResourceCreate -cloudServiceIP $cloudServiceCidr -prefix $prefixLength `
    -dhcpEnabled $dhcpEnabledCluster -clusterGroupName $clusterRoleName

    #CloudService CIDR should not overlap with VIP pool or k8s node pool
    if (-not [string]::IsNullOrWhiteSpace($cloudServiceCidr) -and ($vnet))
    {
        if([AKSHCI.IPUtilities]::CheckIPInIPPool($vnet.VipPoolStart, $vnet.VipPoolEnd, $cloudServiceIP))
        {
            throw [CustomException]::new($([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_csip_in_pool, $cloudServiceIP, $vnet.VipPoolStart, $vnet.VipPoolEnd)), $true)
        }
        
        if(-not [string]::IsNullOrWhiteSpace($vnet.K8snodeIPPoolStart))
        {
            if([AKSHCI.IPUtilities]::CheckIPInIPPool($vnet.K8snodeIPPoolStart, $vnet.K8snodeIPPoolEnd, $cloudServiceIP)){
                throw [CustomException]::new($([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_csip_in_pool, $cloudServiceIP, $vnet.K8snodeIPPoolStart, $vnet.K8snodeIPPoolEnd)), $true)
            }
        }
    }
}

function Test-FailoverClusterResourceCreate
{
        <#
    .DESCRIPTION
        This checks if we are able to create and start Failover Cluster resource.
        Pre-requisites for calling this function :
        1. Multi node setup
        2. Cloud service CIDR is validated, and sent with prefix
        3. Cluster Network check has already been done to determine if DHCP is enabled
        on all interfaces.
 
    .PARAMETER CloudServiceCIDR
        The static IP/network prefix to be assigned to the MOC CloudAgent service.
    .PARAMETER prefix
        The prefix length of the Cloud
    .PARAMETER dhcpEnabled
        This parameter denotes if DHCP is enabled on all the interfaces of the cluster
        network
    .PARAMETER clusterGroupName
        Name of the cluster group (Example: ca-2f87825b-a4af-473f-8a33-8e3bdd5f9b61)
    #>


    param (
        [string] $cloudServiceCIDR,
        [string] $prefix,
        [bool] $dhcpEnabled,
        [string] $clusterGroupName
    )

    try {
        Add-ClusterGroup -Name $clusterGroupName -GroupType GenericService -ErrorAction Stop | Out-Null
        $dnsName = Add-ClusterResource -Name "$clusterGroupName" -ResourceType "Network Name" -Group $clusterGroupName -ErrorAction Stop
        $dnsName | Set-ClusterParameter -Multiple @{"Name"="$clusterGroupName";"DnsName"="$clusterGroupName"} -ErrorAction Stop
        
        # 1. Create and Start the resources in order - IP resource, Service and then start the Resource Group
        if ([string]::IsNullOrWhiteSpace($cloudServiceCIDR))
        {
            # 1.a DHCP Case
            $networkList = Get-ClusterNetwork -ErrorAction SilentlyContinue | Where-Object { $_.Role -eq "ClusterAndClient" }
            foreach ($network in $networkList)
            {
                $IPResourceName = "IPv4 Address on $($network.Address)"
                $IPAddress = Add-ClusterResource -Name $IPResourceName -ResourceType "IP Address" -Group $clusterGroupName -ErrorAction Stop
                $IPAddress | Set-ClusterParameter -Multiple @{"Network"=$network;"EnableDhcp"=1} -ErrorAction Stop
    
                Add-ClusterResourceDependency -Resource "$clusterGroupName" -Provider $IPResourceName -ErrorAction Stop | Out-Null
    
                try 
                {
                    Start-FailoverClusterResource -resourceName $IPResourceName | Out-Null
                }
                catch 
                {
                    $errorMessage = Write-ModuleEventException -message "Start-FailoverClusterResource failed on resource $IPResourceName" -exception $_ -moduleName $global:MocModule
                    Remove-ClusterGroup -Name $clusterGroupName -RemoveResources -Force -ErrorAction Ignore
                    throw $([System.Exception]::new([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_failover_cluster_networks_error, $errorMessage), $_.Exception))
                }
            }
        }
        else
        {
            # We ignore Cloud service CIDR - no need to add the IP param
            if($dhcpEnabled -eq $False)
            {
                $cloudServiceCidrArray = $cloudServiceCIDR.Split("/")
                $subnetMask = Get-Ipv4MaskFromPrefix -PrefixLength $prefixLength

                $IPResourceName = "IPv4 Address $cloudServiceCidrArray[0]"
                $IPAddress = Add-ClusterResource -Name $IPResourceName -ResourceType "IP Address" -Group $clusterGroupName -ErrorAction Stop
                $IPAddress | Set-ClusterParameter -Multiple @{"Address"=$cloudServiceCidrArray[0];"SubnetMask"=$subnetMask;"EnableDhcp"=0} -ErrorAction Stop
                Add-ClusterResourceDependency -Resource "$clusterGroupName" -Provider $IPResourceName -ErrorAction Stop | Out-Null

                try 
                {
                    Start-FailoverClusterResource -resourceName $IPResourceName | Out-Null
                }
                catch 
                {
                    $errorMessage = Write-ModuleEventException -message "Start-FailoverClusterResource failed." -exception $_ -moduleName $global:MocModule
                    Remove-ClusterGroup -Name $clusterGroupName -RemoveResources -Force -ErrorAction Ignore            
                    throw $([System.Exception]::new([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $GenericLocMessage.comm_invalid_ip_address, $($cloudServiceCidrArray[0]), $errorMessage), $_.Exception))
                }
            }
        }
        #Start the failover cluster group
        try {
            Start-FailoverClusterGroup -clusterGroupName $clusterGroupName | Out-Null
        } catch [Exception] {
            Remove-ClusterGroup -Name $clusterGroupName -RemoveResources -Force -ErrorAction Ignore
            throw $([System.String]::Format([System.Globalization.CultureInfo]::InvariantCulture, $MocLocMessage.moc_no_cluster_perm, $clusterGroupName, $($_.Exception.Message.ToString())))
        }
    }
    finally {
        <#Do this after the try block regardless of whether an exception occurred or not#>
        Remove-ClusterGroup -Name $clusterGroupName -RemoveResources -Force -ErrorAction Ignore
    }
}

#endregion

# SIG # Begin signature block
# MIInywYJKoZIhvcNAQcCoIInvDCCJ7gCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAJbqxJuGhqZMlk
# Ngfc3FyUhDGgXZUH5CYqP/I7P+RqraCCDYEwggX/MIID56ADAgECAhMzAAACzI61
# lqa90clOAAAAAALMMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjIwNTEyMjA0NjAxWhcNMjMwNTExMjA0NjAxWjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQCiTbHs68bADvNud97NzcdP0zh0mRr4VpDv68KobjQFybVAuVgiINf9aG2zQtWK
# No6+2X2Ix65KGcBXuZyEi0oBUAAGnIe5O5q/Y0Ij0WwDyMWaVad2Te4r1Eic3HWH
# UfiiNjF0ETHKg3qa7DCyUqwsR9q5SaXuHlYCwM+m59Nl3jKnYnKLLfzhl13wImV9
# DF8N76ANkRyK6BYoc9I6hHF2MCTQYWbQ4fXgzKhgzj4zeabWgfu+ZJCiFLkogvc0
# RVb0x3DtyxMbl/3e45Eu+sn/x6EVwbJZVvtQYcmdGF1yAYht+JnNmWwAxL8MgHMz
# xEcoY1Q1JtstiY3+u3ulGMvhAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUiLhHjTKWzIqVIp+sM2rOHH11rfQw
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDcwNTI5MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAeA8D
# sOAHS53MTIHYu8bbXrO6yQtRD6JfyMWeXaLu3Nc8PDnFc1efYq/F3MGx/aiwNbcs
# J2MU7BKNWTP5JQVBA2GNIeR3mScXqnOsv1XqXPvZeISDVWLaBQzceItdIwgo6B13
# vxlkkSYMvB0Dr3Yw7/W9U4Wk5K/RDOnIGvmKqKi3AwyxlV1mpefy729FKaWT7edB
# d3I4+hldMY8sdfDPjWRtJzjMjXZs41OUOwtHccPazjjC7KndzvZHx/0VWL8n0NT/
# 404vftnXKifMZkS4p2sB3oK+6kCcsyWsgS/3eYGw1Fe4MOnin1RhgrW1rHPODJTG
# AUOmW4wc3Q6KKr2zve7sMDZe9tfylonPwhk971rX8qGw6LkrGFv31IJeJSe/aUbG
# dUDPkbrABbVvPElgoj5eP3REqx5jdfkQw7tOdWkhn0jDUh2uQen9Atj3RkJyHuR0
# GUsJVMWFJdkIO/gFwzoOGlHNsmxvpANV86/1qgb1oZXdrURpzJp53MsDaBY/pxOc
# J0Cvg6uWs3kQWgKk5aBzvsX95BzdItHTpVMtVPW4q41XEvbFmUP1n6oL5rdNdrTM
# j/HXMRk1KCksax1Vxo3qv+13cCsZAaQNaIAvt5LvkshZkDZIP//0Hnq7NnWeYR3z
# 4oFiw9N2n3bb9baQWuWPswG0Dq9YT9kb+Cs4qIIwggd6MIIFYqADAgECAgphDpDS
# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla
# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT
# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG
# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S
# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz
# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7
# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u
# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33
# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl
# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP
# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB
# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF
# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM
# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ
# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud
# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO
# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0
# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p
# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw
# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA
# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY
# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj
# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd
# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ
# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf
# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ
# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j
# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B
# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96
# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7
# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I
# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIZoDCCGZwCAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAsyOtZamvdHJTgAAAAACzDAN
# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgdbPwuak2
# gUiVY0DaSvljesIzVPkINfY3xAa6shSy1icwQgYKKwYBBAGCNwIBDDE0MDKgFIAS
# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN
# BgkqhkiG9w0BAQEFAASCAQBAsMyNolhnqEERwkdZynY4WGogS0a+GPsRnH5OmiRZ
# UlP8yThgsR8JK+FKIk87bJvcuFi8sdHjyzMDXDSOYHh+R2fkpR2TiBCkyjdqEgp3
# qvpRIVpjclVflOcZeprvAQSBpluHVBDy80ax2hHPo1dSseC/GOtgFYVw49XCnbIV
# jUiSYsyr39ctQpHFlrOFnOa3pTdpnkxkKuVY0niDX3HDqaneOUxg3TCaG0HZlvss
# j7RlOBB5xanghWlIn06WFgeMzwb+QgMs+YT54antLPcosr72WPG+YV12Am0gT3S4
# 1u/V54H3Ogtu17kVsyoRQVsmJD5hpDq6LBeQFlPazxTdoYIXKjCCFyYGCisGAQQB
# gjcDAwExghcWMIIXEgYJKoZIhvcNAQcCoIIXAzCCFv8CAQMxDzANBglghkgBZQME
# AgEFADCCAVkGCyqGSIb3DQEJEAEEoIIBSASCAUQwggFAAgEBBgorBgEEAYRZCgMB
# MDEwDQYJYIZIAWUDBAIBBQAEIOnbzxadXZupVeAMnxCUyZJAoY1Wl+XjtJyRDuE+
# DVVDAgZjYtczWgMYEzIwMjIxMTE0MjAyMzM0LjQ0MlowBIACAfSggdikgdUwgdIx
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1p
# Y3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEmMCQGA1UECxMdVGhh
# bGVzIFRTUyBFU046MTc5RS00QkIwLTgyNDYxJTAjBgNVBAMTHE1pY3Jvc29mdCBU
# aW1lLVN0YW1wIFNlcnZpY2WgghF5MIIHJzCCBQ+gAwIBAgITMwAAAbWtGt/XhXBt
# EwABAAABtTANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0Eg
# MjAxMDAeFw0yMjA5MjAyMDIyMTFaFw0yMzEyMTQyMDIyMTFaMIHSMQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQg
# SXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg
# RVNOOjE3OUUtNEJCMC04MjQ2MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt
# cCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlwsKuGVe
# gsKNiYXFwU+CSHnt2a7PfWw2yPwiW+YRlEJsH3ibFIiPfk/yblMp8JGantu+7Di/
# +3e5wWN/nbJUIMUjEWJnc8JMjoPmHCWsMtJOuR/1Ru4aa1RrxQtIelq098TBl4k7
# NsEE87l7qKFmy8iwGNQjkwr0bMu4BJwy7BUXiXHegOSU992rfQ4xNZoxznv42TLQ
# sc9NmcBq5WslkqVATcc8PSfgBLEpdG1Dp2wqNw4JrJFwJNA1bfzTScYABc5smRZB
# gsP4JiK/8CVrlocheEyQonjm3rFttrojAreSUnixALu9pDrsBI4DUPGG34oIbieI
# 1oqFl/xk7A+7uM8k4o8ifMVWNTaczbPldDYtn6hBre7r25RED4uecCxP8Dxy34YP
# UElWllPP3LAXp5cMwRjx+EWzjEtILEKXuAcfxrXCTwyYhm5XNzCCZYh4/gF2U2y/
# bYfekKpaoFYwkoZeT6ZxoQbX5Kftgj+tZkFV21UvZIkJ6b34a/44dtrsK6diTmVn
# NTM9J6P6Ehlk2sfcUwbHIGL8mYqdKOiyd4RxOCmSvcFNkZEgrk548mHCbDbTyO9x
# SzN1EkWxbp8n/LHVnZ9fp5hILGntkMzaD5aXRCQyHSIhsPtR7Q/rKoHyjFqgtGO9
# ftnxYvxzNrbKeMCzwmcqwMrX6Hcxe0SeKZ8CAwEAAaOCAUkwggFFMB0GA1UdDgQW
# BBRsUIbZgoZVXVXVWQX0Ok1VO2bHUzAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJl
# pxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAx
# MCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3Rh
# bXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQM
# MAoGCCsGAQUFBwMIMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEA
# kFGOpyjKV2s2sA+wTqDwDdhp0mFrPtiU4rN3OonTWqb85M6WH19c/P517xujLCih
# /HllP5xKWmXnAIRV1/NQDkJBLSdLTb/NQtcT1FWGQ7CMTnrn9tLZxqIFtKVylvQN
# yh31C/qkC8QmNpyzakO0G38uOGgOkJ9Eq4nA+7QwVfobDlggWuEpzdFnRdyXL32g
# OqSvrLjFKpv4KEVqaBTiaxCWZDlIhG3YgUza7cnG5Z2SA/feMq/IiV06AzUadZw6
# XgcTrqXmEmE0tMmdl44MMFC3wGU9AVeFCWKdD9WOnYA2zHg+XF2LQVto0VYtFLd6
# c6DQFcmB38GvPCKVYSn8r10EoXuRN+gQ7hLcim12esOnW4F4bHCmHWTVWeAGgPiS
# ItHHRfGKLEUZmotVOdFPR8wiuADT/fHSXBkkdpL12tvgEGELeTznzFulZ16b/Nv6
# dtbgSRZreesJBNKpTjdYju/GqnlAkpflL6J0wxk957/UVYnmjjRY61jX90QGQmBz
# m9vs/+2bj02Xx/bXXy8vq57jmNXQ2ufOaJm3nAcD2qOaSyXEOj9mqhMt4tdvMjHh
# iNPldfj0Q7Kq1HgdRBrKWkzCQNi4ts8HRJBipNaVpWfU7BcRn8BeYzdLoIzwRLDt
# atz6aBho3oD/bXHrZagxprM5MsMB/rVfb5Xn1YS7/uEwggdxMIIFWaADAgECAhMz
# AAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9v
# dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0yMTA5MzAxODIyMjVaFw0z
# MDA5MzAxODMyMjVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIICIjAN
# BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5OGmTOe0ciELeaLL1yR5vQ7VgtP9
# 7pwHB9KpbE51yMo1V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64NmeFRiMM
# tY0Tz3cywBAY6GB9alKDRLemjkZrBxTzxXb1hlDcwUTIcVxRMTegCjhuje3XD9gm
# U3w5YQJ6xKr9cmmvHaus9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl3GoPz130
# /o5Tz9bshVZN7928jaTjkY+yOSxRnOlwaQ3KNi1wjjHINSi947SHJMPgyY9+tVSP
# 3PoFVZhtaDuaRr3tpK56KTesy+uDRedGbsoy1cCGMFxPLOJiss254o2I5JasAUq7
# vnGpF1tnYN74kpEeHT39IM9zfUGaRnXNxF803RKJ1v2lIH1+/NmeRd+2ci/bfV+A
# utuqfjbsNkz2K26oElHovwUDo9Fzpk03dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz
# 1dhzPUNOwTM5TI4CvEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLiMxhy16cg8ML6
# EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb0f2y1BzFa/Zc
# UlFdEtsluq9QBXpsxREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6HXtqPnhZy
# acaue7e3PmriLq0CAwEAAaOCAd0wggHZMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJ
# KwYBBAGCNxUCBBYEFCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQWBBSfpxVd
# AF5iXYP05dJlpxtTNRnpcjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdMg30BATBBMD8G
# CCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3Mv
# UmVwb3NpdG9yeS5odG0wEwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYBBAGCNxQC
# BAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYD
# VR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZF
# aHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9v
# Q2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcw
# AoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJB
# dXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1VffwqreEsH2cB
# MSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1OW27DzHkwo/7
# bNGhlBgi7ulmZzpTTd2YurYeeNg2LpypglYAA7AFvonoaeC6Ce5732pvvinLbtg/
# SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OOPcbzaN9l9qRWqveVtihVJ9AkvUCgvxm2
# EhIRXT0n4ECWOKz3+SmJw7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2Fz
# Lixre24/LAl4FOmRsqlb30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW9I/2kQH2zsZ0
# /fZMcm8Qq3UwxTSwethQ/gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9
# swFXSVRk2XPXfx5bRAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pFEUep8beuyOiJ
# Xk+d0tBMdrVXVAmxaQFEfnyhYWxz/gq77EFmPWn9y8FBSX5+k77L+DvktxW/tM4+
# pTFRhLy/AsGConsXHRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW
# 4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N
# 7oJtpQUQwXEGahC0HVUzWLOhcGbyoYIC1TCCAj4CAQEwggEAoYHYpIHVMIHSMQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNy
# b3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxl
# cyBUU1MgRVNOOjE3OUUtNEJCMC04MjQ2MSUwIwYDVQQDExxNaWNyb3NvZnQgVGlt
# ZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQCNMJ9r11RZj0PWu3uk+aQH
# F3IsVaCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqG
# SIb3DQEBBQUAAgUA5xx+UjAiGA8yMDIyMTExNDE2NDQwMloYDzIwMjIxMTE1MTY0
# NDAyWjB1MDsGCisGAQQBhFkKBAExLTArMAoCBQDnHH5SAgEAMAcCAQACAnjsMAgC
# AQACAwEMyjAKAgUA5x3P0gIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZ
# CgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBABOe
# FK4rspTo2HQfsCn7o7+xaLyqmnJQdnWzcqHhnFqY/RmqgFNywuyh+6//5A9yYi4q
# zg3kkbABSy19fW3D0IFSHnuFHGfOkNCpqsuUItXEQTr3Wwt5Wc4+t3q496mWliRF
# NU26EbPMtBZ5xe1gvZ4gcVd+b4R31SrHsqlJ/GEYMYIEDTCCBAkCAQEwgZMwfDEL
# MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v
# bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWlj
# cm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAG1rRrf14VwbRMAAQAAAbUw
# DQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAv
# BgkqhkiG9w0BCQQxIgQgjIFgjoOfwR9VmQsNngyFNwUwac0cHmZf0xc417Y+xK8w
# gfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCAnyg01LWhnFon2HNzlZyKae2JJ
# 9EvCXJVc65QIBfHIgzCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
# YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg
# Q29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAy
# MDEwAhMzAAABta0a39eFcG0TAAEAAAG1MCIEIBtykH/hML5Tdg6s2QNz418ZsGSi
# 4iwozlnk27LAkTHXMA0GCSqGSIb3DQEBCwUABIICAHTcMShNbe/srt0W/fl34JsQ
# fvfGWRQsgWnGXVWWwfYEIputJYYuxpu9LWEgZVMmnjghC5mG7keiKIJa6Y3nuASe
# KVFXOpaYw88MS1t0wNn5vhDLbXaYD8iJnHwiFmmuSFJMMgfTfivKT36EyxSuoV1/
# jODPQ06RROJGEtM2LXIm6Tdz3tpBUZzpHd4OeOJVP9NKOSj/g+LmEKaYBT5NMaBU
# cjUKomhaugtcU6gTHNe8ic+cU7UnVYovp+SWiduVQQVdAvyDpZBOSoEflfvWkKHl
# hQ/YN9U1KEvvWo/iS0uDK9xmTlw7+E3M5JHkMevuCh1r/gqSWvYqRBHCXIznxWoU
# 4a+ZDJ9MoJUBEWLytkl0sKC56axEKbceXyrNXRz4ea2rnp18DqIi7Stm1jk9aqHP
# NyYJo0jkQI1SyE8lVqoXBkd8WPTmGrr/GbLoW7/zN7B8Q14PatPeFG+FXo1yoBAY
# xx0vaD2OI891ZhU/u2wpcM129qDmgBoEQwiJHmihduoYVKE/EI8aqmKM1Cx7jzuA
# 4mthI03j8Y2ZUrnLinsHl5GVYDL0hGaIjIeoeK2yIS2+Lrpn3MlKA4uPjWYAuSZ1
# 8jRmvU5+QQs26oyYNm4ZQMZ3ITdOcd7NubLunFYs09KFVwPe8/+YkikIl17qDItE
# MchiDXLIySaB0Q9qFp1W
# SIG # End signature block