TurnKeySdnInternal.psm1

Import-Module $PSScriptRoot\ConfigManager.psm1
Import-Module $PSScriptRoot\ClusUtils.psm1
Import-Module $PSScriptRoot\Logger.psm1
Import-Module $PSScriptRoot\PsHelper.psm1
Import-Module $PSScriptRoot\Utils.psm1
Import-Module $PSScriptRoot\WinBuildUtils.psm1
Import-Module $PSScriptRoot\TrafficUtil.psm1

function Get-NewDepId {

    return @($id, $idbytes)
}

function Initialize-Credentials {
    try {
        Get-TurnKeySdnCred | Out-Null
        Write-TraceLog "Initialize-Credentials : Existing credential found"
        return
    }
    catch {
        Write-TraceLog "Initialize-Credentials : Credential not found, creating new credential"
    }

    $cred = $null
    $infraconfig = Get-DeploymentConfig
    $domain = $infraconfig.domainName

    if (-not [String]::IsNullOrEmpty($env:TEST_USERNAME) -and -not [String]::IsNullOrEmpty($env:TEST_PASSWORD)) {

        Write-TraceLog "Initialize-Credentials: Credential discovered from ADO environment variables"

        if (-not [String]::IsNullOrEmpty($env:TEST_DOMAIN)) {
            $username = $env:TEST_DOMAIN + "\" + $env:TEST_USERNAME    
        }
        else {
            $username = $env:TEST_USERNAME
        }
        $cred = @{}
        $cred["username"] = $username
        $cred["password"] = $env:TEST_PASSWORD | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString
    }
    elseif ($domain.ToUpper() -eq "CFDEV.NTTEST.MICROSOFT.COM" -and $env:USERNAME -eq "wolfpack") {        
        Write-TraceLog "Initialize-Credentials: Credential defaulted to cfdev"
        $cred = @{}
        $cred["username"] = "$domain\$env:USERNAME"
        $cred["password"] = $env:USERNAME | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString
    }

    if ($cred -ne $null) {
        Set-TurnKeySdnInternalConfig -configType credential -config $cred
    }
    else {
        Write-TraceLog "Initialize-Credentials: Failed to initialize credentials" -Warning
    }
}

function Initialize-WorkloadVMCredentials {

    # initialize workload vm credentials

    $cred = @{}

    if(-not [string]::IsNullOREmpty("$Env:TURNKEY_WORKLOAD_USERNAME") -and -not [string]::IsNullOREmpty("$Env:TURNKEY_WORKLOAD_PASSWORD")) {
        $cred["username"] = $Env:TURNKEY_WORKLOAD_USERNAME
        $cred["password"] = $Env:TURNKEY_WORKLOAD_PASSWORD | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString
    } else {
        $userName = "administrator"
        $cred["username"] = $userName
        $cred["password"] =$userName | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString
    }

    Set-TurnKeySdnInternalConfig -configType workloadcredential -config $cred
}


function Initialize-DomainConfiguration {

    $infraconfig = Get-DeploymentConfig
    $domain = $infraconfig.domainName

    if ([String]::IsNullOrEmpty($domain) -and ((Get-WmiObject -Class Win32_ComputerSystem).PartOfdomain)) {
        $domain = (Get-WmiObject -Class Win32_ComputerSystem).Domain
        $infraconfig.domainName = $domain
        Write-TraceLog "Domain is set to $domain"
    }
    else {
        Write-TraceLog "Domain is not set, using local machine domain"
    }

    Set-DeploymentConfig -deploymentConfig $infraconfig
}

function Initialize-HyperVHostConfig {
    $infraconfig = Get-DeploymentConfig

    try {

        if ($infraconfig.hyperVHosts -ne $null -and $infraconfig.hyperVHosts.Count -gt 0) {
            return
        }

        $infraconfig.hyperVHosts = @()

        if (Test-IsCluster) {
            (get-cluster | get-clusternode).Name | ForEach-Object { $infraconfig.hyperVHosts += $_ }
        }
        else {
            $infraconfig.hyperVHosts += (hostname)
        }

        Set-DeploymentConfig -deploymentConfig $infraconfig
    }
    finally {
        $hosts = $infraconfig.hyperVHosts -join ","
        Write-TraceLog "Initialize-HyperVHostConfig: Hosts are $hosts"
    }
}

function Initialize-VSwitchConfig {

    $infraconfig = Get-DeploymentConfig
    if ([String]::IsNullOrEmpty($infraconfig.internetSwitchName)) {
        $infraconfig.internetSwitchName = Resolve-InternetSwitch
    }

    if ([String]::IsNullOrEmpty($infraconfig.internetSwitchName)) {

        $iSwitchCreated = $true
        $infraconfig.hyperVHosts | ForEach-Object {
            Write-TraceLog "Initialize-VSwitchConfig: Attempting to auto create internet switch on host ($_)"
            $iSwitchCreated = $iSwitchCreated -band (New-InternetSwitch -computer $_ -name "corp")
        }

        if ($iSwitchCreated) {
            $infraconfig.internetSwitchName = "corp"
        }
    }

    if ([String]::IsNullOrEmpty($infraconfig.sdnSwitchName)) {
        $infraconfig.sdnSwitchName = Resolve-SdnSwitch
    }

    if ([String]::IsNullOrEmpty($infraconfig.sdnSwitchName)) {
        $sdnSwitchCreated = $true
        $infraconfig.hyperVHosts | ForEach-Object {
            Write-TraceLog "Initialize-VSwitchConfig: Attempting to auto create sdn switch on host ($_)"
            $sdnSwitchCreated = $sdnSwitchCreated -band (New-SdnSwitch -computer $_ -name "sdnswitch")
        }

        if ($sdnSwitchCreated) {
            $infraconfig.sdnSwitchName = "sdnswitch"
        }
    }

    if ([String]::IsNullOrEmpty($infraconfig.sdnSwitchName)) {
        $infraconfig.sdnSwitchName = $infraconfig.internetSwitchName
        Write-TraceLog "Initialize-VSwitchConfig: Using internetswitch for sdn"
    }

    Write-TraceLog "Initialize-VSwitchConfig: Internet Switch is $($infraconfig.internetSwitchName)"
    Write-TraceLog "Initialize-VSwitchConfig: Sdn Switch is $($infraconfig.sdnSwitchName)"
    Set-DeploymentConfig -deploymentConfig $infraconfig
}

function Initialize-DeploymentVhd {

    $depConfig = Get-DeploymentConfig

    if (-not [String]::IsNullOrEmpty($depConfig.vhdPath) -and `
            -not [String]::IsNullOrEmpty($depConfig.vhdFile)) {
        $vhd = Join-Path $depConfig.vhdPath $depConfig.vhdFile
        Write-TraceLog "Initialize-DeploymentVhd: Deployment VHD is $vhd"
        return
    }

    Write-TraceLog "Initialize-DeploymentVhd: Trying to auto discover deployment vhd"

    if (-not [String]::IsNullOrEmpty($depConfig.windowsBuild)) {
        Write-TraceLog "Initialize-DeploymentVhd: Using specified build $($depConfig.windowsBuild)"
    }

    $bestBuild = Get-BestBuild -SpecificBuild $depConfig.windowsBuild -WindowsBuildBranch $depConfig.windowsBuildBranch

    if ([String]::IsNullOrEmpty($bestBuild)) {
        throw "Initialize-DeploymentVhd: Failed to auto detect a build to use for deployment, please set vhd location manually using Set-TurnKeySdnDeploymentVhd"
    }

    $buildVhd = Get-BuildVersionVhd -buildLocation $bestBuild

    if ([String]::IsNullOrEmpty($buildVhd)) {
        throw "Initialize-DeploymentVhd: Vhd auto discover failed, please set vhd location manually using Set-TurnKeySdnDeploymentVhd"
    }

    $vhdPath = Join-Path (Get-VhdStore) "deploymentvhd"
    
    if (-not (Test-Path $vhdPath)) {
        New-Item $vhdPath -ItemType Directory -ErrorAction Stop | Out-Null
    }


    $fileName = Split-Path $buildVhd -leaf
    $dest = Join-Path $vhdPath $fileName

    if (Test-Path $dest) {
        Write-TraceLog "Initialize-DeploymentVhd: Vhd already exists at $dest, skipping copy"        
    }
    else {
        Write-TraceLog "Initialize-DeploymentVhd: Vhd already exists at $dest, skipping copy"
    } else {
        Write-TraceLog "Initialize-DeploymentVhd: Resolved vhd to $buildVhd"
        Write-TraceLog "Initialize-DeploymentVhd: Copying vhd to $vhdPath"
        #Copy-Item $buildVhd $vhdPath
        try {
            Start-BitsTransfer -Source $buildVhd -Destination $vhdPath -Description $buildVhd -DisplayName "CopyFromWinbuild"
        }
        catch {
            Copy-Item $buildVhd $vhdPath
        }
    }

    $vhdFile = $buildVhd.Split("\") | Select -Last 1
    $depConfig.vhdPath = $vhdPath
    $depConfig.vhdFile = $vhdFile

    Set-DeploymentConfig -deploymentConfig $depConfig
}

function Initialize-DeploymentPath {
    $depConfig = Get-DeploymentConfig

    $defaultPath = $depConfig.vmLocation
    $csv = Get-CSV
    $isCsv = $csv -ne $null

    if ([String]::isnullorempty($defaultPath)) {

        if ($isCsv) {
            $defaultPath = Join-Path $csv "sdnvms"
        }
        else {
            # Use a local default path
            $defaultPath = Join-Path $env:SystemDrive "sdnvms"
        }
    }

    $sb = {
        param ($defaultPath)
        if (-not $(Test-Path($defaultPath))) {
            try {
                New-Item $defaultPath -ItemType Directory -Force -ErrorAction Stop | Out-Null
            }
            catch {
                throw "Unable to access deployment path $defaultPath"
            }
        }
    }

    if (-not $isCsv) {
        #check all hosts
        Invoke-CmdOnInfraHosts -scriptBlock $sb -args @($defaultPath)
    }
    else {
        Invoke-Command -ScriptBlock $sb -ArgumentList @($defaultPath)
    }

    $depConfig.vmLocation = $defaultPath

    Set-DeploymentConfig -deploymentConfig $depConfig
}

function Initialize-NetworkControllerPackage {
    $sdnConfig = get-sdnconfig

    if ([String]::IsNullOrEmpty($sdnconfig.networkController.restIpAddress) -and `
            [String]::IsNullOrEmpty($sdnconfig.networkController.restName)) {
        # Auto generate rest name
        $depId = Get-TurnKeySdnDeploymentId
        $sdnConfig.networkController.restName = "NC-$depId"
        $infraconfig = Get-DeploymentConfig
        if (-not [String]::IsNullOrEmpty($infraconfig.domainName)) {
            $sdnConfig.networkController.restName += "." + $infraconfig.domainName
        }
    }

    Write-TraceLog "Initialize-NetworkControllerPackage: RestName $($sdnConfig.networkController.restName)"

    if ($sdnConfig.networkController.runtime -ne "FC") {
        Set-TurnKeySdnConfig -sdnConfig $sdnConfig
        return
    }

    if ([String]::IsNullOrEmpty($sdnConfig.networkController.FC.PowershellModuleRootPath)) {
        $sdnConfig.networkController.FC.PowershellModuleRootPath = Get-DefaultNetworkControllerPsModulePath
    }

    # Sdnexpress fetches this global var

    $Global:FCNC_MODULE_PATH_ROOT = $sdnConfig.networkController.FC.PowershellModuleRootPath

    if ([String]::IsNullOrEmpty($sdnConfig.networkController.FC.PackageLocation)) {
        $sdnConfig.networkController.FC.PackageLocation = Get-DefaultNetworkControllerPackageLocation
        #$sdnConfig.networkController.FC.PackageLocation = "C:\NetworkController"
    }

    if ([String]::IsNullOrEmpty($sdnConfig.networkController.FC.DatabaseLocation)) {
        $sdnConfig.networkController.FC.DatabaseLocation = Get-DefaultNetworkControllerDBLocation
    }

    Write-TraceLog "Initialize-NetworkControllerPackage: PsModule $($sdnConfig.networkController.FC.PowershellModuleRootPath)"
    Write-TraceLog "Initialize-NetworkControllerPackage: PackageLocation $($sdnConfig.networkController.FC.PackageLocation)"
    Write-TraceLog "Initialize-NetworkControllerPackage: DatabaseLocation $($sdnConfig.networkController.FC.DatabaseLocation)"

    Set-TurnKeySdnConfig -sdnConfig $sdnConfig
}

function Initialize-SdnExpressConfig { 
    param(
        [Parameter(Mandatory = $true)] $sdnExpConfig
    )

    $infraconfig = Get-DeploymentConfig

    $sdnExpConfig.JoinDomain = $infraconfig.domainName
    $cred = Get-TurnKeySdnCred
    $secPass = $cred.password | ConvertFrom-SecureString

    $sdnExpConfig.DomainJoinUsername = $cred.username
    $sdnExpConfig.DomainJoinSecurePassword = $secPass
    $sdnExpConfig.LocalAdminSecurePassword = $secPass
    $sdnExpConfig.LocalAdminDomainUser = $cred.username

    $sdnExpConfig.NCUsername = $cred.username
    $sdnExpConfig.NCSecurePassword = $secPass

    $sdnExpConfig.SwitchName = $infraconfig.sdnSwitchName

    $sdnExpConfig.VHDPath = $infraconfig.vhdPath
    $sdnExpConfig.VHDFile = $infraconfig.vhdFile

    $sdnExpConfig.VMLocation = $infraconfig.vmLocation

    $defaultMacPoolConfig = Get-DefaultMacPoolConfig
    $sdnExpConfig.SDNMacPoolStart = $defaultMacPoolConfig.properties.startMacAddress
    $sdnExpConfig.SDNMacPoolEnd = $defaultMacPoolConfig.properties.endMacAddress

    $mgmtNetConfig = Get-MgmtNetworkConfig
    $sdnExpConfig.ManagementSubnet = $mgmtNetConfig.properties.subnets[0].properties.addressPrefix
    $sdnExpConfig.ManagementGateway = $mgmtNetConfig.properties.subnets[0].properties.defaultGateways[0]
    $sdnExpConfig.ManagementDNS = $mgmtNetConfig.properties.subnets[0].properties.dnsServers
    $sdnExpConfig.ManagementVLANID = $mgmtNetConfig.properties.subnets[0].properties.VLANID
    #$sdnExpConfig.ManagementRoutes = @($mgmtNetConfig.properties.subnets[0].properties.addressPrefix)
    $sdnExpConfig.ManagementRoutes = @()

    $paNetConfig = Get-HnvPaNetworkConfig
    $sdnExpConfig.PASubnet = $paNetConfig.properties.subnets[0].properties.addressPrefix
    $sdnExpConfig.PAGateway = $paNetConfig.properties.subnets[0].properties.defaultGateways[0]
    $sdnExpConfig.PAVLANID = $paNetConfig.properties.subnets[0].properties.vlanID
    $sdnExpConfig.PAPoolStart = $paNetConfig.properties.subnets[0].properties.ipPools[0].properties.startIpAddress
    $sdnExpConfig.PAPoolEnd = $paNetConfig.properties.subnets[0].properties.ipPools[0].properties.endIpAddress

    $currentHostName = $(hostname)
    $depId = Get-TurnKeySdnDeploymentId

    $sdnConfig = get-sdnconfig

    $sdnExpConfig['RestName'] = $sdnconfig.networkController.restName
    $sdnExpConfig['RestIpAddress'] = $sdnConfig.networkController.restIpAddress
    $mgmtIpOffset = 20

    if ([string]::IsNullOrEmpty($sdnconfig.networkController.restName) -or `
            $sdnConfig.networkController.runtime -eq "FC") {
        # Restip auto discovery
        # For FC always generate IP
        # Otherwise use IP if restName is not set
        if ([String]::IsNullOrEmpty($sdnConfig.networkController.restIpAddress)) {
            $restIp = Get-IPAddressInSubnet -Subnet $sdnExpConfig.ManagementSubnet -Offset $mgmtIpOffset
            $prefix = $sdnExpConfig.ManagementSubnet.Split("/")[1]
            $sdnExpConfig['RestIpAddress'] = "$restIp/$prefix"
            $mgmtIpOffset++
        }
    }


    $sdnExpConfig['HyperVHosts'] = $infraconfig.hyperVHosts
    $paPoolOffset = 0

    if ($sdnConfig.networkController.runtime -eq "FC") {
        $sdnExpConfig.UseFCNC = 1
        $sdnExpConfig.FCNCBins = $sdnConfig.networkController.FC.PackageLocation
        $sdnExpConfig.FCNCDBs = $sdnConfig.networkController.FC.DatabaseLocation       
    }
    else {

        $i = 0
        $sdnExpConfig['NCs'] = @()
        $NCNodeCount = $sdnConfig.networkController.SF.nodecount

        while ($i -lt $NCNodeCount) {
            $ncConfig = @{}
            $ncConfig['ComputerName'] = "$depId-NC$i"

            $ncConfig['ManagementIP'] = Get-IPAddressInSubnet -Subnet $sdnExpConfig.ManagementSubnet -Offset $mgmtIpOffset
            $mgmtIpOffset++
            $sdnExpConfig['NCs'] += $ncConfig
            $i++
        }
    }

    $muxNodeCount = $sdnConfig.mux.nodecount
    $i = 0
    $sdnExpConfig['Muxes'] = @()

    if ($sdnConfig.mux.enabled -eq "true") {
        while ($i -lt $muxNodeCount) {
            $mux = @{}
            $mux['ComputerName'] = "$depId-MUX$i"
            $mux['ManagementIP'] = Get-IPAddressInSubnet -Subnet $sdnExpConfig.ManagementSubnet -Offset $mgmtIpOffset
            $mgmtIpOffset++

            $mux['PAIPAddress'] = Get-IPAddressInSubnet -Subnet $sdnExpConfig.PAPoolStart -Offset $paPoolOffset
            $paPoolOffset++

            $sdnExpConfig['Muxes'] += $mux
            $i++
        }
    }

    $sdnExpConfig.ManagementRoutes = Get-MsftCorpRoutes

    $gwNodeCount = $sdnConfig.gateway.nodecount
    $i = 0
    $sdnExpConfig['Gateways'] = @()
    if ($sdnConfig.gateway.enabled -eq "true") {
        $poolType = "IPSec"

        if ($sdnConfig.gateway.gre.enabled -eq "true") {
            $gre = Get-GreVipLogicalNetworkConfig
            $sdnExpConfig['GRESubnet'] = $gre.properties.subnets[0].properties.addressPrefix
            $poolType = "All"
        }

        while ($i -lt $gwNodeCount) {
            $gw = @{}
            $gw['ComputerName'] = "$depId-GW$i"
            $gw['ManagementIP'] = Get-IPAddressInSubnet -Subnet $sdnExpConfig.ManagementSubnet -Offset $mgmtIpOffset
            $mgmtIpOffset++

            $sdnExpConfig['Gateways'] += $gw
            $i++
        }

        $sdnExpConfig['GatewayPoolType'] = $poolType

        if ($gwNodeCount -eq 1) {
            $sdnExpConfig.RedundantCount = 0
        }
    }

    if ($paPoolOffset -lt 10) {
        # Add some space for future scaling
        $paPoolOffset = 10
    }

    $sdnExpConfig.PAPoolStart = Get-IPAddressInSubnet -Subnet $sdnExpConfig.PAPoolStart -Offset $paPoolOffset

    $publicVipNetwork = Get-PublicVipNetworkConfig
    $sdnExpConfig.PublicVIPSubnet = $publicVipNetwork.properties.subnets[0].properties.addressPrefix

    $sdnExpConfig.Routers[0].RouterIPAddress = $paNetConfig.properties.subnets[0].properties.defaultGateways[0]
    $sdnExpConfig.Routers[0].RouterASN = $sdnConfig.mux.peerRouterASN
    $sdnExpConfig.SDNASN = $sdnConfig.mux.asn

    return $sdnExpConfig
}

function Initialize-SdnWorkloadDeploymentPath {
    $workloadConfig = Get-TurnKeySdnWorkloadConfig

    $defaultPath = $workloadConfig.deploymentpath
    $csv = Get-CSV
    $isCsv = $csv -ne $null

    if ([String]::isnullorempty($defaultPath)) {

        if ($isCsv) {
            $defaultPath = Join-Path $csv "SdnWorkload"
        }
        else {
            # Use a local default path
            $defaultPath = Join-Path $env:SystemDrive "SdnWorkload"
        }
    }

    $sb = {
        param ($defaultPath)
        if (-not $(Test-Path($defaultPath))) {
            try {
                New-Item $defaultPath -ItemType Directory -Force -ErrorAction Stop | Out-Null
            }
            catch {
                throw "Unable to access deployment path $defaultPath"
            }
        }
    }

    if (-not $isCsv) {
        #check all hosts
        Invoke-CmdOnInfraHosts -scriptBlock $sb -args @($defaultPath)
    }
    else {
        Invoke-Command -ScriptBlock $sb -ArgumentList @($defaultPath)
    }

    $workloadConfig.deploymentpath = $defaultPath
    Set-TurnKeySdnWorkloadConfig -config $workloadConfig
}

function Invoke-CmdOnInfraHosts {
    param(
        [parameter(Mandatory = $true)][ScriptBlock] $scriptBlock,
        [parameter(Mandatory = $false)][System.Array] $args = @()
    )

    $cred = Get-TurnKeySdnCred
    $hypervHosts = Get-TurnKeySdnHyperVHosts

    $results = @()
    foreach ($h in $hypervHosts) {
        $result = Invoke-ReliableCommand -computerName $h -credential $cred -scriptBlock $scriptBlock -argumentList $args
        $results += $result
    }

    return $results
}

function Get-SdnWorkloadVhd {

    param(
        [ValidateSet("Linux", "Windows")]
        [parameter(Mandatory = $false)][string] $os = "Linux",

        [ValidateSet("Mariner", "WindowsLatest")]
        [parameter(Mandatory = $false)][string] $osSku = "Mariner",

        [parameter(Mandatory=$false)][bool] $useDefaultWindows = $false
    )

    if ($useDefaultWindows) {

        Write-TraceLog "Get-SdnWorkloadVhd: Using default windows vhd"
        $vhdPath = Get-DeploymentConfig | Select -ExpandProperty vhdPath
        $vhdFile = Get-DeploymentConfig | Select -ExpandProperty vhdFile
        return @($vhdPath, $vhdFile)
    }

    $defaultWorkloadVhd = Join-Path (Get-DefaultWorkloadVhdShare) (Get-DefaultWorkloadVhdFile)
    $msg = "Retry after running. A default vhd can be found at $defaultWorkloadVhd."

    $workloadConfig = Get-TurnKeySdnWorkloadConfig
    $osImageConfig = $workloadConfig.imageconfigs | Where-Object { $_.osType -eq $os -and $_.osSku -eq $osSku } | Select -First 1

    if ([String]::IsNullOrEmpty($osImageConfig.vhdPath) -or [string]::IsNullOrEmpty($osImageConfig.vhdFile)) {

        throw "Vhd location for image $($osImageConfig.osType) - $($osImageConfig.osSku) is not set. $msg"
    }

    $vhd = Join-Path $osImageConfig.vhdPath $osImageConfig.vhdFile

    if (-not $(Test-Path($vhd))) {
        throw "$vhd not found. $msg"
    }

    return @($osImageConfig.vhdPath, $osImageConfig.vhdFile)
}

function Enable-Hosts
{
    param (
        [parameter(Mandatory=$true)][array] $Hosts,
        [parameter(Mandatory=$false)][pscredential] $Credential,
        [parameter(Mandatory=$true)][string] $SwitchName,
        [parameter(Mandatory=$true)][string] $MgmtHostNicName,
        [parameter(Mandatory=$true)][string] $ManagementVLANID,
        [parameter(Mandatory=$true)][string] $ManagementSubnet,
        [parameter(Mandatory=$true)][string] $ManagementGateway,
        [parameter(Mandatory=$true)][array] $ManagementDNS,
        [parameter(Mandatory=$true)][bool] $IsFcNc
    )

    $offset = 10
    $prefix = $ManagementSubnet.Split("/")[1]
    $hostNameIPMap = @{}
    $Hosts | Foreach-Object {
            $hostIP = Get-IPAddressInSubnet -subnet $ManagementSubnet -offset $offset
            $hostNameIPMap[$_] = $hostIP
            $offset++
            Write-TraceLog "Enable-Hosts: Host ($_), with IP ($hostIP)"
            Invoke-ReliableCommand -computername $_ -credential $Credential -scriptBlock {
                param(
                    [string]$switchName,
                    [string]$vNicName,
                    [string]$vlanId,
                    [string]$ip,
                    [string]$prefix,
                    [string]$gateway,
                    [array]$dns
                )
                $nic = Get-VMNetworkAdapter -ManagementOS -Name $vNicName -ErrorAction SilentlyContinue

                if ($nic -eq $null)
                {
                    Add-VMNetworkAdapter -ManagementOS -Name $vNicName -SwitchName $switchName
                    $nic = Get-VMNetworkAdapter -ManagementOS -Name $vNicName -ErrorAction SilentlyContinue
                }

                $ifIndex = (Get-NetAdapter -name "*$vNicName*").Ifindex

                Set-VMNetworkAdapterVlan -ManagementOS -VMNetworkAdapterName $vNicName -VlanId $vlanId -access | Out-Null
                $currentIP = Get-NetIPAddress -Interfaceindex $ifIndex -IPAddress $ip -ErrorAction SilentlyContinue -Verbose

                if ($currentIP -eq $null)
                {
                    New-NetIPAddress -Interfaceindex $ifIndex -AddressFamily IPv4 -IPAddress $ip -PrefixLength $prefix | Out-Null
                }
                if ($dns -ne $null -and $dns.Count -gt 0) {
                    Set-DnsClientServerAddress -ServerAddresses $dns -InterfaceIndex $ifIndex -ErrorAction SilentlyContinue
                }

                Set-DnsClientServerAddress -ServerAddresses $dns -InterfaceIndex $ifIndex -ErrorAction SilentlyContinue

                Set-VMSwitch -Name $switchName -EnableRscOffload $true
                Set-VMSwitch -Name $switchName -EnableSoftwareRsc $false
                
            } -ArgumentList @($SwitchName, $MgmtHostNicName, $ManagementVLANID, $hostIP, $prefix, $ManagementGateway, $ManagementDNS)
    }

    Write-TraceLog "Enable-Hosts: Sleeping 5 seconds to allow network to settle"
    Start-Sleep 5

    $Hosts | Foreach-Object { Disable-LinkLocalNics -computerName $_ -credential $Credential}

    if ($IsFcNc) {
        Write-TraceLog "Enable-Hosts: Setting network $ManagementSubnet to ClusterAndClient"
        Wait-ForClusterNetwork -computerName $Hosts[0] -credential $Credential -subnet $ManagementSubnet
        Set-ClusterNetworkToClient -computerName $Hosts[0] -credential $Credential -subnet $ManagementSubnet
    }

    Update-CorpNicDnsServerAddress -hosts $Hosts -credential $Credential -dnsServers $ManagementDNS
    Enable-HostStorageNetwork -ComputerName $Hosts -Credential $Credential -SwitchName $SwitchName

    return $hostNameIPMap
}


function New-TurnKeySdnVM {
    param(
        [parameter(Mandatory = $true)][hashtable] $CreateParams,
        [parameter(Mandatory = $false)][string] $VMCustomTag,
        [parameter(Mandatory = $false)][switch] $WaitForVM
    )
    $vmName = $CreateParams.VMName
    $computerName = $CreateParams.ComputerName
    $cred = Get-TurnKeySdnCred

    try {
        New-SDNExpressVM @CreateParams | Out-Null
    }
    finally {
        
        if ([String]::IsNullOrEmpty($VMCustomTag)) {
            $VMCustomTag = "Client=TurnKeySDN"
        }

        Invoke-ReliableCommand -ComputerName $computerName -Credential $cred -ScriptBlock {
            param($VMName, $VMCustomTag)
            $vm = Get-VM -Name $VMName -ErrorAction SilentlyContinue

            if ($vm -ne $null) {
                $vm | Set-VM -Notes $VMCustomTag
            }
        } -ArgumentList @($vmName, $VMCustomTag)
    }
    
    if ($WaitForVM.IsPresent) {
        WaitforComputerToBeReady -ComputerName $vmName -Credential $cred
    }  
}

function New-TurnKeySdnWorkloadVMUsingSdnExpress {

    param(
        [ValidateSet("Linux", "Windows")]
        [parameter(Mandatory=$false)][string] $os = "Linux",
        [ValidateSet("Mariner", "WindowsLatest")]
        [parameter(Mandatory=$false)][string] $osSku = "Mariner",
        [parameter(Mandatory=$true)][string] $ncNicResourceId,
        [parameter(Mandatory=$true)][string] $ncRestEndpoint,
        [parameter(Mandatory=$false)][string] $hypervHost = $(hostname),
        [parameter(Mandatory=$false)][pscredential] $hypervHostCred = [pscredential]::Empty,
        [parameter(Mandatory=$false)][pscredential] $vmCreds = $false,
        [parameter(Mandatory=$false)][bool] $useDefaultWindows = $false,
        [parameter(Mandatory=$false)][int] $memoryInGB = 1,
        [parameter(Mandatory=$false)][int] $coreCount = 2,
        [parameter(Mandatory=$false)][bool] $enableSecureBoot = $false

    )

    Write-TraceLog "New-TurnKeySdnWorkloadVMUsingSdnExpress: Server $hypervHost, os $os, osSku $osSku, ncNicResourceId $ncNicResourceId, ncRestEndpoint $ncRestEndpointclient"

    if(-not $useDefaultWindows) {
        if([string]::IsNullOrEmpty($os) -or [string]::IsNullOrEmpty($osSku)) {
            throw "New-TurnKeySdnWorkloadVMUsingSdnExpress: os and osSku must be specified when forcing the default Vhd"
        }
    }

    $restEndpoint = Get-TurnKeySdnRestEndpoint
    $restUrl = "https://$restEndpoint"
    $res = Get-NetworkControllerNetworkInterface -ConnectionUri $restUrl -ResourceId $ncNicResourceId -PassInnerException -ErrorAction SilentlyContinue

    if ($res -eq $null) {
        throw "Networkinterface $ncNicResourceId not found"
    }

    if ($res.Properties.ProvisioningState -ne "Succeeded") {
        throw "Networkinterface $ncNicResourceId provisioning state is $($res.Properties.ProvisioningState), cannot continue with deployment"
    }

    $mac = $res.Properties.PrivateMacAddress
    $ipAddress = $res.Properties.IpConfigurations[0].Properties.privateIpaddress

    $instanceId = $res.InstanceId.Guid

    if ([String]::IsNullOrEmpty($res.Tags.vmName)) {
        $vmName = $ncNicResourceId
    } else {
        $vmName = $res.Tags.vmName
    }

    $vhdConfig = Get-SdnWorkloadVhd -os $os -osSku $osSku -useDefaultWindows $useDefaultWindows
    $deploymentPath = (Get-DeploymentConfig).vmLocation
    $sdnSwitchName = Get-SdnSwitchName
    $vmLocalCredential = Get-TurnKeySdnWorkloadVmCred
    $vmPath = Join-Path $deploymentPath $vmName

    #Copy-FileToRemote -srcPath $vhdConfig[0] -srcFile $vhdConfig[1] -destMachine $hypervHost -destMachineCred $hypervHostCred -destDirectory $vmPath -ErrorAction Stop

    $vmVhdPath = Join-Path $vmPath $vhdConfig[1]
    $isCluster = Test-IsCluster


    $vmNic=@(
        @{Name="SdnNic_$vmName"; MacAddress=$mac; SwitchName=$sdnSwitchName}
    )

    Write-SdnExpressLog "New-TurnKeySdnWorkloadVMUsingSdnExpress: Creating VM $vmName on host $hypervHost"
    Write-SdnExpressLog "New-TurnKeySdnWorkloadVMUsingSdnExpress: VM VHD Path is $vmVhdPath"
    Write-SdnExpressLog "New-TurnKeySdnWorkloadVMUsingSdnExpress: IP Address is $ipAddress"

    [bool] $isVmCreated = $false

    try {
        if($isCluster) {

            # check if VMs were deployed on any of the hosts in the cluster
            # throws if VM is not found
            $hypervHost = Resolve-HostName -hostName "" -hostCred $hypervHostCred -vmName $vmName -force $true
            $isVmCreated = $true
        } else {

            # sdn express checks if the vm is already present, so let it handle
            $isVmCreated = $false
        }
    } catch {
        Write-TraceLog "New-TurnKeySdnWorkloadVMUsingSdnExpress: VM $vmName not found on any of the hosts in the cluster, creating new VM"
        $isVmCreated = $false
    }

    if(-not $isVmCreated) {

        New-SDNExpressVM -ComputerName $hypervHost `
            -VMLocation $vmPath `
            -VMName $vmName `
            -VHDSrcPath $vhdConfig[0] `
            -VHDName $vhdConfig[1] `
            -SwitchName $sdnSwitchName `
            -CredentialDomain $Env:TEST_DOMAIN  `
            -CredentialUserName $Env:TEST_USERNAME `
            -CredentialPassword $Env:TEST_PASSWORD `
            -JoinDomain "" `
            -LocalAdminPassword $Env:TURNKEY_WORKLOAD_PASSWORD `
            -VMProcessorCount 4 `
            -Nics $vmNic `
            -EnablePreDeploymentNetworkConnectionCheck $false `
            -EnableProcessorCompatibilityForLiveMigration $true | out-null
    }

    # enable misc settings (add to cluster, guest intergation etc..)
    Invoke-Command -ComputerName $hypervHost -Credential $hypervHostCred -ScriptBlock {
        if ($using:isCluster) {
            $vm = Get-VM -Name $using:vmName

            $currentRole = get-clustergroup -name $using:vmName -ErrorAction SilentlyContinue
            if($null -ne $currentRole -and $currentRole.State -ne "Online") {

                Remove-ClusterGroup -RemoveResources -Force -Name $using:vmName
            }

            $vm | Add-ClusterVirtualMachineRole -ErrorAction SilentlyContinue | Out-Null
            $vm | Get-VMIntegrationService | Enable-VMIntegrationService | Out-Null

        } else {
            $vm = Get-VM -Name $using:vmName
            $vm | Get-VMIntegrationService | Enable-VMIntegrationService | Out-Null
        }
    }
    Enable-SDNExpressVMPort -ComputerName $hypervHost -VMName $vmName -InstanceId "{$instanceId}" -Credential $hypervHostCred

    $trafficEndpoint = New-TrafficEndpoint -vmName $vmName `
        -hostName $hypervHost `
        -ipAddress $ipAddress `
        -port 5001 `
        -endpointType 0 `
        -vmCredential $vmLocalCredential `
        -hostCredential $hypervHostCred `
        -resolveHostName $false

    return $trafficEndpoint
}



function New-TurnKeySdnWorkloadVM {
    param(
        [ValidateSet("Linux", "Windows")]
        [parameter(Mandatory=$false)][string] $os = "Linux",
        [ValidateSet("Mariner", "WindowsLatest")]
        [parameter(Mandatory=$false)][string] $osSku = "Mariner",
        [parameter(Mandatory=$true)][string] $ncNicResourceId,
        [parameter(Mandatory=$true)][string] $ncRestEndpoint,
        [parameter(Mandatory=$false)][string] $hypervHost = $(hostname),
        [parameter(Mandatory=$false)][pscredential] $hypervHostCred = [pscredential]::Empty,
        [parameter(Mandatory=$false)][bool] $useDefaultWindows = $false,
        [parameter(Mandatory=$false)][int] $memoryInGB = 1,
        [parameter(Mandatory=$false)][int] $coreCount = 2,
        [parameter(Mandatory=$false)][bool] $enableSecureBoot = $false

    )

    Write-TraceLog "New-TurnKeySdnVM: Server $hypervHost, os $os, osSku $osSku, ncNicResourceId $ncNicResourceId, ncRestEndpoint $ncRestEndpointclient"

    if(-not $useDefaultWindows) {
        if([string]::IsNullOrEmpty($os) -or [string]::IsNullOrEmpty($osSku)) {
            throw "New-TurnKeySdnVM: os and osSku must be specified when forcing the default Vhd"
        }
    }

    $restEndpoint = Get-TurnKeySdnRestEndpoint
    $restUrl = "https://$restEndpoint"
    $res = Get-NetworkControllerNetworkInterface -ConnectionUri $restUrl -ResourceId $ncNicResourceId -PassInnerException -ErrorAction SilentlyContinue

    if ($res -eq $null) {
        throw "Networkinterface $ncNicResourceId not found"
    }

    if ($res.Properties.ProvisioningState -ne "Succeeded") {
        throw "Networkinterface $ncNicResourceId provisioning state is $($res.Properties.ProvisioningState), cannot continue with deployment"
    }

    $mac = $res.Properties.PrivateMacAddress
    $instanceId = $res.InstanceId.Guid

    if ([String]::IsNullOrEmpty($res.Tags.vmName)) {
        $vmName = $ncNicResourceId
    }
    else {
        $vmName = $res.Tags.vmName
    }

    if ($AllComputers.Count -ne 0) {
        # If the VM is already present in another, override the name
        $hostName = Get-VMHostName -Computers $AllComputers -Credential $hypervHostCred -VMName $vmName
        if (-not [String]::IsNullOrEmpty($hostName)) {
            $hypervHost = $hostName
        }
    }

    $vhdConfig = Get-SdnWorkloadVhd -os $os -osSku $osSku -useDefaultWindows $useDefaultWindows
    $deploymentPath = Get-SdnWorkloadDeploymentPath
    $sdnSwitchName = Get-SdnSwitchName
    $vmPath = Join-Path $deploymentPath $vmName

    Copy-FileToRemote -srcPath $vhdConfig[0] -srcFile $vhdConfig[1] -destMachine $hypervHost -destMachineCred $hypervHostCred -destDirectory $vmPath -ErrorAction Stop

    $vmVhdPath = Join-Path $vmPath $vhdConfig[1]
    $isCluster = Test-IsCluster
    Invoke-Command -ComputerName $hypervHost -Credential $hypervHostCred -ScriptBlock {

        $vm = Get-VM -Name $using:vmName -ErrorAction SilentlyContinue

        [uint64] $memoryStarupBytes = $using:memoryInGB*1024*1024*1024

        if ($vm -eq $null) {
            New-VM -SwitchName $sdnSwitchName -VHDPath $vmVhdPath -Generation 2 -BootDevice VHD -Path $vmPath -name $vmName -MemoryStartupBytes 512MB `
                -ErrorAction Stop | Out-Null    
        }
        else {
            New-VM -SwitchName $using:sdnSwitchName `
                -VHDPath $using:vmVhdPath `
                -Generation 2 `
                -BootDevice VHD `
                -Path $using:vmPath `
                -name $using:vmName `
                -MemoryStartupBytes $memoryStarupBytes `
                -ErrorAction Stop | Out-Null
        } 

        Set-VMNetworkAdapter -VMName $using:vmName -StaticMacAddress $using:mac -ErrorAction Stop

        if ($using:os -eq "Linux") {
            Set-VMFirmware -EnableSecureBoot On -SecureBootTemplate MicrosoftUEFICertificateAuthority -VMName $using:vmName `
                -ErrorAction Stop
        } elseif($disableSecureBoot) {
            Set-VMFirmware -EnableSecureBoot Off -VMName $using:vmName -ErrorAction Stop
        }

        Get-VM -Name $using:vmName | Set-VM -Notes "Client=TurnKeySDN;Role=Workload;IsInfra=$false;Os=$using:os"
        Start-VM -VMName $using:vmName

        if ($using:isCluster) {
            Get-VM -Name $using:vmName | Add-ClusterVirtualMachineRole -ErrorAction SilentlyContinue | Out-Null
        }

    } -ErrorAction Stop

    Enable-SDNExpressVMPort -ComputerName $hypervHost -VMName $vmName -InstanceId "{$instanceId}" -Credential $hypervHostCred
}

function Set-VMVLANID {
    param(
        [Parameter(Mandatory = $true)][String]$VMName,
        [Parameter(Mandatory = $true)][pscredential]$Credential,
        [Parameter(Mandatory = $true)][String]$ComputerName,
        [Parameter(Mandatory = $true)][String]$NicName,
        [Parameter(Mandatory = $true)][int]$VLanID
    )

    Invoke-ReliableCommand -ComputerName $ComputerName -Credential $Credential -ScriptBlock {
        param(
            [Parameter(Mandatory = $true)][String]$VMName,
            [Parameter(Mandatory = $true)][String]$NicName,
            [Parameter(Mandatory = $true)][int]$VLanID
        )

        [int]$currentVlan = (Get-VMNetworkAdapterVLan -VMName $VMName -VMNetworkAdapterName $NicName).AccessVlanId

        if ($currentVlan -eq $VLanID) {
            return
        }

        Set-VMNetworkAdapterVlan -Access -VlanId $VLanID -VMName $VMName -VMNetworkAdapterName $NicName
    } -ArgumentList @($VMName, $NicName, $VLanID)
}
function New-RouterVM {
    param(
        [Parameter(Mandatory = $true)]
        $vmName,
        [Parameter(Mandatory = $true)]
        $MgMtIP,
        [Parameter(Mandatory = $true)]
        $MgmtNicMac,  
        [Parameter(Mandatory = $true)]
        $PAIP,
        [Parameter(Mandatory = $true)]
        $PANicMac,        
        [Parameter(Mandatory = $true)]
        $CorpNicMac,        
        [switch]
        $Force
    )

    $depConfig = Get-DeploymentConfig
    $cred = Get-TurnKeySdnCred

    Write-TraceLog "New-RouterVM: Creating VM $vmName"  

    $hyperVHosts = $depConfig.hyperVHosts
    $vmHost = Get-VMHostName -Computers $hyperVHosts -Credential $cred -VMName $vmName 
    
    $mgmtNetConfig = Get-MgmtNetworkConfig
    $mgmtVlanId = $mgmtNetConfig.properties.subnets[0].properties.VLANID
    
    $paVlanId = Get-HnvPaNetworkConfig
    $paVlanId = $paVlanId.properties.subnets[0].properties.vlanID
    
    $managementNicName = "Management"
    $paNicName = "HNVPA"
    
    if ($vmHost -ne $null) {
       
        if ($Force.IsPresent) {
            Write-TraceLog "New-RouterVM: Recreating the VM"
            Remove-DomainJoinedVMs -Tag "Role=Router"            
        }
        else {
            Write-TraceLog "New-RouterVM: VM already exists, skipping creation"
            # Reset VLAN if it changes between deployments.
            Set-VMVLANID -VMName $vmName -Credential $cred -ComputerName $vmHost -NicName $managementNicName -VLanID $mgmtVlanId
            Set-VMVLANID -VMName $vmName -Credential $cred -ComputerName $vmHost -NicName $paNicName -VLanID $paVlanId
            return
        }
    }

    $createparams = @{
        'ComputerName'        = $(hostname);
        'VMLocation'          = $depConfig.vmLocation;
        'VMName'              = $vmName;
        'VHDSrcPath'          = $depConfig.vhdPath;
        'VHDName'             = $depConfig.vhdFile;
        'VMMemory'            = 4GB;
        'VMProcessorCount'    = 4;
        'Nics'                = @();
        'CredentialDomain'    = $cred.username.Split("\")[0];
        'CredentialUserName'  = $cred.username.Split("\")[1];
        'CredentialPassword'  = $cred.GetNetworkCredential().password;
        'JoinDomain'          = $depConfig.domainName;
        'LocalAdminPassword'  = $cred.GetNetworkCredential().password;
        'DomainAdminDomain'   = $cred.username.Split("\")[0];
        'DomainAdminUserName' = $cred.username.Split("\")[1];
        'SwitchName'          = $depConfig.sdnSwitchName;
    }
  
    $dnsServers = $mgmtNetConfig.properties.subnets[0].properties.dnsServers

    $createparams.Nics = @(
        @{Name = $managementNicName; MacAddress = $MgmtNicMac; VLANID = $mgmtVlanId; IPAddress = $MgMtIP; SwitchName = $depConfig.sdnSwitchName; DNS = $dnsServers; IsMuxPA = $true },
        @{Name = "Corp"; MacAddress = $CorpNicMac; SwitchName = $depConfig.internetSwitchName; IsMuxPA = $true },
        @{Name = $paNicName; MacAddress = $PANicMac; VLANID = $paVlanId; IPAddress = $PAIP; SwitchName = $depConfig.sdnSwitchName; DNS = $dnsServers; IsMuxPA = $true }
    ) 

    $createparams.Roles = @("RemoteAccess", "RemoteAccessServer", "RemoteAccessMgmtTools", "RemoteAccessPowerShell", "RasRoutingProtocols", "Web-Application-Proxy")

    New-TurnKeySdnVM -CreateParams $createparams -VMCustomTag "Client=TurnKeySDN;Role=Router;IsInfra=$true" -WaitForVM   

    Start-Sleep 10

    Enable-DefaultFirewallRules -ComputerName $vmName -Credential $cred
}

function Enable-NatOnRouter {
    param(
        [Parameter(Mandatory = $true)]$VMName,
        [Parameter(Mandatory = $true)]$MgmtNicMac,
        [Parameter(Mandatory = $true)]$CorpNicMac,
        [Parameter(Mandatory = $true)]$PANicMac
    )

    $cred = Get-TurnKeySdnCred

    Invoke-ReliableCommand -ComputerName $VMName -Credential $cred -ScriptBlock {
        param(
            [Parameter(Mandatory = $true)]$MgmtNicMac,
            [Parameter(Mandatory = $true)]$CorpNicMac,
            [Parameter(Mandatory = $true)]$PANicMac
        )
        $external = "External"
        $ext = (Get-NetAdapter -Name $External -ErrorAction SilentlyContinue)
        
        if ($ext -eq $null) {
            $corpNic = Get-NetAdapter | Where-Object { $_.MacAddress -eq $CorpNicMac }
            Rename-NetAdapter -Name $corpNic.Name -NewName $external
        }

        $mgmt = "Manangement"
        $mgmtAdapter = (Get-NetAdapter -Name $mgmt -ErrorAction SilentlyContinue)

        if ($mgmtAdapter -eq $null) {
            $mgmtAdapter = Get-NetAdapter | Where-Object { $_.MacAddress -eq $MgmtNicMac }
            Rename-NetAdapter -Name $mgmtAdapter.Name -NewName $mgmt
        }

        $hnvpa = "HNVPA"
        $hnvpaAdapter = (Get-NetAdapter -Name $hnvpa -ErrorAction SilentlyContinue)

        if ($hnvpaAdapter -eq $null) {
            $hnvpaAdapter = Get-NetAdapter | Where-Object { $_.MacAddress -eq $PANicMac }
            Rename-NetAdapter -Name $hnvpaAdapter.Name -NewName $hnvpa
        }

        #Setup NAT for internet Access
        Install-WindowsFeature -name RemoteAccess -IncludeAllSubFeature -IncludeManagementTools
        Install-RemoteAccess -VpnType RoutingOnly

        cmd.exe /c "netsh routing ip nat uninstall" | Out-Null
        cmd.exe /c "netsh routing ip nat install" | Out-Null
        cmd.exe /c "netsh routing ip nat set global tcptimeoutmins=1 udptimeoutmins=0 loglevel=ERROR" | Out-Null
        cmd.exe /c "netsh routing ip nat add interface $external" | Out-Null
        cmd.exe /c "netsh routing ip nat set interface $external mode=full" | Out-Null
        cmd.exe /c "netsh routing ip nat add interface $mgmt" | Out-Null
        cmd.exe /c "netsh routing ip nat add interface $hnvpa" | Out-Null
    } -ArgumentList @($MgmtNicMac, $CorpNicMac, $PANicMac)
}

function Enable-BgpOnRouter {
    param(
        [Parameter(Mandatory = $true)]
        $vmName,
        [Parameter(Mandatory = $true)]
        $bgpLocalIP,
        [Parameter(Mandatory = $true)]
        $LocalASN
    )

    $cred = Get-TurnKeySdnCred
    Invoke-ReliableCommand -ComputerName $vmName -Credential $cred -ScriptBlock {

        param(
            [Parameter(Mandatory = $true)]
            $bgpLocalIP,
            [Parameter(Mandatory = $true)]
            $LocalASN
        )
        Add-windowsfeature -name RemoteAccess -IncludeAllSubFeature -IncludeManagementTools

        Install-RemoteAccess -RoleType RoutingOnly
       
        try {
            $router = get-bgprouter -ErrorAction SilentlyContinue
        }
        catch {

        }

        if ($router -ne $null) {
            $router | Remove-BgpRouter -force
        }

        Add-BgpRouter -BgpIdentifier $bgpLocalIP -LocalASN $LocalASN

    } -ArgumentList @($bgpLocalIP, $LocalASN)
}

function Add-MuxPeerToBgp {
    param(
        [Parameter(Mandatory = $true)]
        $vmName,
        [Parameter(Mandatory = $true)]
        $bgpPeerName,
        [Parameter(Mandatory = $true)]
        $bgpLocalIP,
        [Parameter(Mandatory = $true)]
        $bgpPeerIP,
        [Parameter(Mandatory = $true)]
        $LocalASN,
        [Parameter(Mandatory = $true)]
        $PeerASN
    )

    $cred = Get-TurnKeySdnCred
    Invoke-ReliableCommand -ComputerName $vmName -Credential $cred -ScriptBlock {

        param(
            [Parameter(Mandatory = $true)]
            $bgpLocalIP,
            [Parameter(Mandatory = $true)]
            $bgpPeerName,
            [Parameter(Mandatory = $true)]
            $bgpPeerIP,
            [Parameter(Mandatory = $true)]
            $LocalASN,
            [Parameter(Mandatory = $true)]
            $PeerASN
        )
       
        Get-BgpRouter -ErrorAction Stop      
        Add-BgpPeer -PeerName $bgpPeerName -PeerIpAddress $bgpPeerIP -PeerASN $PeerASN -LocalIpAddress $bgpLocalIP
    } -ArgumentList @($bgpLocalIP, $bgpPeerName, $bgpPeerIP, $LocalASN, $PeerASN)
}


function Set-StaticRoute {
    param(
        [array] $Computer,
        [pscredential] $Credential = [pscredential]::Empty,
        [string] $SwitchName,
        [string] $NicName,
        [string] $DestinationPrefix,
        [string] $NextHop
    )

    Write-TraceLog "Set-StaticRoute: Computer $Computer Setting route for $DestinationPrefix to $NextHop, NicName $NicName"
    $Computer | Foreach-Object {
        Invoke-ReliableCommand -computerName $_ -Credential $Credential -ScriptBlock {
            param(
                [string] $SwitchName,
                [string] $NicName,
                [string] $DestinationPrefix,
                [string] $NextHop
            )

            if (-not [String]::isnullorempty($SwitchName)) {
                $hostVNic = Get-VMNetworkAdapter -ManagementOS -Name $NicName -switchName $SwitchName -ErrorAction Stop
                $adapter = Get-NetAdapter | Where-Object { $($_.MacAddress -replace "-", "") -eq $($hostVNic.MacAddress) }
            }
            else {
                $adapter = Get-NetAdapter -Name $NicName -ErrorAction Stop
            }

            $index = $adapter.ifIndex

            $route = Get-NetRoute -InterfaceIndex $index -NextHop $NextHop -DestinationPrefix $DestinationPrefix -ErrorAction SilentlyContinue

            if ($route -ne $null) {
                return
            }

            New-NetRoute -DestinationPrefix $DestinationPrefix -InterfaceIndex $index -NextHop $NextHop -ErrorAction Stop | Out-Null
        } -ArgumentList @($SwitchName, $NicName, $DestinationPrefix, $NextHop)
    }
}

function Set-SDNVMNotes {

    $cred = Get-TurnKeySdnCred
    $hyperVHosts = Get-TurnKeySdnHyperVHosts
    $depId = Get-TurnKeySdnDeploymentId

    Write-TraceLog "Set-SDNVMNotes: Setting custom tags on SDN VMs"

    foreach ($h in $hyperVHosts) {
        Invoke-ReliableCommand -ComputerName $h -Credential $cred -ScriptBlock {
            param($depId)
            $ncPrefix = "$depId-NC*"

            $ncVMs = Get-VM -Name $ncPrefix -ErrorAction SilentlyContinue
            if ($ncVMs -ne $null) {
                $ncVMs | Set-VM -Notes "Client=TurnKeySDN;Role=NetworkController;IsSDN=$true"
            }

            $muxPrefix = "$depId-MUX*"
            $muxVMs = Get-VM -Name $muxPrefix -ErrorAction SilentlyContinue
            if ($muxVMs -ne $null) {
                $muxVMs | Set-VM -Notes "Client=TurnKeySDN;Role=MUX;IsSDN=$true"
            }

            $gwPrefix = "$depId-GW*"
            $gwVMs = Get-VM -Name $gwPrefix -ErrorAction SilentlyContinue
            if ($gwVMs -ne $null) {
                $gwVMs | Set-VM -Notes "Client=TurnKeySDN;Role=Gateway;IsSDN=$true"
            }

        } -ArgumentList $($depId)
    }
}

function Invoke-SDNExpress {
    param(
        [Parameter(Mandatory = $true)]$sdnExpConfig
    )

    if ($sdnExpConfig -eq $null) {
        throw "Please load the config and retry"
    }

    $sdnExpress = Get-SdnExpressScript
    $path = Resolve-Path $sdnExpress
    $parent = Split-Path $(Resolve-Path $sdnExpress)
    $retryCount = 0
    while ($retryCount -lt 2) {
        Push-Location $parent
        try {
            & $sdnExpress -ConfigurationData $sdnExpConfig
            break
        }
        catch {
            Write-TraceLog "Invoke-SDNExpress: Sdnexpress failed, error $_"
            $retryCount++

            if ($retryCount -eq 2) {
                throw
            }

            Write-TraceLog "Invoke-SDNExpress: Sdnexpress retrying in 60seconds, RetryCount $retryCount"
            Start-Sleep 60
        }
        finally {
            Pop-Location
        }
    }
}

function Set-PostInstallConfig {
    param(
        [Parameter(Mandatory = $true)]$sdnExpConfig,
        [Parameter(Mandatory = $true)]$hostNameIPMap        
    )

    if ($sdnExpConfig.UseFCNC) {
        return
    }

    $cred = Get-TurnKeySdnCred

    $sdnExpConfig['NCs'] | ForEach-Object {
        Write-TraceLog "Set-PostInstallConfig: [SLBMWorkaround] Updating host file on NC VM $_" -Warning
        # Froce SLBM to use the expected IP of the host. SLBM breaks if host IP changes.
        Invoke-ReliableCommand -ComputerName $_['ComputerName'] -credential $cred -ScriptBlock {

            param($hostIPs)

            function UpdateHostsFile($name, $ip, $skipResolve) {
                if (-not $skipResolve) {
                    $addr = (Resolve-DnsName $name -ErrorAction SilentlyContinue).IPAddress | `
                        Select-Object -First 1
                }

                if ($addr -ne $ip) {
                    Add-Content C:\Windows\System32\drivers\etc\hosts "$ip $name" | out-null
                    return $true
                }

                return $false
            }

            $hosts = $hostIPs.Keys
            $hosts | Foreach-Object {
                $changed = $false
                $name = $_
                $ip = $hostIPs[$name]
                $changed = UpdateHostsFile -name $name -ip $ip -skipResolve $false

                if ($changed -and (-not $name.Contains("."))) {
                    $hostfqdn = "$name.$($env:USERDNSDOMAIN)"
                    UpdateHostsFile -name $hostfqdn -ip $ip -skipResolve $true |  out-null
                }

                if ($changed) {
                    Stop-Process -name SDNSLBM -force -ErrorAction SilentlyContinue
                }
            }

        } -ArgumentList $hostNameIPMap
    }
}

function New-DataCenterGateway {
    param(
        [Parameter(Mandatory = $true)]
        $vmName,
        [Parameter(Mandatory = $true)]
        $IPAddress,
        [Parameter(Mandatory = $true)]
        $MacAddress,
        [Parameter(Mandatory = $true)]
        $VlanID,
        [Parameter(Mandatory = $true)]
        $DefaultGateway,
        [switch]
        $Force
    )

    Write-TraceLog "New-OnPremiseGw: Creating VM $vmName"

    $vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue

    if ($vm -ne $null) {
        if ($Force.IsPresent) {
            Write-TraceLog "New-OnPremiseGw: Recreating the VM"
            $vm | Stop-VM -TurnOff -ErrorAction SilentlyContinue
            $vm | Remove-VM -Force
        }
        else {
            Write-TraceLog "New-OnPremiseGw: NOOP"
            return
        }
    }

    $depConfig = Get-DeploymentConfig
    $cred = Get-TurnKeySdnCred

    $createparams = @{
        'ComputerName'        = $(hostname);
        'VMLocation'          = $depConfig.vmLocation;
        'VMName'              = $vmName;
        'VHDSrcPath'          = $depConfig.vhdPath;
        'VHDName'             = $depConfig.vhdFile;
        'VMMemory'            = 4GB;
        'VMProcessorCount'    = 4;
        'Nics'                = @();
        'CredentialDomain'    = $cred.username.Split("\")[0];
        'CredentialUserName'  = $cred.username.Split("\")[1];
        'CredentialPassword'  = $cred.GetNetworkCredential().password;
        'JoinDomain'          = $depConfig.domainName;
        'LocalAdminPassword'  = $cred.GetNetworkCredential().password;
        'DomainAdminDomain'   = $cred.username.Split("\")[0];
        'DomainAdminUserName' = $cred.username.Split("\")[1];
        'SwitchName'          = $depConfig.sdnSwitchName;
    }

    $mgmtNetConfig = Get-MgmtNetworkConfig
    $dnsServers = $mgmtNetConfig.properties.subnets[0].properties.dnsServers

    $createparams.Nics = @(
        @{Name = "DCNET"; MacAddress = $MacAddress; VLANID = $VlanID; IPAddress = $IPAddress; SwitchName = $depConfig.sdnSwitchName; Gateway = $DefaultGateway; DNS = $dnsServers; IsMuxPA = $false }
    ) 


    $createparams.Roles = @("RemoteAccess", "RemoteAccessServer", "RemoteAccessMgmtTools", "RemoteAccessPowerShell", "RasRoutingProtocols", "Web-Application-Proxy")
    New-TurnKeySdnVM -CreateParams $createparams -VMCustomTag "Client=TurnKeySDN;Role=DataCenterGateway;IsInfra=$false"
}

function Test-DeploymentMachines {

    $depConfig = Get-DeploymentConfig
    $hyperVHosts = $depConfig.hyperVHosts

    if ($hyperVHosts -eq $null -or $hyperVHosts.Count -eq 0) {
        $hyperVHosts = @($(hostname))
    }
    else {
        $curHost = hostname
        $isPresent = ($hyperVHosts | Where-Object { $_.StartsWith($curHost) }) -ne $null

        if (-not $isPresent) {
            $err = "Current host($(hostname)) is not part of the deployment machines, deployment machines are $hyperVHosts"
            $err += "`nSet deployment using Set-TurnKeySdnDeploymentMachines or update $(Get-DeploymentFile)"
            throw $err
        }
    }
}
function Test-CommonConfig {

    if (-not (Get-WmiObject -Class Win32_ComputerSystem).PartOfdomain) {
        throw "Only domain joined setup is supported currently, please join the machine to a domain and retry."
    }

    try {
        $depConfig = Get-DeploymentConfig
    }
    catch {
        Write-TraceLog "Failed to fetch deployment config, please run Initialize-TurnKeySdnDeployment and retry" -Err
        throw
    }

    if ([String]::IsNullOrEmpty($depConfig.domainName)) {
        throw "Invalid domain name."
    }

    try {
        Get-TurnKeySdnCred | Out-Null
    }
    catch {
        throw "Unable to read test credential. Please reset using Set-TurnKeySdnCredentials and retry. Error $_"
    }

    $sdnexpress = Get-SdnExpressModule

    if (-not $(Test-Path $sdnexpress -ErrorAction Stop)) {
        throw "$sdnexpress not found, Please run Set-SdnExpressPath and retry"
    }

    $sdnexpress = Get-SdnExpressScript
    if (-not $(Test-Path $sdnexpress -ErrorAction Stop)) {
        throw "$sdnexpress not found, Please run Set-SdnExpressPath and retry"
    }



    if ([String]::IsNullOrEmpty($depConfig.sdnSwitchName)) {
        throw "SDN Switch $($depConfig.sdnSwitchName) not configured"
    }

    if ([String]::IsNullOrEmpty($depConfig.internetSwitchName) -and $depConfig.useNatNetwork) {
        throw "Internet Switch $($depConfig.internetSwitchName) not configured"
    }

    Test-DeploymentMachines

    $cred = Get-TurnKeySdnCred
    $hyperVHosts = $depConfig.hyperVHosts

    Test-VMSwitchExist -computerName $hyperVHosts -switchName $depConfig.sdnSwitchName -credential $cred

    if ($depConfig.useNatNetwork -and $($depConfig.sdnSwitchName -ne $depConfig.internetSwitchName)) {
        Test-VMSwitchExist -computerName $hyperVHosts -switchName $depConfig.internetSwitchName -credential $cred
    }
}

function Test-NetworkControllerPackage {

    $sdnConfig = Get-SDNConfig

    if ($sdnConfig.networkController.runtime -ne "FC") {
        return
    }

    if (-not (Test-Path $sdnConfig.networkController.FC.PowershellModuleRootPath)) {
        Write-TraceLog "Please run Install-WindowsFeature -name RSAT-NetworkController and retry" -Warning
        Write-TraceLog "To change package loction or update runtime please run Set-TurnKeySdnNCConfig" -Warning

        throw "Networkcontroller PowershellModuleRootPath not found. Configured path is $($sdnConfig.networkController.FC.PowershellModuleRootPath)"
    }

    if (-not (Test-Path $sdnConfig.networkController.FC.PackageLocation)) {
        Write-TraceLog "Please run Install-WindowsFeature -name NetworkController and retry" -Warning
        Write-TraceLog "To change package loction or update runtime please run Set-TurnKeySdnNCConfig" -Warning
        throw "Networkcontroller PackageLocation not found. Configured path is $($sdnConfig.networkController.FC.PackageLocation)"
    }
}

function Test-FreeSpace {
    $depConfig = Get-DeploymentConfig

    if (Test-IsCsvPath -path $depConfig.vmLocation) {
        # For csv skip free space check for now.
        return
    }

    $rpath = Resolve-Path $depConfig.vmLocation

    $scricptBlock = {
        param($rpath)
        $freeGb = $rpath.Drive.Free / (1024 * 1024 * 1024)
        $result = @{}
        $result[$(hostname)] = $freeGb
        return $result
    }

    $results = Invoke-CmdOnInfraHosts -scriptBlock $scricptBlock -args @($rpath)
    $fail = $false
    foreach ($r in $results) {
        $freeSpace = $r[$r.Keys[0]]
        Write-TraceLog "Test-FreeSpace: HypervHost $($r.Keys[0]), Drive $rpath, FreeSpace $freeSpace"
        if ($freeSpace -lt 50) {
            Write-TraceLog "Test-FreeSpace: HypervHost $($r.Keys[0]), has < 50GB in drive $($rpath), FreeSpace $freeSpace" -Warning
            $fail = $true
        }
    }

    if ($fail) {
        Write-TraceLog "Test-FreeSpace: Test failed. Please free up space or change deployment path. Current deployment path is $($depConfig.vmLocation)" -Warning
        Write-TraceLog "Test-FreeSpace: To change the deployment path, please run Set-TurnKeySdnDeploymentPath and retry" -Warning

        throw "All hosts machines should have atleast 50GB free space."
    }
}

function Test-Vhd {
    $depConfig = Get-DeploymentConfig

    if ([String]::IsNullOrEmpty($depConfig.vhdPath) -or [String]::IsNullOrEmpty($depConfig.vhdFile)) {
        Write-TraceLog "Please copy a valid vhd and run Set-TurnKeySdnDeploymentVhd, Eg; Set-TurnKeySdnDeploymentVhd c:\vhd -vhdFile sdnexpress.vhdx" -Warning
        throw "Deployment vhd path is null Please set a deployment vhd using Set-TurnKeySdnDeploymentVhd and retry."
    }

    if ([String]::IsNullOrEmpty($depConfig.vhdPath) -or [String]::IsNullOrEmpty($depConfig.vhdFile)) {
        Write-TraceLog "Please copy a valid vhd and run Set-TurnKeySdnDeploymentVhd, Eg; Set-TurnKeySdnDeploymentVhd c:\vhd -vhdFile sdnexpress.vhdx" -Warning
        throw "Deployment vhdFile is null. Please set a deployment vhd using Set-TurnKeySdnDeploymentVhd and retry."
    }

    $vhd = Join-Path $depConfig.vhdPath $depConfig.vhdFile

    if (-not $(Test-Path $vhd -ErrorAction Stop)) {
        throw "Deployment vhd ($vhd) not found. Please set a deployment vhd using Set-TurnKeySdnDeploymentVhd and retry."
    }
}
function Test-DeploymentConfig
{
    Test-CommonConfig
    Test-FreeSpace
    Test-Vhd
    Test-NetworkControllerPackage
}

function Test-WorkloadConfig {       
    param(
        [parameter(Mandatory = $false)][string]$workLoadConfig = [String]::Empty
    )

    Write-TraceLog "Test-WorkloadConfig: Testing workload config"

    Test-CommonConfig

    $restoreReplayScript = Get-RestoreReplayScript
    if (-not $(Test-Path $restoreReplayScript -ErrorAction Stop)) {
        throw "Restore replay script not found at $restoreReplayScript"
    }

    if ([String]::IsNullOrEmpty($workLoadConfig)) {
        $workLoadConfig = Get-WorkloadConfigPath
    }

    if (-not $(Test-Path $workLoadConfig -ErrorAction Stop)) {
        throw "Worloadconfig file not found at $workLoadConfig"
    }

    $jsonPath = Join-Path $workLoadConfig "jsons"

    
    if (-not $(Test-Path $jsonPath -ErrorAction Stop)) {
        throw "Jsons folder not found under $workLoadConfig"
    }

    $restName = Get-TurnKeySdnRestEndpoint

    if ([String]::IsNullOrEmpty($restName)) {
        throw "NC RestName is empty, run 'Install-TurnKeySdn' to configure SDN. If SDN is already configured, set rest name using Set-TurnKeySdnRestName"
    }

    Get-SdnWorkloadVhd -os Linux -osSku Mariner | Out-Null
}

function Test-DeploymentLogicalNetworkConfig {

    param(
        [parameter(Mandatory = $true)][PSCustomObject] $Network
    )
    if ($Network.Properties.subnets -eq $null -or $Network.Properties.subnets.Count -eq 0) {
        throw "LogicalNetwork has no subnets"
    }

    if ($Network.Properties.subnets[0].properties.addressPrefix -eq $null) {
        throw "LogicalNetwork has no addressPrefix"
    }

    if ($Network.Properties.subnets[0].properties.ipPools -eq $null -or $Network.Properties.subnets[0].properties.ipPools.Count -eq 0) {
        throw "LogicalNetwork has no ipPools"
    }
}

function Get-InfraVmPrefix {
    if (-not $(Test-IsCluster)) {
        return $(Get-TurnKeySdnDeploymentId)
    }

    $clusterName = (Get-Cluster).Name

    if ($clusterName.Length -lt 8) {
        return $clusterName
    }

    return ($clusterName.Substring(0, 8))
}
function Get-RouterVMName {
    (Get-InfraVmPrefix) + "-Router"
}

function Get-BgpVMName {
    (Get-InfraVmPrefix) + "-BGP"
}

function Get-OnPremiseGWVMName {
    (Get-InfraVmPrefix) + "-OGW"
}


function Enable-LogicalNetworkDefaultGateway {
    param(
        [Parameter(Mandatory = $true)]
        [string]$LogicalNetworkResourceId
    )

    <#
    .SYNOPSIS
        Adds the default gw of the logical network to the nat vm
    .PARAMETER LogicalNetworkResourceId
        Name of the logical network
    #>


    $routerVM = Get-RouterVMName
    Write-TraceLog "Enable-LogicalNetworkDefaultGateway: Adding default gateway of logical network $LogicalNetworkResourceId on $routerVM"

    $cred = Get-TurnKeySdnCred
    $depConfig = Get-DeploymentConfig
    $hyperVHosts = $depConfig.hyperVHosts

    $vmHost = Get-VMHostName -Computers $hyperVHosts -Credential $cred -VMName $routerVM 

    if ($vmHost -eq $null) {
        throw "Nat VM $routerVM not found on any hosts"
    }

    $restName = Get-TurnKeySdnRestEndpoint
    $restUrl = "https://$restName"

    $ncLNET = Get-NetworkControllerLogicalNetwork -ConnectionUri $restUrl -ResourceId $LogicalNetworkResourceId -ErrorAction SilentlyContinue

    if ($ncLNET -eq $null) {
        throw "LogicalNetwork $LogicalNetworkResourceId not found"
    }

    $nicsToAdd = @()
    $ncLNET.Properties.Subnets | ForEach-Object {
        $subnetId = $_.ResourceId
        $ip = $_.Properties.DefaultGateways | Select -First 1
        $prefixLen = $_.Properties.AddressPrefix.Split("/") | Select -Last 1
        $addressPrefix = $_.Properties.AddressPrefix
        $vlan = $_.Properties.VlanID
        $nicProperties = New-Object Microsoft.Windows.NetworkController.NetworkInterfaceProperties
        $nicProperties.privateMacAllocationMethod = "Dynamic"
        $nicProperties.IPConfigurations = @()
        $nicProperties.IPConfigurations += New-Object Microsoft.Windows.NetworkController.NetworkInterfaceIpConfiguration
        $nicProperties.IPConfigurations[0].ResourceId = $subnetId
        $nicProperties.IPConfigurations[0].Properties = New-Object Microsoft.Windows.NetworkController.NetworkInterfaceIpConfigurationProperties
        $nicProperties.IPConfigurations[0].Properties.Subnet = New-Object Microsoft.Windows.NetworkController.Subnet
        $nicProperties.IPConfigurations[0].Properties.Subnet.ResourceRef = $_.ResourceRef
        # Unmanaged, nic is not managed by NC, only for MAC address
        $nicProperties.IPConfigurations[0].Properties.PrivateIPAllocationMethod = "Unmanaged"
        $nicProperties.IPConfigurations[0].Properties.PrivateIPAddress = $ip
        $defaultGwNic = New-NetworkcontrollerNetworkinterface -ConnectionUri $restUrl -ResourceId $subnetId -Properties $nicProperties -Force -PassInnerException
        $defaultGwNic = Get-NetworkcontrollerNetworkinterface -ConnectionUri $restUrl -ResourceId $subnetId
        $mac = $defaultGwNic.Properties.PrivateMacAddress
        Write-TraceLog "Enable-LogicalNetworkDefaultGateway: Lnet Subnet $subnetId, IP $ip, Mac $mac, PrefixLen $prefixLen, Vlan $vlan"
        $nicsToAdd += @{Name = $subnetId; MacAddress = $mac; IPAddress = $ip; PrefixLen = $prefixLen; Vlan = $vlan; AddressPrefix = $addressPrefix }
    }

    Invoke-ReliableCommand -computerName $vmHost -Credential $cred -scriptBlock {
        param($routerVM)
        $vm = Get-VM $routerVM
        if ($vm.Generation -lt 2) {
            Stop-VM -VMName $routerVM -Force
        }        
    } -ArgumentList @($routerVM)
    
    $sdnSwitch = Get-SdnSwitchName
    Write-TraceLog "Enable-LogicalNetworkDefaultGateway: Adding default gateway netadapter to nat vm $routerVM on switch $sdnSwitch"
    Invoke-ReliableCommand -computerName $vmHost -Credential $cred -scriptBlock {
        param($routerVM, $nicsToAdd, $switchName)

        foreach ($nic in $nicsToAdd) {
            Get-VMNetworkAdapter -VMName $routerVM -Name $nic.Name -ErrorAction SilentlyContinue | Remove-VMNetworkAdapter -ErrorAction SilentlyContinue -Confirm:$false
            Add-VMNetworkAdapter -VMName $routerVM -SwitchName $switchName -StaticMacAddress $nic.MacAddress -ErrorAction Stop -Name $nic.Name
            Set-VMNetworkAdapterVLan -VMName $routerVM -VMNetworkAdapterName $nic.Name -Access -VlanId $nic.Vlan -ErrorAction Stop
        }
    } -ArgumentList @($routerVM, $nicsToAdd, $sdnSwitch)

    foreach ($nic in $nicsToAdd) {
        Enable-SDNExpressVMPort -ComputerName $vmHost -VMName $routerVM -VMNetworkAdapterName $nic.Name -Credential $cred
    }

    Invoke-ReliableCommand -computerName $vmHost -Credential $cred -scriptBlock {
        param($routerVM)
        Start-VM -VMName $routerVM
    } -ArgumentList @($routerVM)

    WaitforComputerToBeReady -ComputerName $routerVM -Credential $cred

    Write-TraceLog "Enable-LogicalNetworkDefaultGateway: setting ip address for default gw nics on nat vm $routerVM"
    Invoke-ReliableCommand -computerName $routerVM -Credential $cred -scriptBlock {
        param($nicsToAdd)
        foreach ($nic in $nicsToAdd) {
            $netAdapter = Get-NetAdapter | Where-Object { $($_.MacAddress.split("-") -join "") -eq $nic.MacAddress }
            if ($netAdapter -eq $null) {
                throw "Unable to find nic with mac $($nic.MacAddress)"
            }

            if ($(Get-NetAdapter -Name $nic.Name -ErrorAction SilentlyContinue) -eq $null) {
                $netAdapter | Rename-NetAdapter -NewName $nic.Name -ErrorAction SilentlyContinue
                
                #Rename-NetAdapter : {Object Exists} An attempt was made to create an object and the object name already existed.
            }
            
            # $newName = Get-Netadapter -ifIndex $netAdapter.IfIndex
            # cmd.exe /c "netsh routing ip nat add interface $newName" | Out-Null

            New-NetIPAddress -Interfaceindex $netAdapter.IfIndex -AddressFamily IPv4 -IPAddress $nic.IPAddress -PrefixLength $nic.PrefixLen | Out-Null
        }
    } -ArgumentList @($nicsToAdd)

    $mgmtNetConfig = Get-MgmtNetworkConfig
    $mgmtGw = $mgmtNetConfig.properties.subnets[0].properties.defaultGateways[0]

    # Forward lnet traffic to nat VM.
    foreach ($nic in $nicsToAdd) {
        Set-StaticRoute -Computer $hyperVHosts -Credential $cred -SwitchName $sdnSwitch -NicName $(Get-MgmtNicName) -DestinationPrefix $nic.AddressPrefix -NextHop $mgmtGw
    }

    Invoke-ReliableCommand -computerName $routerVM -Credential $cred -scriptBlock {
        Restart-Service RemoteAccess -Force
    }
}

function Add-ManagmentRouteToVirtualGateways {

    <#
    .Synopsis
        Adds the management network route to the virtual gateway resource in NC to enable connectivity from the
        host to VNET
    .PARAMETER NCVirtualGateways
        Array of virtual gateway resources in NC.
    .PARAMETER BgpConnections
        NetworkConnections with BGP configured
    #>

    param(
        [Parameter(Mandatory = $true)]
        [Array]$NCVirtualGateways,
        [Parameter(Mandatory = $true)]
        [hashtable]$BgpConnections
        
    )

    $restEndpoint = Get-TurnKeySdnRestEndpoint
    $restUrl = "https://$restEndpoint"
    $mgmtNetConfig = Get-MgmtNetworkConfig
    $mgmtPrefix = $mgmtNetConfig.properties.subnets[0].properties.addressPrefix

    $NCVirtualGateways = Get-NetworkControllerVirtualGateway -ConnectionUri  $restUrl
    foreach ($vg in $NCVirtualGateways) {
        [array]$bgpCons = $BgpConnections[$vg.ResourceId]
        $update = $false
        foreach ($nw in $vg.Properties.NetworkConnections) {

            if ($bgpCons -ne $null -and $bgpCons.Contains($nw.ResourceId)) {
                # No need to add mgmt route to networkconnection static route if bgp is enabled.
                # Route is pushed by bgp.
                Write-TraceLog "Add-ManagmentRouteToVirtualGateways: Virtual gateway $($vg.ResourceId) nw $($nw.ResourceId) has bgp enabled, skipping static route addition"
                continue
            }

            $foundMgmt = $false
            foreach ($route in $nw.Properties.Routes) {
                if ($route.DestinationPrefix -eq $mgmtPrefix) {
                    $foundMgmt = $true
                    break
                }
            }

            if (-not $foundMgmt) {
                Write-TraceLog "Add-ManagmentRouteToVirtualGateways: Adding management network $mgmtPrefix to the routes of virtual gateway $($vg.ResourceId)"
                $mgmtRoute = New-Object Microsoft.Windows.NetworkController.RouteInfo
                $mgmtRoute.DestinationPrefix = $mgmtPrefix
                $mgmtRoute.NextHop = "0.0.0.0"
                $mgmtRoute.Metric = 10
                $mgmtRoute.Protocol = "Static"
                $nw.Properties.Routes += $mgmtRoute
                $update = $true
            }
        }

        if ($update) {
            New-NetworkControllerVirtualGateway -ResourceId $vg.ResourceId -Properties $vg.Properties -ConnectionUri $restUrl -Force -PassInnerException | Out-Null
        }
    }
}

function Get-SourceNetworkInterface {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Destination,
        [Parameter(Mandatory = $true)]
        [string]$ComputerName,
        [Parameter(Mandatory = $false)]
        [pscredential]$Credential = [pscredential]::Empty
    )

    $result = Invoke-ReliableCommand -ComputerName $ComputerName -Credential $Credential -ScriptBlock {
        param($Destination)
        $result = Test-NetConnection $Destination
        return $result.InterfaceAlias
    } -ArgumentList $($Destination)

    return $result
}

function Update-WorkloadSecrets {
    param(
        [Parameter(Mandatory = $true)]
        [string]$WorkloadConfigPath
    )

    $depId = Get-TurnKeySdnDeploymentId
    $vgwsConfigJson = Get-SdnWorkloadVirtualGateways -WorkloadConfigPath $WorkloadConfigPath

    foreach ($vgw in $vgwsConfigJson) {
        $vgwJson = get-content $vgw.FullName | ConvertFrom-Json
        foreach ($con in $vgwJson.Properties.networkConnections) {
            if ($con.Properties.connectionType -ieq "IPsec") {
                Write-TraceLog "Update-WorkloadSecrets: Updating VG $($vgwJson.resourceId) shared secret for connection $($con.resourceId)"    
                $con.Properties.IPSecConfiguration.SharedSecret = $depId
            }
        }
        $vgwJson | ConvertTo-Json -Depth 100 | Out-File $vgw.FullName -Force
    }
}
function Update-WorkloadIPAddresses {
    param(
        [Parameter(Mandatory = $true)]
        [string]$WorkloadConfigPath
    )

    $stateFile = Join-Path $WorkloadConfigPath "addressRandmoized"

    if (Test-Path $stateFile) {
        Write-TraceLog "Update-WorkloadIPAddresses: IP addresses already randomized in workload config $WorkloadConfigPath"
        return
    }

    Write-TraceLog "Update-WorkloadIPAddresses: Randomizing workload ip addresses in workload config $WorkloadConfigPath"

    #Randmoize lnet ips
    $lnets = Get-SdnWorkloadLogicalNetworks -WorkloadConfigPath $WorkloadConfigPath

    foreach ($lnet in $lnets) {
        $lnetJson = Get-Content $lnet.FullName | ConvertFrom-Json
        $updatedNetwork = Update-LogicalNetworkAddresses -Network $lnetJson
        $updatedNetwork | ConvertTo-Json -Depth 100 | Out-File $lnet.FullName -Force
    }

    #Randmozie gw networkconnection ips
    $vgwsConfigJson = Get-SdnWorkloadVirtualGateways -WorkloadConfigPath $WorkloadConfigPath

    foreach ($vgw in $vgwsConfigJson) {
        $vgwJson = get-content $vgw.FullName | ConvertFrom-Json
        foreach ($con in $vgwJson.Properties.networkConnections) {
            if (-not [String]::IsNullOrEmpty($con.Properties.DestinationIPAddress)) {
                $con.Properties.DestinationIPAddress = Update-IPv4Address -Address $con.Properties.DestinationIPAddress
            }
            
            foreach ($address in $con.Properties.ipAddresses) {
                $address.ipAddress = Update-IPv4Address -Address $address.ipAddress
            }
            
            $updatedPeerAddresses = @()
            foreach ($peerAddress in $con.Properties.peerIPAddresses) {
                $updatedPeerAddresses += Update-IPv4Address -Address $peerAddress
            }
            $con.Properties.peerIPAddresses = $updatedPeerAddresses
            
            $routes = $con.Properties.Routes
            foreach ($r in $routes) {
                $r.DestinationPrefix = Update-IPv4Address -Address $r.DestinationPrefix
            }
        }

        foreach ($router in $vgwJson.Properties.bgpRouters) {
            foreach ($peer in $router.Properties.bgpPeers) {
                $peer.Properties.peerIpAddress = Update-IPv4Address -Address $peer.Properties.peerIpAddress
            }
        }

        $vgwJson | ConvertTo-Json -Depth 100 | Out-File $vgw.FullName -Force
    }

    $nics = Get-SdnWorkloadNetworkInterfaces -WorkloadConfigPath $WorkloadConfigPath

    #Randmozie lnet nic ips
    foreach ($nic in $nics) {
        $nicJson = get-content $nic.FullName | ConvertFrom-Json
        foreach ($ipconfig in $nicJson.Properties.IPConfigurations) {

            if ($ipconfig.Properties.subnet.resourceRef -ilike "/virtualNetworks/*") {
                break
            }

            if ($ipconfig.Properties.PrivateIPAddress -ne $null) {
                $ipconfig.Properties.PrivateIPAddress = Update-IPv4Address -Address $ipconfig.Properties.PrivateIPAddress    
            }
        }

        $nicJson | ConvertTo-Json -Depth 100 | Out-File $nic.FullName -Force
    }

    New-Item $stateFile -ItemType File -Force | Out-Null
}

function Add-TurneKeyVMToCluster {

    param(
        [Parameter(Mandatory = $true)]
        $Tag
    )

    Write-TraceLog "Add-TurneKeyVMToCluster: Adding VMs with tag $Tag to cluster"

    $isCluster = Test-IsCluster

    if (-not $isCluster) {
        Write-TraceLog "Add-TurneKeyVMToCluster: Not a cluster, skipping"
        return
    }

    $addVMToCluster = {

        param($Tag)

        $vms = Get-VM | Where-Object { $_.Notes.Split(";") -icontains $Tag }

        if ($vms -eq $null) {
            return
        }

        foreach ($vm in $vms) {
            $isPresent = (Get-ClusterResource -VMId $vm.VMId -ErrorAction SilentlyContinue) -ne $null

            if ($isPresent) {
                continue
            }

            $vm | Add-ClusterVirtualMachineRole -ErrorAction SilentlyContinue | Out-Null
        }
    }

    Invoke-CmdOnInfraHosts -scriptBlock $addVMToCluster -args @($Tag)
}

function Remove-TurnKeyVMs {
    param(
        [parameter(Mandatory = $true)][string] $Tag
    )

    Write-TraceLog "Remove-TurnKeyVMs: Removing VMs with tag $Tag"
    $isCluster = Test-IsCluster

    $deleteVM = {       
        param($Tag, $IsCluster)
        $vms = Get-VM | Where-Object { $_.Notes.Split(";") -icontains $Tag }
 
        if ($vms -ne $null) {
            if ($IsCluster) {
                foreach ($vm in $vms) {
                    Remove-ClusterGroup -VMId $vm.VMId -RemoveResources -Force -ErrorAction SilentlyContinue
                }
            }
            
            $hardDrives = ($vms | Select-Object -ExpandProperty HardDrives).path
            $vmStorage = $vms.path
            $vms | Stop-VM -TurnOff -force
            $vms | Remove-VM -Force
 
            if ($hardDrives -ne $null -and $hardDrives.Count -gt 0) {
                Remove-Item $hardDrives -ErrorAction SilentlyContinue -Force -Recurse
            }

            if ($vmStorage -ne $null -and $vmStorage.Count -gt 0) {
                Remove-Item $vmStorage -ErrorAction SilentlyContinue -Force -Recurse
            }   
        }
    }

    Invoke-CmdOnInfraHosts -scriptBlock $deleteVM -args @($Tag, $isCluster)
}
function Remove-WorkloadVMs {

    try {
        Write-TraceLog "Remove-WorkloadVMs: Removing workload vms"
        Remove-DomainJoinedVMs -Tag "Role=DataCenterGateway"
        Remove-TurnKeyVMs -Tag "Role=Workload"
    }
    catch {
        Write-TraceLog "Remove-WorkloadVMs: Failed to remove workload vms, error $_" -Warning
    }
}

function Remove-RouterVMWorkloadAdapters {

    $cleanupRouterVM = {       
        $vms = Get-VM | Where-Object { $_.Notes.Split(";") -icontains "Role=Router" }
        
        if ($vms -eq $null) {
            return
        }
        
        $vms | ForEach-Object {
            Get-VMNetworkAdapter -VM $_ | Where-Object { $_.Name -ine "Management" -and $_.Name -ine "Corp" -and $_.Name -ine "HNVPA" } | Remove-VMNetworkAdapter -Confirm:$false -ErrorAction SilentlyContinue
        }
        
        $vms | Restart-VM -Force -Type Reboot
        
    }

    try {
        Write-TraceLog "Remove-RouterVMWorkloadAdapters: Removing workload adapters from router vm"
        Invoke-CmdOnInfraHosts -scriptBlock $cleanupRouterVM
    }
    catch {
        Write-TraceLog "Remove-RouterVMWorkloadAdapters: Failed to remove workload adapters from router vm, error $_" -Warning
    }
}

function Remove-DomainJoinedVMs {
    param(
        [parameter(Mandatory = $true)][string] $Tag
    )

    Write-TraceLog "Remove-DomainJoinedVMs: Removing domain joined vms with tag $Tag"

    $unjoinDomain = {
        param($DomainCredential, $Tag)
        $vms = Get-VM | Where-Object { 
            $_.Notes.Split(";") -icontains $Tag }
        
        if ($vms -ne $null) {
            $vms | ForEach-Object {
                Remove-Computer -UnjoinDomainCredential $DomainCredential -ComputerName $_.Name `
                    -LocalCredential $DomainCredential -ErrorAction SilentlyContinue -Force 
            }          
        }
    }
    
    $cred = Get-TurnKeySdnCred
    Invoke-CmdOnInfraHosts -scriptBlock $unjoinDomain -Args @($cred, $Tag)
    Remove-TurnKeyVMs -Tag $Tag
}
function Remove-SdnConfig {

    try {
        $sdnConfig = Get-SdnConfig
        $fcncdb = $sdnConfig.networkController.FC.DatabaseLocation

        if ([String]::IsNullOrEmpty($fcncdb)) {
            $fcncdb = Get-DefaultNetworkControllerDBLocation
        }

        Write-TraceLog "Uninstall-TurnKeySdn: Workaround - FCNC uninstall does not delete db. Deleting $fcncdb"
        Remove-Item $fcncdb -ErrorAction SilentlyContinue -Recurse -Force
    }
    catch {
        Write-TraceLog "Remove-SdnConfig: Failed to remove sdn config, error $_" -Warning
    }
}

function Remove-SdnVMs {
    try {
        Remove-DomainJoinedVMs -Tag "IsSDN=True"
    }
    catch {
        Write-TraceLog "Remove-SdnVMs: Failed to remove sdn vms, error $_" -Warning
    }
}

function Remove-InfraVMs {
    try {
        Remove-DomainJoinedVMs -Tag "IsInfra=True"
    }
    catch {
        Write-TraceLog "Remove-InfraVMs: Failed to remove infra vms, error $_" -Warning
    }
}

function Remove-PhysicalHostVnic {

    $cleanupScript = {
        param($MgmtHostNicName)
        Remove-VMNetworkAdapter -ManagementOS -Name $MgmtHostNicName -ErrorAction SilentlyContinue
        Remove-VMNetworkAdapter -ManagementOS -Name PA* -ErrorAction SilentlyContinue
        Remove-VMNetworkAdapter -ManagementOS -Name DR* -ErrorAction SilentlyContinue
    }

    try {
        $testNic = Get-MgmtNicName
        Write-TraceLog "Remove-PhysicalHostVnic: Removing $testNic, PA* and DR* adapters from all hosts in the deployment"
        Invoke-CmdOnInfraHosts -scriptBlock $cleanupScript -args @($testNic)
    }
    catch {
        Write-TraceLog "Remove-PhysicalHostVnic: Failed to remove from all hosts in the deployment, error $_" -Warning
    }
}

function Remove-TestRoutesOnHostVnic {

    $testNic = Get-MgmtNicName
    $mgmtNetConfig = Get-MgmtNetworkConfig
    $mgmtGw = $mgmtNetConfig.properties.subnets[0].properties.defaultGateways[0]

    Write-TraceLog "Remove-TestRoutesOnHostVnic: Removing test routes from vnic $testNic on all hosts in the deployment"

    $cleanupScript = {
        param($MgmtHostNicName, $ManagementGateway)
        $nic = Get-VMNetworkAdapter -ManagementOS -Name $MgmtHostNicName -ErrorAction SilentlyContinue
        if ($nic -eq $null) {
            return
        }

        $adapter = Get-NetAdapter | Where-Object { $($_.MacAddress -replace "-", "") -eq $($nic.MacAddress) }

        if ($adapter -eq $null) {
            return
        }

        $routes = Get-NetRoute -ifIndex $adapter.ifIndex | Where-Object { $_.NextHop -eq $ManagementGateway }
        if ($routes -eq $null) {
            return
        }

        $routes | Remove-NetRoute -Confirm:$false
    }

    Invoke-CmdOnInfraHosts -scriptBlock $cleanupScript -args @($testNic, $mgmtGw)
}

function Remove-DeploymentCertificates {

    $cleanupScript = {
        param($depId)
        $rootCerts = Get-ChildItem Cert:\LocalMachine\Root | where-object { $_.Subject -ilike "*$depId*" }
        $rootCerts | Remove-Item -ErrorAction SilentlyContinue -Force
        $myCerts = Get-ChildItem Cert:\LocalMachine\My | where-object { $_.Subject -ilike "*$depId*" }
        $myCerts | Remove-Item -ErrorAction SilentlyContinue -Force
    }

    try {
        $depId = Get-TurnKeySdnDeploymentId
        Write-TraceLog "Remove-DeploymentCertificates: Removing certificates with subject *$depId* from all hosts in the deployment"
        Invoke-CmdOnInfraHosts -scriptBlock $cleanupScript -args @($depId)
    }
    catch {
        Write-TraceLog "Remove-DeploymentCertificates: Failed to remove certificates from all hosts in the deployment, error $_" -Warning
    }
}

function Get-DefaultWorkloadMarinerVhd {

    try {
        Get-SdnWorkloadVhd -os Linux -osSku Mariner | Out-Null
        return $null
    }
    catch {
        Write-TraceLog "Get-DefaultWorkloadMarinerVhd: No workload vhd configured, trying to auto copy"
    }

    try {
        $vhdStore = Get-VhdStore
        $vhdFolder = Join-Path $vhdStore "marinerVhd"
        $vhdFile = Get-DefaultWorkloadVhdFile

        $vhd = Join-Path $vhdFolder (Get-DefaultWorkloadVhdFile)

        if (Test-Path $vhd) {
            Write-TraceLog "Get-DefaultWorkloadMarinerVhd: Existing vhd found at $destVhd"
            return @($vhdFolder, $vhdFile)
        }

        $share = Get-DefaultWorkloadVhdShare
        $cred = Get-WttNAOCred

        $connected = Connect-SMBShare -RemotePath $share -Credential $cred -RestartLanManServer

        if (-not $connected) {
            throw "Unable to connect to $share"
        }

        if (-not $(Test-Path $vhdFolder -ErrorAction Stop)) {
            New-Item $vhdFolder -ItemType Directory | Out-Null
        }

        $shareVhd = Join-Path $share $vhdFile

        Write-TraceLog "Get-DefaultWorkloadMarinerVhd: Copying $shareVhd to $vhdFolder"
        try {
            Start-BitsTransfer -Source $shareVhd -Destination $vhdFolder -Description $shareVhd -DisplayName "WorkloadVHD"
        }
        catch {
            Copy-Item $shareVhd $vhdFolder
        }

        return @($vhdFolder, $vhdFile)
    }
    catch {
        Write-TraceLog "Get-DefaultWorkloadMarinerVhd: Failed to auto copy default workload vhd, error $_" -Warning
    }

    return $null
}
function Get-VirtualGatewayVnetPrefix {
    param(
        [Parameter(Mandatory = $true)]
        [string]$VirtualGatewayResourceId
    )

    $restEndpoint = Get-TurnKeySdnRestEndpoint
    $restUrl = "https://$restEndpoint"  
    $vg = Get-NetworkControllerVirtualGateway -ConnectionUri  $restUrl -ResourceID $VirtualGatewayResourceId
    $subnetEle = $vg.Properties.GatewaySubnets[0].ResourceRef.Split("/", [StringSplitOptions]::RemoveEmptyEntries)
    $vnetId = $subnetEle[1]
    $vnet = Get-NetworkControllerVirtualNetwork -ResourceId $vnetId -ConnectionUri $restUrl
    return $vnet.Properties.AddressSpace.AddressPrefixes
}

function Add-StaticRoutesForVirtualGateways {
    <#
    .SYNOPSIS
        Adds static routes for vnet prefixes corresponding to virtual gateway connections on physical hosts and NAT VM.
    .PARAMETER NCVirtualGateways
        Array of virtual gateway resources in NC.
    #>

    param(
        [Parameter(Mandatory = $true)]
        [Array]$NCVirtualGateways,
        [hashtable]$BgpConnections
    )

    $mgmtNetConfig = Get-MgmtNetworkConfig
    $mgmtGw = $mgmtNetConfig.properties.subnets[0].properties.defaultGateways[0]
    $sdnSwitchName = Get-SdnSwitchName
    $cred = Get-TurnKeySdnCred
    $routerVM = Get-RouterVMName
    $depConfig = Get-DeploymentConfig
    $hyperVHosts = $depConfig.hyperVHosts

    foreach ($vg in $NCVirtualGateways) {

        $destinationPrefixes = Get-VirtualGatewayVnetPrefix -VirtualGatewayResourceId $vg.ResourceId
        [array]$bgpCons = $BgpConnections[$vg.ResourceId]
        foreach ($connection in $vg.Properties.networkConnections) {
            if ($connection.Properties.connectionType -ieq "IPsec") {
                # Next hop for IPsec connection from Router VM is datacenter gateway VM.
                $nextHop = $connection.Properties.DestinationIPAddress 
            }
            
            if ($connection.Properties.connectionType -ieq "L3") {
                # Next hop for L3 connection from Router VM is the L3 IP in tenant compartment of NC gateway VM.
                $nextHop = $connection.Properties.ipAddresses[0].ipAddress 
            }

            $isBgpEnabled = $false
            if ($bgpCons -ne $null -and $bgpCons.Contains($connection.ResourceId)) {
                $isBgpEnabled = $true
            }

            $routerVMNic = Get-SourceNetworkInterface -ComputerName $routerVM -Credential $cred -Destination $nextHop
            Write-TraceLog "Set-StaticRoutesForVirtualGateways: Setting static route for virtual gateway $($vg.ResourceId), `
                connection $($connection.ResourceId) routerVMNic $routerVMNic, nextHop $nextHop"

            foreach ($prefix in $destinationPrefixes) {
                # Add route for each vnet IP prefix on physical host and router vm.
                # For physical host, next hop is the router VM(the default gateway of management network)
                Set-StaticRoute -Computer $hyperVHosts -Credential $cred -SwitchName $sdnSwitchName -NicName $(Get-MgmtNicName) -DestinationPrefix $prefix -NextHop $mgmtGw
                # Ideally if bgp is enabled, VNET routes should come via bgp and static route is not required.
                # But bgp connection is between datacneter gateway and NC gateway VM.
                # There is no upstream bgp btw router vm and datacenter gateway vm.
                # So static routes are still required on router vm.
                # TODO: Add upstream bgp peer to router vm.
                #if (-not $isBgpEnabled) {
                Set-StaticRoute -Computer $routerVM -Credential $cred -NicName $routerVMNic -DestinationPrefix $prefix -NextHop $nextHop
                #}
            }
        }
    }
}

function Get-IPsecSharedSecrets {
    param(
        [Parameter(Mandatory = $true)][String]$WorkloadConfigPath
    )

    $vgwsConfigJsons = Get-SdnWorkloadVirtualGateways -WorkloadConfigPath $WorkloadConfigPath
    if ($vgwsConfigJsons -eq $null -or $vgwsConfigJsons.Count -eq 0) {
        return @{}
    }

    $sharedSecrets = @{}
    foreach ($vgw in $vgwsConfigJsons) {
        $vgwJson = get-content $vgw.FullName | ConvertFrom-Json
        foreach ($con in $vgwJson.Properties.networkConnections) {
            if ($con.Properties.connectionType -ieq "IPsec") {
                $sharedSecrets[$con.Properties.DestinationIPAddress] = $con.Properties.IpSecConfiguration.sharedSecret
            }
        }
    }

    return $sharedSecrets
}
function Initialize-DataCenterGWVM {
    param(
        [Parameter(Mandatory = $true)][String]$ComputerName,
        [Parameter(Mandatory = $true)][pscredential]$Credential
    )

    Invoke-ReliableCommand -ComputerName $ComputerName -Credential $Credential -scriptBlock {
        if ((get-remoteaccess).VpnS2SStatus -ne "Installed") {
            Install-RemoteAccess -VpnType VpnS2S
        }
        else {
            Restart-Service RemoteAccess -Force
        }

        $retries = 5
        while ($retries--) {
            try {
                # Wait for RRAS to be ready
                Get-VpnS2SInterface -ErrorAction Stop | Out-Null                     
            }
            catch {
                Start-Sleep 10
                Continue
            }

            break
        }
    }
}
function Add-BgpPeerOnVM {
    param(
        [Parameter(Mandatory = $true)][String]$ComputerName,
        [Parameter(Mandatory = $true)][pscredential]$Credential,
        [Parameter(Mandatory = $true)][String]$RouterIP,
        [Parameter(Mandatory = $true)][String]$LocalASN,
        [Parameter(Mandatory = $true)][String]$PeerIP,
        [Parameter(Mandatory = $true)][String]$PeerASN,
        [Parameter(Mandatory = $true)][Array]$CustomRoutes
    )

    $addBgpPeer = {
        param($RouterIP, $LocalASN, $PeerIP, $PeerASN, $CustomRoutes)
        try {
            $router = Get-BgpRouter -ErrorAction SilentlyContinue
        }
        catch {}

        if ($router -eq $null) {
            Add-BgpRouter -BgpIdentifier $RouterIP -LocalASN $LocalASN | Out-Null
        }

        $bgpPeer = Get-BgpPeer -Name $PeerIP -ErrorAction SilentlyContinue

        if ($bgpPeer -ne $null) {
            Remove-BgpPeer -Name $PeerIP -Force -Confirm:$false -ErrorAction SilentlyContinue
        }

        Add-BgpPeer -Name $PeerIP -LocalIPAddress $RouterIP -PeerIPAddress $PeerIP -PeerASN $PeerASN | Out-Null
        Add-BgpCustomRoute -Network $CustomRoutes
    }

    Invoke-ReliableCommand -ComputerName $ComputerName -Credential $Credential -scriptBlock $addBgpPeer `
        -ArgumentList @($RouterIP, $LocalASN, $PeerIP, $PeerASN, $CustomRoutes)
}

function Add-IPsecVPNConnection {

    param(
        [Parameter(Mandatory = $true)][Array]$DataCenterGWs,
        [Parameter(Mandatory = $true)][hashtable]$SharedSecrets
    )

    $restEndpoint = Get-TurnKeySdnRestEndpoint
    $restUrl = "https://$restEndpoint"  

    $addVpnConnection = {
        param($connectionName, $destinationIP, $mainMode, $quickMode, $sharedSecret, $destinationPrefixes)
        
        $con = Get-VpnS2SInterface -Name $connectionName -ErrorAction SilentlyContinue

        if ($con -ne $null) {
            Set-VpnS2SInterface -Name $connectionName -AdminStatus $false -Confirm:$false -Force
            Remove-VpnS2SInterface -Name $connectionName -Force -Confirm:$false -ErrorAction SilentlyContinue
        }

        Add-VpnS2SInterface `
            -Name $connectionName `
            -CustomPolicy `
            -Destination $destinationIP `
            -Protocol IKEv2 `
            -SharedSecret $sharedSecret `
            -CipherTransformConstants $quickMode.CipherTransformationConstant `
            -AuthenticationTransformConstants $quickMode.AuthenticationTransformationConstant `
            -DHGroup $mainMode.DiffieHellmanGroup `
            -EncryptionMethod $mainMode.EncryptionAlgorithm `
            -IntegrityCheckMethod $mainMode.IntegrityAlgorithm `
            -AuthenticationMethod PSKOnly `
            -ResponderAuthenticationMethod PSKOnly `
            -Persistent

        $prefixArray = @()
        foreach ($prefix in $destinationPrefixes) {
            $prefixArray += $($prefix + ":10")
        }

        Set-VpnS2SInterface -Name $connectionName -IPv4Subnet $prefixArray
    }

    $vgws = Get-NetworkControllerVirtualGateway -ConnectionUri  $restUrl
    $cred = Get-TurnKeySdnCred

    foreach ($gw in $DataCenterGWs) {
        Initialize-DataCenterGWVM -ComputerName $gw.Name -Credential $cred

        $connection = $null
        foreach ($vg in $vgws) {
            $connection = $vg.Properties.networkConnections | Where-Object {
                $_.properties.destinationIPAddress -eq $gw.IPAddress -and $_.properties.connectionType -ieq "IPsec"
            }

            if ($connection -ne $null) {
                break
            }
        }

        if ($connection -eq $null) {
            Write-TraceLog "No virtual gateway found for datacenter gateway $($gw.Name)"
            continue
        }

        $destinationPrefixes = Get-VirtualGatewayVnetPrefix -VirtualGatewayResourceId $vg.ResourceId

        $sharedSecret = $SharedSecrets[$connection.Properties.DestinationIPAddress]
        Write-TraceLog "Adding VPN connection for $($gw.Name), ConnectionName: $($connection.ResourceId), $($connection.Properties.DestinationIPAddress) SharedSecret: $sharedSecret, DestinationPrefixes: $destinationPrefixes"
        Invoke-ReliableCommand -computerName $gw.Name -Credential $cred -scriptBlock $addVpnConnection `
            -ArgumentList @($connection.ResourceId, $connection.Properties.SourceIPAddress, `
                $connection.Properties.IpSecConfiguration.MainMode, $connection.Properties.IpSecConfiguration.QuickMode, $sharedSecret, $destinationPrefixes)
    }
}

function Add-BgpPeerOnDataCenterGW {

    param(
        [Parameter(Mandatory = $true)][Array]$DataCenterGWs
    )

    $restEndpoint = Get-TurnKeySdnRestEndpoint
    $restUrl = "https://$restEndpoint"  

    
    $vgws = Get-NetworkControllerVirtualGateway -ConnectionUri  $restUrl
    $cred = Get-TurnKeySdnCred
    $bgpConnections = @{}
    foreach ($gw in $DataCenterGWs) {

        $connection = $null
        foreach ($vg in $vgws) {
            $connection = $vg.Properties.networkConnections | Where-Object {
                $_.properties.destinationIPAddress -eq $gw.IPAddress -and $_.properties.connectionType -ieq "IPsec"
            }
            if ($connection -ne $null) {
                $router = $vg.Properties.BgpRouters | where-object { $_.Properties.BgpPeers | Where-Object { $_.Properties.peerIpAddress -eq $gw.IPAddress } }
                break
            }
        }

        if ($router -eq $null) {
            Write-TraceLog "Add-BgpPeerOnDataCenterGW No bgp peer found for datacenter gateway $($gw.Name), skipping"
            continue
        }

        $localASN = $router.Properties.localASN
        $peerIP = $router.Properties.RouterIP[0]
        $peerASN = $router.Properties.ExtAsNumber.Split(".") | Select -Last 1
        $localPeer = $router.Properties.BgpPeers | Where-Object { $_.Properties.peerIpAddress -eq $gw.IPAddress }
        $localASN = $localPeer.Properties.AsNumber
        $routerIP = $localPeer.Properties.PeerIpAddress
        $mgmtNetConfig = Get-MgmtNetworkConfig
        $mgmtPrefix = $mgmtNetConfig.properties.subnets[0].properties.addressPrefix

        Write-TraceLog "Add-BgpPeerOnDataCenterGW Adding BGP peer on $($gw.Name), LocalASN: $localASN, PeerIP: $peerIP, PeerASN: $peerASN"
        Add-BgpPeerOnVM -ComputerName $gw.Name -Credential $cred -RouterIP $routerIP `
            -LocalASN $localASN -PeerIP $peerIP -PeerASN $peerASN -CustomRoutes @($mgmtPrefix)

        $bgpConnections[$vg.ResourceId] = $connection.ResourceId
    }

    return $bgpConnections
}

function Add-DataCenterGatewayNetwork {

    <#
    # .SYNOPSIS
    # Enables gateway workload data path
    # .DESCRIPTION
    # Creates a datacenter gateway VM for each of the virtual gateway connection configured in the workload config.
    # S2S vpn interface is added to the datacenter gateway VM for each gateway connection.
    # Network for datacenter gateway VM is configured using the jsons under workload\gateway\datacenternetwork folder.
    # The logical network resources specify the logical network to be used for the datacenter gateway.
    # Datacenter network is configured as an actual logical network in SDN.
    # This data center logical network corresponds to the destination ip address of the virtual gateway connection.
    # Under workload\gateway\datacenternetwork\jsons\networkinterfaces, a network interface must exist with the tag "GatewayConnection" for each datacenter gateway VM.
    # IP address of this networkinterface must correspond to the destination ip address of the virtual gateway connection.
    # To avoid conflict between different setups, various IP addresses are randmoized unless randomizeAddresses is set to false in the deployment config.
    # To enable datapath from physical host following static routes are added,
    # - On physical host, route to the vnet ip with next hop as management gateway(nat vm)
    # - On nat vm, route to the vnet ip with next hop as datacenter gateway corresponding to the vnet.
    # - Management address prefix is auto appended to the virtual gateway connection routes. This enables return traffic from the VNET VM to physical host.
    # Traffic flow -> Physical Host->Nat VM->Datacenter Gateway VM->S2S VPN->SDN Gateway->VNET VM
    # .PARAMETER WorkloadConfigPath
    # Path to the datacenternetwork network workload config
    #>


    param(
        $WorkloadConfigPath
    )

    Write-TraceLog "Enable-GatewayWorkload: Enabling gateway workload"

    $mgmtNetConfig = Get-MgmtNetworkConfig
    $mgmtGw =  $mgmtNetConfig.properties.subnets[0].properties.defaultGateways[0]
    $depId = Get-TurnKeySdnDeploymentId
    $restEndpoint = Get-TurnKeySdnRestEndpoint
    $restUrl = "https://$restEndpoint"

    $vgwsConfigJson = Get-SdnWorkloadVirtualGateways -WorkloadConfigPath $WorkloadConfigPath
    if ($vgwsConfigJson -eq $null -or $vgwsConfigJson.Count -eq 0) {
        Write-TraceLog "No virtual gateway configuration found. Cannot configure gateway connections."
        return
    }

    $dcNetworkPath = Join-Path $WorkloadConfigPath "datacenternetwork"
    if (-not (Test-Path $dcNetworkPath)) {
        return @()
    }

    Update-WorkloadIPAddresses -WorkloadConfigPath $dcNetworkPath

    $dcLnets = Get-SdnWorkloadLogicalNetworks -WorkloadConfigPath $dcNetworkPath

    if ($dcLnets -eq $null -or $dcLnets.Count -eq 0) {
        Write-TraceLog "No datacenter logical networks found at $dcNetworkPath. IPsec VPN will not be configured" -Warning
        return @()
    }   

    $nics = Get-SdnWorkloadNetworkInterfaces -WorkloadConfigPath $dcNetworkPath

    if ($nics -eq $null ) {
        Write-TraceLog "No network interface json under datacenter network, $dcNetworkPath. IPsec VPN will not be configured" -Warning
        return @()
    }  

    $gwNics = @()
    $nics | ForEach-Object {
        $nicJson = get-content $_.FullName | ConvertFrom-Json
        if ($nicJson.Tags.Role -ieq "GatewayConnection" ) {
            $gwNics += $_
        }
    }

    if ($gwNics.Count -eq 0 ) {
        Write-TraceLog "No networkinterface json under datacenter network $dcNetworkPath with GatewayConnection tag. IPsec VPN will not be configured" -Warning
        return @()
    } 

    $restEndpoint = Get-TurnKeySdnRestEndpoint
    $restoreReplayScript = Get-RestoreReplayScript
    & $restoreReplayScript -OperationType Put -BackupDirectoryOrZipFile $dcNetworkPath -BackupType Manual -Verbose -NCRestEndPoint $restEndpoint -Force -Confirm:$false    
    
    $dcLnets | ForEach-Object {
        $lnetJson = get-content $_.FullName | ConvertFrom-Json
        $resourceId = $lnetJson.resourceId
        Enable-LogicalNetworkDefaultGateway -LogicalNetworkResourceId $resourceId  
    }


    $dataCenterGWs = @()
    $depId = Get-TurnKeySdnDeploymentId
    $restUrl = "https://$restEndpoint"  

    $gwNics | ForEach-Object {
        $nic = $_
        $nicJson = get-content $nic.FullName | ConvertFrom-Json
        $resourceId = $nicJson.resourceId
        $res = Get-NetworkControllerNetworkInterface -ConnectionUri $restUrl -ResourceId $resourceId -PassInnerException -ErrorAction Stop
        $mac = $res.Properties.PrivateMacAddress
        $ipAddress = $res.Properties.IpConfigurations[0].Properties.PrivateIPAddress
        $subnetRef = $res.Properties.IpConfigurations[0].Properties.Subnet.ResourceRef

        $subEle = $subnetRef.Split("/", [StringSplitOptions]::RemoveEmptyEntries)
        $lnetId = $subEle[1]
        $subnetId = $subEle[3]
        $subnet = Get-NetworkControllerLogicalSubnet -LogicalNetworkId $lnetId -ResourceId $subnetId -ConnectionUri $restUrl
        $vlanId = $subnet.Properties.VlanID
        $defaultGw = $subnet.Properties.DefaultGateways[0]
        $prefixLen = $subnet.Properties.AddressPrefix.Split("/") | Select-Object -last 1
        $ipAddressWithPrefix = $ipAddress + "/" + $prefixLen

        $vmName = "$depId-$resourceId"
        New-DataCenterGateway -vmName $vmName `
            -MacAddress $mac -IPAddress $ipAddressWithPrefix `
            -VlanId $vlanId -DefaultGateway $defaultGw

        $instanceId = $res.InstanceId.Guid
        # Enable-SDNExpressVMPort -ComputerName (hostname) -VMName $dataCenterGW -InstanceId "{$instanceId}" -Credential $cred
        $dataCenterGWs += @{"Name" = $vmName; "IPAddress" = $ipAddress; "InstanceId" = $instanceId; "VMHost" = (hostname) }
    }

    $cred = Get-TurnKeySdnCred
    foreach ($dataCenterGW in $dataCenterGWs) {
        WaitforComputerToBeReady -ComputerName $dataCenterGW.Name -Credential $cred
        $instanceId = $dataCenterGW.InstanceId
        Enable-SDNExpressVMPort -ComputerName $dataCenterGW.VMHost -VMName $dataCenterGW.Name -InstanceId "{$instanceId}" -Credential $cred
        WaitforComputerToBeReady -ComputerName $dataCenterGW.Name -Credential $cred
        Enable-DefaultFirewallRules -ComputerName $dataCenterGW.Name -Credential $cred
    }

    return $dataCenterGWs
}
function Enable-GatewayWorkload {

    <#
    # .SYNOPSIS
    # Enables gateway workload data path
    # .DESCRIPTION
    # Creates a datacenter gateway VM for each of the virtual gateway connection configured in the workload config.
    # S2S vpn interface is added to the datacenter gateway VM for each gateway connection.
    # Network for datacenter gateway VM is configured using the jsons under workload\gateway\datacenternetwork folder.
    # The logical network resources specify the logical network to be used for the datacenter gateway.
    # Datacenter network is configured as an actual logical network in SDN.
    # This data center logical network corresponds to the destination ip address of the virtual gateway connection.
    # Under workload\gateway\datacenternetwork\jsons\networkinterfaces, a network interface must exist with the tag "GatewayConnection" for each datacenter gateway VM.
    # IP address of this networkinterface must correspond to the destination ip address of the virtual gateway connection.
    # To avoid conflict between different setups, various IP addresses are randmoized unless randomizeAddresses is set to false in the deployment config.
    # To enable datapath from physical host following static routes are added,
    # - On physical host, route to the vnet ip with next hop as management gateway(router vm)
    # - On router vm, route to the vnet ip with next hop as datacenter gateway corresponding to the vnet.
    # - Management address prefix is auto appended to the virtual gateway connection routes. This enables return traffic from the VNET VM to physical host.
    # Traffic flow -> Physical Host->Router VM->Datacenter Gateway VM->S2S VPN->SDN Gateway->VNET VM
    # For L3 connections, the logical network correpsonding to the L3 is enabled on the router VM.
    # .PARAMETER WorkloadConfigPath
    # Path to the datacenternetwork network workload config
    #>


    param(
        $WorkloadConfigPath
    )

    Write-TraceLog "Enable-GatewayWorkload: Enabling gateway workload"
    
    $vgwsConfigJson = Get-SdnWorkloadVirtualGateways -WorkloadConfigPath $WorkloadConfigPath
    if ($vgwsConfigJson -eq $null -or $vgwsConfigJson.Count -eq 0) {
        Write-TraceLog "Enable-GatewayWorkload: No virtual gateway configuration found. Skipping gateway workload configuration"
        return
    }

    $dataCenterGWs = Add-DataCenterGatewayNetwork -WorkloadConfigPath $WorkloadConfigPath
    
    $sharedSecrets = Get-IPsecSharedSecrets -WorkloadConfigPath $WorkloadConfigPath
    Add-IPsecVPNConnection -DataCenterGWs $dataCenterGWs -SharedSecrets $sharedSecrets    
    $bgpConnections = Add-BgpPeerOnDataCenterGW -DataCenterGWs $dataCenterGWs

    $restEndpoint = Get-TurnKeySdnRestEndpoint
    $restUrl = "https://$restEndpoint"  
    $vgws = Get-NetworkControllerVirtualGateway -ConnectionUri  $restUrl
    Add-StaticRoutesForVirtualGateways -NCVirtualGateways $vgws -BgpConnections $bgpConnections
    Add-ManagmentRouteToVirtualGateways -NCVirtualGateways $vgws -BgpConnections $bgpConnections
    $routerVM = Get-RouterVMName
    Invoke-ReliableCommand -computerName $routerVM -Credential $cred -scriptBlock {
        Restart-Service RemoteAccess -Force
    }

    Add-TurneKeyVMToCluster -Tag "Role=DataCenterGateway"
}

function Set-PublicVipRouteOnHosts {
    <#
    .SYNOPSIS
        Adds a static route for the public vip network on the hosts.
        Next hop is the default gateway of the management network, the router VM.
    #>


    $depConfig = Get-DeploymentConfig
    $hyperVHosts = $depConfig.hyperVHosts
    
    $sdnSwitchName = Get-SdnSwitchName
    $cred = Get-TurnKeySdnCred

    $publicVipNetwork = Get-PublicVipNetworkConfig    
    $publicVipPrefix = $publicVipNetwork.properties.subnets[0].properties.addressPrefix

    $mgmtNetConfig = Get-MgmtNetworkConfig
    $nextHop = $mgmtNetConfig.properties.subnets[0].properties.defaultGateways[0]

    Set-StaticRoute -Computer $hyperVHosts -SwitchName $sdnSwitchName `
        -DestinationPrefix $publicVipPrefix `
        -NextHop $nextHop `
        -NicName $(Get-MgmtNicName) -Credential $cred

}