Azs.Deployment.Worksheet.psm1

function Initialize-Environment
{
    try
    {
        Invoke-LoadLocalizedData

        # Non-localized strings
        $script:arrIdentityStore = @('Azure Active Directory','Active Directory Federation Services')
        $script:arrAzureEnvironment = @('AzureCloud','AzureChinaCloud','AzureGermanCloud','AzureUSGovernment','Custom')
        $script:arrRoutingMethod = @('BGP Routing','Static Routing')
        $script:objCiscoUCS = @('lblSwitchInfraSubnet','txtSwitchInfraSubnet','lblTORASN','txtTORASN')
        $script:strMinWinVer = '6.1.7601.65536'
        $script:strMinWMFVer = '5.1.0.0'
        $script:strMaxWMFVer = '6.0.0.0'
        $script:strSwitchFilePath = Join-Path $PSScriptRoot 'JSON\switch.json'
        $script:strSwitchFilterPath = Join-Path $PSScriptRoot 'JSON\filter.json'
        $script:strPartnerJsonFile = Join-Path $PSScriptRoot 'JSON\partner.json'
        $script:switchTypes = @('TOR','BMC','SPINE')
        $script:spineless = @('CiscoUCS')
        $script:logPath = Join-Path $env:TEMP 'Microsoft_AzureStack\DeploymentWorksheet_Log.txt'
        $script:dllIPCalculator = 'Microsoft.AzureStack.PartnerToolkit.IPCalculator.dll'
        $script:dllJsonParser = 'Newtonsoft.Json.dll'
        $script:dllIPNetwork = 'System.Net.IPNetwork.dll'
        $script:arrDeployProperties = @('BorderConnectivity')
        $script:arrCloudProperties = @('NodeCount','ConnectToAzure','NamingPrefix','PhysicalNamingPrefix','CompanyName','RegionName','DomainFQDN','ExternalDomainFQDN','DNSForwarder','InfraAzureEnvironment','InfraAzureDirectoryTenantName','TimeServer','SyslogServerIPv4Address','ADFSForestFQDN','ADFSProviderName','ADFSMetadataUri')
        $script:strPartnerExportFile = 'PartnerData.json'

        # RegEx validators
        $script:validNumber = '^[0-9]+$'
        $script:validIPAddress = "^([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3}$"
        $script:validAlphaNumeric = '^[a-zA-Z0-9]+$'
        $script:validDomain = "(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9]\.)+[a-zA-Z]{2,63}$)"

        $script:validNodeMin = 4
        $script:validNodeMax = 16
        $script:validCloudMin = 1
        $script:validCloudMax = 16
        $script:validSubnetExternalMax = 26
        $script:validSubnetExternalMin = 22
        $script:maxSU = 4
        $script:maxEdge = 4
        $script:validEdge = 2
        $script:validASNMin = 1
        $script:validASNMax = 4294967295
        $script:maxDNSForwarders = 2

        #$script:defaultNodeCount = '4'
        $script:defaultSUCount = '1'
        $script:defaultCloudCount = '1'
        $script:defaultSpineCount = '2'
        $script:defaultEdgeCount = '2'
        $script:defaultExternalSubnetSize = '24'
        $script:defaultSpineSubnetSize = '25'
        $script:defaultBMCSubnetSize = '26'
        $script:defaultStorageSubnetSize = '25'
        $script:defaultInfraSubnetSize = '24'
        $script:defaultSwInfraSubnetSize = '26'
        $script:defaultSupernetSize = '24'

        $script:defaultMainFormSize = '980,640'
        $script:defaultIPCellWidth = '110'
        $script:lenIPAddress = '15'
        $script:lenText = '50'
        $script:lenDomain = '120'
        $script:lenURI = '180'
        $script:lenPrefix = '8'
        $script:txtPrefixSize = '100,20'
        $script:lblMaxSize = '120,40'
        $script:lblLeft = '10'
        $script:lblLeftNet2 = '500'
        $script:txtLeft = '130'
        $script:txtLeftNet = '150'
        $script:txtLeftNet2 = '620'
        $script:txtSize = '280,20'
        $script:txtSizeNet = '200,20'
        $script:lstRoutingMethodSize = '150,20'
        $script:txtCountSize = '40,20'
        $script:lblLeftNetASN = '555'
        $script:txtLeftNetASN = '666'
        $script:waitSize = '500,200'
        $script:txtSubnetLeft = '240'
        $script:txtSubnetSizeLeft = '280'
        $script:txtSubnetLeft2 = '780'
        $script:txtSubnetSizeLeft2 = '820'
        [int]$script:maxComment = 680

        # Tab tracking variables
        $script:scenario = "Customer"
        $script:savedTabs = @()
        $script:scaleUnitTabs = @{}

        if(-not($host.Runspace.ApartmentState -eq 'STA'))
        {
            [System.Threading.Thread]::CurrentThread.ApartmentState = [System.Threading.ApartmentState]::STA
        }
        $script:scriptPath = $MyInvocation.MyCommand.Definition

        Test-ScriptRequirements

        $script:defaultForeColor = [System.Drawing.Color]::Black
        $script:defaultFailColor = [System.Drawing.Color]::Red
        $script:disabledBackColor = [System.Drawing.Color]::LightGray
        $script:defaultLabelFont = [System.Drawing.Font]::New("Microsoft Sans Serif", 8.5)
        $script:defaultAutoScaleMode = [System.Windows.Forms.AutoScaleMode]::Font
        $script:defaultAutoScaleDimensions = New-Object System.Drawing.SizeF @([double]6, [double]13)
        $script:defaultAutoSizeMode = [System.Windows.Forms.AutoSizeMode]::GrowAndShrink
        $script:topMost = New-Object System.Windows.Forms.Form -Property @{Topmost=$true}

        if($locString.lblFontTypeFace -and $locString.lblFontSize)
        {
            $script:defaultLabelFont = [System.Drawing.Font]::New($locString.lblFontTypeFace, $locString.lblFontSize)
        }
    }
    catch
    {
        throw $PSItem
    }
}

function Test-ScriptRequirements
{
    [string[]]$reqError = @()
    if($host.Version -lt $strMinWMFVer)
    {
        $reqError += ($locString.errScriptHostVer -f $strMinWMFVer,$strMaxWMFVer)
    }
    if($host.Version -ge $strMaxWMFVer)
    {
        $reqError += ($locString.errScriptHostVer -f $strMinWMFVer,$strMaxWMFVer)
    }
    if([System.Environment]::OSVersion.Version -lt $strMinWinVer)
    {
        $reqError += ($locString.errWinVer -f $strMinWinVer)
    }
    if($reqError.Count -eq 0)
    {
        try
        {
            Add-Assembly System.Windows.Forms
            Add-Assembly System.Drawing
            Add-Assembly PresentationFramework
            Add-Assembly (Join-Path $PSScriptRoot $dllIPCalculator)
            Add-Assembly (Join-Path $PSScriptRoot $dllJsonParser)
            Add-Assembly (Join-Path $PSScriptRoot $dllIPNetwork)
        }
        catch
        {
            $errMsg = $PSItem.Exception.Message
            $reqError += ($locString.errAssemblyLoad -f $errMsg)
        }
    }
    if($reqError.Count -ne 0)
    {
        $reqError | ForEach-Object `
        {
            Write-Error $PSItem
        }
        throw $locString.errScriptRequirements
    }
}

function Invoke-LoadLocalizedData
{
    $loc = (Get-UICulture).Name
    $locStringFile = "Azs.Deployment.Worksheet.Strings.$($loc).psd1"
    $locStringPath = Join-Path $PSScriptRoot $locStringFile
    if(-not(Test-Path $locStringPath))
    {
        Write-Warning -Message "Localized strings file for '$($loc)' could not be found - defaulting to 'en-US'"
        $locStringFile = "Azs.Deployment.Worksheet.Strings.en-US.psd1"
        $locStringPath = Join-Path $PSScriptRoot $locStringFile
    }
    if(Test-Path $locStringPath)
    {
        if((Get-AuthenticodeSignature $locStringPath).Status -ne 'Valid')
        {
            $message = "'$($locStringFile)' does not have a valid digital signature."
            $bar = '-' * [int]($message).Length
            Write-Warning $bar
            Write-Warning $message
            Write-Warning $bar
            Start-Sleep -Seconds 3
        }
        Write-Verbose -Message "Import-LocalizedData from '$($locStringPath)'" -Verbose
        $script:locString = Import-LocalizedData -BaseDirectory $PSScriptRoot -Filename $locStringFile
    }
    else
    {
        throw "$($locStringFile) could not be found."
    }
}

function Keystroke_Enter
{
    [System.Windows.Forms.SendKeys]::SendWait("{TAB}")
}

function Add-Assembly
{
    param
    (
        [string]$AssemblyName
    )
    if(-not([AppDomain]::CurrentDomain.GetAssemblies().Location -like "*$AssemblyName*"))
    {
        if($AssemblyName -like "*.dll")
        {
            Add-Type -Path $AssemblyName
        }
        else
        {
            Add-Type -AssemblyName $AssemblyName
        }
    }
}

function Trim-Json
{
    param
    (
        [Parameter(Mandatory=$true, ValueFromPipeline)][string]$Json
    )
    $indent = 0
    $formatted = `
    foreach($line in $Json -split '\n')
    {
        if($line -match '[\}\]]')
        {
            $indent--
        }
        $newline = (' ' * $indent * 2) + $line.Trim().Replace(': ', ': ')
        if($line -match '[\{\[]')
        {
            $indent++
        }
        $newline
    }
    $formatted -join "`n"
}

function Get-FolderName
{
    param
    (
        [string]$InitialDirectory = $scriptPath,
        [string]$Description
    )
    $openFolderDialog = New-Object System.Windows.Forms.FolderBrowserDialog
    $openFolderDialog.Description = $Description
    $openFolderDialog.ShowNewFolderButton = $true
    $openFolderDialog.RootFolder = [System.Environment+SpecialFolder]::MyComputer
    $openFolderDialog.SelectedPath = $InitialDirectory
    $res = $openFolderDialog.ShowDialog($frmMain)
    $openFolderDialog.Dispose()
    if($res -eq [System.Windows.Forms.DialogResult]::OK)
    {
        return $openFolderDialog.SelectedPath
    }
}

function Get-SaveFileName
{
    param
    (
        [string]$InitialDirectory = $scriptPath
    )
    Write-Log -Message "$($MyInvocation.MyCommand.Name) - called"
    $saveFileDialog = New-Object System.Windows.Forms.SaveFileDialog
    $saveFileDialog.DefaultExt = 'json'
    $saveFileDialog.Filter = 'JSON files | *.json'
    $saveFileDialog.InitialDirectory = $InitialDirectory
    $saveFileDialog.FileName = "$($locString.strDefaultSaveFileName).json"
    $res = $saveFileDialog.ShowDialog($frmMain)
    $saveFileDialog.Dispose()
    if($res -eq [System.Windows.Forms.DialogResult]::OK)
    {
        return $saveFileDialog.FileName
    }
}

function Get-FileName
{
    param
    (
        [string]$InitialDirectory = $scriptPath,
        [string]$Filter = "ConfigurationData files|ConfigurationData.json|JSON files (*.json)|*.json|All files (*.*)|*.*"
    )
    Write-Log -Message "$($MyInvocation.MyCommand.Name) - called"
    $openFileDialog = New-Object System.Windows.Forms.OpenFileDialog
    $openFileDialog.Filter = $Filter
    $openFileDialog.InitialDirectory = $InitialDirectory
    $res = $openFileDialog.ShowDialog($frmMain)
    $openFileDialog.Dispose()
    if($res -eq [System.Windows.Forms.DialogResult]::OK)
    {
        return $openFileDialog.FileName
    }
}

function Write-Log
{
    param
    (
        [string]$Message,
        [string]$LogFile = $logPath,
        [switch]$Quiet,
        [switch]$Force
    )
    if((-not $logging) -and (-not $Force)) { return }
    $output = "$(Get-Date -Format yyyyMMdd-HHmm) : $($Message)"
    if(-not $Quiet)
    {
        Write-Verbose -Message $output -Verbose
    }
    if($logging)
    {
        if(-not(Test-Path (Split-Path $LogFile -Parent)))
        {
            $null = New-Item (Split-Path $LogFile -Parent) -ItemType Directory
        }
        $output | Add-Content $LogFile
    }
}

function Invoke-InitializeLogging
{
    Write-Verbose -Message "$($MyInvocation.MyCommand.Name)" -Verbose
    if(Test-Path $logPath)
    {
        Remove-Item $logPath | Out-Null
    }
    Write-Log -Message "Log will be written to: '$($logPath)'"
}

function Get-FileVersionInfo
{
    try
    {
        [string]$thisVersion = ((Get-Module Azs.Deployment.Worksheet) | Select-Object -Last 1).Version
        return $thisVersion
    }
    catch
    {
        throw $PSItem
    }
}

function Show-WaitForm
{
    param
    (
        [string]$Message
    )
    Close-WaitForm
    $script:waitForm = New-Object System.Windows.Forms.Form
    $script:waitLabel = New-Object System.Windows.Forms.Label
    $waitForm.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen
    $waitForm.TopMost = $true
    $waitForm.ShowIcon = $false
    $waitForm.Size = New-Object System.Drawing.Size($waitSize)
    $waitForm.Text = $locString.msgPleaseWait
    $waitForm.Controls.Add($waitLabel)
    $waitLabel.AutoSize = $true
    $waitForm.Visible = $true
    $waitLabel.Text = "`n$($Message)`n`n $($locString.msgPleaseWait)`n"
    $waitForm.Update()
}

function Close-WaitForm
{
    if(Get-Variable waitForm -ea SilentlyContinue)
    {
        $waitForm.Close()
        $waitForm.Dispose()
        $waitForm = $null
        $waitLabel = $null
    }
}

function Get-SwitchVendors
{
    try
    {
        if(-not $switches)
        {
            $script:switches = Get-Content -Raw $strSwitchFilePath | ConvertFrom-Json
        }
        if(Test-Path $strSwitchFilterPath)
        {
            $filter = (Get-Content $strSwitchFilterPath -Raw | ConvertFrom-Json).filter
        }
        if($filter.Count -ne 0)
        {
            $switchInfo = @()
            $portmap = $switches.portmap
            $filter | ForEach-Object `
            {
                if($portmap.$PSItem)
                {
                    $switchInfo += $PSItem
                }
                else
                {
                    Write-Log -Message "$($MyInvocation.MyCommand.Name) - WARNING: switch make '$($PSItem)' could not be found in switch.json" -Force
                }
            }
            if($switchInfo.Count -gt 0)
            {
                return $switchInfo
            }
        }
        $switchInfo = $switches.portmap
        return @(($switchInfo | Get-Member -Type NoteProperty).Name)
    }
    catch
    {
        throw $PSItem
    }
}

function Invoke-RefreshTotalNodeCount
{
    $thisName = 'txtNodeCount'
    $total = 0
    (1..$maxSU) | Foreach-Object `
    {
        $countTab = "tabSU$($PSItem)"
        if($scaleUnitTabs.$countTab)
        {
            $total += [int]$scaleUnitTabs.$countTab.Objects.$thisName.Text
        }
    }
    $txtTotalNodeCount.Text = $total
}

function Set-ScaleUnitsCiscoUCS
{
    param
    (
        [Parameter(Mandatory=$true)]
        [bool]$Enabled
    )
    (2..$maxSU) | Foreach-Object `
    {
        $setTab = "tabSU$($PSItem)"
        if($scaleUnitTabs.$setTab)
        {
            foreach($item in $objCiscoUCS)
            {
                $scaleUnitTabs.$setTab.Objects.$item.Enabled = $Enabled
                $scaleUnitTabs.$setTab.Objects.$item.Visible = $Enabled
                if((-not $Enabled) -and ($scaleUnitTabs.$setTab.Objects.$item.GetType().Name -ne "Label"))
                {
                    $scaleUnitTabs.$setTab.Objects.$item.Text = [string]::Empty
                }
            }
            $scaleUnitTabs.$setTab.Objects.lblSwitchInfraSubnetSize.Visible = $Enabled
            $scaleUnitTabs.$setTab.Objects.txtSwitchInfraSubnetSize.Visible = $Enabled
        }
    }
}

function Remove-GeneratedTabs
{
    if($tabControl.TabPages["tabSubnetSummary"])
    {
        $tabControl.TabPages.Remove($tabControl.TabPages["tabSubnetSummary"])
        $tabSubnetSummary.Dispose()
    }
    if($tabControl.TabPages["tabIPAssignments"])
    {
        $tabControl.TabPages.Remove($tabControl.TabPages["tabIPAssignments"])
        $tabIPAssignments.Dispose()
    }
}

function Get-DataGridViewSize
{
    param
    (
        [Parameter(Mandatory=$true)]
        [System.Windows.Forms.DataGridView]$Table
    )
    [int]$h = 3
    $h += $Table.ColumnHeadersHeight
    $Table.Rows | ForEach-Object `
    {
        $h += $PSitem.Height
    }
    [int]$w = 3
    $Table.Columns | ForEach-Object { $w += $PSItem.Width }

    $size = New-Object System.Drawing.Size($w,$h)

    return $size
}

function Add-ScaleUnitTab
{
    param
    (
        [Parameter(Mandatory=$true)]
        [int]$ID
    )
    try
    {
        $tabName = "tabSU$($ID)"
        if(-not($scaleUnitTabs.$tabName))
        {
            $objTab = New-Object System.Windows.Forms.TabPage
            $typTab = $objTab.GetType()
            $prpTab = $typTab.GetProperty('DoubleBuffered',('Instance','NonPublic'))
            $prpTab.SetValue($objTab, $true, $null)
            $scaleUnitTabs.Add($tabName,@{Tab=$objTab;Objects=@{}})
            $scaleUnitTabs.$tabName.Tab.Name = $tabName
            $scaleUnitTabs.$tabName.Tab.Text = "$($locString.lblScaleUnit) $($ID)"
            $tabControl.Controls.Add($scaleUnitTabs.$tabName.Tab)

            $tabRow = 20

            # ---------------------------- SCALE UNIT ID ------------------------------
            $thisName = 'lblScaleUnit'
            $thisObj = New-Object System.Windows.Forms.Label
            $scaleUnitTabs.$tabName.Objects.Add($thisName,$thisObj)
            $scaleUnitTabs.$tabName.Objects.$thisName.Location = New-Object System.Drawing.Size($lblLeft,($tabRow))
            $scaleUnitTabs.$tabName.Objects.$thisName.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
            $scaleUnitTabs.$tabName.Objects.$thisName.AutoSize = $true
            $scaleUnitTabs.$tabName.Objects.$thisName.Text = "$($locString.lblScaleUnit) : $($ID)"
            $scaleUnitTabs.$tabName.Objects.$thisName.Font = [System.Drawing.Font]::New("Microsoft Sans Serif", 12, [System.Drawing.FontStyle]::Bold)
            $scaleUnitTabs.$tabName.Tab.Controls.Add($scaleUnitTabs.$tabName.Objects.$thisName)

            $tabRow += 60

            # ------------------------------ NODE COUNT -------------------------------
            $thisName = 'lblNodeCount'
            $thisObj = New-Object System.Windows.Forms.Label
            $scaleUnitTabs.$tabName.Objects.Add($thisName,$thisObj)
            $scaleUnitTabs.$tabName.Objects.$thisName.Location = New-Object System.Drawing.Size($lblLeft,($tabRow+2))
            $scaleUnitTabs.$tabName.Objects.$thisName.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
            $scaleUnitTabs.$tabName.Objects.$thisName.AutoSize = $true
            $scaleUnitTabs.$tabName.Objects.$thisName.Text = $locString.lblNodeCount
            $scaleUnitTabs.$tabName.Objects.$thisName.Font = $defaultLabelFont
            $scaleUnitTabs.$tabName.Tab.Controls.Add($scaleUnitTabs.$tabName.Objects.$thisName)

            $thisName = 'txtNodeCount'
            $thisObj = New-Object System.Windows.Forms.TextBox
            $scaleUnitTabs.$tabName.Objects.Add($thisName,$thisObj)
            $scaleUnitTabs.$tabName.Objects.$thisName.Location = New-Object System.Drawing.Size($txtLeft,$tabRow)
            $scaleUnitTabs.$tabName.Objects.$thisName.Size = New-Object System.Drawing.Size($txtCountSize)
            $scaleUnitTabs.$tabName.Objects.$thisName.TextAlign = [System.Windows.Forms.HorizontalAlignment]::Center
            $scaleUnitTabs.$tabName.Objects.$thisName.Font = $defaultLabelFont
            $scaleUnitTabs.$tabName.Objects.$thisName.MaxLength = 2
            #$scaleUnitTabs.$tabName.Objects.$thisName.Text = $defaultNodeCount
            $scaleUnitTabs.$tabName.Objects.$thisName.Name = $thisName
            $scaleUnitTabs.$tabName.Tab.Controls.Add($scaleUnitTabs.$tabName.Objects.$thisName)
            $txtNodeCount_Changed = `
            {
                $thisName = 'txtNodeCount'
                $thisTab = $tabControl.SelectedTab
                $tabName = $thisTab.Name
                $scaleUnitTabs.$tabName.Objects.$thisName.Text = ($scaleUnitTabs.$tabName.Objects.$thisName.Text).Trim()
                if(-not([string]::IsNullOrEmpty($scaleUnitTabs.$tabName.Objects.$thisName.Text)))
                {
                    $valid = $true
                    if($scaleUnitTabs.$tabName.Objects.$thisName.Text -notmatch '^[0-9]+$')
                    {
                        $valid = $false
                    }
                    elseif(([int]$scaleUnitTabs.$tabName.Objects.$thisName.Text -lt $validNodeMin) -or ([int]$scaleUnitTabs.$tabName.Objects.$thisName.Text -gt $validNodeMax))
                    {
                        $valid = $false
                    }
                    if($valid)
                    {
                        $scaleUnitTabs.$tabName.Objects.$thisName.Text = ($scaleUnitTabs.$tabName.Objects.$thisName.Text).TrimStart('0')
                        $scaleUnitTabs.$tabName.Objects.$thisName.ForeColor = $defaultForeColor
                        Invoke-RefreshTotalNodeCount
                    }
                    else
                    {
                        [string]$errMessage = ($locString.errNumberNotInRange -f $validNodeMin,$validNodeMax)
                        Close-WaitForm
                        [System.Windows.Forms.MessageBox]::Show($errMessage, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
                        $scaleUnitTabs.$tabName.Objects.$thisName.ForeColor = $defaultFailColor
                        $scaleUnitTabs.$tabName.Objects.$thisName.Select()
                    }
                    $scaleUnitTabs.$tabName.Objects.$thisName.Refresh()
                }
            }
            $scaleUnitTabs.$tabName.Objects.$thisName.Add_Leave($txtNodeCount_Changed)

            $tabRow += 60

            # -------------------------- SCALE-UNIT SUBNETS ---------------------------
            $thisName = 'lblScaleUnitSubnets'
            $thisObj = New-Object System.Windows.Forms.Label
            $scaleUnitTabs.$tabName.Objects.Add($thisName,$thisObj)
            $scaleUnitTabs.$tabName.Objects.$thisName.Location = New-Object System.Drawing.Size($lblLeft,($tabRow))
            $scaleUnitTabs.$tabName.Objects.$thisName.AutoSize = $true
            $scaleUnitTabs.$tabName.Objects.$thisName.Text = $locString.lblScaleUnitSubnets
            $scaleUnitTabs.$tabName.Objects.$thisName.Font = [System.Drawing.Font]::New("Microsoft Sans Serif", 10, [System.Drawing.FontStyle]::Bold)
            $scaleUnitTabs.$tabName.Tab.Controls.Add($scaleUnitTabs.$tabName.Objects.$thisName)

            $tabRow += 40

            # ------------------------------ BMC SUBNET -------------------------------
            $thisName = 'lblBMCSubnet'
            $thisObj = New-Object System.Windows.Forms.Label
            $scaleUnitTabs.$tabName.Objects.Add($thisName,$thisObj)
            $scaleUnitTabs.$tabName.Objects.$thisName.Location = New-Object System.Drawing.Size($lblLeft,($tabRow+2))
            $scaleUnitTabs.$tabName.Objects.$thisName.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
            $scaleUnitTabs.$tabName.Objects.$thisName.AutoSize = $true
            $scaleUnitTabs.$tabName.Objects.$thisName.Text = $locString.lblBMCSubnet
            $scaleUnitTabs.$tabName.Objects.$thisName.Font = $defaultLabelFont
            $scaleUnitTabs.$tabName.Tab.Controls.Add($scaleUnitTabs.$tabName.Objects.$thisName)

            $thisName = 'txtBMCSubnet'
            $thisObj = New-Object System.Windows.Forms.TextBox
            $scaleUnitTabs.$tabName.Objects.Add($thisName,$thisObj)
            $scaleUnitTabs.$tabName.Objects.$thisName.Location = New-Object System.Drawing.Size($txtLeft,$tabRow)
            $scaleUnitTabs.$tabName.Objects.$thisName.Size = New-Object System.Drawing.Size($txtPrefixSize)
            $scaleUnitTabs.$tabName.Objects.$thisName.MaxLength = $lenIPAddress
            $scaleUnitTabs.$tabName.Objects.$thisName.Font = $defaultLabelFont
            $scaleUnitTabs.$tabName.Objects.$thisName.Name = $thisName
            $scaleUnitTabs.$tabName.Tab.Controls.Add($scaleUnitTabs.$tabName.Objects.$thisName)
            $txtBMCSubnet_Changed = `
            {
                $thisName = 'txtBMCSubnet'
                $thisTab = $tabControl.SelectedTab
                $tabName = $thisTab.Name
                if(-not([string]::IsNullOrEmpty($scaleUnitTabs.$tabName.Objects.$thisName.Text)))
                {
                    $scaleUnitTabs.$tabName.Objects.$thisName.Text = ($scaleUnitTabs.$tabName.Objects.$thisName.Text).Trim()
                    if($scaleUnitTabs.$tabName.Objects.$thisName.Text -notmatch $validIPAddress)
                    {
                        Close-WaitForm
                        [System.Windows.Forms.MessageBox]::Show($locString.errIPAddressInvalid, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
                        $scaleUnitTabs.$tabName.Objects.$thisName.ForeColor = $defaultFailColor
                        $scaleUnitTabs.$tabName.Objects.$thisName.Select()
                    }
                    elseif(-not(Get-IsValidSubnet -IPAddress $scaleUnitTabs.$tabName.Objects.$thisName.Text -CIDR $scaleUnitTabs.$tabName.Objects.'txtBMCSubnetSize'.Text))
                    {
                        Close-WaitForm
                        $message = ($locString.errSubnetInvalid -f $scaleUnitTabs.$tabName.Objects.$thisName.Text,$scaleUnitTabs.$tabName.Objects.'txtBMCSubnetSize'.Text)
                        [System.Windows.Forms.MessageBox]::Show($message, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
                        $scaleUnitTabs.$tabName.Objects.$thisName.ForeColor = $defaultFailColor
                        $scaleUnitTabs.$tabName.Objects.$thisName.Select()
                    }
                    else
                    {
                        $scaleUnitTabs.$tabName.Objects.$thisName.ForeColor = $defaultForeColor
                    }
                    $scaleUnitTabs.$tabName.Objects.$thisName.Refresh()
                }
            }
            $scaleUnitTabs.$tabName.Objects.$thisName.Add_Leave($txtBMCSubnet_Changed)

            $thisName = 'lblBMCSubnetSize'
            $thisObj = New-Object System.Windows.Forms.Label
            $scaleUnitTabs.$tabName.Objects.Add($thisName,$thisObj)
            $scaleUnitTabs.$tabName.Objects.$thisName.Location = New-Object System.Drawing.Size($txtSubnetLeft,($tabRow+2))
            $scaleUnitTabs.$tabName.Objects.$thisName.Size = New-Object System.Drawing.Size($txtCountSize)
            $scaleUnitTabs.$tabName.Objects.$thisName.Text = $locString.lblSubnetSize
            $scaleUnitTabs.$tabName.Objects.$thisName.Font = $defaultLabelFont
            $scaleUnitTabs.$tabName.Tab.Controls.Add($scaleUnitTabs.$tabName.Objects.$thisName)

            $thisName = 'txtBMCSubnetSize'
            $thisObj = New-Object System.Windows.Forms.TextBox
            $scaleUnitTabs.$tabName.Objects.Add($thisName,$thisObj)
            $scaleUnitTabs.$tabName.Objects.$thisName.Location = New-Object System.Drawing.Size($txtSubnetSizeLeft,$tabRow)
            $scaleUnitTabs.$tabName.Objects.$thisName.Size = New-Object System.Drawing.Size($txtCountSize)
            $scaleUnitTabs.$tabName.Objects.$thisName.MaxLength = 2
            $scaleUnitTabs.$tabName.Objects.$thisName.Font = $defaultLabelFont
            $scaleUnitTabs.$tabName.Objects.$thisName.Text = $defaultBMCSubnetSize
            $scaleUnitTabs.$tabName.Objects.$thisName.Name = $thisName
            $scaleUnitTabs.$tabName.Objects.$thisName.Enabled = $false
            $scaleUnitTabs.$tabName.Objects.$thisName.ReadOnly = $true
            $scaleUnitTabs.$tabName.Tab.Controls.Add($scaleUnitTabs.$tabName.Objects.$thisName)

            $tabRow += 40

            # ---------------------------- STORAGE SUBNET -----------------------------
            $thisName = 'lblStorageSubnet'
            $thisObj = New-Object System.Windows.Forms.Label
            $scaleUnitTabs.$tabName.Objects.Add($thisName,$thisObj)
            $scaleUnitTabs.$tabName.Objects.$thisName.Location = New-Object System.Drawing.Size($lblLeft,($tabRow+2))
            $scaleUnitTabs.$tabName.Objects.$thisName.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
            $scaleUnitTabs.$tabName.Objects.$thisName.AutoSize = $true
            $scaleUnitTabs.$tabName.Objects.$thisName.Text = $locString.lblStorageSubnet
            $scaleUnitTabs.$tabName.Objects.$thisName.Font = $defaultLabelFont
            $scaleUnitTabs.$tabName.Tab.Controls.Add($scaleUnitTabs.$tabName.Objects.$thisName)

            $thisName = 'txtStorageSubnet'
            $thisObj = New-Object System.Windows.Forms.TextBox
            $scaleUnitTabs.$tabName.Objects.Add($thisName,$thisObj)
            $scaleUnitTabs.$tabName.Objects.$thisName.Location = New-Object System.Drawing.Size($txtLeft,$tabRow)
            $scaleUnitTabs.$tabName.Objects.$thisName.Size = New-Object System.Drawing.Size($txtPrefixSize)
            $scaleUnitTabs.$tabName.Objects.$thisName.MaxLength = $lenIPAddress
            $scaleUnitTabs.$tabName.Objects.$thisName.Font = $defaultLabelFont
            $scaleUnitTabs.$tabName.Objects.$thisName.Name = $thisName
            $scaleUnitTabs.$tabName.Tab.Controls.Add($scaleUnitTabs.$tabName.Objects.$thisName)
            $txtStorageSubnet_Changed = `
            {
                $thisName = 'txtStorageSubnet'
                $thisTab = $tabControl.SelectedTab
                $tabName = $thisTab.Name
                if(-not([string]::IsNullOrEmpty($scaleUnitTabs.$tabName.Objects.$thisName.Text)))
                {
                    $scaleUnitTabs.$tabName.Objects.$thisName.Text = ($scaleUnitTabs.$tabName.Objects.$thisName.Text).Trim()
                    if($scaleUnitTabs.$tabName.Objects.$thisName.Text -notmatch $validIPAddress)
                    {
                        Close-WaitForm
                        [System.Windows.Forms.MessageBox]::Show($locString.errIPAddressInvalid, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
                        $scaleUnitTabs.$tabName.Objects.$thisName.ForeColor = $defaultFailColor
                        $scaleUnitTabs.$tabName.Objects.$thisName.Select()
                    }
                    elseif(-not(Get-IsValidSubnet -IPAddress $scaleUnitTabs.$tabName.Objects.$thisName.Text -CIDR $scaleUnitTabs.$tabName.Objects.'txtStorageSubnetSize'.Text))
                    {
                        Close-WaitForm
                        $message = ($locString.errSubnetInvalid -f $scaleUnitTabs.$tabName.Objects.$thisName.Text,$scaleUnitTabs.$tabName.Objects.'txtStorageSubnetSize'.Text)
                        [System.Windows.Forms.MessageBox]::Show($message, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
                        $scaleUnitTabs.$tabName.Objects.$thisName.ForeColor = $defaultFailColor
                        $scaleUnitTabs.$tabName.Objects.$thisName.Select()
                    }
                    else
                    {
                        $scaleUnitTabs.$tabName.Objects.$thisName.ForeColor = $defaultForeColor
                    }
                    $scaleUnitTabs.$tabName.Objects.$thisName.Refresh()
                }
            }
            $scaleUnitTabs.$tabName.Objects.$thisName.Add_Leave($txtStorageSubnet_Changed)

            $thisName = 'lblStorageSubnetSize'
            $thisObj = New-Object System.Windows.Forms.Label
            $scaleUnitTabs.$tabName.Objects.Add($thisName,$thisObj)
            $scaleUnitTabs.$tabName.Objects.$thisName.Location = New-Object System.Drawing.Size($txtSubnetLeft,($tabRow+2))
            $scaleUnitTabs.$tabName.Objects.$thisName.Size = New-Object System.Drawing.Size($txtCountSize)
            $scaleUnitTabs.$tabName.Objects.$thisName.Text = $locString.lblSubnetSize
            $scaleUnitTabs.$tabName.Objects.$thisName.Font = $defaultLabelFont
            $scaleUnitTabs.$tabName.Tab.Controls.Add($scaleUnitTabs.$tabName.Objects.$thisName)
            if($tabName -eq "tabSU1")
            {
                $thisSize = '24'
            }
            else
            {
                $thisSize = $defaultStorageSubnetSize
            }

            $thisName = 'txtStorageSubnetSize'
            $thisObj = New-Object System.Windows.Forms.TextBox
            $scaleUnitTabs.$tabName.Objects.Add($thisName,$thisObj)
            $scaleUnitTabs.$tabName.Objects.$thisName.Location = New-Object System.Drawing.Size($txtSubnetSizeLeft,$tabRow)
            $scaleUnitTabs.$tabName.Objects.$thisName.Size = New-Object System.Drawing.Size($txtCountSize)
            $scaleUnitTabs.$tabName.Objects.$thisName.MaxLength = 2
            $scaleUnitTabs.$tabName.Objects.$thisName.Font = $defaultLabelFont
            $scaleUnitTabs.$tabName.Objects.$thisName.Text = $thisSize
            $scaleUnitTabs.$tabName.Objects.$thisName.Enabled = $false
            $scaleUnitTabs.$tabName.Objects.$thisName.ReadOnly = $true
            $scaleUnitTabs.$tabName.Objects.$thisName.Name = $thisName
            $scaleUnitTabs.$tabName.Tab.Controls.Add($scaleUnitTabs.$tabName.Objects.$thisName)
            if($tabName -eq "tabSU1")
            {
                $thisName = 'lblStorageSubnetSizeNote'
                $thisObj = New-Object System.Windows.Forms.Label
                $scaleUnitTabs.$tabName.Objects.Add($thisName,$thisObj)
                $scaleUnitTabs.$tabName.Objects.$thisName.Location = New-Object System.Drawing.Size(330,($tabRow+2))
                $scaleUnitTabs.$tabName.Objects.$thisName.Size = New-Object System.Drawing.Size(300,20)
                $scaleUnitTabs.$tabName.Objects.$thisName.Text = $locString.strStorageSubnetSizeNote
                $scaleUnitTabs.$tabName.Objects.$thisName.Font = $defaultLabelFont
                $scaleUnitTabs.$tabName.Tab.Controls.Add($scaleUnitTabs.$tabName.Objects.$thisName)
            }

            $tabRow += 40

            # ----------------------------- INFRA SUBNET ------------------------------
            $thisName = 'lblInfraSubnet'
            $thisObj = New-Object System.Windows.Forms.Label
            $scaleUnitTabs.$tabName.Objects.Add($thisName,$thisObj)
            $scaleUnitTabs.$tabName.Objects.$thisName.Location = New-Object System.Drawing.Size($lblLeft,($tabRow+2))
            $scaleUnitTabs.$tabName.Objects.$thisName.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
            $scaleUnitTabs.$tabName.Objects.$thisName.AutoSize = $true
            $scaleUnitTabs.$tabName.Objects.$thisName.Text = $locString.lblInfraSubnet
            $scaleUnitTabs.$tabName.Objects.$thisName.Font = $defaultLabelFont
            $scaleUnitTabs.$tabName.Tab.Controls.Add($scaleUnitTabs.$tabName.Objects.$thisName)

            $thisName = 'txtInfraSubnet'
            $thisObj = New-Object System.Windows.Forms.TextBox
            $scaleUnitTabs.$tabName.Objects.Add($thisName,$thisObj)
            $scaleUnitTabs.$tabName.Objects.$thisName.Location = New-Object System.Drawing.Size($txtLeft,$tabRow)
            $scaleUnitTabs.$tabName.Objects.$thisName.Size = New-Object System.Drawing.Size($txtPrefixSize)
            $scaleUnitTabs.$tabName.Objects.$thisName.MaxLength = $lenIPAddress
            $scaleUnitTabs.$tabName.Objects.$thisName.Font = $defaultLabelFont
            $scaleUnitTabs.$tabName.Objects.$thisName.Name = $thisName
            $scaleUnitTabs.$tabName.Tab.Controls.Add($scaleUnitTabs.$tabName.Objects.$thisName)
            $txtInfraSubnet_Changed = `
            {
                $thisName = 'txtInfraSubnet'
                $thisTab = $tabControl.SelectedTab
                $tabName = $thisTab.Name
                if(-not([string]::IsNullOrEmpty($scaleUnitTabs.$tabName.Objects.$thisName.Text)))
                {
                    $scaleUnitTabs.$tabName.Objects.$thisName.Text = ($scaleUnitTabs.$tabName.Objects.$thisName.Text).Trim()
                    if($scaleUnitTabs.$tabName.Objects.$thisName.Text -notmatch $validIPAddress)
                    {
                        Close-WaitForm
                        [System.Windows.Forms.MessageBox]::Show($locString.errIPAddressInvalid, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
                        $scaleUnitTabs.$tabName.Objects.$thisName.ForeColor = $defaultFailColor
                        $scaleUnitTabs.$tabName.Objects.$thisName.Select()
                    }
                    elseif(-not(Get-IsValidSubnet -IPAddress $scaleUnitTabs.$tabName.Objects.$thisName.Text -CIDR $scaleUnitTabs.$tabName.Objects.'txtInfraSubnetSize'.Text))
                    {
                        Close-WaitForm
                        $message = ($locString.errSubnetInvalid -f $scaleUnitTabs.$tabName.Objects.$thisName.Text,$scaleUnitTabs.$tabName.Objects.'txtInfraSubnetSize'.Text)
                        [System.Windows.Forms.MessageBox]::Show($message, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
                        $scaleUnitTabs.$tabName.Objects.$thisName.ForeColor = $defaultFailColor
                        $scaleUnitTabs.$tabName.Objects.$thisName.Select()
                    }
                    else
                    {
                        $scaleUnitTabs.$tabName.Objects.$thisName.ForeColor = $defaultForeColor
                    }
                    $scaleUnitTabs.$tabName.Objects.$thisName.Refresh()
                }
            }
            $scaleUnitTabs.$tabName.Objects.$thisName.Add_Leave($txtInfraSubnet_Changed)

            $thisName = 'lblInfraSubnetSize'
            $thisObj = New-Object System.Windows.Forms.Label
            $scaleUnitTabs.$tabName.Objects.Add($thisName,$thisObj)
            $scaleUnitTabs.$tabName.Objects.$thisName.Location = New-Object System.Drawing.Size($txtSubnetLeft,($tabRow+2))
            $scaleUnitTabs.$tabName.Objects.$thisName.Size = New-Object System.Drawing.Size($txtCountSize)
            $scaleUnitTabs.$tabName.Objects.$thisName.Text = $locString.lblSubnetSize
            $scaleUnitTabs.$tabName.Objects.$thisName.Font = $defaultLabelFont
            $scaleUnitTabs.$tabName.Tab.Controls.Add($scaleUnitTabs.$tabName.Objects.$thisName)

            $thisName = 'txtInfraSubnetSize'
            $thisObj = New-Object System.Windows.Forms.TextBox
            $scaleUnitTabs.$tabName.Objects.Add($thisName,$thisObj)
            $scaleUnitTabs.$tabName.Objects.$thisName.Location = New-Object System.Drawing.Size($txtSubnetSizeLeft,$tabRow)
            $scaleUnitTabs.$tabName.Objects.$thisName.Size = New-Object System.Drawing.Size($txtCountSize)
            $scaleUnitTabs.$tabName.Objects.$thisName.MaxLength = 2
            $scaleUnitTabs.$tabName.Objects.$thisName.Font = $defaultLabelFont
            $scaleUnitTabs.$tabName.Objects.$thisName.Text = $defaultInfraSubnetSize
            $scaleUnitTabs.$tabName.Objects.$thisName.Enabled = $false
            $scaleUnitTabs.$tabName.Objects.$thisName.ReadOnly = $true
            $scaleUnitTabs.$tabName.Objects.$thisName.Name = $thisName
            $scaleUnitTabs.$tabName.Tab.Controls.Add($scaleUnitTabs.$tabName.Objects.$thisName)

            $tabRow += 40

            # -------------------------- SWITCH INFRA SUBNET --------------------------
            $thisName = 'lblSwitchInfraSubnet'
            $thisObj = New-Object System.Windows.Forms.Label
            $scaleUnitTabs.$tabName.Objects.Add($thisName,$thisObj)
            $scaleUnitTabs.$tabName.Objects.$thisName.Location = New-Object System.Drawing.Size($lblLeft,($tabRow+2))
            $scaleUnitTabs.$tabName.Objects.$thisName.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
            $scaleUnitTabs.$tabName.Objects.$thisName.AutoSize = $true
            $scaleUnitTabs.$tabName.Objects.$thisName.Font = $defaultLabelFont
            $scaleUnitTabs.$tabName.Objects.$thisName.Text = $locString.lblSwitchInfraSubnet
            $scaleUnitTabs.$tabName.Tab.Controls.Add($scaleUnitTabs.$tabName.Objects.$thisName)

            $thisName = 'txtSwitchInfraSubnet'
            $thisObj = New-Object System.Windows.Forms.TextBox
            $scaleUnitTabs.$tabName.Objects.Add($thisName,$thisObj)
            $scaleUnitTabs.$tabName.Objects.$thisName.Location = New-Object System.Drawing.Size($txtLeft,$tabRow)
            $scaleUnitTabs.$tabName.Objects.$thisName.Size = New-Object System.Drawing.Size($txtPrefixSize)
            $scaleUnitTabs.$tabName.Objects.$thisName.MaxLength = $lenIPAddress
            $scaleUnitTabs.$tabName.Objects.$thisName.Font = $defaultLabelFont
            $scaleUnitTabs.$tabName.Objects.$thisName.Name = $thisName
            $scaleUnitTabs.$tabName.Tab.Controls.Add($scaleUnitTabs.$tabName.Objects.$thisName)
            $txtSwitchInfraSubnet_Changed = `
            {
                $thisName = 'txtSwitchInfraSubnet'
                $thisTab = $tabControl.SelectedTab
                $tabName = $thisTab.Name
                if(-not([string]::IsNullOrEmpty($scaleUnitTabs.$tabName.Objects.$thisName.Text)))
                {
                    $scaleUnitTabs.$tabName.Objects.$thisName.Text = ($scaleUnitTabs.$tabName.Objects.$thisName.Text).Trim()
                    if($scaleUnitTabs.$tabName.Objects.$thisName.Text -notmatch $validIPAddress)
                    {
                        Close-WaitForm
                        [System.Windows.Forms.MessageBox]::Show($locString.errIPAddressInvalid, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
                        $scaleUnitTabs.$tabName.Objects.$thisName.ForeColor = $defaultFailColor
                        $scaleUnitTabs.$tabName.Objects.$thisName.Select()
                    }
                    elseif(-not(Get-IsValidSubnet -IPAddress $scaleUnitTabs.$tabName.Objects.$thisName.Text -CIDR $scaleUnitTabs.$tabName.Objects.'txtSwitchInfraSubnetSize'.Text))
                    {
                        Close-WaitForm
                        $message = ($locString.errSubnetInvalid -f $scaleUnitTabs.$tabName.Objects.$thisName.Text,$scaleUnitTabs.$tabName.Objects.'txtSwitchInfraSubnetSize'.Text)
                        [System.Windows.Forms.MessageBox]::Show($message, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
                        $scaleUnitTabs.$tabName.Objects.$thisName.ForeColor = $defaultFailColor
                        $scaleUnitTabs.$tabName.Objects.$thisName.Select()
                    }
                    else
                    {
                        $scaleUnitTabs.$tabName.Objects.$thisName.ForeColor = $defaultForeColor
                    }
                    $scaleUnitTabs.$tabName.Objects.$thisName.Refresh()
                }
            }
            $scaleUnitTabs.$tabName.Objects.$thisName.Add_Leave($txtSwitchInfraSubnet_Changed)

            $thisName = 'lblSwitchInfraSubnetSize'
            $thisObj = New-Object System.Windows.Forms.Label
            $scaleUnitTabs.$tabName.Objects.Add($thisName,$thisObj)
            $scaleUnitTabs.$tabName.Objects.$thisName.Location = New-Object System.Drawing.Size($txtSubnetLeft,($tabRow+2))
            $scaleUnitTabs.$tabName.Objects.$thisName.Size = New-Object System.Drawing.Size($txtCountSize)
            $scaleUnitTabs.$tabName.Objects.$thisName.Text = $locString.lblSubnetSize
            $scaleUnitTabs.$tabName.Objects.$thisName.Font = $defaultLabelFont
            $scaleUnitTabs.$tabName.Tab.Controls.Add($scaleUnitTabs.$tabName.Objects.$thisName)

            $thisName = 'txtSwitchInfraSubnetSize'
            $thisObj = New-Object System.Windows.Forms.TextBox
            $scaleUnitTabs.$tabName.Objects.Add($thisName,$thisObj)
            $scaleUnitTabs.$tabName.Objects.$thisName.Location = New-Object System.Drawing.Size($txtSubnetSizeLeft,$tabRow)
            $scaleUnitTabs.$tabName.Objects.$thisName.Size = New-Object System.Drawing.Size($txtCountSize)
            $scaleUnitTabs.$tabName.Objects.$thisName.MaxLength = 2
            $scaleUnitTabs.$tabName.Objects.$thisName.Font = $defaultLabelFont
            $scaleUnitTabs.$tabName.Objects.$thisName.Text = $defaultSwInfraSubnetSize
            $scaleUnitTabs.$tabName.Objects.$thisName.Enabled = $false
            $scaleUnitTabs.$tabName.Objects.$thisName.ReadOnly = $true
            $scaleUnitTabs.$tabName.Objects.$thisName.Name = $thisName
            $scaleUnitTabs.$tabName.Tab.Controls.Add($scaleUnitTabs.$tabName.Objects.$thisName)

            $tabRow += 60

            # ------------------------------ TOR BGP ASN ------------------------------
            $thisName = 'lblTORASN'
            $thisObj = New-Object System.Windows.Forms.Label
            $scaleUnitTabs.$tabName.Objects.Add($thisName,$thisObj)
            $scaleUnitTabs.$tabName.Objects.$thisName.Location = New-Object System.Drawing.Size($lblLeft,($tabRow+2))
            $scaleUnitTabs.$tabName.Objects.$thisName.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
            $scaleUnitTabs.$tabName.Objects.$thisName.AutoSize = $true
            $scaleUnitTabs.$tabName.Objects.$thisName.Font = $defaultLabelFont
            $scaleUnitTabs.$tabName.Objects.$thisName.Text = $locString.lblTORASN
            $scaleUnitTabs.$tabName.Tab.Controls.Add($scaleUnitTabs.$tabName.Objects.$thisName)

            $thisName = 'txtTORASN'
            $thisObj = New-Object System.Windows.Forms.TextBox
            $scaleUnitTabs.$tabName.Objects.Add($thisName,$thisObj)
            $scaleUnitTabs.$tabName.Objects.$thisName.Location = New-Object System.Drawing.Size($txtLeft,$tabRow)
            $scaleUnitTabs.$tabName.Objects.$thisName.Size = New-Object System.Drawing.Size($txtPrefixSize)
            $scaleUnitTabs.$tabName.Objects.$thisName.TextAlign = [System.Windows.Forms.HorizontalAlignment]::Center
            $scaleUnitTabs.$tabName.Objects.$thisName.MaxLength = $lenIPAddress
            $scaleUnitTabs.$tabName.Objects.$thisName.Font = $defaultLabelFont
            $scaleUnitTabs.$tabName.Objects.$thisName.Name = $thisName
            $scaleUnitTabs.$tabName.Tab.Controls.Add($scaleUnitTabs.$tabName.Objects.$thisName)
            $txtTORASN_Changed = `
            {
                $thisName = 'txtTORASN'
                $thisTab = $tabControl.SelectedTab
                $tabName = $thisTab.Name
                if(-not([string]::IsNullOrEmpty($scaleUnitTabs.$tabName.Objects.$thisName.Text)))
                {
                    $scaleUnitTabs.$tabName.Objects.$thisName.Text = ($scaleUnitTabs.$tabName.Objects.$thisName.Text).Trim()
                    $valid = $true
                    if($scaleUnitTabs.$tabName.Objects.$thisName.Text -notmatch $validNumber)
                    {
                        $valid = $false
                    }
                    else
                    {
                        [uint64]$thisASN = $scaleUnitTabs.$tabName.Objects.$thisName.Text
                        if(-not(Get-IsValidASN -ASN $thisASN))
                        {
                            $valid = $false
                        }
                    }
                    if($valid)
                    {
                        $scaleUnitTabs.$tabName.Objects.$thisName.Text = $scaleUnitTabs.$tabName.Objects.$thisName.Text -replace '\b0+\B'
                        $scaleUnitTabs.$tabName.Objects.$thisName.ForeColor = $defaultForeColor
                    }
                    else
                    {
                        Close-WaitForm
                        [string]$errMessage = ($locString.errNumberNotInRange -f $validASNMin,$validASNMax)
                        [System.Windows.Forms.MessageBox]::Show($errMessage, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
                        $scaleUnitTabs.$tabName.Objects.$thisName.ForeColor = $defaultFailColor
                        $scaleUnitTabs.$tabName.Objects.$thisName.Select()
                    }
                    $scaleUnitTabs.$tabName.Objects.$thisName.Refresh()
                }
            }
            $scaleUnitTabs.$tabName.Objects.$thisName.Add_Leave($txtTORASN_Changed)
        }
    }
    catch
    {
        throw $PSItem
    }
}

function Remove-ScaleUnit
{
    param
    (
        [Parameter(Mandatory=$true)]
        [int]$ID
    )
    try
    {
        $tabName = "tabSU$($ID)"
        if($scaleUnitTabs.$tabName)
        {
            $tabRemove = $tabControl.TabPages[$tabName]
            $tabControl.Controls.Remove($tabRemove)
            $scaleUnitTabs.Remove($tabName)
            Invoke-RefreshTotalNodeCount
        }
    }
    catch
    {
        throw $PSItem
    }
}

function Clear-ListSelection
{
    param
    (
        [Parameter(Mandatory=$true)]
        $Control
    )
    try
    {
        if($Control.Items.Count -ge 1)
        {
            $Control.SelectedIndex = -1
        }
        else
        {
            $Control.Text = [string]::Empty
        }
    }
    catch
    {
        throw $PSItem.Exception.Message
    }
}

function Get-SwitchModels
{
    param
    (
        [Parameter(Mandatory=$true)]
        [string]$Make,
        [Parameter(Mandatory=$true)]
        [ValidateSet("TOR","BMC","SPINE")]
        [string]$Type
    )
    try
    {
        if(-not $switches)
        {
            $script:switches = Get-Content -Raw $strSwitchFilePath | ConvertFrom-Json
        }
        $switchInfo = $switches.portmap
        if($switchInfo.$Make.$Type -ne $null)
        {
            return @(($switchInfo.$Make.$Type | Get-Member -Type NoteProperty).Name)
        }
    }
    catch
    {
        throw $PSItem
    }
}

function Get-SwitchFirmware
{
    param
    (
        [Parameter(Mandatory=$true)]
        [string]$Make,
        [Parameter(Mandatory=$true)]
        [string]$Model,
        [Parameter(Mandatory=$true)]
        [ValidateSet("TOR","BMC","SPINE")]
        [string]$Type
    )
    try
    {
        if(-not $switches)
        {
            $script:switches = Get-Content -Raw $strSwitchFilePath | ConvertFrom-Json
        }
        $switchInfo = $switches.portmap
        if($switchInfo.$Make.$Type.$Model.FIRMWAREVERSION -ne $null)
        {
            return @(($switchInfo.$Make.$Type.$Model.FIRMWAREVERSION).Version)
        }
    }
    catch
    {
        throw $PSItem
    }
}

function Get-IsValidURI
{
    param
    (
        [Parameter(Mandatory=$true)]
        [string]$URI
    )
    $sysURI = $URI -as [System.URI]
    $sysURI.AbsoluteURI -ne $null -and $sysURI.Scheme -match 'http|https'
}

function Get-IsValidSubnet
{
    param
    (
        [Parameter(Mandatory=$true)]
        [string]$IPAddress,
        [Parameter(Mandatory=$true)]
        [string]$CIDR
    )
    [System.Net.IPNetwork]$net = $IPAddress + '/' + $CIDR
    if($IPAddress -ne $net.Network.IPAddressToString)
    {
        return $false
    }
    else
    {
        return $true
    }
}

function Get-IsValidASN
{
    param
    (
        [uint64]$ASN
    )
    if($ASN -lt $validASNMin)
    {
        return $false
    }
    if($ASN -gt $validASNMax)
    {
        return $false
    }
    return $true
}

function Disable-EdgeBGP
{
    param
    (
        [Parameter(Mandatory=$true)]
        [int]$Num
    )
    $thisScenario = $scenario
    if($scenario -eq "AddRack")
    {
         $thisScenario = "Customer"
    }
    switch ($thisScenario)
    {
        "Customer"
        {
            $varLbl = $tabNetwork.Controls | Where-Object Name -eq "lblEdgeBGP$($Num)"
            $varTxt = $tabNetwork.Controls | Where-Object Name -eq "txtEdgeBGP$($Num)"
            if($varLbl -and $varTxt)
            {
                $varLbl.Enabled = $false
                $varLbl.Visible = $false
                $varTxt.Enabled = $false
                $varTxt.Visible = $false
                $varTxt.Text = [string]::Empty
                $varLbl.Refresh()
                $varTxt.Refresh()
            }
            else
            {
                Write-Log -Message "$($MyInvocation.MyCommand.Name) - WARNING: unable to find Edge BGP ASN controls" -Force
            }
        }
        default
        {
        }
    }
}

function Enable-EdgeBGP
{
    param
    (
        [Parameter(Mandatory=$true)]
        [int]$Num
    )
    $thisScenario = $scenario
    if($scenario -eq "AddRack")
    {
         $thisScenario = "Customer"
    }
    switch ($thisScenario)
    {
        "Customer"
        {
            $varLbl = $tabNetwork.Controls | Where-Object Name -eq "lblEdgeBGP$($Num)"
            $varTxt = $tabNetwork.Controls | Where-Object Name -eq "txtEdgeBGP$($Num)"
            if($varLbl -and $varTxt)
            {
                $varLbl.Enabled = $true
                $varLbl.Visible = $true
                $varTxt.Enabled = $true
                $varTxt.Visible = $true
                $varLbl.Refresh()
                $varTxt.Refresh()
            }
            else
            {
                Write-Log -Message "$($MyInvocation.MyCommand.Name) - WARNING: unable to find Edge BGP ASN controls" -Force
            }
        }
        default
        {
        }
    }
}

function Disable-SpineBGP
{
    $thisScenario = $scenario
    if($scenario -eq "AddRack")
    {
         $thisScenario = "Customer"
    }
    switch ($thisScenario)
    {
        "Customer"
        {
            $lblSpineBGP.Enabled = $false
            $lblSpineBGP.Visible = $false
            $txtSpineBGP.Enabled = $false
            $txtSpineBGP.Visible = $false
            $txtSpineBGP.Text = [string]::Empty
            $lblSpineBGP.Refresh()
            $txtSpineBGP.Refresh()
        }
        default
        {
        }
    }
}

function Enable-SpineBGP
{
    $thisScenario = $scenario
    if($scenario -eq "AddRack")
    {
         $thisScenario = "Customer"
    }
    switch ($thisScenario)
    {
        "Customer"
        {
            $lblSpineBGP.Enabled = $true
            $lblSpineBGP.Visible = $true
            $txtSpineBGP.Enabled = $true
            $txtSpineBGP.Visible = $true
            $lblSpineBGP.Refresh()
            $txtSpineBGP.Refresh()
        }
        default
        {
        }
    }
}

function Enable-SpineCount
{
    $thisScenario = $scenario
    if($scenario -eq "AddRack")
    {
         $thisScenario = "Customer"
    }
    switch ($thisScenario)
    {
        "Customer"
        {
            $lblSpineCount.Enabled = $true
            $lblSpineCount.Visible = $true
            $txtSpineCount.Enabled = $true
            $txtSpineCount.Visible = $true
            $txtSpineCount.Text = $defaultSpineCount
            $lblSpineCount.Refresh()
            $txtSpineCount.Refresh()
        }
        default
        {
        }
    }
}

function Disable-SpineCount
{
    $thisScenario = $scenario
    if($scenario -eq "AddRack")
    {
         $thisScenario = "Customer"
    }
    switch ($thisScenario)
    {
        "Customer"
        {
            $lblSpineCount.Enabled = $false
            $lblSpineCount.Visible = $false
            $txtSpineCount.Enabled = $false
            $txtSpineCount.Visible = $false
            $txtSpineCount.Text = [string]::Empty
            $lblSpineCount.Refresh()
            $txtSpineCount.Refresh()
        }
        default
        {
        }
    }
}

function Enable-SpineSubnet
{
    $thisScenario = $scenario
    if($scenario -eq "AddRack")
    {
         $thisScenario = "Customer"
    }
    switch ($thisScenario)
    {
        "Customer"
        {
            $txtSpineSubnet.Enabled = $true
            $txtSpineSubnet.Visible = $true
            $txtSpineSubnet.Refresh()
            $lblSpineSubnet.Enabled = $true
            $lblSpineSubnet.Visible = $true
            $lblSpineSubnet.Refresh()
            $lblSpineSubnetSize.Enabled = $true
            $lblSpineSubnetSize.Visible = $true
            $txtSpineSubnetSize.Enabled = $false
            $txtSpineSubnetSize.Visible = $true
        }
        default
        {
        }
    }
}

function Disable-SpineSubnet
{
    $thisScenario = $scenario
    if($scenario -eq "AddRack")
    {
         $thisScenario = "Customer"
    }
    switch ($thisScenario)
    {
        "Customer"
        {
            $txtSpineSubnet.Enabled = $false
            $txtSpineSubnet.Visible = $false
            $txtSpineSubnet.Text = [string]::Empty
            $txtSpineSubnet.Refresh()
            $lblSpineSubnet.Enabled = $false
            $lblSpineSubnet.Visible = $false
            $lblSpineSubnet.Refresh()
            $lblSpineSubnetSize.Enabled = $false
            $lblSpineSubnetSize.Visible = $false
            $txtSpineSubnetSize.Enabled = $false
            $txtSpineSubnetSize.Visible = $false
        }
        default
        {
        }
    }
}

function Invoke-RefreshEdgeBGP
{
    param
    (
        [string]$EdgeCount
    )
    (1..$maxEdge) | ForEach-Object `
    {
        if($PSItem -le $EdgeCount)
        {
            Enable-EdgeBGP $PSItem
        }
        else
        {
            Disable-EdgeBGP $PSItem
        }
    }
}

function Clear-ExistingData
{
    # Set textbox items
    $objTextBox = `
    @(
        $txtEdgeBGP1
        $txtEdgeBGP2
        $txtEdgeBGP3
        $txtEdgeBGP4
        $txtSpineBGP
        $txtSoftwareBGP
        $txtExternalSubnet
        $txtCompanyName
        $txtRegionName
        $txtExternalDomain
        $txtPrivateDomain
        $txtDeployPrefix
        $txtPhysicalPrefix
        $txtTimeServer
        $txtDNSForwarder
        $txtSyslogServer
        $txtSpineSubnet
        $txtAADTenantName
        $txtADFSForestFQDN
        $txtADFSProviderName
        $txtADFSMetadataUri
    )
    # Set values on the Scale-Unit tab
    $objScaleUnit = `
    @(
        'txtNodeCount'
        'txtBMCSubnet'
        'txtStorageSubnet'
        'txtInfraSubnet'
        'txtSwitchInfraSubnet'
        'txtTORASN'
    )
    # Set combobox items
    $objComboBox = `
    @(
        $lstSwitchVendor
        $lstTORModel
        $lstTORFirmware
        $lstBMCModel
        $lstBMCFirmware
        $lstSpineModel
        $lstSpineFirmware
        $lstAzureEnvironment
    )
    # Clear Scale-Unit objects
    $txtScaleUnitCount.Text = '1'
    Invoke-Command -ScriptBlock $txtScaleUnitCount_Changed
    $objScaleUnit | ForEach-Object `
    {
        $scaleUnitTabs.tabSU1.Objects.$PSItem.Text = [string]::Empty
    }
    # Reset Customer items to default settings
    $lstIdentityStore.SelectedIndex = 0
    $lstRoutingMethod.SelectedIndex = 0
    $txtEdgeCount.Text = $defaultEdgeCount
    $txtExternalSubnetSize.Text = $defaultExternalSubnetSize
    Invoke-RefreshTotalNodeCount

    # Clear textbox objects
    $objTextBox | Foreach-Object `
    {
        $PSitem.Text = [string]::Empty
    }
    # Clear combobox selections
    foreach($obj in $objComboBox)
    {
        if($obj.Items.Count -ge 1)
        {
            $obj.SelectedIndex = -1
        }
        else
        {
            $obj.Text = [string]::Empty
        }
    }
    # Clear Partner tab if present
    if($tabControl.TabPages["tabPartner"])
    {
        $tabPartner.Controls | ForEach-Object `
        {
            if($PSItem -is [System.Windows.Forms.TextBox])
            {
                $PSItem.Text = [string]::Empty
            }
            elseif($PSItem -is [System.Windows.Forms.ComboBox])
            {
                $PSItem.SelectedIndex = -1
            }
        }
    }
    Remove-GeneratedTabs
    Invoke-Command -ScriptBlock $txtEdgeCount_Changed
}

function Get-SubnetSize
{
    param
    (
        [Parameter(Mandatory=$true)]
        [int]$CloudCount,
        [Parameter(Mandatory=$true)]
        [int]$SupernetSize
    )
    $ipsInSupernet = [System.Math]::Pow(2, 32 - $SupernetSize)
    $ipsInCluster = $ipsInSupernet/$CloudCount
    $roundDownLogVal = [System.Math]::Floor([System.Math]::Log($ipsInCluster, 2))
    [double]$subnetsizePerCluster = 32 - $roundDownLogVal
    return [int]$subnetsizePerCluster
}

function New-Deployment
{
    $thisDeployment = New-Object Microsoft.AzureStack.PartnerToolkit.IPCalculator.Deployment
    return $thisDeployment
}

function Get-PartnerData
{
    $script:partnerData = @{}
    $tabPartner.Controls | ForEach-Object `
    {
        if($PSItem -is [System.Windows.Forms.TextBox])
        {
            $partnerData.Add($PSItem.Name,$PSItem.Text)
        }
        elseif($PSItem -is [System.Windows.Forms.ComboBox])
        {
            $partnerData.Add($PSItem.Name,$PSItem.SelectedItem)
        }
    }
}

function Add-PartnerTab
{
    $script:tabPartner = New-Object System.Windows.Forms.TabPage
    $typTab = $tabPartner.GetType()
    $prpTab = $typTab.GetProperty('DoubleBuffered',('Instance','NonPublic'))
    $prpTab.SetValue($tabPartner, $true, $null)
    $tabPartner.BackColor = [System.Drawing.Color]::Lavender
    $tabPartner.Name = "tabPartner"
    $tabPartner.Text = $locString.tabPartner
    $tabPartner.AutoScroll = $true
    $tabControl.Controls.Add($tabPartner)
}

function Move-PartnerTab
{
    if($tabControl.TabPages.ContainsKey('tabPartner'))
    {
        $tabMove += $tabControl.TabPages['tabPartner']
        $tabControl.TabPages.Remove($tabMove)
        $tabControl.TabPages.Add($tabMove)
    }
}

function Import-PartnerExtension
{
    param
    (
        [Parameter(Mandatory=$true)]
        [string]$FileName
    )
    Write-Log -Message "$($MyInvocation.MyCommand.Name) - called with a FileName of '$($FileName)'"
    [int]$script:oemrow = 20
    [int]$lblSubSection = [int]$lblLeft + 40
    [int]$lblSection = $lblLeft
    $partnerExtension = Get-Content $FileName -Raw | ConvertFrom-Json
    Add-PartnerTab

    function Add-PartnerDataSectionToTab
    {
        param
        (
            $Section,
            $Left
        )
        [int]$lblPartner = $Left + 40
        [int]$txtPartner = $Left + 80
        $sectionTitle = $Section.title
        # Get-Member automatically sorts the list of names, so don't us it
        # $valueNames = ($Section.Properties | Get-Member -Type NoteProperty).Name
        $valueNames = ($Section.Properties.PSObject.Properties).Name
        if($valueNames.Count -gt 0)
        {
            foreach($item in $valueNames)
            {
                $thisLabel = New-Object System.Windows.Forms.Label
                $thisLabel.Location = New-Object System.Drawing.Size($lblPartner,($script:oemrow))
                $thisLabel.AutoSize = $true
                $thisLabel.Font = $defaultLabelFont
                $thisLabel.Text = "$($item) :"
                $tabPartner.Controls.Add($thisLabel)

                $thisType = $Section.Properties.$($item).type
                switch ($thisType)
                {
                    "string"
                    {
                        $script:oemrow += 20
                        $thisText = New-Object System.Windows.Forms.TextBox
                        $thisText.Location = New-Object System.Drawing.Size($txtPartner,$script:oemrow)
                        $thisText.Size = New-Object System.Drawing.Size($txtSize)
                        $thisText.Font = $defaultLabelFont
                        $thisText.Name = $item
                        if($Section.Properties.$item.maxlength)
                        {
                            $thisText.MaxLength = $Section.Properties.$item.maxlength
                        }
                        $tabPartner.Controls.Add($thisText)
                    }
                    "list"
                    {
                        $script:oemrow += 20
                        $thisList = New-Object System.Windows.Forms.ComboBox
                        $thisList.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList
                        $thisList.Location = New-Object System.Drawing.Size($txtPartner,$script:oemrow)
                        $thisList.Size = New-Object System.Drawing.Size($txtSize)
                        $thisList.Font = $defaultLabelFont
                        $thisList.Name = $item
                        [void]$thisList.Items.Clear()
                        $Section.Properties.$item.data | Foreach-Object `
                        {
                            [void]$thisList.Items.Add($PSItem)
                        }
                        $tabPartner.Controls.Add($thisList)
                    }
                    default
                    {
                        Write-Warning "An unknown partner extension Type of '$($thisType)' was encountered."
                    }
                }
                $script:oemrow += 40
            }
        }
    }

    foreach($section in $partnerExtension.Sections)
    {
        if($section.title)
        {
            $sectionTitle = $section.title
            $lblPartnerSection = New-Object System.Windows.Forms.Label
            $lblPartnerSection.Location = New-Object System.Drawing.Size($lblSection,($script:oemrow))
            $lblPartnerSection.AutoSize = $true
            $lblPartnerSection.Text = $sectionTitle
            $lblPartnerSection.Font = [System.Drawing.Font]::New("Microsoft Sans Serif", 12, [System.Drawing.FontStyle]::Bold)
            $tabPartner.Controls.Add($lblPartnerSection)
            $script:oemrow += $lblPartnerSection.Size.Height + 8
        }
        if($section.comment)
        {
            $sectionComment = $section.comment
            $lblPartnerSectionComment = New-Object System.Windows.Forms.Label
            $lblPartnerSectionComment.Location = New-Object System.Drawing.Size($lblSection,($script:oemrow))
            $lblPartnerSectionComment.MaximumSize = New-Object System.Drawing.Size($maxComment, 0)
            $lblPartnerSectionComment.AutoSize = $true
            $lblPartnerSectionComment.Text = $sectionComment
            $lblPartnerSectionComment.Font = [System.Drawing.Font]::New("Microsoft Sans Serif", 8.5, [System.Drawing.FontStyle]::Italic)
            $tabPartner.Controls.Add($lblPartnerSectionComment)
            $script:oemrow += $lblPartnerSectionComment.Size.Height + 12
        }
        else
        {
            $script:oemrow += 10
        }
        if($section.SubSections)
        {
            foreach($subsection in $section.SubSections)
            {
                if($subsection.title)
                {
                    $sectionTitle = $subsection.title
                    $lblPartnerSubSection = New-Object System.Windows.Forms.Label
                    $lblPartnerSubSection.Location = New-Object System.Drawing.Size($lblSubSection,($script:oemrow))
                    $lblPartnerSubSection.AutoSize = $true
                    $lblPartnerSubSection.Text = $sectionTitle
                    $lblPartnerSubSection.Font = [System.Drawing.Font]::New("Microsoft Sans Serif", 10, [System.Drawing.FontStyle]::Bold)
                    $tabPartner.Controls.Add($lblPartnerSubSection)
                    $script:oemrow += $lblPartnerSubSection.Size.Height + 8
                }
                if($subsection.comment)
                {
                    $sectionComment = $subsection.comment
                    $lblPartnerSubSectionComment = New-Object System.Windows.Forms.Label
                    $lblPartnerSubSectionComment.Location = New-Object System.Drawing.Size($lblSubSection,($script:oemrow))
                    $lblPartnerSubSectionComment.MaximumSize = New-Object System.Drawing.Size(($maxComment-$lblSubSection), 0)
                    $lblPartnerSubSectionComment.AutoSize = $true
                    $lblPartnerSubSectionComment.Text = $sectionComment
                    $lblPartnerSubSectionComment.Font = [System.Drawing.Font]::New("Microsoft Sans Serif", 8.5, [System.Drawing.FontStyle]::Italic)
                    $tabPartner.Controls.Add($lblPartnerSubSectionComment)
                    $script:oemrow += $lblPartnerSubSectionComment.Size.Height + 12
                }
                else
                {
                    $script:oemrow += 10
                }
                Add-PartnerDataSectionToTab -Section $subsection -Left $lblSubSection
            }
        }
        else
        {
            Add-PartnerDataSectionToTab -Section $section -Left $lblSection
        }
        $script:oemrow += 20
    }
    $script:oemrow += 20
    $lblPadding = New-Object System.Windows.Forms.Label
    $lblPadding.Location = New-Object System.Drawing.Size($lblSection,($script:oemrow))
    $lblPadding.AutoSize = $true
    $lblPadding.Text = ""
    $lblPadding.Font = [System.Drawing.Font]::New("Microsoft Sans Serif", 12, [System.Drawing.FontStyle]::Bold)
    $tabPartner.Controls.Add($lblPadding)
}

function Get-ValidationError
{
    param
    (
        [Parameter(Mandatory=$true)]$ErrorList
    )
    $retMessage = @()
    foreach($item in $ErrorList)
    {
        # Weed out some of the duplicate errors to make it easier on the user
        $item = $item.Trim()
        if((-not [string]::IsNullOrEmpty($item)) -and ($retMessage -notcontains $item))
        {
            $add = $true
            if($item -match "correct all validation issues")
            {
                $add = $false
            }
            elseif($item -match "overlap 0.0.0.0")
            {
                $add = $false
            }
            elseif(($item -match "TOR2") -or ($item -match "TOR-2"))
            {
                $add = $false
            }
            elseif(($item -like "*Model is empty for (prefix-Rack0?-Spine*") -and ($retMessage -like "*Model is empty for (prefix-Rack0?-Spine*"))
            {
                $add = $false
            }
            elseif(($item -like "*Spine BGP ASN value is empty*") -and ($retMessage -like "*Spine BGP ASN value is empty*"))
            {
                $add = $false
            }
            elseif(($item -like "*firmware version is empty for (prefix-Rack0?-Spine*") -and ($retMessage -like "*firmware version is empty for (prefix-Rack0?-Spine*"))
            {
                $add = $false
            }
            elseif(($item -like "*TOR? and Border*") -or ($item -like "*Border and TOR?*"))
            {
                if($retMessage -like "*TOR? and Border*") { $add = $false }
                if($retMessage -like "*Border and TOR?*") { $add = $false }
            }
            elseif(($item -like "*MUX and TOR?*") -or ($item -like "*TOR? and MUX*"))
            {
                if($retMessage -like "*MUX and TOR?*") { $add = $false }
                if($retMessage -like "*TOR? and MUX*") { $add = $false }
            }
            elseif(($item -like "*MUX and Border*") -or ($item -like "*Border and MUX*"))
            {
                if($retMessage -like "*MUX and Border*") { $add = $false }
                if($retMessage -like "*Border and MUX*") { $add = $false }
            }
            elseif($item -like "*Edge Switch BGP ASN value is empty*")
            {
                if($retMessage -like "*Edge Switch BGP ASN value is empty*") { $add = $false }
            }
            elseif($item -like "*TOR2 BGP ASN value is empty*")
            {
                $add = $false
            }
            elseif(($item -match "at System") -or ($item -match "at Microsoft") -or ($item -match "at CallSite"))
            {
                $add = $false
            }
            if($add)
            {
                $retMessage += "`n$($item)`n"
            }
        }
    }
    return $retMessage
}

function Get-DomainValidationError
{
    param
    (
        [Parameter(Mandatory=$true)][System.Windows.Forms.TextBox]$TextControl
    )
    [string[]]$errDetails = @()
    if($TextControl.Text.Split('.').Count -le 1)
    {
        [string[]]$errDetails += $locString.msgFQDNTwoLevels
    }
    else
    {
        [string]$tld = $TextControl.Text.Split('.') | Select-Object -Last 1
        if($tld.Length -lt 2)
        {
            [string[]]$errDetails += $locString.msgFQDNTLDTwoChars
        }
        if($tld -match '[0-9]')
        {
            [string[]]$errDetails += $locString.msgFQDNTLDNoNum
        }
        if($tld -match '_|\W+')
        {
            [string[]]$errDetails += $locString.msgFQDNTLDNoDash
        }
    }
    return $errDetails
}

function Start-DeploymentWorksheet
{
    param
    (
        [switch]$EnableLogging,
        [switch]$EnableExperimental
    )
    try
    {
        $script:logging = $false
        Initialize-Environment
        if($EnableLogging)
        {
            $script:logging = $true
            Invoke-InitializeLogging
            Write-Log -Message "$($locString.titleMain) - v$(Get-FileVersionInfo)"
        }
        if($psISE)
        {
            Write-Log -Message "$($locString.powershellISE)" -Quiet
            $bar = '-' * [int]($locString.powershellISE).Length
            Write-Warning $bar
            Write-Warning $locString.powershellISE
            Write-Warning $bar
        }
        if(([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))
        {
            Write-Log -Message "$($locString.powershellAdmin)" -Quiet
            $bar = '-' * [int]($locString.powershellAdmin).Length
            Write-Warning $bar
            Write-Warning $locString.powershellAdmin
            Write-Warning $bar
        }
    }
    catch
    {
        throw $PSItem
    }

    # =============================== MAIN FORM ===============================
    $script:frmMain = New-Object System.Windows.Forms.Form
    $frmMain.AutoScaleDimensions = $defaultAutoScaleDimensions
    $frmMain.AutoScaleMode = $defaultAutoScaleMode
    $frmMain.AutoSizeMode = $defaultAutoSizeMode
    $frmMain.SuspendLayout()
    $typForm = $frmMain.GetType()
    $prpForm = $typForm.GetProperty('DoubleBuffered',('Instance','NonPublic'))
    $prpForm.SetValue($frmMain, $true, $null)
    $frmMain.Size = New-Object System.Drawing.Size($defaultMainFormSize)
    $frmMain.ShowIcon = $false
    $frmMain.MaximizeBox = $true
    $frmMain.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::Sizable
    $frmMain.MinimumSize = New-Object System.Drawing.Size($defaultMainFormSize)
    $frmMain.StartPosition = "CenterScreen"
    $frmMain.Text = "$($locString.titleMain) - v$(Get-FileVersionInfo)"
    $frmMain.Name = 'DeploymentWorksheet'
    $frmMain.BackColor = [System.Drawing.Color]::LightSlateGray
    $frmMain.KeyPreview = $true
    $script:btnEnter = New-Object System.Windows.Forms.Button
    $btnEnter.Add_Click({Keystroke_Enter})
    $frmMain.AcceptButton = $btnEnter
    #$frmMain.Add_KeyDown({if($PSItem.KeyCode -eq "Enter"){Keystroke_Enter}})
    #$frmMain.Add_KeyDown({if($PSItem.KeyCode -eq "Escape"){$frmMain.Close()}})
    $frmMain.Add_Resize( { OnResize_frmMain $frmMain $EventArgs } )
    function OnResize_frmMain
    {
        $w = [int]$frmMain.Size.Width - 22
        $h = [int]$frmMain.Size.Height - 70
        $tabControl.Size = New-Object System.Drawing.Size($w,$h)
        $frmMenu.Width = $frmMain.Width
    }

    # ---------------------------- MAIN FORM TABS -----------------------------
    $script:tabControl = New-Object System.Windows.Forms.TabControl
    $tabControl.BackColor = [System.Drawing.Color]::SlateGray
    $tabControl.Alignment = 'Top'
    $tabControl.Location = '2,28'
    $tabControl.Multiline = $true
    $tabControl.Name = 'TabControl'
    $tabControl.SelectedIndex = 0
    $tabControl.AutoSize = $true
    $tabControl.MinimumSize = New-Object System.Drawing.Size(([int]$frmMain.Size.Width - 22),([int]$frmMain.Size.Height - 70))

    $script:tabCustomer = New-Object System.Windows.Forms.TabPage
    $typTab = $tabCustomer.GetType()
    $prpTab = $typTab.GetProperty('DoubleBuffered',('Instance','NonPublic'))
    $prpTab.SetValue($tabCustomer, $true, $null)
    $tabCustomer.Name = "tabCustomer"
    $tabCustomer.Text = $locString.tabCustomer

    $script:tabNetwork = New-Object System.Windows.Forms.TabPage
    $typTab = $tabNetwork.GetType()
    $prpTab = $typTab.GetProperty('DoubleBuffered',('Instance','NonPublic'))
    $prpTab.SetValue($tabNetwork, $true, $null)
    $tabNetwork.Name = "tabNetwork"
    $tabNetwork.Text = $locString.tabNetwork

    $frmMain.Controls.Add($tabControl)
    $tabControl.Controls.Add($tabCustomer)
    $tabControl.Controls.Add($tabNetwork)
    Add-ScaleUnitTab -ID 1

    # ============================= CUSTOMER TAB ==============================
    $row = 20

    # ----------------------------- DOCUMENT LINK -----------------------------
    $script:lnkCustomerDoc = New-Object System.Windows.Forms.LinkLabel
    $lnkCustomerDoc.Location = New-Object System.Drawing.Size(680,($row+2))
    $lnkCustomerDoc.Size = New-Object System.Drawing.Size(300,20)
    $lnkCustomerDoc.Text = $locString.customerLinkText
    $lnkCustomerDoc.Font = $defaultLabelFont
    $lnkCustomerDoc.Add_Click({[system.Diagnostics.Process]::Start($locString.customerLink)})
    $tabCustomer.Controls.Add($lnkCustomerDoc)

    # --------------------------- ON-HOVER TOOLTIPS ---------------------------
    $tooltip1 = New-Object System.Windows.Forms.ToolTip
    $tooltip1.ShowAlways = $true
    $tooltip1.ReshowDelay = 500
    $tooltip1.AutoPopDelay = 10000
    $tooltip1.IsBalloon = $true
    $ShowHelp = `
    {
        # Display popup help - Each value is the name of a control on the form.
        switch ($this.Name)
        {
            "txtTimeServer"
            {
                $tip = $locString.tipTimeServer
                break
            }
            "txtDNSForwarder"
            {
                $tip = $locString.tipDNSForwarder
                break
            }
            "txtSyslogServer"
            {
                $tip = $locString.tipSyslogServer
                break
            }
            "txtADFSForestFQDN"
            {
                $tip = $locString.tipADFSForestFQDN
                break
            }
            "txtADFSProviderName"
            {
                $tip = $locString.tipADFSProviderName
                break
            }
            "txtADFSMetadataUri"
            {
                $tip = $locString.tipADFSMetadataUri
                break
            }
            default
            {
                Write-Log -Message "ShowHelp encountered an unhandled property named '$($this.Name)'"
                break
            }
        }
        $tooltip1.SetToolTip($this,$tip)
    }

    # ---------------------------- AZURE IDENTITY -----------------------------
    $script:lblAzureIdentity = New-Object System.Windows.Forms.Label
    $lblAzureIdentity.Location = New-Object System.Drawing.Size($lblLeft,($row))
    $lblAzureIdentity.AutoSize = $true
    $lblAzureIdentity.Text = $locString.lblAzureIdentity
    $lblAzureIdentity.Font = [System.Drawing.Font]::New("Microsoft Sans Serif", 12, [System.Drawing.FontStyle]::Bold)
    $tabCustomer.Controls.Add($lblAzureIdentity)

    $row += 40

    # --------------------------- IDENTITY STORE ------------------------------
    $script:lblIdentityStore = New-Object System.Windows.Forms.Label
    $lblIdentityStore.Location = New-Object System.Drawing.Size($lblLeft,($row+2))
    $lblIdentityStore.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblIdentityStore.AutoSize = $true
    $lblIdentityStore.Font = $defaultLabelFont
    $lblIdentityStore.Text = $locString.lblIdentityStore
    $tabCustomer.Controls.Add($lblIdentityStore)

    $script:lstIdentityStore = New-Object System.Windows.Forms.ComboBox
    $lstIdentityStore.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList
    $lstIdentityStore.Location = New-Object System.Drawing.Size($txtLeft,$row)
    $lstIdentityStore.Size = New-Object System.Drawing.Size($txtSize)
    $lstIdentityStore.Font = $defaultLabelFont
    $lstIdentityStore.Name = 'lstIdentityStore'
    [void]$lstIdentityStore.Items.Clear()
    $arrIdentityStore | Foreach-Object `
    {
        [void]$lstIdentityStore.Items.Add($PSItem)
    }
    $lstIdentityStore.SelectedIndex = -1
    $tabCustomer.Controls.Add($lstIdentityStore)
    $lstIdentityStore_Changed = `
    {
        switch ($lstIdentityStore.SelectedItem)
        {
            "Azure Active Directory"
            {
                $lblAADTenantName.Enabled = $true
                $lblAADTenantName.Visible = $true
                $lblAzureEnvironment.Enabled = $true
                $lblAzureEnvironment.Visible = $true
                $txtAADTenantName.Enabled = $true
                $txtAADTenantName.Visible = $true
                $lstAzureEnvironment.Enabled = $true
                $lstAzureEnvironment.Visible = $true
                # Disable ADFS objects
                $txtADFSForestFQDN.Text = $null
                $txtADFSForestFQDN.Enabled = $false
                $txtADFSForestFQDN.Visible = $false
                $txtADFSProviderName.Text = $null
                $txtADFSProviderName.Enabled = $false
                $txtADFSProviderName.Visible = $false
                $txtADFSMetadataUri.Text = $null
                $txtADFSMetadataUri.Enabled = $false
                $txtADFSMetadataUri.Visible = $false
                $lblADFSForestFQDN.Enabled = $false
                $lblADFSProviderName.Enabled = $false
                $lblADFSMetadataUri.Enabled = $false
                $lblADFSForestFQDN.Visible = $false
                $lblADFSProviderName.Visible = $false
                $lblADFSMetadataUri.Visible = $false
            }
            "Active Directory Federation Services"
            {
                $txtADFSForestFQDN.Enabled = $true
                $txtADFSForestFQDN.Visible = $true
                $txtADFSProviderName.Enabled = $true
                $txtADFSProviderName.Visible = $true
                $txtADFSMetadataUri.Enabled = $true
                $txtADFSMetadataUri.Visible = $true
                $lblADFSForestFQDN.Enabled = $true
                $lblADFSProviderName.Enabled = $true
                $lblADFSMetadataUri.Enabled = $true
                $lblADFSForestFQDN.Visible = $true
                $lblADFSProviderName.Visible = $true
                $lblADFSMetadataUri.Visible = $true
                # Disable AAD objects
                $lblAADTenantName.Enabled = $false
                $lblAADTenantName.Visible = $false
                $lblAzureEnvironment.Enabled = $false
                $lblAzureEnvironment.Visible = $false
                $txtAADTenantName.Enabled = $false
                $txtAADTenantName.Visible = $false
                $txtAADTenantName.Text = $null
                $lstAzureEnvironment.Enabled = $false
                $lstAzureEnvironment.Visible = $false
                $lstAzureEnvironment.SelectedIndex = -1
            }
            default
            {
            }
        }
        $lblAADTenantName.Refresh()
        $lblAzureEnvironment.Refresh()
        $txtAADTenantName.Refresh()
        $lstAzureEnvironment.Refresh()
        $txtADFSForestFQDN.Refresh()
        $txtADFSProviderName.Refresh()
        $txtADFSMetadataUri.Refresh()
        $lblADFSForestFQDN.Refresh()
        $lblADFSProviderName.Refresh()
        $lblADFSMetadataUri.Refresh()
    }
    $lstIdentityStore.Add_SelectedIndexChanged($lstIdentityStore_Changed)

    $row += 25

    # ---------------------------- TENANT NAME --------------------------------
    $script:lblAADTenantName = New-Object System.Windows.Forms.Label
    $lblAADTenantName.Location = New-Object System.Drawing.Size($lblLeft,($row+2))
    $lblAADTenantName.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblAADTenantName.AutoSize = $true
    $lblAADTenantName.Font = $defaultLabelFont
    $lblAADTenantName.Text = $locString.lblAADTenantName
    $lblAADTenantName.Enabled = $false
    $lblAADTenantName.Visible = $false
    $tabCustomer.Controls.Add($lblAADTenantName)

    $script:txtAADTenantName = New-Object System.Windows.Forms.TextBox
    $txtAADTenantName.Location = New-Object System.Drawing.Size($txtLeft,$row)
    $txtAADTenantName.Size = New-Object System.Drawing.Size($txtSize)
    $txtAADTenantName.Font = $defaultLabelFont
    $txtAADTenantName.Name = 'txtAADTenantName'
    $txtAADTenantName.MaxLength = $lenText
    $txtAADTenantName.Enabled = $false
    $txtAADTenantName.Visible = $false
    $tabCustomer.Controls.Add($txtAADTenantName)
    $txtAADTenantName_Changed = `
    {
        if((-not [string]::IsNullOrEmpty($txtAADTenantName.Text)) -and ($txtAADTenantName.Text -notmatch $validDomain))
        {
            Close-WaitForm
            [System.Windows.Forms.MessageBox]::Show($locString.errDomainInvalid, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
            $txtAADTenantName.ForeColor = $defaultFailColor
            $txtAADTenantName.Select()
        }
        else
        {
            $txtAADTenantName.ForeColor = $defaultForeColor
        }
        $txtAADTenantName.Refresh()
    }
    $txtAADTenantName.Add_Leave($txtAADTenantName_Changed)

    # ----------------------------- ADFS FQDN ---------------------------------
    $script:lblADFSForestFQDN = New-Object System.Windows.Forms.Label
    $lblADFSForestFQDN.Location = New-Object System.Drawing.Size($lblLeft,($row+2))
    $lblADFSForestFQDN.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblADFSForestFQDN.AutoSize = $true
    $lblADFSForestFQDN.Font = $defaultLabelFont
    $lblADFSForestFQDN.Text = $locString.lblADFSForestFQDN
    $lblADFSForestFQDN.Enabled = $false
    $lblADFSForestFQDN.Visible = $false
    $tabCustomer.Controls.Add($lblADFSForestFQDN)

    $script:txtADFSForestFQDN = New-Object System.Windows.Forms.TextBox
    $txtADFSForestFQDN.Location = New-Object System.Drawing.Size($txtLeft,$row)
    $txtADFSForestFQDN.Size = New-Object System.Drawing.Size($txtSize)
    $txtADFSForestFQDN.Font = $defaultLabelFont
    $txtADFSForestFQDN.Name = 'txtADFSForestFQDN'
    $txtADFSForestFQDN.MaxLength = $lenDomain
    $txtADFSForestFQDN.Enabled = $false
    $txtADFSForestFQDN.Visible = $false
    $txtADFSForestFQDN.Add_MouseHover($ShowHelp)
    $tabCustomer.Controls.Add($txtADFSForestFQDN)
    $txtADFSForestFQDN_Changed = `
    {
        if((-not [string]::IsNullOrEmpty($txtADFSForestFQDN.Text)) -and ($txtADFSForestFQDN.Text -notmatch $validDomain))
        {
            Close-WaitForm
            [string[]]$detailErr = Get-DomainValidationError $txtADFSForestFQDN
            [string]$message = $locString.errDomainInvalid
            if($detailErr.Count -gt 0)
            {
                $message += "`n`n$($locString.strDetails)`n"
            }
            foreach($item in $detailErr)
            {
                $message += "`n$($item)"
            }
            [System.Windows.Forms.MessageBox]::Show($message, $locString.capWarning, [System.Windows.Forms.MessageBoxButtons]::OK)
            $txtADFSForestFQDN.ForeColor = $defaultForeColor
        }
        else
        {
            $txtADFSForestFQDN.ForeColor = $defaultForeColor
        }
        $txtADFSForestFQDN.Refresh()
    }
    $txtADFSForestFQDN.Add_Leave($txtADFSForestFQDN_Changed)

    $row += 25

    # --------------------------- AZURE DIRECTORY -----------------------------
    $script:lblAzureEnvironment = New-Object System.Windows.Forms.Label
    $lblAzureEnvironment.Location = New-Object System.Drawing.Size($lblLeft,($row+2))
    $lblAzureEnvironment.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblAzureEnvironment.AutoSize = $true
    $lblAzureEnvironment.Font = $defaultLabelFont
    $lblAzureEnvironment.Text = $locString.lblAzureEnvironment
    $lblAzureEnvironment.Enabled = $false
    $lblAzureEnvironment.Visible = $false
    $tabCustomer.Controls.Add($lblAzureEnvironment)

    $script:lstAzureEnvironment = New-Object System.Windows.Forms.ComboBox
    $lstAzureEnvironment.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList
    $lstAzureEnvironment.Location = New-Object System.Drawing.Size($txtLeft,$row)
    $lstAzureEnvironment.Size = New-Object System.Drawing.Size($txtSize)
    $lstAzureEnvironment.Font = $defaultLabelFont
    $lstAzureEnvironment.Name = 'lstAzureEnvironment'
    $lstAzureEnvironment.Enabled = $false
    $lstAzureEnvironment.Visible = $false
    [void]$lstAzureEnvironment.Items.Clear()
    $arrAzureEnvironment | Foreach-Object `
    {
        [void]$lstAzureEnvironment.Items.Add($PSItem)
    }
    $tabCustomer.Controls.Add($lstAzureEnvironment)

    # ----------------------------- ADFS NAME ---------------------------------
    $script:lblADFSProviderName = New-Object System.Windows.Forms.Label
    $lblADFSProviderName.Location = New-Object System.Drawing.Size($lblLeft,($row+2))
    $lblADFSProviderName.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblADFSProviderName.AutoSize = $true
    $lblADFSProviderName.Font = $defaultLabelFont
    $lblADFSProviderName.Text = $locString.lblADFSProviderName
    $lblADFSProviderName.Enabled = $false
    $lblADFSProviderName.Visible = $false
    $tabCustomer.Controls.Add($lblADFSProviderName)

    $script:txtADFSProviderName = New-Object System.Windows.Forms.TextBox
    $txtADFSProviderName.Location = New-Object System.Drawing.Size($txtLeft,$row)
    $txtADFSProviderName.Size = New-Object System.Drawing.Size($txtSize)
    $txtADFSProviderName.Font = $defaultLabelFont
    $txtADFSProviderName.Name = 'txtADFSProviderName'
    $txtADFSProviderName.MaxLength = $lenText
    $txtADFSProviderName.Enabled = $false
    $txtADFSProviderName.Visible = $false
    $txtADFSProviderName.Add_MouseHover($ShowHelp)
    $tabCustomer.Controls.Add($txtADFSProviderName)
    $txtADFSProviderName_Changed = `
    {
        # Not doing validation on the entered data at this time
        if(-not [string]::IsNullOrEmpty($txtADFSProviderName.Text))
        {
            #Close-WaitForm
            #$txtADFSProviderName.ForeColor = $defaultFailColor
            #$txtADFSProviderName.Select()
        }
        else
        {
            $txtADFSProviderName.ForeColor = $defaultForeColor
        }
        $txtADFSProviderName.Refresh()
    }
    $txtADFSProviderName.Add_Leave($txtADFSProviderName_Changed)

    $row += 25

    # ------------------------- ADFS METADATA URI -----------------------------
    $script:lblADFSMetadataUri = New-Object System.Windows.Forms.Label
    $lblADFSMetadataUri.Location = New-Object System.Drawing.Size($lblLeft,($row+2))
    $lblADFSMetadataUri.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblADFSMetadataUri.AutoSize = $true
    $lblADFSMetadataUri.Font = $defaultLabelFont
    $lblADFSMetadataUri.Text = $locString.lblADFSMetadataUri
    $lblADFSMetadataUri.Enabled = $false
    $lblADFSMetadataUri.Visible = $false
    $tabCustomer.Controls.Add($lblADFSMetadataUri)

    $script:txtADFSMetadataUri = New-Object System.Windows.Forms.TextBox
    $txtADFSMetadataUri.Location = New-Object System.Drawing.Size($txtLeft,$row)
    $txtADFSMetadataUri.Size = New-Object System.Drawing.Size($txtSize)
    $txtADFSMetadataUri.Font = $defaultLabelFont
    $txtADFSMetadataUri.Name = 'txtADFSMetadataUri'
    $txtADFSMetadataUri.MaxLength = $lenURI
    $txtADFSMetadataUri.Enabled = $false
    $txtADFSMetadataUri.Visible = $false
    $txtADFSMetadataUri.Add_MouseHover($ShowHelp)
    $tabCustomer.Controls.Add($txtADFSMetadataUri)
    $txtADFSMetadataUri_Changed = `
    {
        if((-not [string]::IsNullOrEmpty($txtADFSMetadataUri.Text)) -and (-not(Get-IsValidURI -URI $txtADFSMetadataUri.Text)))
        {
            Close-WaitForm
            [System.Windows.Forms.MessageBox]::Show($locString.errURIInvalid, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
            $txtADFSMetadataUri.ForeColor = $defaultFailColor
            $txtADFSMetadataUri.Select()
        }
        else
        {
            $txtADFSMetadataUri.ForeColor = $defaultForeColor
        }
        $txtADFSMetadataUri.Refresh()
    }
    $txtADFSMetadataUri.Add_Leave($txtADFSMetadataUri_Changed)

    $row += 40

    # --------------------------- CUSTOMER INFO -------------------------------
    $script:lblCustomerInfo = New-Object System.Windows.Forms.Label
    $lblCustomerInfo.Location = New-Object System.Drawing.Size($lblLeft,($row))
    $lblCustomerInfo.AutoSize = $true
    $lblCustomerInfo.Text = $locString.lblCustomerInfo
    $lblCustomerInfo.Font = [System.Drawing.Font]::New("Microsoft Sans Serif", 12, [System.Drawing.FontStyle]::Bold)
    $tabCustomer.Controls.Add($lblCustomerInfo)

    $row += 40

    # ---------------------------- COMPANY NAME -------------------------------
    $script:lblCompanyName = New-Object System.Windows.Forms.Label
    $lblCompanyName.Location = New-Object System.Drawing.Size($lblLeft,($row+2))
    $lblCompanyName.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblCompanyName.AutoSize = $true
    $lblCompanyName.Font = $defaultLabelFont
    $lblCompanyName.Text = $locString.lblCompanyName
    $tabCustomer.Controls.Add($lblCompanyName)

    $script:txtCompanyName = New-Object System.Windows.Forms.TextBox
    $txtCompanyName.Location = New-Object System.Drawing.Size($txtLeft,$row)
    $txtCompanyName.Size = New-Object System.Drawing.Size($txtSize)
    $txtCompanyName.Font = $defaultLabelFont
    $txtCompanyName.Name = 'txtCompanyName'
    $txtCompanyName.MaxLength = $lenText
    $tabCustomer.Controls.Add($txtCompanyName)

    $row += 30

    # --------------------------- EXTERNAL DOMAIN -----------------------------
    $script:lblExternalDomain = New-Object System.Windows.Forms.Label
    $lblExternalDomain.Location = New-Object System.Drawing.Size($lblLeft,($row+2))
    $lblExternalDomain.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblExternalDomain.AutoSize = $true
    $lblExternalDomain.Font = $defaultLabelFont
    $lblExternalDomain.Text = $locString.lblExternalDomain
    $tabCustomer.Controls.Add($lblExternalDomain)

    $script:txtExternalDomain = New-Object System.Windows.Forms.TextBox
    $txtExternalDomain.Location = New-Object System.Drawing.Size($txtLeft,$row)
    $txtExternalDomain.Size = New-Object System.Drawing.Size($txtSize)
    $txtExternalDomain.Font = $defaultLabelFont
    $txtExternalDomain.Name = 'txtExternalDomain'
    $txtExternalDomain.MaxLength = $lenDomain
    $tabCustomer.Controls.Add($txtExternalDomain)
    $txtExternalDomain_Changed = `
    {
        if((-not [string]::IsNullOrEmpty($txtExternalDomain.Text)) -and ($txtExternalDomain.Text -notmatch $validDomain))
        {
            Close-WaitForm
            [System.Windows.Forms.MessageBox]::Show($locString.errDomainInvalid, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
            $txtExternalDomain.ForeColor = $defaultFailColor
            $txtExternalDomain.Select()
        }
        else
        {
            $txtExternalDomain.ForeColor = $defaultForeColor
        }
        $txtExternalDomain.Refresh()
    }
    $txtExternalDomain.Add_Leave($txtExternalDomain_Changed)

    $row += 30

    # --------------------------- PRIVATE DOMAIN ------------------------------
    $script:lblPrivateDomain = New-Object System.Windows.Forms.Label
    $lblPrivateDomain.Location = New-Object System.Drawing.Size($lblLeft,($row+2))
    $lblPrivateDomain.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblPrivateDomain.AutoSize = $true
    $lblPrivateDomain.Font = $defaultLabelFont
    $lblPrivateDomain.Text = $locString.lblPrivateDomain
    $tabCustomer.Controls.Add($lblPrivateDomain)

    $script:txtPrivateDomain = New-Object System.Windows.Forms.TextBox
    $txtPrivateDomain.Location = New-Object System.Drawing.Size($txtLeft,$row)
    $txtPrivateDomain.Size = New-Object System.Drawing.Size($txtSize)
    $txtPrivateDomain.Font = $defaultLabelFont
    $txtPrivateDomain.Name = 'txtPrivateDomain'
    $txtPrivateDomain.MaxLength = $lenDomain
    $tabCustomer.Controls.Add($txtPrivateDomain)
    $txtPrivateDomain_Changed = `
    {
        if((-not [string]::IsNullOrEmpty($txtPrivateDomain.Text)) -and ($txtPrivateDomain.Text -notmatch $validDomain))
        {
            Close-WaitForm
            [System.Windows.Forms.MessageBox]::Show($locString.errDomainInvalid, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
            $txtPrivateDomain.ForeColor = $defaultFailColor
            $txtPrivateDomain.Select()
        }
        else
        {
            $txtPrivateDomain.ForeColor = $defaultForeColor
        }
        $txtPrivateDomain.Refresh()
    }
    $txtPrivateDomain.Add_Leave($txtPrivateDomain_Changed)

    $row += 30

    # ---------------------------- REGION NAME --------------------------------
    $script:lblRegionName = New-Object System.Windows.Forms.Label
    $lblRegionName.Location = New-Object System.Drawing.Size($lblLeft,($row+2))
    $lblRegionName.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblRegionName.AutoSize = $true
    $lblRegionName.Font = $defaultLabelFont
    $lblRegionName.Text = $locString.lblRegionName
    $tabCustomer.Controls.Add($lblRegionName)

    $script:txtRegionName = New-Object System.Windows.Forms.TextBox
    $txtRegionName.Location = New-Object System.Drawing.Size($txtLeft,$row)
    $txtRegionName.Size = New-Object System.Drawing.Size($txtPrefixSize)
    $txtRegionName.Font = $defaultLabelFont
    $txtRegionName.Name = 'txtRegionName'
    $txtRegionName.MaxLength = $lenText
    $tabCustomer.Controls.Add($txtRegionName)
    $txtRegionName_Changed = `
    {
        if((-not [string]::IsNullOrEmpty($txtRegionName.Text)) -and ($txtRegionName.Text -notmatch $validAlphaNumeric))
        {
            Close-WaitForm
            [System.Windows.Forms.MessageBox]::Show($locString.errAlphaNumericInvalid, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
            $txtRegionName.ForeColor = $defaultFailColor
            $txtRegionName.Select()
        }
        else
        {
            $txtRegionName.ForeColor = $defaultForeColor
        }
        $txtRegionName.Refresh()
    }
    $txtRegionName.Add_Leave($txtRegionName_Changed)

    $row += 30

    # --------------------------- DEPLOY PREFIX -------------------------------
    $script:lblDeployPrefix = New-Object System.Windows.Forms.Label
    $lblDeployPrefix.Location = New-Object System.Drawing.Size($lblLeft,($row+2))
    $lblDeployPrefix.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblDeployPrefix.AutoSize = $true
    $lblDeployPrefix.Font = $defaultLabelFont
    $lblDeployPrefix.Text = $locString.lblDeployPrefix
    $tabCustomer.Controls.Add($lblDeployPrefix)

    $script:txtDeployPrefix = New-Object System.Windows.Forms.TextBox
    $txtDeployPrefix.Location = New-Object System.Drawing.Size($txtLeft,$row)
    $txtDeployPrefix.Size = New-Object System.Drawing.Size($txtPrefixSize)
    $txtDeployPrefix.Font = $defaultLabelFont
    $txtDeployPrefix.Name = 'txtDeployPrefix'
    $txtDeployPrefix.MaxLength = $lenPrefix
    $tabCustomer.Controls.Add($txtDeployPrefix)
    $txtDeployPrefix_Changed = `
    {
        if((-not [string]::IsNullOrEmpty($txtDeployPrefix.Text)) -and ($txtDeployPrefix.Text -notmatch $validAlphaNumeric))
        {
            Close-WaitForm
            [System.Windows.Forms.MessageBox]::Show($locString.errAlphaNumericInvalid, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
            $txtDeployPrefix.ForeColor = $defaultFailColor
            $txtDeployPrefix.Select()
        }
        else
        {
            $txtDeployPrefix.ForeColor = $defaultForeColor
        }
        $txtDeployPrefix.Refresh()
    }
    $txtDeployPrefix.Add_Leave($txtDeployPrefix_Changed)

    $row += 30

    # -------------------------- PHYSICAL PREFIX ------------------------------
    $script:lblPhysicalPrefix = New-Object System.Windows.Forms.Label
    $lblPhysicalPrefix.Location = New-Object System.Drawing.Size($lblLeft,($row+2))
    $lblPhysicalPrefix.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblPhysicalPrefix.AutoSize = $true
    $lblPhysicalPrefix.Font = $defaultLabelFont
    $lblPhysicalPrefix.Text = $locString.lblPhysicalPrefix
    $tabCustomer.Controls.Add($lblPhysicalPrefix)

    $script:txtPhysicalPrefix = New-Object System.Windows.Forms.TextBox
    $txtPhysicalPrefix.Location = New-Object System.Drawing.Size($txtLeft,$row)
    $txtPhysicalPrefix.Size = New-Object System.Drawing.Size($txtPrefixSize)
    $txtPhysicalPrefix.Font = $defaultLabelFont
    $txtPhysicalPrefix.Name = 'txtPhysicalPrefix'
    $txtPhysicalPrefix.MaxLength = $lenPrefix
    $tabCustomer.Controls.Add($txtPhysicalPrefix)
    $txtPhysicalPrefix_Changed = `
    {
        if((-not [string]::IsNullOrEmpty($txtPhysicalPrefix.Text)) -and ($txtPhysicalPrefix.Text -notmatch $validAlphaNumeric))
        {
            Close-WaitForm
            [System.Windows.Forms.MessageBox]::Show($locString.errAlphaNumericInvalid, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
            $txtPhysicalPrefix.ForeColor = $defaultFailColor
            $txtPhysicalPrefix.Select()
        }
        else
        {
            $txtPhysicalPrefix.ForeColor = $defaultForeColor
        }
        $txtPhysicalPrefix.Refresh()
    }
    $txtPhysicalPrefix.Add_Leave($txtPhysicalPrefix_Changed)

    $row += 40

    # -------------------------- ENVIRONMENT INFO -----------------------------
    $script:lblEnvironmentInfo = New-Object System.Windows.Forms.Label
    $lblEnvironmentInfo.Location = New-Object System.Drawing.Size($lblLeft,($row))
    $lblEnvironmentInfo.AutoSize = $true
    $lblEnvironmentInfo.Text = $locString.lblEnvironmentInfo
    $lblEnvironmentInfo.Font = [System.Drawing.Font]::New("Microsoft Sans Serif", 12, [System.Drawing.FontStyle]::Bold)
    $tabCustomer.Controls.Add($lblEnvironmentInfo)

    $row += 40

    # ---------------------------- TIME SERVER --------------------------------
    $script:lblTimeServer = New-Object System.Windows.Forms.Label
    $lblTimeServer.Location = New-Object System.Drawing.Size($lblLeft,($row+2))
    $lblTimeServer.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblTimeServer.AutoSize = $true
    $lblTimeServer.Font = $defaultLabelFont
    $lblTimeServer.Text = $locString.lblTimeServer
    $tabCustomer.Controls.Add($lblTimeServer)

    $script:txtTimeServer = New-Object System.Windows.Forms.TextBox
    $txtTimeServer.Location = New-Object System.Drawing.Size($txtLeft,$row)
    $txtTimeServer.Size = New-Object System.Drawing.Size($txtSize)
    $txtTimeServer.Font = $defaultLabelFont
    $txtTimeServer.Name = 'txtTimeServer'
    $txtTimeServer.MaxLength = $lenDomain
    $txtTimeServer.Add_MouseHover($ShowHelp)
    $tabCustomer.Controls.Add($txtTimeServer)
    $txtTimeServer_Changed = `
    {
        if(-not([string]::IsNullOrEmpty($txtTimeServer.Text)))
        {
            $valid = $false
            if($txtTimeServer.Text -match $validIPAddress)
            {
                $valid = $true
            }
            elseif($txtTimeServer.Text -match $validDomain)
            {
                $valid = $true
            }
            if(-not $valid)
            {
                Close-WaitForm
                [System.Windows.Forms.MessageBox]::Show($locString.errIPorFQDNInvalid, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
                $txtTimeServer.ForeColor = $defaultFailColor
                $txtTimeServer.Select()
            }
            else
            {
                $txtTimeServer.ForeColor = $defaultForeColor
            }
        }
        $txtTimeServer.Refresh()
    }
    $txtTimeServer.Add_Leave($txtTimeServer_Changed)

    $row += 30

    # ----------------------------- DNS SERVER --------------------------------
    $script:lblDNSForwarder = New-Object System.Windows.Forms.Label
    $lblDNSForwarder.Location = New-Object System.Drawing.Size($lblLeft,($row+2))
    $lblDNSForwarder.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblDNSForwarder.AutoSize = $true
    $lblDNSForwarder.Font = $defaultLabelFont
    $lblDNSForwarder.Text = $locString.lblDNSForwarder
    $tabCustomer.Controls.Add($lblDNSForwarder)

    $script:txtDNSForwarder = New-Object System.Windows.Forms.RichTextBox
    $txtDNSForwarder.Location = New-Object System.Drawing.Size($txtLeft,$row)
    $txtDNSForwarder.Size = New-Object System.Drawing.Size($txtSize)
    $txtDNSForwarder.Font = $defaultLabelFont
    $txtDNSForwarder.Name = 'txtDNSForwarder'
    $txtDNSForwarder.MaxLength = 32
    $txtDNSForwarder.Multiline = $false
    $tabCustomer.Controls.Add($txtDNSForwarder)
    $txtDNSForwarder.Add_MouseHover($ShowHelp)
    $txtDNSForwarder_Changed = `
    {
        if(-not([string]::IsNullOrEmpty($txtDNSForwarder.Text)))
        {
            $txtDNSForwarder.Text = [string]($txtDNSForwarder.Text).Replace(' ','')
            $txtDNSForwarder.Text = [string]($txtDNSForwarder.Text).Replace(';',',')
            $errItem = @()
            $dnsList = @()
            if($txtDNSForwarder.Text -match ',')
            {
                ($txtDNSForwarder.Text).Split(',').Trim() | Foreach-Object `
                {
                    $dnsList += $PSItem
                    $newList = @()
                    foreach($dns in $dnsList)
                    {
                        if(($newList -notcontains $dns) -and ($newList.Count -lt $maxDNSForwarders))
                        {
                            $newList += $dns
                        }
                    }
                    $dnsList = $newList
                    $txtDNSForwarder.Text = ($dnsList -join ',')
                }
            }
            else
            {
                $dnsList += $txtDNSForwarder.Text
            }
            foreach($dns in $dnsList)
            {
                if($dns -notmatch $validIPAddress)
                {
                    $errItem += $dns
                    $txtDNSForwarder.SelectionStart = $txtDNSForwarder.Text.IndexOf($dns)
                    $txtDNSForwarder.SelectionLength = $dns.Length
                    $txtDNSForwarder.SelectionColor = $defaultFailColor
                }
                else
                {
                    $txtDNSForwarder.SelectionStart = $txtDNSForwarder.Text.IndexOf($dns)
                    $txtDNSForwarder.SelectionLength = $dns.Length
                    $txtDNSForwarder.SelectionColor = $defaultForeColor
                    $txtDNSForwarder.SelectionStart = 0
                    $txtDNSForwarder.SelectionLength = 0
                }
            }
            if($errItem.Count -gt 0)
            {
                $errItem | ForEach-Object `
                {
                    [string]$errMessage += "$($locString.errInvalidIPList) $($PSItem)`n"
                }
                Close-WaitForm
                [System.Windows.Forms.MessageBox]::Show($errMessage, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
                $txtDNSForwarder.Select()
            }
            else
            {
                $txtDNSForwarder.SelectionStart = 0
                $txtDNSForwarder.SelectionLength = $txtDNSForwarder.Text.Length
                $txtDNSForwarder.ForeColor = $defaultForeColor
            }
        }
        $txtDNSForwarder.Refresh()
    }
    $txtDNSForwarder.Add_Leave($txtDNSForwarder_Changed)

    $row += 30

    # ---------------------------- SYSLOG SERVER ------------------------------
    $script:lblSyslogServer = New-Object System.Windows.Forms.Label
    $lblSyslogServer.Location = New-Object System.Drawing.Size($lblLeft,($row+2))
    $lblSyslogServer.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblSyslogServer.AutoSize = $true
    $lblSyslogServer.Font = $defaultLabelFont
    $lblSyslogServer.Text = $locString.lblSyslogServer
    $tabCustomer.Controls.Add($lblSyslogServer)

    $script:txtSyslogServer = New-Object System.Windows.Forms.TextBox
    $txtSyslogServer.Location = New-Object System.Drawing.Size($txtLeft,$row)
    $txtSyslogServer.Size = New-Object System.Drawing.Size($txtPrefixSize)
    $txtSyslogServer.Font = $defaultLabelFont
    $txtSyslogServer.Name = 'txtSyslogServer'
    $txtSyslogServer.MaxLength = $lenIPAddress
    $txtSyslogServer.Add_MouseHover($ShowHelp)
    $tabCustomer.Controls.Add($txtSyslogServer)
    $txtSyslogServer_Changed = `
    {
        if(-not([string]::IsNullOrEmpty($txtSyslogServer.Text)) -and ($txtSyslogServer.Text -notmatch $validIPAddress))
        {
            Close-WaitForm
            [System.Windows.Forms.MessageBox]::Show($locString.errIPAddressInvalid, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
            $txtSyslogServer.ForeColor = $defaultFailColor
            $txtSyslogServer.Select()
        }
        else
        {
            $txtSyslogServer.ForeColor = $defaultForeColor
        }
        $txtSyslogServer.Refresh()
    }
    $txtSyslogServer.Add_Leave($txtSyslogServer_Changed)

    $script:lblSyslogServerOptional = New-Object System.Windows.Forms.Label
    $lblSyslogServerOptional.Location = New-Object System.Drawing.Size(($txtSyslogServer.Right+8),($txtSyslogServer.Location.Y+2))
    $lblSyslogServerOptional.AutoSize = $true
    $lblSyslogServerOptional.Font = $defaultLabelFont
    $lblSyslogServerOptional.Text = $locString.strOptionalNote
    $tabCustomer.Controls.Add($lblSyslogServerOptional)

    # ============================== NETWORK TAB ==============================
    $row = 20

    # ----------------------------- DOCUMENT LINK -----------------------------
    $script:lnkNetworkDoc = New-Object System.Windows.Forms.LinkLabel
    $lnkNetworkDoc.Location = New-Object System.Drawing.Size(680,($row+2))
    $lnkNetworkDoc.Size = New-Object System.Drawing.Size(300,20)
    $lnkNetworkDoc.Font = $defaultLabelFont
    $lnkNetworkDoc.AutoSize = $true
    $lnkNetworkDoc.Text = $locString.networkLinkText
    $lnkNetworkDoc.Add_Click({[system.Diagnostics.Process]::Start($locString.networkLink)})
    $tabNetwork.Controls.Add($lnkNetworkDoc)

    # ---------------------------- NETWORK SETTINGS ---------------------------
    $script:lblNetworkSettings = New-Object System.Windows.Forms.Label
    $lblNetworkSettings.Location = New-Object System.Drawing.Size($lblLeft,($row))
    $lblNetworkSettings.AutoSize = $true
    $lblNetworkSettings.Text = $locString.lblNetworkSettings
    $lblNetworkSettings.Font = [System.Drawing.Font]::New("Microsoft Sans Serif", 12, [System.Drawing.FontStyle]::Bold)
    $tabNetwork.Controls.Add($lblNetworkSettings)

    $row += 40

    # ------------------------------- TOPOLOGY --------------------------------
    $script:lblTopology = New-Object System.Windows.Forms.Label
    $lblTopology.Location = New-Object System.Drawing.Size($lblLeft,($row))
    $lblTopology.AutoSize = $true
    $lblTopology.Text = $locString.lblTopology
    $lblTopology.Font = [System.Drawing.Font]::New("Microsoft Sans Serif", 10, [System.Drawing.FontStyle]::Bold)
    $tabNetwork.Controls.Add($lblTopology)

    $row += 30

    # ----------------------------- SCALE UNITS -------------------------------
    $script:lblScaleUnitCount = New-Object System.Windows.Forms.Label
    $lblScaleUnitCount.Location = New-Object System.Drawing.Size($lblLeft,($row+2))
    $lblScaleUnitCount.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblScaleUnitCount.AutoSize = $true
    $lblScaleUnitCount.Font = $defaultLabelFont
    $lblScaleUnitCount.Text = $locString.lblScaleUnitCount
    $tabNetwork.Controls.Add($lblScaleUnitCount)

    $script:txtScaleUnitCount = New-Object System.Windows.Forms.TextBox
    $txtScaleUnitCount.Location = New-Object System.Drawing.Size($txtLeftNet,$row)
    $txtScaleUnitCount.Size = New-Object System.Drawing.Size($txtCountSize)
    $txtScaleUnitCount.Font = $defaultLabelFont
    $txtScaleUnitCount.TextAlign = [System.Windows.Forms.HorizontalAlignment]::Center
    $txtScaleUnitCount.Name = 'txtScaleUnitCount'
    $txtScaleUnitCount.MaxLength = 1
    $txtScaleUnitCount.Text = $defaultSUCount
    $txtScaleUnitCount.Enabled = $false
    $txtScaleUnitCount.ReadOnly = $true
    $tabNetwork.Controls.Add($txtScaleUnitCount)
    $txtScaleUnitCount_Changed = `
    {
        if(-not([string]::IsNullOrEmpty($txtScaleUnitCount.Text)) -and ($txtScaleUnitCount.Text -notmatch "^[1-$($maxSU)]+$"))
        {
            [string]$errMessage = ($locString.errNumberNotInRange -f '1',$maxSU)
            Close-WaitForm
            [System.Windows.Forms.MessageBox]::Show($errMessage, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
            $txtScaleUnitCount.ForeColor = $defaultFailColor
            $txtScaleUnitCount.SelectionStart = 0
            $txtScaleUnitCount.SelectionLength = 1
            $txtScaleUnitCount.Select()
        }
        else
        {
            Remove-GeneratedTabs
            $txtScaleUnitCount.ForeColor = $defaultForeColor
            (1..$maxSU) | Foreach-Object `
            {
                if($PSItem -le [int]$txtScaleUnitCount.Text)
                {
                    Add-ScaleUnitTab -ID $PSItem
                }
                else
                {
                    Remove-ScaleUnit -ID $PSItem
                }
            }
            # Clear and repopulate the switch vendors list to insure all required switch types are available
            $currentSwitchVendor = $lstSwitchVendor.SelectedItem
            [void]$lstSwitchVendor.Items.Clear()
            Get-SwitchVendors | Foreach-Object `
            {
                $make = $PSItem
                $add = $true
                $requireSpine = $false
                if(([int]$txtScaleUnitCount.Text -gt 1) -and ($spineless -notcontains $make))
                {
                    $requireSpine = $true
                }
                foreach($type in $switchTypes)
                {
                    if(-not(($type -eq 'SPINE') -and (-not $requireSpine)))
                    {
                        [string[]]$result = Get-SwitchModels -Make $make -Type $type
                        if($result.Count -eq 0)
                        {
                            $add = $false
                            Write-Log -Message "WARNING: switch make '$($make)' does not contain any models for the required type '$($type)'" -Force
                        }
                    }
                }
                if($add)
                {
                    [void]$lstSwitchVendor.Items.Add($make)
                }
            }
            # If the previously selected switch vendor is still applicable set it back to that vendor
            if($currentSwitchVendor -and $lstSwitchVendor.Items -contains $currentSwitchVendor)
            {
                $lstSwitchVendor.SelectedItem = $currentSwitchVendor
            }
            else
            {
                $lstTORModel.SelectedIndex = -1
                $lstBMCModel.SelectedIndex = -1
                $lstSpineModel.SelectedIndex = -1
            }
            if([int]$txtScaleUnitCount.Text -gt 1)
            {
                # Multi Scale-Unit
                $script:validEdge = 4
                if($lstSwitchVendor.SelectedItem -eq "CiscoUCS")
                {
                    Disable-SpineSubnet
                    $lblSpineFirmware.Enabled = $false
                    $lblSpineFirmware.Visible = $false
                    $lstSpineFirmware.Enabled = $false
                    $lstSpineFirmware.Visible = $false
                    Clear-ListSelection $lstSpineFirmware
                    [void]$lstSpineModel.Items.Add('-none-')
                    $lstSpineModel.Enabled = $false
                    $lstSpineModel.Visible = $false
                    $lblSpineModel.Enabled = $false
                    $lblSpineModel.Visible = $false
                    $lstSpineModel.SelectedIndex = 0
                    Set-ScaleUnitsCiscoUCS -Enabled $false
                }
                else
                {
                    Enable-SpineSubnet
                    $lblSpineFirmware.Enabled = $true
                    $lblSpineFirmware.Visible = $true
                    $lstSpineFirmware.Enabled = $true
                    $lstSpineFirmware.Visible = $true
                    $lblSpineModel.Enabled = $true
                    $lblSpineModel.Visible = $true
                    $lstSpineModel.Enabled = $true
                    $lstSpineModel.Visible = $true
                    if(-not $retainSpineSettings)
                    {
                        Clear-ListSelection $lstSpineFirmware
                        if($lstSpineModel.Items.Count -gt 0)
                        {
                            $lstSpineModel.SelectedIndex = 0
                        }
                        else
                        {
                            Clear-ListSelection $lstSpineModel
                        }
                    }
                    Enable-SpineCount
                    Set-ScaleUnitsCiscoUCS -Enabled $true
                }
                if($lstSwitchVendor.SelectedItem -eq "CiscoUCS")
                {
                    Disable-SpineBGP
                }
                else
                {
                    Enable-SpineBGP
                }
            }
            else
            {
                # Single Scale-Unit
                Disable-SpineCount
                Disable-SpineBGP
                Disable-SpineSubnet
                $lblSpineFirmware.Enabled = $false
                $lblSpineFirmware.Visible = $false
                $lstSpineFirmware.Enabled = $false
                $lstSpineFirmware.Visible = $false
                Clear-ListSelection $lstSpineFirmware
                $lblSpineModel.Enabled = $false
                $lblSpineModel.Visible = $false
                $lstSpineModel.Enabled = $false
                $lstSpineModel.Visible = $false
                Clear-ListSelection $lstSpineModel
                $script:validEdge = 2
                if($lstSwitchVendor.SelectedItem -eq "CiscoUCS")
                {
                    $script:validEdge = 4
                }
            }
        }
        if([int]$txtEdgeCount.Text -gt $validEdge)
        {
            $txtEdgeCount.Text = $validEdge
        }
        Move-PartnerTab
    }
    $txtScaleUnitCount.Add_Leave($txtScaleUnitCount_Changed)

    $row += 25

    # -------------------------- TOTAL NODE COUNT -----------------------------
    $script:lblTotalNodeCount = New-Object System.Windows.Forms.Label
    $lblTotalNodeCount.Location = New-Object System.Drawing.Size($lblLeft,($row+2))
    $lblTotalNodeCount.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblTotalNodeCount.AutoSize = $true
    $lblTotalNodeCount.Font = $defaultLabelFont
    $lblTotalNodeCount.Text = $locString.lblTotalNodeCount
    $tabNetwork.Controls.Add($lblTotalNodeCount)

    $script:txtTotalNodeCount = New-Object System.Windows.Forms.TextBox
    $txtTotalNodeCount.Location = New-Object System.Drawing.Size($txtLeftNet,$row)
    $txtTotalNodeCount.Size = New-Object System.Drawing.Size($txtCountSize)
    $txtTotalNodeCount.Font = $defaultLabelFont
    $txtTotalNodeCount.Name = 'txtTotalNodeCount'
    $txtTotalNodeCount.TextAlign = [System.Windows.Forms.HorizontalAlignment]::Center
    $txtTotalNodeCount.Text = "0"
    $txtTotalNodeCount.Enabled = $false
    $txtTotalNodeCount.ReadOnly = $true
    $tabNetwork.Controls.Add($txtTotalNodeCount)

    $row += 40
    $row2 = $row

    # ----------------------------- SWITCH INFO -------------------------------
    $script:lblSwitchInfo = New-Object System.Windows.Forms.Label
    $lblSwitchInfo.Location = New-Object System.Drawing.Size($lblLeft,($row))
    $lblSwitchInfo.AutoSize = $true
    $lblSwitchInfo.Text = $locString.lblSwitchInfo
    $lblSwitchInfo.Font = [System.Drawing.Font]::New("Microsoft Sans Serif", 10, [System.Drawing.FontStyle]::Bold)
    $tabNetwork.Controls.Add($lblSwitchInfo)

    $row += 30

    # ---------------------------- SWITCH VENDOR ------------------------------
    $script:lblSwitchVendor = New-Object System.Windows.Forms.Label
    $lblSwitchVendor.Location = New-Object System.Drawing.Size($lblLeft,($row+2))
    $lblSwitchVendor.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblSwitchVendor.AutoSize = $true
    $lblSwitchVendor.Font = $defaultLabelFont
    $lblSwitchVendor.Text = $locString.lblSwitchVendor
    $lblSwitchVendor.Visible = $true
    $tabNetwork.Controls.Add($lblSwitchVendor)

    $script:lstSwitchVendor = New-Object System.Windows.Forms.ComboBox
    $lstSwitchVendor.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList
    $lstSwitchVendor.Location = New-Object System.Drawing.Size($txtLeft,$row)
    $lstSwitchVendor.Size = New-Object System.Drawing.Size($txtSizeNet)
    $lstSwitchVendor.Font = $defaultLabelFont
    $lstSwitchVendor.Name = 'lstSwitchVendor'
    $lstSwitchVendor.Visible = $true
    [void]$lstSwitchVendor.Items.Clear()
    Get-SwitchVendors | Foreach-Object `
    {
        [void]$lstSwitchVendor.Items.Add($PSItem)
    }
    $lstSwitchVendor.SelectedIndex = -1
    $lstSwitchVendor_Changed = `
    {
        [void]$lstTORFirmware.Items.Clear()
        [void]$lstTORModel.Items.Clear()
        [void]$lstBMCFirmware.Items.Clear()
        [void]$lstBMCModel.Items.Clear()
        [void]$lstSpineFirmware.Items.Clear()
        [void]$lstSpineModel.Items.Clear()
        if($lstSwitchVendor.SelectedIndex -ge 0)
        {
            Get-SwitchModels -Make $lstSwitchVendor.SelectedItem -Type 'TOR' | Foreach-Object `
            {
                [void]$lstTORModel.Items.Add($PSItem)
            }
            $lstTORModel.SelectedIndex = 0
            $lstTORModel.Refresh()
            if($lstSwitchVendor.SelectedItem -eq "CiscoUCS")
            {
                $script:validEdge = 4
                [void]$lstBMCModel.Items.Add('-none-')
                $lstBMCModel.Enabled = $false
                $lstBMCModel.Visible = $false
                $lblBMCModel.Enabled = $false
                $lblBMCModel.Visible = $false

                [void]$lstSpineModel.Items.Add('-none-')
                $lstSpineModel.Enabled = $false
                $lstSpineModel.Visible = $false
                $lblSpineModel.Enabled = $false
                $lblSpineModel.Visible = $false
                $lstSpineModel.SelectedIndex = 0

                Disable-SpineBGP
                Disable-SpineCount
                Disable-SpineSubnet
                Set-ScaleUnitsCiscoUCS -Enabled $false
            }
            else
            {
                $script:validEdge = 2
                if([int]$txtScaleUnitCount.Text -gt 1)
                {
                    $script:validEdge = 4
                    Enable-SpineSubnet
                    Enable-SpineBGP
                    Enable-SpineCount
                    Set-ScaleUnitsCiscoUCS -Enabled $true
                }
                else
                {
                    Disable-SpineBGP
                    Disable-SpineCount
                    Disable-SpineSubnet
                }
                Get-SwitchModels -Make $lstSwitchVendor.SelectedItem -Type 'BMC' | Foreach-Object `
                {
                    [void]$lstBMCModel.Items.Add($PSItem)
                }
                $lblBMCModel.Enabled = $true
                $lblBMCModel.Visible = $true
                $lstBMCModel.Enabled = $true
                $lstBMCModel.Visible = $true

                $models = Get-SwitchModels -Make $lstSwitchVendor.SelectedItem -Type 'SPINE'
                if($models.Count -gt 0)
                {
                    $models | Foreach-Object `
                    {
                        [void]$lstSpineModel.Items.Add($PSItem)
                    }
                    if($txtScaleUnitCount.Text -eq '1')
                    {
                        $lblSpineModel.Enabled = $false
                        $lblSpineModel.Visible = $false
                        $lstSpineModel.Enabled = $false
                        $lstSpineModel.Visible = $false
                        Clear-ListSelection $lstSpineModel
                    }
                    else
                    {
                        $lblSpineModel.Enabled = $true
                        $lblSpineModel.Visible = $true
                        $lstSpineModel.Enabled = $true
                        $lstSpineModel.Visible = $true
                        $lstSpineModel.SelectedIndex = 0
                    }
                }
                else
                {
                    $lblSpineModel.Enabled = $false
                    $lblSpineModel.Visible = $false
                    $lstSpineModel.Enabled = $false
                    $lstSpineModel.Visible = $false
                    Clear-ListSelection $lstSpineModel

                    $lblSpineFirmware.Enabled = $false
                    $lblSpineFirmware.Visible = $false
                    $lstSpineFirmware.Enabled = $false
                    $lstSpineFirmware.Visible = $false
                    Clear-ListSelection $lstSpineFirmware
                }
            }
            if([int]$txtEdgeCount.Text -gt $validEdge)
            {
                $txtEdgeCount.Text = $validEdge
            }
            $lstBMCModel.SelectedIndex = 0
            $lstBMCModel.Refresh()
            $lstSpineModel.Refresh()
        }
    }
    $lstSwitchVendor.Add_SelectedIndexChanged($lstSwitchVendor_Changed)
    $tabNetwork.Controls.Add($lstSwitchVendor)

    $row += 25

    # ------------------------------ TOR MODEL --------------------------------
    $script:lblTORModel = New-Object System.Windows.Forms.Label
    $lblTORModel.Location = New-Object System.Drawing.Size($lblLeft,($row+2))
    $lblTORModel.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblTORModel.AutoSize = $true
    $lblTORModel.Font = $defaultLabelFont
    $lblTORModel.Text = $locString.lblTORModel
    $lblTORModel.Visible = $true
    $tabNetwork.Controls.Add($lblTORModel)

    $script:lstTORModel = New-Object System.Windows.Forms.ComboBox
    $lstTORModel.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList
    $lstTORModel.Location = New-Object System.Drawing.Size($txtLeft,$row)
    $lstTORModel.Size = New-Object System.Drawing.Size($txtSizeNet)
    $lstTORModel.Font = $defaultLabelFont
    $lstTORModel.Name = 'lstTORModel'
    $lstTORModel.Visible = $true
    [void]$lstTORModel.Items.Clear()
    $lstTORModel.SelectedIndex = -1
    $lstTORModel_Changed = `
    {
        [void]$lstTORFirmware.Items.Clear()
        if($lstTORModel.SelectedItem)
        {
            $firmware = @(Get-SwitchFirmware -Make $lstSwitchVendor.SelectedItem -Type 'TOR' -Model $lstTORModel.SelectedItem)
            if($firmware.Count -gt 0)
            {
                $firmware | Foreach-Object `
                {
                    [void]$lstTORFirmware.Items.Add($PSItem)
                }
                $lstTORFirmware.SelectedIndex = 0
            }
            else
            {
                Clear-ListSelection $lstTORFirmware
            }
        }
        else
        {
            Clear-ListSelection $lstTORFirmware
        }
        $lstTORFirmware.Refresh()
    }
    $lstTORModel.Add_SelectedIndexChanged($lstTORModel_Changed)
    $tabNetwork.Controls.Add($lstTORModel)

    $row += 25

    # ---------------------------- TOR FIRMWARE -------------------------------
    $script:lblTORFirmware = New-Object System.Windows.Forms.Label
    $lblTORFirmware.Location = New-Object System.Drawing.Size($lblLeft,($row+2))
    $lblTORFirmware.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblTORFirmware.AutoSize = $true
    $lblTORFirmware.Font = $defaultLabelFont
    $lblTORFirmware.Text = $locString.lblTORFirmware
    $lblTORFirmware.Visible = $true
    $tabNetwork.Controls.Add($lblTORFirmware)

    $script:lstTORFirmware = New-Object System.Windows.Forms.ComboBox
    $lstTORFirmware.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList
    $lstTORFirmware.Location = New-Object System.Drawing.Size($txtLeft,$row)
    $lstTORFirmware.Size = New-Object System.Drawing.Size($txtSizeNet)
    $lstTORFirmware.Font = $defaultLabelFont
    $lstTORFirmware.Name = 'lstTORFirmware'
    $lstTORFirmware.Visible = $true
    [void]$lstTORFirmware.Items.Clear()
    $lstTORFirmware.SelectedIndex = -1
    $tabNetwork.Controls.Add($lstTORFirmware)

    $row += 25

    # ------------------------------ BMC MODEL --------------------------------
    $script:lblBMCModel = New-Object System.Windows.Forms.Label
    $lblBMCModel.Location = New-Object System.Drawing.Size($lblLeft,($row+2))
    $lblBMCModel.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblBMCModel.AutoSize = $true
    $lblBMCModel.Font = $defaultLabelFont
    $lblBMCModel.Text = $locString.lblBMCModel
    $lblBMCModel.Visible = $true
    $tabNetwork.Controls.Add($lblBMCModel)

    $script:lstBMCModel = New-Object System.Windows.Forms.ComboBox
    $lstBMCModel.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList
    $lstBMCModel.Location = New-Object System.Drawing.Size($txtLeft,$row)
    $lstBMCModel.Size = New-Object System.Drawing.Size($txtSizeNet)
    $lstBMCModel.Font = $defaultLabelFont
    $lstBMCModel.Name = 'lstBMCModel'
    $lstBMCModel.Visible = $true
    [void]$lstBMCModel.Items.Clear()
    $lstBMCModel.SelectedIndex = -1
    $lstBMCModel_Changed = `
    {
        [void]$lstBMCFirmware.Items.Clear()
        if($lstSwitchVendor.SelectedItem -eq "CiscoUCS")
        {
            $lstBMCFirmware.Enabled = $false
            $lstBMCFirmware.Visible = $false
            Clear-ListSelection $lstBMCFirmware
            $lblBMCFirmware.Enabled = $false
            $lblBMCFirmware.Visible = $false
        }
        else
        {
            $lstBMCFirmware.Enabled = $true
            $lstBMCFirmware.Visible = $true
            $lblBMCFirmware.Enabled = $true
            $lblBMCFirmware.Visible = $true
            if($lstBMCModel.SelectedItem)
            {
                $firmware = @(Get-SwitchFirmware -Make $lstSwitchVendor.SelectedItem -Type 'BMC' -Model $lstBMCModel.SelectedItem)
                if($firmware.Count -gt 0)
                {
                    $firmware | Foreach-Object `
                    {
                        [void]$lstBMCFirmware.Items.Add($PSItem)
                    }
                    $lstBMCFirmware.SelectedIndex = 0
                }
                else
                {
                    Clear-ListSelection $lstBMCFirmware
                }
            }
            else
            {
                Clear-ListSelection $lstBMCFirmware
            }
        }
        $lstBMCFirmware.Refresh()
    }
    $lstBMCModel.Add_SelectedIndexChanged($lstBMCModel_Changed)
    $tabNetwork.Controls.Add($lstBMCModel)

    $row += 25

    # ---------------------------- BMC FIRMWARE -------------------------------
    $script:lblBMCFirmware = New-Object System.Windows.Forms.Label
    $lblBMCFirmware.Location = New-Object System.Drawing.Size($lblLeft,($row+2))
    $lblBMCFirmware.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblBMCFirmware.AutoSize = $true
    $lblBMCFirmware.Font = $defaultLabelFont
    $lblBMCFirmware.Text = $locString.lblBMCFirmware
    $lblBMCFirmware.Visible = $true
    $tabNetwork.Controls.Add($lblBMCFirmware)

    $script:lstBMCFirmware = New-Object System.Windows.Forms.ComboBox
    $lstBMCFirmware.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList
    $lstBMCFirmware.Location = New-Object System.Drawing.Size($txtLeft,$row)
    $lstBMCFirmware.Size = New-Object System.Drawing.Size($txtSizeNet)
    $lstBMCFirmware.Font = $defaultLabelFont
    $lstBMCFirmware.Name = 'lstBMCFirmware'
    $lstBMCFirmware.Visible = $true
    [void]$lstBMCFirmware.Items.Clear()
    $lstBMCFirmware.SelectedIndex = -1
    $tabNetwork.Controls.Add($lstBMCFirmware)

    $row += 25

    # ----------------------------- SPINE MODEL -------------------------------
    $script:lblSpineModel = New-Object System.Windows.Forms.Label
    $lblSpineModel.Location = New-Object System.Drawing.Size($lblLeft,($row+2))
    $lblSpineModel.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblSpineModel.AutoSize = $true
    $lblSpineModel.Font = $defaultLabelFont
    $lblSpineModel.Text = $locString.lblSpineModel
    $lblSpineModel.Enabled = $true
    $lblSpineModel.Visible = $true
    $tabNetwork.Controls.Add($lblSpineModel)

    $script:lstSpineModel = New-Object System.Windows.Forms.ComboBox
    $lstSpineModel.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList
    $lstSpineModel.Location = New-Object System.Drawing.Size($txtLeft,$row)
    $lstSpineModel.Size = New-Object System.Drawing.Size($txtSizeNet)
    $lstSpineModel.Font = $defaultLabelFont
    $lstSpineModel.Name = 'lstSpineModel'
    $lstSpineModel.Enabled = $true
    $lstSpineModel.Visible = $true
    [void]$lstSpineModel.Items.Clear()
    $lstSpineModel.SelectedIndex = -1
    $tabNetwork.Controls.Add($lstSpineModel)

    $row += 25

    # ---------------------------- SPINE FIRMWARE -------------------------------
    $script:lblSpineFirmware = New-Object System.Windows.Forms.Label
    $lblSpineFirmware.Location = New-Object System.Drawing.Size($lblLeft,($row+2))
    $lblSpineFirmware.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblSpineFirmware.AutoSize = $true
    $lblSpineFirmware.Font = $defaultLabelFont
    $lblSpineFirmware.Text = $locString.lblSpineFirmware
    $lblSpineFirmware.Enabled = $true
    $lblSpineFirmware.Visible = $true
    $tabNetwork.Controls.Add($lblSpineFirmware)

    $script:lstSpineFirmware = New-Object System.Windows.Forms.ComboBox
    $lstSpineFirmware.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList
    $lstSpineFirmware.Location = New-Object System.Drawing.Size($txtLeft,$row)
    $lstSpineFirmware.Size = New-Object System.Drawing.Size($txtSizeNet)
    $lstSpineFirmware.Font = $defaultLabelFont
    $lstSpineFirmware.Name = 'lstSpineFirmware'
    $lstSpineFirmware.Enabled = $true
    $lstSpineFirmware.Visible = $true
    [void]$lstSpineFirmware.Items.Clear()
    $lstSpineFirmware.SelectedIndex = -1
    $tabNetwork.Controls.Add($lstSpineFirmware)
    $lstSpineModel_Changed = `
    {
        [void]$lstSpineFirmware.Items.Clear()
        if($lstSwitchVendor.SelectedItem -eq "CiscoUCS")
        {
            $lblSpineFirmware.Enabled = $false
            $lblSpineFirmware.Visible = $false
            $lstSpineFirmware.Enabled = $false
            $lstSpineFirmware.Visible = $false
            Clear-ListSelection $lstSpineFirmware
        }
        else
        {
            if($lstSpineModel.SelectedItem)
            {
                $lblSpineFirmware.Enabled = $true
                $lblSpineFirmware.Visible = $true
                $lstSpineFirmware.Enabled = $true
                $lstSpineFirmware.Visible = $true
                $firmware = @(Get-SwitchFirmware -Make $lstSwitchVendor.SelectedItem -Type 'SPINE' -Model $lstSpineModel.SelectedItem)
                if($firmware.Count -gt 0)
                {
                    $firmware | Foreach-Object `
                    {
                        [void]$lstSpineFirmware.Items.Add($PSItem)
                    }
                    $lstSpineFirmware.Enabled = $true
                    $lstSpineFirmware.Visible = $true
                    $lstSpineFirmware.SelectedIndex = 0
                    $lblSpineFirmware.Enabled = $true
                    $lblSpineFirmware.Visible = $true
                }
                else
                {
                    $lstSpineFirmware.Enabled = $false
                    $lstSpineFirmware.Visible = $false
                    Clear-ListSelection $lstSpineFirmware
                    $lblSpineFirmware.Enabled = $false
                    $lblSpineFirmware.Visible = $false
                }
                if($txtScaleUnitCount.Text -eq '1')
                {
                    Clear-ListSelection $lstSpineFirmware
                    $lstSpineFirmware.Enabled = $false
                    $lstSpineFirmware.Visible = $false
                    $lblSpineFirmware.Enabled = $false
                    $lblSpineFirmware.Visible = $false
                    $lstSpineModel.Enabled = $false
                    $lstSpineModel.Visible = $false
                    $lblSpineModel.Enabled = $false
                    $lblSpineModel.Visible = $false
                }
            }
        }
        $lstSpineModel.Refresh()
        $lblSpineModel.Refresh()
        $lblSpineFirmware.Refresh()
        $lstSpineFirmware.Refresh()
    }
    $lstSpineModel.Add_SelectedIndexChanged($lstSpineModel_Changed)

    # -------------------------- BORDER CONNECTIVITY --------------------------
    $script:lblBorderConnect = New-Object System.Windows.Forms.Label
    $lblBorderConnect.Location = New-Object System.Drawing.Size($lblLeftNet2,($row2))
    $lblBorderConnect.AutoSize = $true
    $lblBorderConnect.Text = $locString.lblBorderConnect
    $lblBorderConnect.Font = [System.Drawing.Font]::New("Microsoft Sans Serif", 10, [System.Drawing.FontStyle]::Bold)
    $tabNetwork.Controls.Add($lblBorderConnect)

    $row2 += 30

    # --------------------------- ROUTING METHOD ------------------------------
    $script:lblRoutingMethod = New-Object System.Windows.Forms.Label
    $lblRoutingMethod.Location = New-Object System.Drawing.Size($lblLeftNet2,($row2+2))
    $lblRoutingMethod.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblRoutingMethod.AutoSize = $true
    $lblRoutingMethod.Font = $defaultLabelFont
    $lblRoutingMethod.Text = $locString.lblRoutingMethod
    $tabNetwork.Controls.Add($lblRoutingMethod)

    $script:lstRoutingMethod = New-Object System.Windows.Forms.ComboBox
    $lstRoutingMethod.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList
    $lstRoutingMethod.Location = New-Object System.Drawing.Size($txtLeftNet2,$row2)
    $lstRoutingMethod.Size = New-Object System.Drawing.Size($lstRoutingMethodSize)
    $lstRoutingMethod.Font = $defaultLabelFont
    $lstRoutingMethod.Name = 'lstRoutingMethod'
    [void]$lstRoutingMethod.Items.Clear()
    $arrRoutingMethod | Foreach-Object `
    {
        [void]$lstRoutingMethod.Items.Add($PSItem)
    }
    $lstRoutingMethod.SelectedIndex = 0
    $tabNetwork.Controls.Add($lstRoutingMethod)
    $lstRoutingMethod_Changed = `
    {
        switch ($lstRoutingMethod.SelectedItem)
        {
            "Static Routing"
            {
                Disable-EdgeBGP 1
                Disable-EdgeBGP 2
                Disable-EdgeBGP 3
                Disable-EdgeBGP 4
            }
            "BGP Routing"
            {
                Invoke-RefreshEdgeBGP -EdgeCount $txtEdgeCount.Text
                if([int]$txtScaleUnitCount.Text -gt 1)
                {
                    if($lstSwitchVendor.SelectedItem -eq "CiscoUCS")
                    {
                        Disable-SpineBGP
                    }
                    else
                    {
                        Enable-SpineBGP
                    }
                }
            }
            default
            {
            }
        }
    }
    $lstRoutingMethod.Add_SelectedIndexChanged($lstRoutingMethod_Changed)

    $row2 += 40

    # ------------------------------ EDGE COUNT -------------------------------
    $script:lblEdgeCount = New-Object System.Windows.Forms.Label
    $lblEdgeCount.Location = New-Object System.Drawing.Size($lblLeftNet2,($row2+2))
    $lblEdgeCount.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblEdgeCount.AutoSize = $true
    $lblEdgeCount.Font = $defaultLabelFont
    $lblEdgeCount.Text = $locString.lblEdgeCount
    $tabNetwork.Controls.Add($lblEdgeCount)

    $script:txtEdgeCount = New-Object System.Windows.Forms.TextBox
    $txtEdgeCount.Location = New-Object System.Drawing.Size($txtLeftNet2,$row2)
    $txtEdgeCount.Size = New-Object System.Drawing.Size($txtCountSize)
    $txtEdgeCount.Font = $defaultLabelFont
    $txtEdgeCount.Name = 'txtEdgeCount'
    $txtEdgeCount.TextAlign = [System.Windows.Forms.HorizontalAlignment]::Center
    $txtEdgeCount.MaxLength = 1
    $txtEdgeCount.Text = $defaultEdgeCount
    $tabNetwork.Controls.Add($txtEdgeCount)
    $txtEdgeCount_Changed = `
    {
        if(-not([string]::IsNullOrEmpty($txtEdgeCount.Text)) -and ($txtEdgeCount.Text -notmatch "^[1-$($validEdge)]+$"))
        {
            [string]$errMessage = ($locString.errNumberNotInRange -f '1',$validEdge)
            Close-WaitForm
            [System.Windows.Forms.MessageBox]::Show($errMessage, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
            $txtEdgeCount.ForeColor = $defaultFailColor
            $txtEdgeCount.SelectionStart = 0
            $txtEdgeCount.SelectionLength = 1
            $txtEdgeCount.Select()
        }
        else
        {
            $txtEdgeCount.ForeColor = $defaultForeColor
        }
        $txtEdgeCount.Refresh()
        if($lstRoutingMethod.SelectedItem -eq "BGP Routing")
        {
            Invoke-RefreshEdgeBGP -EdgeCount $txtEdgeCount.Text
        }
    }
    $txtEdgeCount.Add_Leave($txtEdgeCount_Changed)

    $row2 += 30

    # ---------------------------- EDGE 1 BGP ASN -----------------------------
    $script:lblEdgeBGP1 = New-Object System.Windows.Forms.Label
    $lblEdgeBGP1.Name = 'lblEdgeBGP1'
    $lblEdgeBGP1.Location = New-Object System.Drawing.Size($lblLeftNetASN,($row2+2))
    $lblEdgeBGP1.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblEdgeBGP1.AutoSize = $true
    $lblEdgeBGP1.Font = $defaultLabelFont
    $lblEdgeBGP1.Text = $locString.lblEdgeBGP1
    $tabNetwork.Controls.Add($lblEdgeBGP1)

    $script:txtEdgeBGP1 = New-Object System.Windows.Forms.TextBox
    $txtEdgeBGP1.Location = New-Object System.Drawing.Size($txtLeftNetASN,$row2)
    $txtEdgeBGP1.Size = New-Object System.Drawing.Size($txtPrefixSize)
    $txtEdgeBGP1.Font = $defaultLabelFont
    $txtEdgeBGP1.Name = 'txtEdgeBGP1'
    $txtEdgeBGP1.TextAlign = [System.Windows.Forms.HorizontalAlignment]::Center
    $txtEdgeBGP1.MaxLength = 10
    $tabNetwork.Controls.Add($txtEdgeBGP1)
    $txtEdgeBGP1_Changed = `
    {
        if(-not([string]::IsNullOrEmpty($txtEdgeBGP1.Text)))
        {
            $valid = $true
            if($txtEdgeBGP1.Text -notmatch $validNumber)
            {
                $valid = $false
            }
            else
            {
                [uint64]$thisASN = $txtEdgeBGP1.Text
                if(-not(Get-IsValidASN -ASN $thisASN))
                {
                    $valid = $false
                }
            }
            if(-not $valid)
            {
                [string]$errMessage = ($locString.errNumberNotInRange -f $validASNMin,$validASNMax)
                Close-WaitForm
                [System.Windows.Forms.MessageBox]::Show($errMessage, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
                $txtEdgeBGP1.ForeColor = $defaultFailColor
                $txtEdgeBGP1.Select()
            }
            else
            {
                $txtEdgeBGP1.ForeColor = $defaultForeColor
                $txtEdgeBGP1.Text = $txtEdgeBGP1.Text -replace '\b0+\B'
            }
        }
        $txtEdgeBGP1.Refresh()
    }
    $txtEdgeBGP1.Add_Leave($txtEdgeBGP1_Changed)

    $row2 += 25

    # ---------------------------- EDGE 2 BGP ASN -----------------------------
    $script:lblEdgeBGP2 = New-Object System.Windows.Forms.Label
    $lblEdgeBGP2.Name = 'lblEdgeBGP2'
    $lblEdgeBGP2.Location = New-Object System.Drawing.Size($lblLeftNetASN,($row2+2))
    $lblEdgeBGP2.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblEdgeBGP2.AutoSize = $true
    $lblEdgeBGP2.Font = $defaultLabelFont
    $lblEdgeBGP2.Text = $locString.lblEdgeBGP2
    $tabNetwork.Controls.Add($lblEdgeBGP2)

    $script:txtEdgeBGP2 = New-Object System.Windows.Forms.TextBox
    $txtEdgeBGP2.Location = New-Object System.Drawing.Size($txtLeftNetASN,$row2)
    $txtEdgeBGP2.Size = New-Object System.Drawing.Size($txtPrefixSize)
    $txtEdgeBGP2.Font = $defaultLabelFont
    $txtEdgeBGP2.Name = 'txtEdgeBGP2'
    $txtEdgeBGP2.TextAlign = [System.Windows.Forms.HorizontalAlignment]::Center
    $txtEdgeBGP2.MaxLength = 10
    $tabNetwork.Controls.Add($txtEdgeBGP2)
    $txtEdgeBGP2_Changed = `
    {
        if(-not([string]::IsNullOrEmpty($txtEdgeBGP2.Text)))
        {
            $valid = $true
            if($txtEdgeBGP2.Text -notmatch $validNumber)
            {
                $valid = $false
            }
            else
            {
                [uint64]$thisASN = $txtEdgeBGP2.Text
                if(-not(Get-IsValidASN -ASN $thisASN))
                {
                    $valid = $false
                }
            }
            if(-not $valid)
            {
                [string]$errMessage = ($locString.errNumberNotInRange -f $validASNMin,$validASNMax)
                Close-WaitForm
                [System.Windows.Forms.MessageBox]::Show($errMessage, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
                $txtEdgeBGP2.ForeColor = $defaultFailColor
                $txtEdgeBGP2.Select()
            }
            else
            {
                $txtEdgeBGP2.ForeColor = $defaultForeColor
                $txtEdgeBGP2.Text = $txtEdgeBGP2.Text -replace '\b0+\B'
            }
        }
        $txtEdgeBGP2.Refresh()
    }
    $txtEdgeBGP2.Add_Leave($txtEdgeBGP2_Changed)

    $row2 += 25

    # ---------------------------- EDGE 3 BGP ASN -----------------------------
    $script:lblEdgeBGP3 = New-Object System.Windows.Forms.Label
    $lblEdgeBGP3.Name = 'lblEdgeBGP3'
    $lblEdgeBGP3.Location = New-Object System.Drawing.Size($lblLeftNetASN,($row2+2))
    $lblEdgeBGP3.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblEdgeBGP3.AutoSize = $true
    $lblEdgeBGP3.Font = $defaultLabelFont
    $lblEdgeBGP3.Text = $locString.lblEdgeBGP3
    $lblEdgeBGP3.Enabled = $false
    $lblEdgeBGP3.Visible = $false
    $tabNetwork.Controls.Add($lblEdgeBGP3)

    $script:txtEdgeBGP3 = New-Object System.Windows.Forms.TextBox
    $txtEdgeBGP3.Location = New-Object System.Drawing.Size($txtLeftNetASN,$row2)
    $txtEdgeBGP3.Size = New-Object System.Drawing.Size($txtPrefixSize)
    $txtEdgeBGP3.Name = 'txtEdgeBGP3'
    $txtEdgeBGP3.TextAlign = [System.Windows.Forms.HorizontalAlignment]::Center
    $txtEdgeBGP3.Font = $defaultLabelFont
    $txtEdgeBGP3.MaxLength = 10
    $txtEdgeBGP3.Enabled = $false
    $txtEdgeBGP3.Visible = $false
    $tabNetwork.Controls.Add($txtEdgeBGP3)
    $txtEdgeBGP3_Changed = `
    {
        if(-not([string]::IsNullOrEmpty($txtEdgeBGP3.Text)))
        {
            $valid = $true
            if($txtEdgeBGP3.Text -notmatch $validNumber)
            {
                $valid = $false
            }
            else
            {
                [uint64]$thisASN = $txtEdgeBGP3.Text
                if(-not(Get-IsValidASN -ASN $thisASN))
                {
                    $valid = $false
                }
            }
            if(-not $valid)
            {
                [string]$errMessage = ($locString.errNumberNotInRange -f $validASNMin,$validASNMax)
                Close-WaitForm
                [System.Windows.Forms.MessageBox]::Show($errMessage, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
                $txtEdgeBGP3.ForeColor = $defaultFailColor
                $txtEdgeBGP3.Select()
            }
            else
            {
                $txtEdgeBGP3.ForeColor = $defaultForeColor
                $txtEdgeBGP3.Text = $txtEdgeBGP3.Text -replace '\b0+\B'
            }
        }
        $txtEdgeBGP3.Refresh()
    }
    $txtEdgeBGP3.Add_Leave($txtEdgeBGP3_Changed)

    $row2 += 25

    # ---------------------------- EDGE 4 BGP ASN -----------------------------
    $script:lblEdgeBGP4 = New-Object System.Windows.Forms.Label
    $lblEdgeBGP4.Name = 'lblEdgeBGP4'
    $lblEdgeBGP4.Location = New-Object System.Drawing.Size($lblLeftNetASN,($row2+2))
    $lblEdgeBGP4.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblEdgeBGP4.AutoSize = $true
    $lblEdgeBGP4.Font = $defaultLabelFont
    $lblEdgeBGP4.Text = $locString.lblEdgeBGP4
    $lblEdgeBGP4.Enabled = $false
    $lblEdgeBGP4.Visible = $false
    $tabNetwork.Controls.Add($lblEdgeBGP4)

    $script:txtEdgeBGP4 = New-Object System.Windows.Forms.TextBox
    $txtEdgeBGP4.Location = New-Object System.Drawing.Size($txtLeftNetASN,$row2)
    $txtEdgeBGP4.Size = New-Object System.Drawing.Size($txtPrefixSize)
    $txtEdgeBGP4.Font = $defaultLabelFont
    $txtEdgeBGP4.Name = 'txtEdgeBGP4'
    $txtEdgeBGP4.TextAlign = [System.Windows.Forms.HorizontalAlignment]::Center
    $txtEdgeBGP4.MaxLength = 10
    $txtEdgeBGP4.Enabled = $false
    $txtEdgeBGP4.Visible = $false
    $tabNetwork.Controls.Add($txtEdgeBGP4)
    $txtEdgeBGP4_Changed = `
    {
        if(-not([string]::IsNullOrEmpty($txtEdgeBGP4.Text)))
        {
            $valid = $true
            if($txtEdgeBGP4.Text -notmatch $validNumber)
            {
                $valid = $false
            }
            else
            {
                [uint64]$thisASN = $txtEdgeBGP4.Text
                if(-not(Get-IsValidASN -ASN $thisASN))
                {
                    $valid = $false
                }
            }
            if(-not $valid)
            {
                [string]$errMessage = ($locString.errNumberNotInRange -f $validASNMin,$validASNMax)
                Close-WaitForm
                [System.Windows.Forms.MessageBox]::Show($errMessage, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
                $txtEdgeBGP4.ForeColor = $defaultFailColor
                $txtEdgeBGP4.Select()
            }
            else
            {
                $txtEdgeBGP4.ForeColor = $defaultForeColor
                $txtEdgeBGP4.Text = $txtEdgeBGP4.Text -replace '\b0+\B'
            }
        }
        $txtEdgeBGP4.Refresh()
    }
    $txtEdgeBGP4.Add_Leave($txtEdgeBGP4_Changed)

    $row2 += 40

    # ----------------------------- SPINE COUNT -------------------------------
    $script:lblSpineCount = New-Object System.Windows.Forms.Label
    $lblSpineCount.Location = New-Object System.Drawing.Size($lblLeftNet2,($row2+2))
    $lblSpineCount.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblSpineCount.AutoSize = $true
    $lblSpineCount.Font = $defaultLabelFont
    $lblSpineCount.Text = $locString.lblSpineCount
    $tabNetwork.Controls.Add($lblSpineCount)

    $script:txtSpineCount = New-Object System.Windows.Forms.TextBox
    $txtSpineCount.Location = New-Object System.Drawing.Size($txtLeftNet2,$row2)
    $txtSpineCount.Size = New-Object System.Drawing.Size($txtCountSize)
    $txtSpineCount.Font = $defaultLabelFont
    $txtSpineCount.Name = 'txtSpineCount'
    $txtSpineCount.TextAlign = [System.Windows.Forms.HorizontalAlignment]::Center
    $txtSpineCount.MaxLength = 1
    $txtSpineCount.Text = $defaultSpineCount
    $tabNetwork.Controls.Add($txtSpineCount)
    $txtSpineCount_Changed = `
    {
        if(-not([string]::IsNullOrEmpty($txtSpineCount.Text)) -and ($txtSpineCount.Text -notmatch '^[2-4]+$'))
        {
            [string]$errMessage = ($locString.errNumberNotInRange -f '2','4')
            Close-WaitForm
            [System.Windows.Forms.MessageBox]::Show($errMessage, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
            $txtSpineCount.ForeColor = $defaultFailColor
            $txtSpineCount.SelectionStart = 0
            $txtSpineCount.SelectionLength = 1
            $txtSpineCount.Select()
        }
        else
        {
            $txtSpineCount.ForeColor = $defaultForeColor
        }
        $txtSpineCount.Refresh()
    }
    $txtSpineCount.Add_Leave($txtSpineCount_Changed)

    $row2 += 30

    # ---------------------------- SPINE BGP ASN ------------------------------
    $script:lblSpineBGP = New-Object System.Windows.Forms.Label
    $lblSpineBGP.Location = New-Object System.Drawing.Size($lblLeftNetASN,($row2+2))
    $lblSpineBGP.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblSpineBGP.AutoSize = $true
    $lblSpineBGP.Font = $defaultLabelFont
    $lblSpineBGP.Text = $locString.lblSpineBGP
    $tabNetwork.Controls.Add($lblSpineBGP)

    $script:txtSpineBGP = New-Object System.Windows.Forms.TextBox
    $txtSpineBGP.Location = New-Object System.Drawing.Size($txtLeftNetASN,$row2)
    $txtSpineBGP.Size = New-Object System.Drawing.Size($txtPrefixSize)
    $txtSpineBGP.Font = $defaultLabelFont
    $txtSpineBGP.Name = 'txtSpineBGP'
    $txtSpineBGP.TextAlign = [System.Windows.Forms.HorizontalAlignment]::Center
    $txtSpineBGP.MaxLength = 10
    $tabNetwork.Controls.Add($txtSpineBGP)
    $txtSpineBGP_Changed = `
    {
        if(-not([string]::IsNullOrEmpty($txtSpineBGP.Text)))
        {
            $valid = $true
            if($txtSpineBGP.Text -notmatch $validNumber)
            {
                $valid = $false
            }
            else
            {
                [uint64]$thisASN = $txtSpineBGP.Text
                if(-not(Get-IsValidASN -ASN $thisASN))
                {
                    $valid = $false
                }
            }
            if(-not $valid)
            {
                [string]$errMessage = ($locString.errNumberNotInRange -f $validASNMin,$validASNMax)
                Close-WaitForm
                [System.Windows.Forms.MessageBox]::Show($errMessage, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
                $txtSpineBGP.ForeColor = $defaultFailColor
                $txtSpineBGP.Select()
            }
            else
            {
                $txtSpineBGP.ForeColor = $defaultForeColor
                $txtSpineBGP.Text = $txtSpineBGP.Text -replace '\b0+\B'
            }
        }
        $txtSpineBGP.Refresh()
    }
    $txtSpineBGP.Add_Leave($txtSpineBGP_Changed)

    $row2 += 40

    # --------------------------- SOFTWARE BGP ASN ----------------------------
    $script:lblSoftwareBGP = New-Object System.Windows.Forms.Label
    $lblSoftwareBGP.Location = New-Object System.Drawing.Size($lblLeftNetASN,($row2+2))
    $lblSoftwareBGP.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblSoftwareBGP.AutoSize = $true
    $lblSoftwareBGP.Font = $defaultLabelFont
    $lblSoftwareBGP.Text = $locString.lblSoftwareBGP
    $tabNetwork.Controls.Add($lblSoftwareBGP)

    $script:txtSoftwareBGP = New-Object System.Windows.Forms.TextBox
    $txtSoftwareBGP.Location = New-Object System.Drawing.Size($txtLeftNetASN,$row2)
    $txtSoftwareBGP.Size = New-Object System.Drawing.Size($txtPrefixSize)
    $txtSoftwareBGP.Font = $defaultLabelFont
    $txtSoftwareBGP.Name = 'txtSoftwareBGP'
    $txtSoftwareBGP.TextAlign = [System.Windows.Forms.HorizontalAlignment]::Center
    $txtSoftwareBGP.MaxLength = 10
    $tabNetwork.Controls.Add($txtSoftwareBGP)
    $txtSoftwareBGP_Changed = `
    {
        if(-not([string]::IsNullOrEmpty($txtSoftwareBGP.Text)))
        {
            $valid = $true
            if($txtSoftwareBGP.Text -notmatch $validNumber)
            {
                $valid = $false
            }
            else
            {
                [uint64]$thisASN = $txtSoftwareBGP.Text
                if(-not(Get-IsValidASN -ASN $thisASN))
                {
                    $valid = $false
                }
            }
            if(-not $valid)
            {
                [string]$errMessage = ($locString.errNumberNotInRange -f $validASNMin,$validASNMax)
                Close-WaitForm
                [System.Windows.Forms.MessageBox]::Show($errMessage, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
                $txtSoftwareBGP.ForeColor = $defaultFailColor
                $txtSoftwareBGP.Select()
            }
            else
            {
                $txtSoftwareBGP.ForeColor = $defaultForeColor
                $txtSoftwareBGP.Text = $txtSoftwareBGP.Text -replace '\b0+\B'
            }
        }
        $txtSoftwareBGP.Refresh()
    }
    $txtSoftwareBGP.Add_Leave($txtSoftwareBGP_Changed)

    if($txtScaleUnitCount.Text -eq '1')
    {
        $lblSpineModel.Enabled = $false
        $lblSpineModel.Visible = $false
        $lstSpineModel.Enabled = $false
        $lstSpineModel.Visible = $false
        $lblSpineModel.Refresh()
        $lstSpineModel.Refresh()
        $lblSpineFirmware.Enabled = $false
        $lblSpineFirmware.Visible = $false
        $lstSpineFirmware.Enabled = $false
        $lstSpineFirmware.Visible = $false
        $lblSpineFirmware.Refresh()
        $lstSpineFirmware.Refresh()
        Disable-SpineBGP
        Disable-SpineCount
    }
    else
    {
        $lblSpineModel.Enabled = $true
        $lblSpineModel.Visible = $true
        $lstSpineModel.Enabled = $true
        $lstSpineModel.Visible = $true
        $lblSpineModel.Refresh()
        $lstSpineModel.Refresh()
        $lblSpineFirmware.Enabled = $true
        $lblSpineFirmware.Visible = $true
        $lstSpineFirmware.Enabled = $true
        $lstSpineFirmware.Visible = $true
        $lblSpineFirmware.Refresh()
        $lstSpineFirmware.Refresh()
        Enable-SpineBGP
        Enable-SpineCount
    }

    $row += 50

    # ---------------------------- CLOUD SUBNETS ------------------------------
    $script:lblCloudSubnets = New-Object System.Windows.Forms.Label
    $lblCloudSubnets.Location = New-Object System.Drawing.Size($lblLeft,($row))
    $lblCloudSubnets.AutoSize = $true
    $lblCloudSubnets.Text = $locString.lblCloudSubnets
    $lblCloudSubnets.Font = [System.Drawing.Font]::New("Microsoft Sans Serif", 10, [System.Drawing.FontStyle]::Bold)
    $tabNetwork.Controls.Add($lblCloudSubnets)

    $row += 30

    # --------------------------- EXTERNAL SUBNET -----------------------------
    $script:lblExternalSubnet = New-Object System.Windows.Forms.Label
    $lblExternalSubnet.Location = New-Object System.Drawing.Size($lblLeft,($row+2))
    $lblExternalSubnet.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblExternalSubnet.AutoSize = $true
    $lblExternalSubnet.Font = $defaultLabelFont
    $lblExternalSubnet.Text = $locString.lblExternalSubnet
    $tabNetwork.Controls.Add($lblExternalSubnet)

    $script:txtExternalSubnet = New-Object System.Windows.Forms.TextBox
    $txtExternalSubnet.Location = New-Object System.Drawing.Size($txtLeft,$row)
    $txtExternalSubnet.Size = New-Object System.Drawing.Size($txtPrefixSize)
    $txtExternalSubnet.Font = $defaultLabelFont
    $txtExternalSubnet.Name = 'txtExternalSubnet'
    $txtExternalSubnet.MaxLength = $lenIPAddress
    $tabNetwork.Controls.Add($txtExternalSubnet)
    $txtExternalSubnet_Changed = `
    {
        if(-not([string]::IsNullOrEmpty($txtExternalSubnet.Text)))
        {
            if($txtExternalSubnet.Text -notmatch $validIPAddress)
            {
                Close-WaitForm
                [System.Windows.Forms.MessageBox]::Show($locString.errIPAddressInvalid, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
                $txtExternalSubnet.ForeColor = $defaultFailColor
                $txtExternalSubnet.Select()
            }
            elseif(-not(Get-IsValidSubnet -IPAddress $txtExternalSubnet.Text -CIDR $txtExternalSubnetSize.Text))
            {
                Close-WaitForm
                $message = ($locString.errSubnetInvalid -f $txtExternalSubnet.Text,$txtExternalSubnetSize.Text)
                [System.Windows.Forms.MessageBox]::Show($message, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
                $txtExternalSubnet.ForeColor = $defaultFailColor
                $txtExternalSubnet.Select()
            }
            else
            {
                $txtExternalSubnet.ForeColor = $defaultForeColor
            }
            $txtExternalSubnet.Refresh()
        }
    }
    $txtExternalSubnet.Add_Leave($txtExternalSubnet_Changed)

    $script:lblExternalSubnetSize = New-Object System.Windows.Forms.Label
    $lblExternalSubnetSize.Location = New-Object System.Drawing.Size(($txtExternalSubnet.Right+10),($txtExternalSubnet.Location.Y+2))
    $lblExternalSubnetSize.AutoSize = $true
    $lblExternalSubnetSize.Font = $defaultLabelFont
    $lblExternalSubnetSize.Text = $locString.lblSubnetSize
    $tabNetwork.Controls.Add($lblExternalSubnetSize)

    $script:txtExternalSubnetSize = New-Object System.Windows.Forms.TextBox
    $txtExternalSubnetSize.Location = New-Object System.Drawing.Size(($lblExternalSubnetSize.Right+10),($txtExternalSubnet.Location.Y))
    $txtExternalSubnetSize.Size = New-Object System.Drawing.Size($txtCountSize)
    $txtExternalSubnetSize.Font = $defaultLabelFont
    $txtExternalSubnetSize.Name = 'txtExternalSubnetSize'
    $txtExternalSubnetSize.MaxLength = 2
    $txtExternalSubnetSize.Text = $defaultExternalSubnetSize
    $tabNetwork.Controls.Add($txtExternalSubnetSize)
    $txtExternalSubnetSize_Changed = `
    {
        [string]$message = ''
        $valid = $true
        if(-not([string]::IsNullOrEmpty($txtExternalSubnetSize.Text)) -and ($txtExternalSubnetSize.Text -notmatch $validNumber))
        {
            $valid = $false
        }
        elseif(([int]$txtExternalSubnetSize.Text -gt $validSubnetExternalMax) -or ([int]$txtExternalSubnetSize.Text -lt $validSubnetExternalMin))
        {
            $valid = $false
        }
        elseif(-not([string]::IsNullOrEmpty($txtExternalSubnet.Text)))
        {
            if(-not(Get-IsValidSubnet -IPAddress $txtExternalSubnet.Text -CIDR $txtExternalSubnetSize.Text))
            {
                $message = ($locString.errSubnetInvalid -f $txtExternalSubnet.Text,$txtExternalSubnetSize.Text)
                $valid = $false
            }
        }
        if(-not $valid)
        {
            Close-WaitForm
            if(-not $message)
            {
                [string]$message = ($locString.errNumberNotInRange -f $validSubnetExternalMin,$validSubnetExternalMax)
            }
            [System.Windows.Forms.MessageBox]::Show($message, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
            $txtExternalSubnetSize.Text = $defaultExternalSubnetSize
            $txtExternalSubnetSize.Select()
            $txtExternalSubnetSize.ForeColor = $defaultFailColor
        }
        else
        {
            $txtExternalSubnetSize.ForeColor = $defaultForeColor
        }
        $txtExternalSubnetSize.Refresh()
    }
    $txtExternalSubnetSize.Add_Leave($txtExternalSubnetSize_Changed)

    $row += 40

    # ----------------------------- SPINE SUBNET ------------------------------
    $script:lblSpineSubnet = New-Object System.Windows.Forms.Label
    $lblSpineSubnet.Location = New-Object System.Drawing.Size($lblLeft,($row+2))
    $lblSpineSubnet.MaximumSize = New-Object System.Drawing.Size($lblMaxSize)
    $lblSpineSubnet.AutoSize = $true
    $lblSpineSubnet.Font = $defaultLabelFont
    $lblSpineSubnet.Text = $locString.lblSpineSubnet
    $lblSpineSubnet.Enabled = $false
    $lblSpineSubnet.Visible = $false
    $tabNetwork.Controls.Add($lblSpineSubnet)

    $script:txtSpineSubnet = New-Object System.Windows.Forms.TextBox
    $txtSpineSubnet.Location = New-Object System.Drawing.Size($txtLeft,$row)
    $txtSpineSubnet.Size = New-Object System.Drawing.Size($txtPrefixSize)
    $txtSpineSubnet.Font = $defaultLabelFont
    $txtSpineSubnet.Name = 'txtSpineSubnet'
    $txtSpineSubnet.MaxLength = $lenIPAddress
    $txtSpineSubnet.Enabled = $false
    $txtSpineSubnet.Visible = $false
    $tabNetwork.Controls.Add($txtSpineSubnet)
    $txtSpineSubnet_Changed = `
    {
        if(-not([string]::IsNullOrEmpty($txtSpineSubnet.Text)))
        {
            if($txtSpineSubnet.Text -notmatch $validIPAddress)
            {
                Close-WaitForm
                [System.Windows.Forms.MessageBox]::Show($locString.errIPAddressInvalid, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
                $txtSpineSubnet.ForeColor = $defaultFailColor
                $txtSpineSubnet.Select()
            }
            elseif(-not(Get-IsValidSubnet -IPAddress $txtSpineSubnet.Text -CIDR $txtSpineSubnetSize.Text))
            {
                Close-WaitForm
                $message = ($locString.errSubnetInvalid -f $txtSpineSubnet.Text,$txtSpineSubnetSize.Text)
                [System.Windows.Forms.MessageBox]::Show($message, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
                $txtSpineSubnet.ForeColor = $defaultFailColor
                $txtSpineSubnet.Select()
            }
            else
            {
                $txtSpineSubnet.ForeColor = $defaultForeColor
            }
            $txtSpineSubnet.Refresh()
        }
    }
    $txtSpineSubnet.Add_Leave($txtSpineSubnet_Changed)

    $script:lblSpineSubnetSize = New-Object System.Windows.Forms.Label
    $lblSpineSubnetSize.Location = New-Object System.Drawing.Size(($txtSpineSubnet.Right+10),($txtSpineSubnet.Location.Y+2))
    $lblSpineSubnetSize.AutoSize = $true
    $lblSpineSubnetSize.Font = $defaultLabelFont
    $lblSpineSubnetSize.Text = $locString.lblSubnetSize
    $lblSpineSubnetSize.Enabled = $false
    $lblSpineSubnetSize.Visible = $false
    $tabNetwork.Controls.Add($lblSpineSubnetSize)

    $script:txtSpineSubnetSize = New-Object System.Windows.Forms.TextBox
    $txtSpineSubnetSize.Location = New-Object System.Drawing.Size(($lblSpineSubnetSize.Right+10),($txtSpineSubnet.Location.Y))
    $txtSpineSubnetSize.Size = New-Object System.Drawing.Size($txtCountSize)
    $txtSpineSubnetSize.Font = $defaultLabelFont
    $txtSpineSubnetSize.Name = 'txtSpineSubnetSize'
    $txtSpineSubnetSize.MaxLength = 2
    $txtSpineSubnetSize.Text = $defaultSpineSubnetSize
    $txtSpineSubnetSize.Enabled = $false
    $txtSpineSubnetSize.Visible = $false
    $tabNetwork.Controls.Add($txtSpineSubnetSize)
    $txtSpineSubnetSize_Changed = `
    {
        $valid = $true
        $message = ''
        if(-not([string]::IsNullOrEmpty($txtSpineSubnetSize.Text)) -and ($txtSpineSubnetSize.Text -notmatch $validNumber))
        {
            $valid = $false
        }
        elseif(([int]$txtSpineSubnetSize.Text -gt $subnetExternalMax) -or ([int]$txtSpineSubnetSize.Text -lt $subnetExternalMin))
        {
            $valid = $false
        }
        elseif(-not(Get-IsValidSubnet -IPAddress $txtSpineSubnet.Text -CIDR $txtSpineSubnetSize.Text))
        {
            $message = ($locString.errSubnetInvalid -f $txtSpineSubnet.Text,$txtSpineSubnetSize.Text)
            $valid = $false
        }
        if(-not $valid)
        {
            Close-WaitForm
            if(-not $message)
            {
                $message = ($locString.errNumberNotInRange -f $subnetExternalMin,$subnetExternalMax)
            }
            [System.Windows.Forms.MessageBox]::Show($message, $locString.capValidationError, [System.Windows.Forms.MessageBoxButtons]::OK)
            $txtSpineSubnetSize.Text = $defaultExternalSubnetSize
            $txtSpineSubnetSize.ForeColor = $defaultFailColor
            $txtSpineSubnetSize.Select()
        }
        else
        {
            $txtSpineSubnetSize.ForeColor = $defaultForeColor
        }
        $txtSpineSubnetSize.Refresh()
    }
    $txtSpineSubnetSize.Add_Leave($txtSpineSubnetSize_Changed)

    # ========================= ADD MENUS TO THE FORM =========================
    $frmMenu = New-Object System.Windows.Forms.MenuStrip
    $menuFile = New-Object System.Windows.Forms.ToolStripMenuItem
    $menuItemOpen = New-Object System.Windows.Forms.ToolStripMenuItem
    $menuItemSave = New-Object System.Windows.Forms.ToolStripMenuItem
    $menuItemExit = New-Object System.Windows.Forms.ToolStripMenuItem

    $menuAction = New-Object System.Windows.Forms.ToolStripMenuItem
    $menuItemGenerate = New-Object System.Windows.Forms.ToolStripMenuItem
    $menuItemImport = New-Object System.Windows.Forms.ToolStripMenuItem
    $menuItemExport = New-Object System.Windows.Forms.ToolStripMenuItem
    $menuItemClear = New-Object System.Windows.Forms.ToolStripMenuItem

    $menuScenario = New-Object System.Windows.Forms.ToolStripMenuItem
    $menuItemCustomer = New-Object System.Windows.Forms.ToolStripMenuItem
    $menuItemPreviewMultiSU = New-Object System.Windows.Forms.ToolStripMenuItem
    $menuItemPreviewAddSU = New-Object System.Windows.Forms.ToolStripMenuItem
    if($EnableExperimental)
    {
        $menuScenario.DropDownItems.AddRange(@($menuItemCustomer,(New-Object System.Windows.Forms.ToolStripSeparator),$menuItemPreviewMultiSU,$menuItemPreviewAddSU))
    }
    else
    {
        $menuScenario.DropDownItems.AddRange(@($menuItemCustomer))
    }

    $frmMenu.Items.AddRange(@($menuFile,$menuAction,$menuScenario))
    $frmMenu.Location = New-Object System.Drawing.Point(0,0)
    $frmMenu.Name = "frmMenu"
    $frmMenu.AutoSize = $true
    $frmMenu.MinimumSize = "$($frmMain.Width),24"

    $menuFile.DropDownItems.AddRange(@($menuItemOpen,$menuItemSave,(New-Object System.Windows.Forms.ToolStripSeparator),$menuItemExit))
    $menuFile.Name = "menuFile"
    $menuFile.AutoSize = $true
    $menuFile.Text = $locString.menuFile

    $menuAction.DropDownItems.AddRange(@($menuItemImport,$menuItemExport,$menuItemGenerate,(New-Object System.Windows.Forms.ToolStripSeparator),$menuItemClear))
    $menuAction.Name = "menuAction"
    $menuAction.AutoSize = $true
    $menuAction.Text = $locString.menuAction

    $menuScenario.Name = "menuScenario"
    $menuScenario.AutoSize = $true
    $menuScenario.Text = $locString.menuScenario

    # ---------------------------- FILE MENU ITEMS ----------------------------
    $menuItemOpen.Name = "menuItemOpen"
    $menuItemOpen.AutoSize = $true
    $menuItemOpen.Text = $locString.menuOpen
    function OnClick_menuItemOpen($Sender,$e)
    {
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - called"
        $script:openFile = Get-FileName -Filter 'JSON files | *.json'
        if($openFile)
        {
            Show-WaitForm "`n $($locString.msgPleaseWait)`n`n $($locString.msgWait)`n`n"
            Clear-ExistingData
            Remove-GeneratedTabs
            $script:invalidOpenData = @()
            $script:openData = Get-Content $openFile | ConvertFrom-Json
            $openScenario = $openData.scenario
            if(-not $openScenario)
            {
                Close-WaitForm
                $caption = $locString.capError
                $message = $locString.msgInvalidOpenFile
                [System.Windows.Forms.MessageBox]::Show($message, $caption, [System.Windows.Forms.MessageBoxButtons]::OK)
                return
            }
            if($openScenario -eq "Lab")
            {
                Close-WaitForm
                $caption = $locString.capError
                $message = $locString.msgLabNotSupported
                [System.Windows.Forms.MessageBox]::Show($message, $caption, [System.Windows.Forms.MessageBoxButtons]::OK)
                return
            }
            $openVersion = $openData.Version
            if([version](Get-FileVersionInfo) -lt [version]$openVersion)
            {
                Write-Log -Message "$($MyInvocation.MyCommand.Name) - WARNING: the file opened is from a newer version of this application" -Force
            }
            switch ($openScenario)
            {
                "Customer"
                {
                    OnClick_menuItemCustomer
                    if([int]$openData.tabNetwork.txtScaleUnitCount -gt 1)
                    {
                        $txtScaleUnitCount.Enabled = $true
                        $txtScaleUnitCount.ReadOnly = $false
                        $menuScenario.DropDownItems | Foreach-Object `
                        {
                            if($PSItem.Name -match "MultiSU")
                            {
                                $PSItem.Checked = $true
                            }
                            else
                            {
                                if($PSItem -is [System.Windows.Forms.ToolStripMenuItem])
                                {
                                    $PSItem.Checked = $false
                                }
                            }
                        }
                    }
                }
                "AddRack"
                {
                    Set-AddRack
                }
            }
            Write-OpenDataToTab -TabName 'tabCustomer'
            Write-OpenDataToTab -TabName 'tabNetwork'
            $script:retainSpineSettings = $true
            Invoke-Command -ScriptBlock $txtScaleUnitCount_Changed
            $script:retainSpineSettings = $false
            [int]$suCount = $txtScaleUnitCount.Text
            for($i = 1; $i -le $suCount; $i++)
            {
                $tabName = "tabSU$i"
                ($openData.$tabName | Get-Member -Type NoteProperty).Name | ForEach-Object `
                {
                    if($openData.$tabName.$PSItem)
                    {
                        if($scaleUnitTabs.$tabName.Objects.$PSItem -is [System.Windows.Forms.TextBox])
                        {
                            $scaleUnitTabs.$tabName.Objects.$PSItem.Text = $openData.$tabName.$PSItem
                        }
                        elseif($scaleUnitTabs.$tabName.Objects.$PSItem -is [System.Windows.Forms.ComboBox])
                        {
                            $scaleUnitTabs.$tabName.Objects.$PSItem.SelectedItem = $openData.$tabName.$PSItem
                        }
                    }
                }
            }
            Invoke-RefreshTotalNodeCount
            Invoke-Command -ScriptBlock $txtEdgeCount_Changed
            if([int]$txtScaleUnitCount.Text -gt 1)
            {
                $txtScaleUnitCount.Enabled = $true
                $txtScaleUnitCount.ReadOnly = $false
            }
            if(($tabControl.TabPages["tabPartner"]) -and ($openData.tabPartner))
            {
                Write-OpenDataToTab -TabName 'tabPartner'
            }
            Move-PartnerTab
            Close-WaitForm
            if($invalidOpenData.Count -ne 0)
            {
                $caption = $locString.capWarning
                $message = $locString.msgInvalidOpenData
                [System.Windows.Forms.MessageBox]::Show($message, $caption, [System.Windows.Forms.MessageBoxButtons]::OK)
            }
        }
        else
        {
            Write-Log -Message "$($MyInvocation.MyCommand.Name) - aborted as no file name was selected"
        }
    }
    $menuItemOpen.Add_Click( { OnClick_menuItemOpen $menuItemOpen $EventArgs } )

    $menuItemSave.Name = "menuItemSave"
    $menuItemSave.AutoSize = $true
    $menuItemSave.Text = $locString.menuSave
    function OnClick_menuItemSave($Sender,$e)
    {
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - called"
        $saveFile = Get-SaveFileName
        if($saveFile)
        {
            Show-WaitForm "`n $($locString.msgPleaseWait)`n`n $($locString.msgWait)`n`n"
            $script:saveData = @{}
            $saveData.Add('scenario',$scenario)
            $saveData.Add('version',(Get-FileVersionInfo))
            foreach($tabName in @('tabCustomer','tabNetwork'))
            {
                $thisObj = @{}
                $thisTab = $tabControl.TabPages[$tabName]
                $thisTab.Controls | ForEach-Object `
                {
                    if(($PSItem -is [System.Windows.Forms.TextBox]) -or ($PSItem -is [System.Windows.Forms.RichTextBox]))
                    {
                        $thisObj.Add($PSItem.Name,$PSItem.Text)
                    }
                    elseif($PSItem -is [System.Windows.Forms.ComboBox])
                    {
                        $thisObj.Add($PSItem.Name,$PSItem.SelectedItem)
                    }
                }
                $saveData.Add($tabName,$thisObj)
            }
            for($i=1; $i -le 4; $i++)
            {
                $thisTab = "tabSU$i"
                if($tabControl.TabPages[$thisTab])
                {
                    $thisObj = @{}
                    $scaleUnitTabs.$thisTab.Objects.Keys | ForEach-Object `
                    {
                        if($scaleUnitTabs.$thisTab.Objects.$PSItem -is [System.Windows.Forms.TextBox])
                        {
                            $thisObj.Add($PSItem,$scaleUnitTabs.$thisTab.Objects.$PSItem.Text)
                        }
                        elseif($scaleUnitTabs.$thisTab.Objects.$PSItem -is [System.Windows.Forms.ComboBox])
                        {
                            $thisObj.Add($PSItem,$scaleUnitTabs.$thisTab.Objects.$PSItem.SelectedItem)
                        }
                    }
                    $saveData.Add($thisTab,$thisObj)
                }
            }
            if($tabControl.TabPages["tabPartner"])
            {
                $thisObj = @{}
                $tabPartner.Controls | ForEach-Object `
                {
                    if($PSItem -is [System.Windows.Forms.TextBox])
                    {
                        $thisObj.Add($PSItem.Name,$PSItem.Text)
                    }
                    elseif($PSItem -is [System.Windows.Forms.ComboBox])
                    {
                        $thisObj.Add($PSItem.Name,$PSItem.SelectedItem)
                    }
                }
                $saveData.Add("tabPartner",$thisObj)
            }
            $saveData | ConvertTo-Json -Depth 4 | Set-Content $saveFile
            $message = ($locString.msgSaveSucceeded -f $saveFile)
            $caption = $locString.capSuccess
            Close-WaitForm
            [System.Windows.Forms.MessageBox]::Show($message, $caption, [System.Windows.Forms.MessageBoxButtons]::OK)
        }
        else
        {
            Write-Log -Message "$($MyInvocation.MyCommand.Name) - aborted as no save file name was given"
        }
    }
    $menuItemSave.Add_Click( { OnClick_menuItemSave $menuItemSave $EventArgs } )

    $menuItemExit.Name = $locString.menuExit
    $menuItemExit.AutoSize = $true
    $menuItemExit.Text = $locString.menuExit
    function OnClick_menuItemExit($Sender,$e)
    {
        Write-Log -Message "Exit"
        $frmMain.Close()
    }
    $menuItemExit.Add_Click( { OnClick_menuItemExit $menuItemExit $EventArgs } )

    # --------------------------- ACTION MENU ITEMS ---------------------------
    $menuItemImport.Name = "menuItemImport"
    $menuItemImport.AutoSize = $true
    $menuItemImport.Text = $locString.menuImport
    function OnClick_menuItemImport($Sender,$e)
    {
        try
        {
            Invoke-ImportDeploymentData
        }
        catch
        {
            throw $PSItem
        }
    }
    $menuItemImport.Add_Click( { OnClick_menuItemImport $menuItemExport $EventArgs } )

    $menuItemExport.Name = "menuItemExport"
    $menuItemExport.AutoSize = $true
    $menuItemExport.Text = $locString.menuExport
    function OnClick_menuItemExport($Sender,$e)
    {
        try
        {
            Invoke-ExportDeploymentData
        }
        catch
        {
            throw $PSItem
        }
    }
    $menuItemExport.Add_Click( { OnClick_menuItemExport $menuItemExport $EventArgs } )

    $menuItemGenerate.Name = "menuItemGenerate"
    $menuItemGenerate.AutoSize = $true
    $menuItemGenerate.Text = $locString.menuGenerate
    function OnClick_menuItemGenerate($Sender,$e)
    {
        try
        {
            Invoke-GenerateDeploymentData
        }
        catch
        {
            throw $PSItem
        }
    }
    $menuItemGenerate.Add_Click( { OnClick_menuItemGenerate $menuItemGenerate $EventArgs } )

    $menuItemClear.Name = "menuItemClear"
    $menuItemClear.AutoSize = $true
    $menuItemClear.Text = $locString.menuClear
    function OnClick_menuItemClear($Sender,$e)
    {
        Clear-ExistingData
    }
    $menuItemClear.Add_Click( { OnClick_menuItemClear $menuItemClear $EventArgs } )

    # -------------------------- SCENARIO MENU ITEMS --------------------------
    $menuItemCustomer.Name = "menuItemCustomer"
    $menuItemCustomer.AutoSize = $true
    $menuItemCustomer.Text = $locString.menuCustomer
    $menuItemCustomer.Checked = $true
    function OnClick_menuItemCustomer($Sender,$e)
    {
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - current scenario: $($scenario)"
        $menuScenario.DropDownItems | Foreach-Object `
        {
            if($PSItem.Name -eq $menuItemCustomer.Name)
            {
                $PSItem.Checked = $true
            }
            else
            {
                if($PSItem -is [System.Windows.Forms.ToolStripMenuItem])
                {
                    $PSItem.Checked = $false
                }
            }
        }
        if($scenario -ne "Customer")
        {
            Clear-ExistingData
            foreach($tab in $script:savedTabs)
            {
                $tabControl.TabPages.Add($tab)
            }
            $script:savedTabs = @()
            foreach($tab in $script:savedTabs)
            {
                $tabControl.TabPages.Remove($tab)
            }
            Remove-GeneratedTabs
            Move-PartnerTab
        }
        $tabControl.TabPages['tabCustomer'].Enabled = $true
        $script:scenario = "Customer"
        $txtScaleUnitCount.Enabled = $false
        $txtScaleUnitCount.ReadOnly = $true
        $txtScaleUnitCount.Text = $defaultSUCount
        Invoke-Command -ScriptBlock $txtScaleUnitCount_Changed
    }
    $menuItemCustomer.Add_Click( { OnClick_menuItemCustomer $menuItemCustomer $EventArgs } )

    $menuItemPreviewMultiSU.Name = "menuItemPreviewMultiSU"
    $menuItemPreviewMultiSU.AutoSize = $true
    $menuItemPreviewMultiSU.Text = $locString.menuMultiSU
    function OnClick_menuItemPreviewMultiSU($Sender,$e)
    {
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - current scenario: $($scenario)"
        Remove-GeneratedTabs
        $menuScenario.DropDownItems | Foreach-Object `
        {
            if($PSItem.Name -eq $menuItemPreviewMultiSU.Name)
            {
                $PSItem.Checked = $true
            }
            else
            {
                if($PSItem -is [System.Windows.Forms.ToolStripMenuItem])
                {
                    $PSItem.Checked = $false
                }
            }
        }
        if($scenario -ne "Customer")
        {
            Clear-ExistingData
            foreach($tab in $script:savedTabs)
            {
                $tabControl.TabPages.Add($tab)
            }
            $script:savedTabs = @()
            foreach($tab in $script:savedTabs)
            {
                $tabControl.TabPages.Remove($tab)
            }
        }
        $tabControl.TabPages['tabCustomer'].Enabled = $true
        $script:scenario = "Customer"
        $txtScaleUnitCount.Enabled = $true
        $txtScaleUnitCount.ReadOnly = $false
        Close-WaitForm
        [System.Windows.Forms.MessageBox]::Show($locString.msgMultiSUPreview, $locString.capAttention, [System.Windows.Forms.MessageBoxButtons]::OK)
        Move-PartnerTab
    }
    $menuItemPreviewMultiSU.Add_Click( { OnClick_menuItemPreviewMultiSU $menuItemPreviewMultiSU $EventArgs } )

    $menuItemPreviewAddSU.Name = "menuItemPreviewAddSU"
    $menuItemPreviewAddSU.AutoSize = $true
    $menuItemPreviewAddSU.Text = $locString.menuAddSU
    function Set-AddRack
    {
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - called"
        $menuScenario.DropDownItems | Foreach-Object `
        {
            if($PSItem.Name -eq $menuItemPreviewAddSU.Name)
            {
                $PSItem.Checked = $true
            }
            else
            {
                if($PSItem -is [System.Windows.Forms.ToolStripMenuItem])
                {
                    $PSItem.Checked = $false
                }
            }
        }
        Remove-GeneratedTabs
        if($scenario -ne "Customer")
        {
            Clear-ExistingData
            foreach($tab in $script:savedTabs)
            {
                $tabControl.TabPages.Add($tab)
            }
            $script:savedTabs = @()
            foreach($tab in $script:savedTabs)
            {
                $tabControl.TabPages.Remove($tab)
            }
        }
        $script:scenario = "AddRack"
        $tabControl.TabPages['tabCustomer'].Enabled = $false
        $txtScaleUnitCount.Enabled = $true
        $txtScaleUnitCount.ReadOnly = $false
        Clear-ExistingData
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - completed"
    }
    function OnClick_menuItemPreviewAddSU($Sender,$e)
    {
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - current scenario: $($scenario)"
        if($scenario -ne "AddRack")
        {
            Set-AddRack
            Close-WaitForm
            [System.Windows.Forms.MessageBox]::Show($locString.msgAddSUPreview, $locString.capAttention, [System.Windows.Forms.MessageBoxButtons]::OK)
            [string]$importJsonFile = Get-FileName -Filter "AzureStackStampInformation (*.json)| *.json| All files (*.*) | *.*"
            if($importJsonFile)
            {
                Write-Log -Message "$($MyInvocation.MyCommand.Name) - file selected: '$($importJsonFile)'"
                $script:importData = Get-Content $importJsonFile -Raw | ConvertFrom-Json
                Show-WaitForm "`n AddRack : $($locString.msgImportExisting)`n`n$($locString.msgWait)`n`n"
                $txtScaleUnitCount.Text = '2'
                Invoke-Command -ScriptBlock $txtScaleUnitCount_Changed
                if($importData.Prefix)
                {
                    Write-Log -Message "$($MyInvocation.MyCommand.Name) - importing 'Prefix' setting"
                    $txtPhysicalPrefix.Text = $importData.Prefix
                }
                if($importData.CompanyName)
                {
                    Write-Log -Message "$($MyInvocation.MyCommand.Name) - importing 'CompanyName' setting"
                    $txtCompanyName.Text = $importData.CompanyName
                }
                if($importData.RegionName)
                {
                    Write-Log -Message "$($MyInvocation.MyCommand.Name) - importing 'RegionName' setting"
                    $txtRegionName.Text = $importData.RegionName
                }
                if($importData.ExternalDNSIPAddress01 -or $importData.ExternalDNSIPAddress02)
                {
                    Write-Log -Message "$($MyInvocation.MyCommand.Name) - importing 'DNSForwarder' setting"
                    [string]$dnsList = $importData.ExternalDNSIPAddress01
                    if($importData.ExternalDNSIPAddress02)
                    {
                        $dnsList += ',' + $importData.ExternalDNSIPAddress02
                    }
                    $txtDNSForwarder.Text = $dnsList
                }
                if($importData.NumberOfNodes)
                {
                    Write-Log -Message "$($MyInvocation.MyCommand.Name) - importing 'SU01-NodeCount' setting"
                    $scaleUnitTabs.tabSU1.Objects.txtNodeCount.Text = $importData.NumberOfNodes
                    Invoke-RefreshTotalNodeCount
                }
            }
            else
            {
                $message = $locString.msgNoFileSelected
                $caption = $locString.capWarning
                Close-WaitForm
                [System.Windows.Forms.MessageBox]::Show($message, $caption, [System.Windows.Forms.MessageBoxButtons]::OK)
            }
            Move-PartnerTab
        }
        Close-WaitForm
    }
    $menuItemPreviewAddSU.Add_Click( { OnClick_menuItemPreviewAddSU $menuItemPreviewAddSU $EventArgs } )

    $frmMain.Controls.Add($frmMenu)
    $frmMain.MainMenuStrip = $frmMenu

    # ============================= SHOW THE FORM =============================
    $frmMain.Topmost = $false
    $frmMain.Add_Shown({$frmMain.Activate()})
    if(Test-Path $strPartnerJsonFile)
    {
        Import-PartnerExtension -FileName $strPartnerJsonFile
    }
    $frmMain.ResumeLayout()
    [void]$frmMain.ShowDialog()
    [void]$frmMain.Dispose()
    [System.GC]::Collect()
}

function New-SubnetSummary
{
    param
    (
        [bool]$ReadOnly = $true
    )
    $script:tabSubnetSummary = New-Object System.Windows.Forms.TabPage
    $typTab = $tabSubnetSummary.GetType()
    $prpTab = $typTab.GetProperty('DoubleBuffered',('Instance','NonPublic'))
    $prpTab.SetValue($tabSubnetSummary, $true, $null)
    $tabSubnetSummary.Name = 'tabSubnetSummary'
    $tabSubnetSummary.Text = $locString.tabSubnetSummary
    $tabSubnetSummary.AutoScroll = $true
    $tabSubnetSummary.BackColor = [System.Drawing.Color]::MistyRose

    $lblSubnetSummary = New-Object System.Windows.Forms.Label
    $lblSubnetSummary.Location = New-Object System.Drawing.Size(10,10)
    $lblSubnetSummary.AutoSize = $true
    $lblSubnetSummary.Text = $locString.tabSubnetSummary
    $lblSubnetSummary.Font = [System.Drawing.Font]::New("Microsoft Sans Serif", 10)
    $tabSubnetSummary.Controls.Add($lblSubnetSummary)

    $script:dgvSubnetSummary = New-Object System.Windows.Forms.DataGridView
    $typDgv = $dgvSubnetSummary.GetType()
    $prpDgv = $typDgv.GetProperty('DoubleBuffered',('Instance','NonPublic'))
    $prpDgv.SetValue($dgvSubnetSummary, $true, $null)
    $dgvSubnetSummary.RowHeadersVisible = $false
    $dgvSubnetSummary.AllowUserToDeleteRows = $false
    $dgvSubnetSummary.AllowUserToAddRows = $false
    $dgvSubnetSummary.AllowUserToOrderColumns = $true
    $dgvSubnetSummary.AllowUserToResizeColumns = $true
    $dgvSubnetSummary.AllowUserToResizeRows = $false
    $dgvSubnetSummary.SelectionMode = [System.Windows.Forms.DataGridViewSelectionMode]::CellSelect
    $dgvSubnetSummary.MultiSelect = $true
    $dgvSubnetSummary.ColumnHeadersHeightSizeMode = [System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode]::DisableResizing
    $dgvSubnetSummary.RowHeadersWidthSizeMode = [System.Windows.Forms.DataGridViewRowHeadersWidthSizeMode]::DisableResizing
    $dgvSubnetSummary.AutoSizeColumnsMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::AllCells
    $dgvSubnetSummary.DefaultCellStyle.Font = $defaultLabelFont
    $dgvSubnetSummary.Location = New-Object System.Drawing.Size(10,40)
    $dgvSubnetSummary.ReadOnly = $ReadOnly
    $tabSubnetSummary.Controls.Add($dgvSubnetSummary)

    $col01 = New-Object -TypeName System.Windows.Forms.DataGridViewTextBoxColumn
    $col01.Name = "Association"
    $col01.HeaderText = "Association"
    $col01.ReadOnly = $ReadOnly
    $col01.MinimumWidth = 120
    $col01.DefaultCellStyle.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleLeft
    $col01.HeaderCell.Style.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleLeft
    #$col01.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::NotSortable

    $col02 = New-Object -TypeName System.Windows.Forms.DataGridViewTextBoxColumn
    $col02.Name = "TORGroup"
    $col02.HeaderText = "TOR Group"
    $col02.ReadOnly = $ReadOnly
    $col02.MinimumWidth = 60
    $col02.DefaultCellStyle.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleCenter
    $col02.HeaderCell.Style.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleCenter
    #$col02.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::NotSortable

    $col03 = New-Object -TypeName System.Windows.Forms.DataGridViewTextBoxColumn
    $col03.Name = "SubnetType"
    $col03.HeaderText = "Subnet Type"
    $col03.ReadOnly = $ReadOnly
    $col03.MinimumWidth = 80
    $col03.DefaultCellStyle.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleLeft
    $col03.HeaderCell.Style.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleLeft
    #$col03.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::NotSortable

    $col04 = New-Object -TypeName System.Windows.Forms.DataGridViewTextBoxColumn
    $col04.Name = "VLANID"
    $col04.HeaderText = "VLAN ID"
    $col04.ReadOnly = $ReadOnly
    $col04.MinimumWidth = 50
    $col04.DefaultCellStyle.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleCenter
    $col04.HeaderCell.Style.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleCenter
    #$col04.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::NotSortable

    $col05 = New-Object -TypeName System.Windows.Forms.DataGridViewTextBoxColumn
    $col05.Name = "Supernet"
    $col05.HeaderText = "Supernet"
    $col05.ReadOnly = $ReadOnly
    $col05.MinimumWidth = 180
    $col05.DefaultCellStyle.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleLeft
    $col05.HeaderCell.Style.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleLeft
    #$col05.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::NotSortable

    $col06 = New-Object -TypeName System.Windows.Forms.DataGridViewTextBoxColumn
    $col06.Name = "Name"
    $col06.HeaderText = "Name"
    $col06.ReadOnly = $ReadOnly
    $col06.MinimumWidth = 240
    $col06.DefaultCellStyle.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleLeft
    $col06.HeaderCell.Style.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleLeft
    #$col06.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::NotSortable

    $col07 = New-Object -TypeName System.Windows.Forms.DataGridViewTextBoxColumn
    $col07.Name = "IPv4Subnet"
    $col07.HeaderText = "IPv4 Subnet"
    $col07.ReadOnly = $ReadOnly
    $col07.MinimumWidth = 120
    $col07.DefaultCellStyle.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleRight
    $col07.HeaderCell.Style.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleRight
    #$col07.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::NotSortable

    $col08 = New-Object -TypeName System.Windows.Forms.DataGridViewTextBoxColumn
    $col08.Name = "Addresses"
    $col08.HeaderText = "Addresses"
    $col08.ReadOnly = $ReadOnly
    $col08.MinimumWidth = 60
    $col08.DefaultCellStyle.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleCenter
    $col08.HeaderCell.Style.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleCenter
    #$col08.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::NotSortable

    $col09 = New-Object -TypeName System.Windows.Forms.DataGridViewTextBoxColumn
    $col09.Name = "MaskBits"
    $col09.HeaderText = "Mask Bits"
    $col09.ReadOnly = $ReadOnly
    $col09.MinimumWidth = 60
    $col09.DefaultCellStyle.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleCenter
    $col09.HeaderCell.Style.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleCenter
    #$col09.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::NotSortable

    $col10 = New-Object -TypeName System.Windows.Forms.DataGridViewTextBoxColumn
    $col10.Name = "Mask"
    $col10.HeaderText = "Mask"
    $col10.ReadOnly = $ReadOnly
    $col10.MinimumWidth = $defaultIPCellWidth
    $col10.DefaultCellStyle.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleRight
    $col10.HeaderCell.Style.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleRight
    #$col10.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::NotSortable

    $col11 = New-Object -TypeName System.Windows.Forms.DataGridViewTextBoxColumn
    $col11.Name = "Address"
    $col11.HeaderText = "Address"
    $col11.ReadOnly = $ReadOnly
    $col11.MinimumWidth = $defaultIPCellWidth
    $col11.DefaultCellStyle.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleRight
    $col11.HeaderCell.Style.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleRight
    #$col11.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::NotSortable

    $col12 = New-Object -TypeName System.Windows.Forms.DataGridViewTextBoxColumn
    $col12.Name = "Gateway"
    $col12.HeaderText = "Gateway"
    $col12.ReadOnly = $ReadOnly
    $col12.MinimumWidth = $defaultIPCellWidth
    $col12.DefaultCellStyle.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleRight
    $col12.HeaderCell.Style.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleRight
    #$col12.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::NotSortable

    $col13 = New-Object -TypeName System.Windows.Forms.DataGridViewTextBoxColumn
    $col13.Name = "First"
    $col13.HeaderText = "First"
    $col13.ReadOnly = $ReadOnly
    $col13.MinimumWidth = $defaultIPCellWidth
    $col13.DefaultCellStyle.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleRight
    $col13.HeaderCell.Style.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleRight
    #$col13.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::NotSortable

    $col14 = New-Object -TypeName System.Windows.Forms.DataGridViewTextBoxColumn
    $col14.Name = "Last"
    $col14.HeaderText = "Last"
    $col14.ReadOnly = $ReadOnly
    $col14.MinimumWidth = $defaultIPCellWidth
    $col14.DefaultCellStyle.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleRight
    $col14.HeaderCell.Style.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleRight
    #$col14.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::NotSortable

    $col15 = New-Object -TypeName System.Windows.Forms.DataGridViewTextBoxColumn
    $col15.Name = "Broadcast"
    $col15.HeaderText = "Broadcast"
    $col15.ReadOnly = $ReadOnly
    $col15.MinimumWidth = $defaultIPCellWidth
    $col15.DefaultCellStyle.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleRight
    $col15.HeaderCell.Style.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleRight
    #$col15.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::NotSortable

    [void]$dgvSubnetSummary.Columns.Add($col01)
    [void]$dgvSubnetSummary.Columns.Add($col02)
    [void]$dgvSubnetSummary.Columns.Add($col03)
    [void]$dgvSubnetSummary.Columns.Add($col04)
    [void]$dgvSubnetSummary.Columns.Add($col05)
    [void]$dgvSubnetSummary.Columns.Add($col06)
    [void]$dgvSubnetSummary.Columns.Add($col07)
    [void]$dgvSubnetSummary.Columns.Add($col08)
    [void]$dgvSubnetSummary.Columns.Add($col09)
    [void]$dgvSubnetSummary.Columns.Add($col10)
    [void]$dgvSubnetSummary.Columns.Add($col11)
    [void]$dgvSubnetSummary.Columns.Add($col12)
    [void]$dgvSubnetSummary.Columns.Add($col13)
    [void]$dgvSubnetSummary.Columns.Add($col14)
    [void]$dgvSubnetSummary.Columns.Add($col15)

    $tabControl.Controls.Add($tabSubnetSummary)
}

function New-IPAssignmentsTab
{
    param
    (
        [Parameter(Mandatory=$false)]
        [bool]$ReadOnly = $true
    )
    $script:tabIPAssignments = New-Object System.Windows.Forms.TabPage
    $typTab = $tabIPAssignments.GetType()
    $prpTab = $typTab.GetProperty('DoubleBuffered',('Instance','NonPublic'))
    $prpTab.SetValue($tabIPAssignments, $true, $null)
    $tabIPAssignments.Name = 'tabIPAssignments'
    $tabIPAssignments.Text = $locString.tabIPAssignments
    $tabIPAssignments.BackColor = [System.Drawing.Color]::MistyRose
    $tabIPAssignments.AutoScroll = $true
    $tabControl.Controls.Add($tabIPAssignments)
}

function New-IPAssignmentTable
{
    param
    (
        [Parameter(Mandatory=$true)]
        [string]$Name,
        [Parameter(Mandatory=$false)]
        [bool]$ReadOnly = $true
    )
    $script:dgvIPAssignments = New-Object System.Windows.Forms.DataGridView
    $typDgv = $dgvIPAssignments.GetType()
    $prpDgv = $typDgv.GetProperty('DoubleBuffered',('Instance','NonPublic'))
    $prpDgv.SetValue($dgvIPAssignments, $true, $null)
    $dgvIPAssignments.Name = $Name
    $dgvIPAssignments.RowHeadersVisible = $false
    $dgvIPAssignments.AllowUserToDeleteRows = $false
    $dgvIPAssignments.AllowUserToAddRows = $false
    $dgvIPAssignments.AllowUserToOrderColumns = $false
    $dgvIPAssignments.AllowUserToResizeColumns = $true
    $dgvIPAssignments.AllowUserToResizeRows = $false
    $dgvIPAssignments.SelectionMode = [System.Windows.Forms.DataGridViewSelectionMode]::CellSelect
    $dgvIPAssignments.MultiSelect = $true
    $dgvIPAssignments.ColumnHeadersHeightSizeMode = [System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode]::DisableResizing
    $dgvIPAssignments.RowHeadersWidthSizeMode = [System.Windows.Forms.DataGridViewRowHeadersWidthSizeMode]::DisableResizing
    $dgvIPAssignments.AutoSizeColumnsMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::AllCells
    $dgvIPAssignments.DefaultCellStyle.Font = $defaultLabelFont
    $dgvIPAssignments.ReadOnly = $ReadOnly
    $dgvIPAssignments.Location = New-Object System.Drawing.Size(10,10)

    $col01 = New-Object -TypeName System.Windows.Forms.DataGridViewTextBoxColumn
    $col01.Name = "AssignedTo"
    $col01.HeaderText = "AssignedTo"
    $col01.ReadOnly = $ReadOnly
    $col01.MinimumWidth = 240
    $col01.DefaultCellStyle.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleLeft
    $col01.HeaderCell.Style.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleLeft
    #$col01.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::NotSortable

    $col02 = New-Object -TypeName System.Windows.Forms.DataGridViewTextBoxColumn
    $col02.Name = "IPv4Address"
    $col02.HeaderText = "IPv4 Address"
    $col02.ReadOnly = $ReadOnly
    $col02.MinimumWidth = 110
    $col02.DefaultCellStyle.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleRight
    $col02.HeaderCell.Style.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleRight
    #$col02.SortMode = [System.Windows.Forms.DataGridViewColumnSortMode]::NotSortable

    [void]$dgvIPAssignments.Columns.Add($col01)
    [void]$dgvIPAssignments.Columns.Add($col02)

    return $dgvIPAssignments
}

function Write-SubnetSummary
{
    Write-Log -Message "$($MyInvocation.MyCommand.Name) - called"
    try
    {
        $name = $locString.tabSubnetSummary
        if(-not $activeStamp)
        {
            $msg = '$activeStamp variable could not be found'
            Write-Log -Message "$($MyInvocation.MyCommand.Name) - ERROR: $msg" -Force
            throw $msg
        }
        if($tabControl.TabPages["tabSubnetSummary"])
        {
            $tabControl.TabPages.Remove($tabControl.TabPages["tabSubnetSummary"])
        }
        if($tabSubnetSummary)
        {
            $tabSubnetSummary.Dispose()
        }
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - create a new tab named '$($name)'"
        New-SubnetSummary

        Write-Log -Message "$($MyInvocation.MyCommand.Name) - write values to '$($name)'"
        foreach($network in $activeStamp.Supernets.Networks)
        {
            [int]$currentRow = $dgvSubnetSummary.Rows.Add()
            [int]$currentCell = 0
            [string]$gateway = $network.IPv4.Network.ToString()
            [string]$first = $network.IPv4.Network.ToString()
            [string]$last = $network.IPv4.Network.ToString()
            if($network.IPv4.Cidr -eq [byte]30)
            {
                $gateway = [string]::Empty
                $first = $network.IPv4.FirstUsable.ToString()
                $last = $network.IPv4.LastUsable.ToString()
            }
            else
            {
                $gateway = $network.IPv4.FirstUsable.ToString()
                [string]$first = [System.Net.IPNetwork]::ListIPAddress($network.IPv4)[2]
                [string]$last = [System.Net.IPNetwork]::ListIPAddress($network.IPv4)[$network.IPv4.Usable]
            }
            [string]$rkName = $network.Parent.Name.Split('-') | Select-String -Pattern "Rack(\d{2}$)"
            [string]$clName = $network.Parent.Name.Split('-') | Select-String -Pattern "CL(\d{2}$)"
            [string]$suName = $network.Parent.Name.Split('-') | Select-String -Pattern "SU(\d{2}$)"
            [string]$assoc = ''
            if($rkName)
            {
                $assoc += $rkName
            }
            if($clName)
            {
                if($assoc -eq '')
                {
                    $assoc += $clName
                }
                else
                {
                    $assoc += "-$($clName)"
                }
            }
            if($suName)
            {
                if($assoc -eq '')
                {
                    $assoc += $suName
                }
                else
                {
                    $assoc += "-$($suName)"
                }
            }
            if($assoc -eq '')
            {
                $assoc = "N/A"
            }
            $dgvSubnetSummary.Rows[$currentRow].Cells[$currentCell].Value = $assoc
            $currentCell++
            $dgvSubnetSummary.Rows[$currentRow].Cells[$currentCell].Value = $network.TorGroup
            $currentCell++
            $dgvSubnetSummary.Rows[$currentRow].Cells[$currentCell].Value = $network.SubnetType
            $currentCell++
            $dgvSubnetSummary.Rows[$currentRow].Cells[$currentCell].Value = $network.VlanId
            $currentCell++
            $dgvSubnetSummary.Rows[$currentRow].Cells[$currentCell].Value = $network.Parent.Name
            $currentCell++
            $dgvSubnetSummary.Rows[$currentRow].Cells[$currentCell].Value = $network.Name
            $currentCell++
            $dgvSubnetSummary.Rows[$currentRow].Cells[$currentCell].Value = $network.IPv4.ToString()
            $currentCell++
            $dgvSubnetSummary.Rows[$currentRow].Cells[$currentCell].Value = $network.IPv4.Total
            $currentCell++
            $dgvSubnetSummary.Rows[$currentRow].Cells[$currentCell].Value = $network.IPv4.Cidr
            $currentCell++
            $dgvSubnetSummary.Rows[$currentRow].Cells[$currentCell].Value = $network.IPv4.Netmask
            $currentCell++
            $dgvSubnetSummary.Rows[$currentRow].Cells[$currentCell].Value = $network.IPv4.Network
            $currentCell++
            $dgvSubnetSummary.Rows[$currentRow].Cells[$currentCell].Value = $gateway
            $currentCell++
            $dgvSubnetSummary.Rows[$currentRow].Cells[$currentCell].Value = $first
            $currentCell++
            $dgvSubnetSummary.Rows[$currentRow].Cells[$currentCell].Value = $last
            $currentCell++
            $dgvSubnetSummary.Rows[$currentRow].Cells[$currentCell].Value = $network.IPv4.Broadcast
        }
        $dgvSubnetSummary.AutoResizeColumns()
        $dgvSubnetSummary.AutoResizeRows()
        $dgvSubnetSummary.Location = New-Object System.Drawing.Size(10,40)
        $dgvSubnetSummary.Size = (Get-DataGridViewSize $dgvSubnetSummary)
    }
    catch
    {
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - ERROR: $($PSItem.Exception.Message)" -Force
        throw $PSItem
    }
    finally
    {
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - completed"
    }
}

function Write-IPAssignments
{
    Write-Log -Message "$($MyInvocation.MyCommand.Name) - called"
    try
    {
        $name = $locString.tabIPAssignments
        if(-not $activeStamp)
        {
            $msg = '$activeStamp variable could not be found'
            Write-Log -Message "$($MyInvocation.MyCommand.Name) - ERROR: $msg" -Force
            throw $msg
        }
        if($tabControl.TabPages["tabIPAssignments"])
        {
            $tabControl.TabPages.Remove($tabControl.TabPages["tabIPAssignments"])
        }
        if($tabIPAssignments)
        {
            $tabIPAssignments.Dispose()
        }
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - create a new tab named '$($name)'"
        New-IPAssignmentsTab

        Write-Log -Message "$($MyInvocation.MyCommand.Name) - write values to '$($name)'"
        $thisRow = 10
        $script:ipAssignmentObjects = @{}
        foreach($network in $activeStamp.Supernets.Networks)
        {
            Write-Log -Message "$($MyInvocation.MyCommand.Name) - process network: '$($network.Name)'"
            if(($network.Assignments.Count -gt 0) -OR ($network.Name -match "Loopback"))
            {
                $netName = $network.Name
                if($network.Name -match "Loopback")
                {
                    $netName = $network.Name.Replace("Loopback", "BGPPeer")
                }
                $thisLBL = New-Object System.Windows.Forms.Label
                $thisDGV = New-IPAssignmentTable -Name $netName
                $ipAssignmentObjects.Add($netName,@{Table=$thisDGV;Label=$thisLBL})
                $ipAssignmentObjects.$netName.Label.Location = New-Object System.Drawing.Size($lblLeft,$thisRow)
                $ipAssignmentObjects.$netName.Label.AutoSize = $true
                $ipAssignmentObjects.$netName.Label.Text = "$($locString.name) $($netName)"
                $ipAssignmentObjects.$netName.Label.Font = [System.Drawing.Font]::New("Microsoft Sans Serif", 10)
                $tabIPAssignments.Controls.Add($ipAssignmentObjects.$netName.Label)
                $thisRow += 20
                $tabIPAssignments.Controls.Add($ipAssignmentObjects.$netName.Table)
                foreach($ip in [System.Net.IPNetwork]::ListIPAddress($network.IPv4))
                {
                    # Write-Log -Message "$($MyInvocation.MyCommand.Name) - processing IP Address '$($ip.ToString())'"
                    $currentCell = 0
                    $currentRow = $ipAssignmentObjects.$netName.Table.Rows.Add()
                    $activeAssignment = $network.Assignments | Where-Object { $PSItem.Address.ToString() -eq $ip.ToString() }
                    if($activeAssignment.Count -eq 1)
                    {
                        $ipAssignmentObjects.$netName.Table.Rows[$currentRow].Cells[$currentCell].Value = $activeAssignment.Name
                        $ipAssignmentObjects.$netName.Table.Rows[$currentRow].Cells[$currentCell+1].Value = $activeAssignment.Address.ToString()
                    }
                    else
                    {
                        # Write-Log -Message "$($MyInvocation.MyCommand.Name) - found '$($activeAssignment.Count)' assignments for '$($ip.ToString())'"
                        if($ip.ToString() -eq $network.IPv4.Broadcast.ToString())
                        {
                            $ipAssignmentObjects.$netName.Table.Rows[$currentRow].Cells[$currentCell].Value = "Broadcast"
                            if($network.Name -like "*Loopback*")
                            {
                                $ipAssignmentObjects.$netName.Table.Rows[$currentRow].Cells[$currentCell].Value = "BGP Peer"
                            }
                            if($network.Name -like "*BMCMgmt*")
                            {
                                $currentCell++
                                $ipAssignmentObjects.$netName.Table.Rows[$currentRow].Cells[$currentCell].Value = [string]::Empty
                            }
                            $currentCell++
                            $ipAssignmentObjects.$netName.Table.Rows[$currentRow].Cells[$currentCell].Value = $ip.ToString()
                        }
                        elseif($ip.ToString() -eq $network.IPv4.Network.ToString())
                        {
                            $ipAssignmentObjects.$netName.Table.Rows[$currentRow].Cells[$currentCell].Value = "Network"
                            if($network.Name -like "*BMCMgmt*")
                            {
                                $currentCell++
                                $ipAssignmentObjects.$netName.Table.Rows[$currentRow].Cells[$currentCell].Value = [string]::Empty
                            }
                            $currentCell++
                            $ipAssignmentObjects.$netName.Table.Rows[$currentRow].Cells[$currentCell].Value = $ip.ToString()
                        }
                        else
                        {
                            $ipAssignmentObjects.$netName.Table.Rows.RemoveAt($ipAssignmentObjects.$netName.Table.Rows.Count-1)
                        }
                    }
                }
                $ipAssignmentObjects.$netName.Table.AutoResizeColumns()
                $ipAssignmentObjects.$netName.Table.AutoResizeRows()
                $ipAssignmentObjects.$netName.Table.Location = New-Object System.Drawing.Size($lblLeft,$thisRow)
                $ipAssignmentObjects.$netName.Table.Size = (Get-DataGridViewSize $ipAssignmentObjects.$netName.Table)
                [int]$h = (Get-DataGridViewSize $ipAssignmentObjects.$netName.Table).Height
                $thisRow += ($h+20)
            }
        }

    }
    catch
    {
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - ERROR: $($PSItem.Exception.Message)" -Force
        throw $PSItem
    }
    finally
    {
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - completed"
    }
}

function Invoke-GenerateDeploymentData
{
    Write-Log -Message "$($MyInvocation.MyCommand.Name) - called"
    try
    {
        Show-WaitForm ("`n $($MyInvocation.MyCommand.Name) : {0}.`n`n {1}" -f $locString.msgGeneratingData,$locString.msgWait)
        $global:activeStamp = $null
        $global:activeStamp = New-Deployment
        [int]$cloudCount = 1
        [string]$switchMake = $lstSwitchVendor.SelectedItem
        if($scenario -eq "AddRack")
        {
            $activeStamp.IsAddRack = $true
        }
        # Set per-Deployment properties
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - set per-Deployment properties"
        foreach($property in $arrDeployProperties)
        {
            switch ($property)
            {
                "BorderConnectivity"
                {
                    $activeStamp."$property" = $lstRoutingMethod.SelectedItem
                    break
                }
                default
                {
                    Write-Log "$($MyInvocation.MyCommand.Name) - WARNING: Unhandled property named '$($property)'" -Force
                }
            }
        }
        # Set per-Cloud properties
        for($cl = 1; $cl -le $cloudCount; $cl++)
        {
            [string]$clID = "{0:d2}" -f $cl
            [string]$clName = "CL$($clID)"
            Write-Log -Message "$($MyInvocation.MyCommand.Name) - set per-Cloud properties for $($clName)"
            $cloud = $activeStamp.AddCloud()
            foreach($property in $arrCloudProperties)
            {
                Write-Log -Message "$($MyInvocation.MyCommand.Name) - process: '$($property)'"
                $val = $null
                switch ($property)
                {
                    "NodeCount"
                    {
                        $val = $txtTotalNodeCount.Text
                        break
                    }
                    "TimeServer"
                    {
                        $val = $txtTimeServer.Text
                        break
                    }
                    "SyslogServerIPv4Address"
                    {
                        $val = $txtSyslogServer.Text
                        if(-not $val)
                        {
                            $property = $null
                        }
                        break
                    }
                    "DNSForwarder"
                    {
                        # Populate DNSForwarder differently as it could be a comma-separated array
                        [string[]]$dnsFwd = ($txtDNSForwarder.Text).Split(',')
                        if($dnsFwd.Count -gt 1)
                        {
                            $val = $dnsFwd
                        }
                        else
                        {
                            $val = $dnsFwd[0]
                        }
                        if(-not $val)
                        {
                            $property = $null
                        }
                        break
                    }
                    "ConnectToAzure"
                    {
                        $val = $lstIdentityStore.SelectedItem
                        break
                    }
                    "InfraAzureEnvironment"
                    {
                        if($lstAzureEnvironment.Enabled)
                        {
                            $val = $lstAzureEnvironment.SelectedItem
                        }
                        else
                        {
                            $property = $null
                        }
                        break
                    }
                    "InfraAzureDirectoryTenantName"
                    {
                        if($txtAADTenantName.Enabled)
                        {
                            $val = $txtAADTenantName.Text
                        }
                        else
                        {
                            $property = $null
                        }
                        break
                    }
                    "ADFSForestFQDN"
                    {
                        if($txtADFSForestFQDN.Enabled)
                        {
                            $val = $txtADFSForestFQDN.Text
                        }
                        else
                        {
                            $property = $null
                        }
                        break
                    }
                    "ADFSProviderName"
                    {
                        if($txtADFSProviderName.Enabled)
                        {
                            $val = $txtADFSProviderName.Text
                        }
                        else
                        {
                            $property = $null
                        }
                        break
                    }
                    "ADFSMetadataUri"
                    {
                        if($txtADFSMetadataUri.Enabled)
                        {
                            $val = $txtADFSMetadataUri.Text
                        }
                        else
                        {
                            $property = $null
                        }
                        break
                    }
                    "NamingPrefix"
                    {
                        $val = $txtDeployPrefix.Text
                        break
                    }
                    "PhysicalNamingPrefix"
                    {
                        $val = $txtPhysicalPrefix.Text
                        break
                    }
                    "ExternalDomainFQDN"
                    {
                        $val = $txtExternalDomain.Text
                        break
                    }
                    "DomainFQDN"
                    {
                        $val = $txtPrivateDomain.Text
                        break
                    }
                    "RegionName"
                    {
                        $val = $txtRegionName.Text
                        break
                    }
                    "CompanyName"
                    {
                        $val = $txtCompanyName.Text
                        break
                    }
                    default
                    {
                        Write-Log  -Message "$($MyInvocation.MyCommand.Name) - WARNING: Unhandled property named '$($property)'" -Force
                        $property = $null
                        break
                    }
                }
                if($property)
                {
                    $cloud.$property = $val
                }
            }
            # Add Racks and Scale-Unit(s) first
            Write-Log -Message "$($MyInvocation.MyCommand.Name) - set $($clName) per-Scale-Unit properties"
            [int]$suCount = $txtScaleUnitCount.Text
            for($su = 1; $su -le $suCount; $su++)
            {
                $suID = "SU{0:d2}" -f $su
                $needNewRack = $false
                if($clName -eq 'CL01')
                {
                    $needNewRack = $true
                }
                if($needNewRack)
                {
                    Write-Log -Message "$($MyInvocation.MyCommand.Name) - $($clName) add a new Rack to deployment"
                    $newRack = $activeStamp.AddRack()
                    if($newRack)
                    {
                        Write-Log -Message "$($MyInvocation.MyCommand.Name) - $($clName) successfully added Rack '$($newRack.Name)'"
                        $thisRack = $newRack
                    }
                    else
                    {
                        throw "Failed to add Rack for '$($clId)' Scale-Unit '$($suID)'"
                    }
                }
                else
                {
                    $thisRack = $activeStamp.Racks | Where-Object Name -eq "Rack01"
                }
                if(-not $thisRack)
                {
                    throw "Unable to find Rack for '$($clName)' Scale-Unit '$($suId)'"
                }
                Write-Log -Message "$($MyInvocation.MyCommand.Name) - $($clName) add Scale-Unit '$($suID)' assigned to Rack '$($thisRack.Name)'"
                $newScaleUnit = $cloud.AddScaleUnit($thisRack.Name)
                if($newScaleUnit)
                {
                    Write-Log -Message "$($MyInvocation.MyCommand.Name) - successfully added $($clName) Scale-Unit '$($newScaleUnit.Name)'"
                }
                else
                {
                    throw "Failed to add $($clName) Scale-Unit ''$($suID)'"
                }
            }
            # Populate per-Scale-Unit properties
            for($su = 1; $su -le $suCount; $su++)
            {
                $suID = "{0:d2}" -f $su
                $suName = "SU$($suID)"
                $suTab = "tabSU$su"
                Write-Log -Message "$($MyInvocation.MyCommand.Name) - populate values for Scale-Unit '$($suName)'"
                $thisScaleUnit = $cloud.ScaleUnits | Where-Object Name -eq $suName
                if(-not $thisScaleUnit)
                {
                    throw "Unable to find $($clName) Scale-Unit named '$($suName)'"
                }
                $rack = $activeStamp.Racks | Where-Object Name -eq $thisScaleUnit.RackId
                if(-not $rack)
                {
                    throw "Unable to find Rack object named '$($thisScaleUnit.RackId)'"
                }
                $thisScaleUnit.NodeCount = $scaleUnitTabs.$suTab.Objects.txtNodeCount.Text
                # Set switch values
                Write-Log -Message "$($MyInvocation.MyCommand.Name) - $($clName)-$($suName) set network switch properties"
                if($cloud.Name -eq "CL01")
                {
                    $skipThis = $false
                    if(($switchMake -eq "CiscoUCS") -and ($rack.Index -gt 1))
                    {
                        $skipThis = $true
                    }
                    if(-not $skipThis)
                    {
                        Write-Log -Message "$($MyInvocation.MyCommand.Name) - $($rack.Name)-BMC switch"
                        $bmc = $activeStamp.Switches | Where-Object Hostname -like "*-$($rack.Name)-BMC-1"
                        if(-not $bmc)
                        {
                            throw "Unable to find Switch named '*-$($rack.Name)-BMC-1'"
                        }
                        $bmc.Make = $switchMake
                        Write-Log -Message "$($MyInvocation.MyCommand.Name) - $($rack.Name)-TOR switches"
                        $tor1 = $activeStamp.Switches | Where-Object Hostname -like "*-$($rack.Name)-TOR-1"
                        if(-not $tor1)
                        {
                            throw "Unable to find Switch named '*-$($rack.Name)-TOR-1'"
                        }
                        $tor2 = $activeStamp.Switches | Where-Object Hostname -like "*-$($rack.Name)-TOR-2"
                        if(-not $tor2)
                        {
                            throw "Unable to find Switch named '*-$($rack.Name)-TOR-2'"
                        }
                        $tor1.Make = $switchMake
                        $tor2.Make = $switchMake
                        $bmc.Model = $lstBMCModel.SelectedItem
                        $bmc.FIRMWAREVERSION = $lstBMCFirmware.SelectedItem
                        $bmc.Asn = $scaleUnitTabs.$suTab.Objects.txtTORASN.Text
                        $tor1.Model = $lstTORModel.SelectedItem
                        $tor2.Model = $lstTORModel.SelectedItem
                        $tor1.FIRMWAREVERSION = $lstTORFirmware.SelectedItem
                        $tor2.FIRMWAREVERSION = $lstTORFirmware.SelectedItem
                        $tor1.Asn = $scaleUnitTabs.$suTab.Objects.txtTORASN.Text
                        $tor2.Asn = $scaleUnitTabs.$suTab.Objects.txtTORASN.Text
                    }
                }
                Write-Log -Message "$($MyInvocation.MyCommand.Name) - $($clName)-MUX switch"
                $mux = $activeStamp.Switches | Where-Object Hostname -match "-$($clName)-MUX-1"
                if(-not $mux)
                {
                    throw "Unable to find switch matching the name '-$($clName)-MUX-1'"
                }
                $mux.Make = 'Microsoft'
                $mux.Model = 'VirtualSwitch'
                $mux.Asn = $txtSoftwareBGP.Text
                # Spine switches are only present if the SU count is greater than 1 and we only set Spine properties for SU01
                if($switchMake -ne "CiscoUCS")
                {
                    if($suCount -gt 1 -and $su -eq 1)
                    {
                        Write-Log -Message "$($MyInvocation.MyCommand.Name) - $($rack.Name)-SpineInfrastructure switches"
                        $spineNet = $activeStamp.Supernets | Where-Object Name -eq ("$($rack.Name)-SpineInfrastructure")
                        if(-not $spineNet)
                        {
                            throw "Unable to find Supernet named '$($rack.Name)-SpineInfrastructure'"
                        }
                        [string]$ip = $txtSpineSubnet.Text
                        [string]$sz = $txtSpineSubnetSize.Text
                        if(-not $ip)
                        {
                            $ip = '0.0.0.0'
                            $sz = $defaultSpineSubnetSize
                        }
                        $spineNet.IPv4 = $ip + '/' + $sz
                    }
                }
                # Set Supernet values
                Write-Log -Message "$($MyInvocation.MyCommand.Name) - $($clName)-$($suName) set Supernet values"
                if($cloud.Name -eq "CL01")
                {
                    Write-Log -Message "$($MyInvocation.MyCommand.Name) - $($rack.Name)-BMC set Supernet values"
                    $bmcNet = $activeStamp.Supernets | Where-Object Name -eq ("$($rack.Name)-BMC")
                    if(-not $bmcNet)
                    {
                        throw "Unable to find Supernet named '$($rack.Name)-BMC'"
                    }
                    $ip = $scaleUnitTabs.$suTab.Objects.txtBMCSubnet.Text
                    $sz = $scaleUnitTabs.$suTab.Objects.txtBMCSubnetSize.Text
                    if(-not $ip)
                    {
                        $ip = '0.0.0.0'
                        $sz = $defaultBMCSubnetSize
                    }
                    $bmcNet.IPv4 = $ip + '/' + $sz

                    $skipThis = $false
                    if(($switchMake -eq "CiscoUCS") -and ($rack.Index -gt 1))
                    {
                        $skipThis = $true
                    }
                    if(-not $skipThis)
                    {
                        Write-Log -Message "$($MyInvocation.MyCommand.Name) - $($rack.Name)-SwitchInfrastructure set Supernet values"
                        $switchNet = $activeStamp.Supernets | Where-Object Name -eq ("$($rack.Name)-SwitchInfrastructure")
                        if(-not $switchNet)
                        {
                            throw "Unable to find Supernet named '$($rack.Name)-SwitchInfrastructure'"
                        }
                        $ip = $scaleUnitTabs.$suTab.Objects.txtSwitchInfraSubnet.Text
                        $sz = $scaleUnitTabs.$suTab.Objects.txtSwitchInfraSubnetSize.Text
                        if(-not $ip)
                        {
                            $ip = '0.0.0.0'
                            $sz = $defaultSwInfraSubnetSize
                        }
                        $switchNet.IPv4 = $ip + '/' + $sz
                    }
                }
                Write-Log -Message "$($MyInvocation.MyCommand.Name) - $($Rack.Name)-$($clName)-$($suName)-Infrastructure set Supernet values"
                $infraNet = $activeStamp.Supernets | Where-Object Name -eq ("$($Rack.Name)-$($clName)-$($suName)-Infrastructure")
                if(-not $infraNet)
                {
                    throw "Unable to find Supernet named '$($Rack.Name)-$($clName)-$($suName)-Infrastructure'"
                }
                $ip = $scaleUnitTabs.$suTab.Objects.txtInfraSubnet.Text
                $sz = $scaleUnitTabs.$suTab.Objects.txtInfraSubnetSize.Text
                if(-not $ip)
                {
                    $ip = '0.0.0.0'
                    $sz = $defaultInfraSubnetSize
                }
                $infraNet.IPv4 = $ip + '/' + $sz

                Write-Log -Message "$($MyInvocation.MyCommand.Name) - $($Rack.Name)-$($clName)-$($suName)-Storage set Supernet values"
                $storNet = $activeStamp.Supernets | Where-Object Name -eq ("$($Rack.Name)-$($clName)-$($suName)-Storage")
                if(-not $storNet)
                {
                    throw "Unable to find Supernet named '$($Rack.Name)-$($clName)-$($suName)-Storage'"
                }
                $ip = $scaleUnitTabs.$suTab.Objects.txtStorageSubnet.Text
                $sz = $scaleUnitTabs.$suTab.Objects.txtStorageSubnetSize.Text
                if(-not $ip)
                {
                    $ip = '0.0.0.0'
                    $sz = $defaultStorageSubnetSize
                }
                $storNet.IPv4 = $ip + '/' + $sz

                Write-Log -Message "$($MyInvocation.MyCommand.Name) - $($clName)-External set Supernet values"
                $ext = $activeStamp.Supernets | Where-Object Name -eq "$($clName)-External"
                if(-not $ext)
                {
                    throw "Unable to find Supernet named '$($clName)-External'"
                }
                $ip = $txtExternalSubnet.Text
                $sz = $txtExternalSubnetSize.Text
                if(-not $ip)
                {
                    $ip = '0.0.0.0'
                    $sz = $defaultExternalSubnetSize
                }
                $ext.IPv4 = $ip + '/' + $sz
            }
        }
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - set BORDER switch count"
        $borderCount = $txtEdgeCount.Text
        $activeStamp.SetBorderCount($borderCount)
        for($b = 1; $b -le $borderCount; $b++)
        {
            Write-Log -Message "$($MyInvocation.MyCommand.Name) - set BORDER switch $($b) values"
            $border = $activeStamp.Switches | Where-Object Hostname -like ("*-Border-Border-" + $b)
            if(-not $border)
            {
                throw "Unable to find switch matching the name '-Border-Border-$($b)'"
            }
            $border.Make = "border"
            if($lstRoutingMethod.SelectedItem -eq "BGP Routing")
            {
                switch ($b)
                {
                    '1'
                    {
                        $thisAsn = $txtEdgeBGP1.Text
                    }
                    '2'
                    {
                        $thisAsn = $txtEdgeBGP2.Text
                    }
                    '3'
                    {
                        $thisAsn = $txtEdgeBGP3.Text
                    }
                    '4'
                    {
                        $thisAsn = $txtEdgeBGP4.Text
                    }
                    default
                    {
                        $thisAsn = $null
                    }
                }
                $border.Asn = $thisAsn
            }
        }
        if(($suCount -gt 1) -and ($switchMake -ne "CiscoUCS"))
        {
            Write-Log -Message "$($MyInvocation.MyCommand.Name) - set SPINE switch count"
            $spineCount = $txtSpineCount.Text
            $activeStamp.SetSpineCount($spineCount)
            for($s = 1; $s -le $spineCount; $s++)
            {
                Write-Log -Message "$($MyInvocation.MyCommand.Name) - set SPINE switch $($s) properties"
                [string]$match = "*-Spine-Spine-" + $s
                $spine = @($activeStamp.Switches | Where-Object Hostname -like $match)
                if(-not $spine)
                {
                    throw "Unable to find switch matching the name '$($match)'"
                }
                if($spine.Count -gt 1)
                {
                    throw "Found more than one Spine switch matching the name '$($match)'"
                }
                $spine[0].Asn = $txtSpineBGP.Text
                $spine[0].Make = $switchMake
                $spine[0].Model = $lstSpineModel.SelectedItem
                $spine[0].FIRMWAREVERSION = $lstSpineFirmware.SelectedItem
            }
        }
        # Validate the input data
        $activeStamp.SetCurrentPath($PSScriptRoot)
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - call the Validate() method"
        $caption = $locString.capValidationError
        $ref = New-Object -TypeName "System.Text.StringBuilder"
        $script:errorText = [ref]$ref
        [bool]$isValid = $activeStamp.Validate($errorText)
        if($lstIdentityStore.SelectedIndex -eq -1)
        {
            $isValid = $false
            [string]$thisErr = $locString.msgValidationEmpty -f $locString.lblIdentityStore.Replace(':','')
            $ref.Insert(0,"$($thisErr)`n")
        }
        if($isValid)
        {
            Write-Log -Message "$($MyInvocation.MyCommand.Name) - deployment data has passed validation"

            # Generate IP Usage and assignments
            Write-Log -Message "$($MyInvocation.MyCommand.Name) - call the Generate() method"
            $activeStamp.Generate()

            # Write values to spreadsheet
            Write-Log -Message "$($MyInvocation.MyCommand.Name) - write subnet values to the '$($locString.tabSubnetSummary)' tab"
            Show-WaitForm ("`n $($MyInvocation.MyCommand.Name) : {0} '{1}'.`n`n {2}" -f $locString.msgWritingValuesTo,$locString.tabSubnetSummary,$locString.msgWait)
            Write-SubnetSummary

            Write-Log -Message "$($MyInvocation.MyCommand.Name) - write IP values to the '$($locString.tabIPAssignments)' tab"
            Show-WaitForm ("`n $($MyInvocation.MyCommand.Name) : {0} '{1}'.`n`n {2}" -f $locString.msgWritingValuesTo,$locString.tabIPAssignments,$locString.msgWait)
            Write-IPAssignments

            $tabControl.SelectedTab = $tabControl.TabPages["tabSubnetSummary"]
            $script:genRes = $true
        }
        else
        {
            Write-Log -Message "$($MyInvocation.MyCommand.Name) - WARNING: deployment data failed validation" -Force
            $errList = @($errorText.Value.ToString().Split("`n"))
            [string[]]$message = @(Get-ValidationError -ErrorList $errList)
            $message += "`n`n$($locString.msgFixValidationErrors)"
            Close-WaitForm
            [System.Windows.Forms.MessageBox]::Show($message, $caption, [System.Windows.Forms.MessageBoxButtons]::OK)
            $script:genRes = $false
        }
    }
    catch
    {
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - ERROR: $($PSItem.Exception.Message)" -Force
        $caption = $locString.capError
        [string]$message = $PSItem.Exception.Message
        $message += "`n`n$($locString.msgFixValidationErrors)"
        Close-WaitForm
        [System.Windows.Forms.MessageBox]::Show($message, $caption, [System.Windows.Forms.MessageBoxButtons]::OK)
        $script:genRes = $false
    }
    finally
    {
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - completed"
        Close-WaitForm
    }
}

function Add-PartnerToolkitVersion
{
    param
    (
        [string]$ConfigFile,
        [string]$Version
    )
    try
    {
        if(Test-Path $ConfigFile)
        {
            if(-not $Version)
            {
                $Version = Get-FileVersionInfo
            }
            $cfgData = Get-Content $ConfigFile -Raw | ConvertFrom-Json
            $cfgData | Add-Member -Name "PartnerToolkitVersion" -Value $Version -MemberType NoteProperty
            $cfgData | ConvertTo-Json -Depth 100 | Trim-Json | Out-File $ConfigFile -Encoding utf8 -Force
            return $true
        }
        else
        {
            throw "File not found"
        }
    }
    catch
    {
        return $false
    }
}

function Invoke-ExportDeploymentData
{
    Write-Log -Message "$($MyInvocation.MyCommand.Name) - called"
    try
    {
        [bool]$script:genRes = $false
        Invoke-GenerateDeploymentData
        if($genRes)
        {
            $subnets = $activeStamp.Supernets.Networks
            if($subnets.Count -eq 0)
            {
                $message = $locString.errMissingData
                $caption = $locString.capMissingData
                Close-WaitForm
                [System.Windows.Forms.MessageBox]::Show($message, $caption, [System.Windows.Forms.MessageBoxButtons]::OK)
            }
            else
            {
                [string]$folder = Get-FolderName -Description $locString.msgSelectFolder
                if($folder)
                {
                    Write-Log -Message "$($MyInvocation.MyCommand.Name) - files will be created in '$($folder)'"
                    # Read values from the tabs and convert to JSON
                    $combinedSerializer = New-Object Microsoft.AzureStack.PartnerToolkit.IPCalculator.Serializer.CombinedJsonDataSerializer
                    Write-Log -Message "$($MyInvocation.MyCommand.Name) - calling CombinedJsonDataSerializer.ProcessExport with a CurrentDirectory of '$($PSScriptRoot)'"
                    $combinedSerializer.CurrentDirectory = $PSScriptRoot
                    [bool]$success = $combinedSerializer.ProcessExport($activeStamp, $folder)
                    if($success)
                    {
                        # Write OEM Partner data to file
                        if($tabControl.TabPages["tabPartner"])
                        {
                            Write-Log -Message "$($MyInvocation.MyCommand.Name) - write PartnerData to $($strPartnerExportFile) file"
                            Get-PartnerData
                            $partnerData | ConvertTo-Json -Depth 10 | Set-Content (Join-Path $folder $strPartnerExportFile)
                        }
                        $message = $locString.msgExportSucceeded
                        $caption = $locString.capSuccess
                        Close-WaitForm
                        [System.Windows.Forms.MessageBox]::Show($message, $caption, [System.Windows.Forms.MessageBoxButtons]::OK)
                    }
                    else
                    {
                        $message = $locString.errExportFailed
                        $caption = $locString.capError
                        Close-WaitForm
                        [System.Windows.Forms.MessageBox]::Show($message, $caption, [System.Windows.Forms.MessageBoxButtons]::OK)
                    }
                }
                else
                {
                    $message = $locString.msgNoFolderSelected
                    $caption = $locString.capWarning
                    Close-WaitForm
                    [System.Windows.Forms.MessageBox]::Show($message, $caption, [System.Windows.Forms.MessageBoxButtons]::OK)
                }
            }
        }
    }
    catch
    {
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - ERROR: $($PSItem.Exception.Message)" -Force
        $errorText = New-Object -TypeName "System.Text.StringBuilder"
        $null = $errorText.Append("An exception occured while generating Json files.")
        $null = $errorText.AppendLine()
        $null = $errorText.AppendFormat("Exception: {0}" -f $PSItem.Exception.Message)
        $null = $errorText.AppendLine()
        $null = $errorText.AppendFormat("Source: {0}" -f $PSItem.Exception.Source)
        $caption = $locString.capError
        $message = $errorText.ToString()
        Close-WaitForm
        [System.Windows.Forms.MessageBox]::Show($message, $caption, [System.Windows.Forms.MessageBoxButtons]::OK)
    }
    finally
    {
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - completed"
        Close-WaitForm
    }
}

function Invoke-ImportDeploymentData
{
    Write-Log -Message "$($MyInvocation.MyCommand.Name) - called"
    try
    {
        [string]$message = $locString.msgImport
        [string]$caption = $locString.capImport
        Close-WaitForm
        $response = [System.Windows.Forms.MessageBox]::Show($message, $caption, [System.Windows.Forms.MessageBoxButtons]::YesNo)
        if($response -eq [System.Windows.Forms.DialogResult]::Yes)
        {
            Remove-GeneratedTabs
            Clear-ExistingData
            if([string]($txtEdgeCount.Text) -ne $defaultEdgeCount)
            {
                Write-Log -Message "$($MyInvocation.MyCommand.Name) - set the EdgeCount to $($defaultEdgeCount)"
                $lstRoutingMethod.SelectedIndex = 0
                $txtEdgeCount.Text = $defaultEdgeCount
                Invoke-RefreshEdgeBGP -EdgeCount $txtEdgeCount.Text
            }
            if([string]($txtSpineCount.Text) -ne $defaultSpineCount)
            {
                Write-Log -Message "$($MyInvocation.MyCommand.Name) - set the SpineCount to $($defaultSpineCount)"
                $txtSpineCount.Text = $defaultSpineCount
            }
            if([string]($txtScaleUnitCount.Text) -ne '1')
            {
                Write-Log -Message "$($MyInvocation.MyCommand.Name) - set the ScaleUnitCount to $($defaultSUCount)"
                $txtScaleUnitCount.Text = $defaultSUCount
                Invoke-Command -ScriptBlock $txtScaleUnitCount_Changed
            }
            [string]$importJsonFile = Get-FileName
            if($importJsonFile)
            {
                Show-WaitForm ("`n $($MyInvocation.MyCommand.Name) : {0}.`n`n {1}" -f $locString.msgImportingData,$locString.msgWait)
                $jsonData = Get-Content $importJsonFile -Raw | ConvertFrom-Json
                # Abort if the ConfigData file was created using newer version with the Vanadium network changes implemented
                if($jsonData.EnvironmentData.DatacenterNetworks)
                {
                    Write-Log -Message "$($MyInvocation.MyCommand.Name) - WARNING: aborting import as the file is from a newer version that is not supported by this script" -Force
                    $caption = $locString.capError
                    $message = "$($locString.errImportFileUnsupported)"
                    Close-WaitForm
                    [System.Windows.Forms.MessageBox]::Show($message, $caption, [System.Windows.Forms.MessageBoxButtons]::OK)
                    return
                }
                # Warn but continue if the ConfigData file was created with a newer version of this module
                if($jsonData.PartnerToolkitVersion)
                {
                    [version]$importVersion = $jsonData.PartnerToolkitVersion
                }
                elseif($jsonData.Version)
                {
                    [version]$importVersion = $jsonData.Version
                }
                if($importVersion -and $importVersion -gt [version](Get-FileVersionInfo))
                {
                    Write-Log -Message "$($MyInvocation.MyCommand.Name) - WARNING: the import file is from a newer version - unexpected values and/or errors may occur as a result" -Force
                }
                $jsonData = $null
                $global:activeStamp = New-Deployment
                Invoke-DeserializeJson $importJsonFile
                if($activeStamp.IsLabScenario -eq $true)
                {
                    Close-WaitForm
                    return
                }
                elseif($scenario -eq "AddRack")
                {
                    $tabControl.SelectedTab = $tabControl.TabPages["tabNetwork"]
                    $activeStamp.IsAddRack = $true
                }
                else
                {
                    $tabControl.SelectedTab = $tabControl.TabPages["tabCustomer"]
                }
                if($tabControl.TabPages.ContainsKey('tabPartner'))
                {
                    $missingNames = @()
                    $partnerDataJsonFile = Join-Path ($importJsonFile | Split-Path -Parent) $strPartnerExportFile
                    if(Test-Path $partnerDataJsonFile)
                    {
                        $importedPartnerData = Get-Content $partnerDataJsonFile | ConvertFrom-Json
                        foreach($name in ($importedPartnerData | Get-Member -Type NoteProperty).Name)
                        {
                            $thisControl = $tabPartner.Controls | Where-Object Name -eq $name
                            if($thisControl)
                            {
                                if($thisControl -is [System.Windows.Forms.TextBox])
                                {
                                    $thisControl.Text = $importedPartnerData.$name
                                }
                                elseif($thisControl -is [System.Windows.Forms.ComboBox])
                                {
                                    $thisControl.SelectedItem = $importedPartnerData.$name
                                }
                                else
                                {
                                    Write-Log -Message "$($MyInvocation.MyCommand.Name) - WARNING: Unknown control type for object named '$($name)'" -Force
                                }
                            }
                            else
                            {
                                Write-Log -Message "$($MyInvocation.MyCommand.Name) - WARNING: The item named '$($name)' no longer exists on the 'OEM Settings' tab" -Force
                                $missingNames += $name
                            }
                        }
                        if($missingNames.Count -ne 0)
                        {
                            $caption = $locString.capWarning
                            $message = "$($locString.msgMissingOEMItems):`n`n"
                            foreach($name in $missingNames)
                            {
                                $message += "$($name)`n"
                            }
                            Close-WaitForm
                            [System.Windows.Forms.MessageBox]::Show($message, $caption, [System.Windows.Forms.MessageBoxButtons]::OK)
                        }
                    }
                    else
                    {
                        Write-Log -Message "$($MyInvocation.MyCommand.Name) - WARNING: $($strPartnerExportFile) was not found with the exported files" -Force
                    }
                }
            }
            else
            {
                Close-WaitForm
                Write-Log -Message "$($MyInvocation.MyCommand.Name) - the user did not select a file to import"
            }
        }
        else
        {
            Write-Log -Message "$($MyInvocation.MyCommand.Name) - the user chose to abort the import"
        }
    }
    catch
    {
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - ERROR: $($PSItem)" -Force
        throw $PSItem
    }
    finally
    {
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - IsLabScenario : $($activeStamp.IsLabScenario)"
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - IsAddRack : $($activeStamp.IsAddRack)"
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - completed"
        Close-WaitForm
    }
}

function Invoke-DeserializeJson
{
    param
    (
        [string]$JsonFileName
    )
    Write-Log -Message "$($MyInvocation.MyCommand.Name) - called with a JsonFileName of '$($JsonFileName)'"
    try
    {
        $jsonDeserializer = New-Object Microsoft.AzureStack.PartnerToolkit.IPCalculator.Deserializer.JsonDeserializer
        $global:activeStamp = $jsonDeserializer::DeserializeJson($JsonFileName)
        if(($activeStamp.Clouds).Count -eq 0)
        {
            throw "Imported cloud count is '0'"
        }

        Write-Log -Message "$($MyInvocation.MyCommand.Name) - IsLabScenario : $($activeStamp.IsLabScenario)"
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - IsAddRack : $($activeStamp.IsAddRack)"

        if($activeStamp.IsAddRack -eq $true)
        {
            # AddRack scenario for sure
            Write-Log -Message "$($MyInvocation.MyCommand.Name) - imported data is for an AddRack scenario"
            Set-AddRack
        }
        if($activeStamp.IsLabScenario -eq $true)
        {
            # Lab Scenario for sure
            Close-WaitForm
            $message = $locString.msgLabNotSupported
            $caption = $locString.capError
            [System.Windows.Forms.MessageBox]::Show($message, $caption, [System.Windows.Forms.MessageBoxButtons]::OK)
            $activeStamp = $null
            return
        }
        elseif((($activeStamp.Clouds).Count -eq 1) -and ($activeStamp.Clouds[0].ScaleUnits).Count -gt 1)
        {
            # Multi-SU, so assume this is a Customer scenario
            Write-Log -Message "$($MyInvocation.MyCommand.Name) - imported data is for a Customer scenario"
            if(($txtScaleUnitCount.Enabled -eq $false) -or ($scenario -eq "AddRack"))
            {
                OnClick_menuItemPreviewMultiSU
            }
        }
        elseif(($activeStamp.Clouds).Count -gt 1)
        {
            Close-WaitForm
            $message = $locString.msgLabNotSupported
            $caption = $locString.capError
            [System.Windows.Forms.MessageBox]::Show($message, $caption, [System.Windows.Forms.MessageBoxButtons]::OK)
            return
        }
        # Write the imported data to the forms
        Write-DeploymentData
    }
    catch
    {
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - $(($locString.capError).ToUpper()): $($PSItem.Exception.Message)"
        $errorText = New-Object -TypeName "System.Text.StringBuilder"
        $null = $errorText.Append($locString.errImportException)
        $null = $errorText.AppendLine()
        $null = $errorText.AppendFormat("{0}: {1}" -f $locString.strException,$PSItem.Exception.Message)
        $null = $errorText.AppendLine()
        $null = $errorText.AppendFormat("{0}: {1}" -f $locString.strSource,$PSItem.Exception.Source)
        $message = $errorText.ToString()
        $caption = $locString.capError
        Close-WaitForm
        [System.Windows.Forms.MessageBox]::Show($message, $caption, [System.Windows.Forms.MessageBoxButtons]::OK)
        throw $PSItem
    }
    finally
    {
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - completed"
    }
}

function Write-DeploymentData
{
    Write-Log -Message "$($MyInvocation.MyCommand.Name) - called"
    try
    {
        # Clouds - there should only be one Cloud for Customer scenarios
        $cloudObj = @($activeStamp.Clouds)[0]
        foreach($property in $arrDeployProperties)
        {
            Write-Log -Message "$($MyInvocation.MyCommand.Name) - process: '$($property)'"
            switch ($property)
            {
                "BorderConnectivity"
                {
                    $index = 1
                    if($activeStamp."$property" -match "BGP")
                    {
                        $index = 0
                    }
                    $lstRoutingMethod.SelectedIndex = $index
                    Invoke-Command -ScriptBlock $lstRoutingMethod_Changed
                }
                default
                {
                }
            }
        }
        foreach($cloud in $cloudObj)
        {
            [string]$strID = $cloud.Name.Substring($cloud.Name.Length -2)
            [int]$numID = $cloud.Name.Substring($cloud.Name.Length -2)
            foreach($property in $arrCloudProperties)
            {
                Write-Log -Message "$($MyInvocation.MyCommand.Name) - process: '$($property)'"
                switch ($property)
                {
                    "NodeCount"
                    {
                        # Do nothing as this will be calculated based on SU node counts
                    }
                    "DNSForwarder"
                    {
                        # Handle DNSForwarder differently as it could be an array that needs to be passed as a comma-separated string
                        $list = @($cloud."$property")
                        [string]$listStr = $list -Join ','
                        $txtDNSForwarder.Text = $listStr
                    }
                    "TimeServer"
                    {
                        $txtTimeServer.Text = $cloud."$property"
                    }
                    "SyslogServerIPv4Address"
                    {
                        $list = @($cloud."$property")
                        [string]$listStr = $list -Join ','
                        if([string]::IsNullOrEmpty($listStr))
                        {
                                $txtSyslogServer.Text = [string]::Empty
                        }
                        else
                        {
                            $txtSyslogServer.Text = $listStr
                        }
                    }
                    "ConnectToAzure"
                    {
                        if($cloud.InfraAzureDirectoryTenantName)
                        {
                            $index = 0
                        }
                        elseif($cloud."$property" -eq "Azure Active Directory")
                        {
                            $index = 0
                        }
                        else
                        {
                            $index = 1
                        }
                        $lstIdentityStore.SelectedIndex = $index
                        Invoke-Command -ScriptBlock $lstIdentityStore_Changed
                    }
                    "InfraAzureEnvironment"
                    {
                        [string]$val = $cloud."$property"
                        if($val)
                        {
                            if($arrAzureEnvironment -contains $val)
                            {
                                $lstAzureEnvironment.SelectedItem = $val
                            }
                            else
                            {
                                Write-Log -Message "$($MyInvocation.MyCommand.Name) - WARNING: Unknown InfraAzureEnvironment value of '$($val)'" -Force
                            }
                        }
                    }
                    "InfraAzureDirectoryTenantName"
                    {
                        [string]$val = $cloud."$property"
                        if($val)
                        {
                            $txtAADTenantName.Text = $val
                        }
                    }
                    "ADFSForestFQDN"
                    {
                        [string]$val = $cloud."$property"
                        if($val)
                        {
                            $txtADFSForestFQDN.Text = $val
                        }
                    }
                    "ADFSProviderName"
                    {
                        [string]$val = $cloud."$property"
                        if($val)
                        {
                            $txtADFSProviderName.Text = $val
                        }
                    }
                    "ADFSMetadataUri"
                    {
                        [string]$val = $cloud."$property"
                        if($val)
                        {
                            $txtADFSMetadataUri.Text = $val
                        }
                    }
                    "PhysicalNamingPrefix"
                    {
                        [string]$val = $cloud."$property"
                        $txtPhysicalPrefix.Text = $val
                    }
                    "NamingPrefix"
                    {
                        [string]$val = $cloud."$property"
                        $txtDeployPrefix.Text = $val
                    }
                    "CompanyName"
                    {
                        [string]$val = $cloud."$property"
                        $txtCompanyName.Text = $val
                    }
                    "RegionName"
                    {
                        [string]$val = $cloud."$property"
                        $txtRegionName.Text = $val
                    }
                    "DomainFQDN"
                    {
                        [string]$val = $cloud."$property"
                        $txtPrivateDomain.Text = $val
                    }
                    "ExternalDomainFQDN"
                    {
                        [string]$val = $cloud."$property"
                        $txtExternalDomain.Text = $val
                    }
                    default
                    {
                        [string]$val = $cloud."$property"
                        Write-Log  -Message "$($MyInvocation.MyCommand.Name) - WARNING: Unhandled property '$($property)' with a value of '$($val)'" -Force
                    }
                }
            }
            # ScaleUnits - there could be multiple Scale-Units for Customer scenarios
            $scaleUnits = @($cloud.ScaleUnits)
            [string]$suCount = $scaleUnits.Count
            $txtScaleUnitCount.Text = $suCount
            Invoke-Command -ScriptBlock $txtScaleUnitCount_Changed
            if([int]$suCount -gt 1)
            {
                $txtScaleUnitCount.Enabled = $true
                $txtScaleUnitCount.ReadOnly = $false
            }
        }
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - process: Edge Count"
        $switchObj = $activeStamp.Switches
        $borderObj = @($switchObj | Where-Object SwitchType -eq "Border")
        $torObj = @($switchObj | Where-Object SwitchType -eq "TOR1")
        [string]$val = $borderObj.Count
        # Some older version may have allowed more that 2 Border devices for single-SU, so we will adjust it here to make it correct now
        if((($activeStamp.Racks).Count -eq 1) -and [int]$val -gt 2)
        {
            $validEdge = 2
            $val = $validEdge
        }
        elseif(($activeStamp.Racks).Count -gt 1)
        {
            $validEdge = $maxEdge
        }
        $txtEdgeCount.Text = $val
        Invoke-Command -ScriptBlock $txtEdgeCount_Changed
        $invalidSwitchImportData = @()
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - process: Switch Vendor"
        $validSwitchMakes = Get-SwitchVendors
        $switchMake = $torObj[0].Make
        $validMake = $false
        Clear-ListSelection $lstSwitchVendor
        if($validSwitchMakes -contains $switchMake)
        {
            $lstSwitchVendor.SelectedItem = $switchMake
            $validMake = $true
            Invoke-Command -ScriptBlock $lstSwitchVendor_Changed
        }
        else
        {
            Write-Log -Message "$($MyInvocation.MyCommand.Name) - WARNING: The imported Switch Vendor '$($switchMake)' is no longer valid" -Force
            $invalidSwitchImportData += "SwitchMake: $($switchMake)"
            Clear-ListSelection $lstSwitchVendor
        }
        Clear-ListSelection $lstTORModel
        Clear-ListSelection $lstTORFirmware
        if($switchMake -ne "CiscoUCS")
        {
            Clear-ListSelection $lstBMCModel
            Clear-ListSelection $lstBMCFirmware
            Clear-ListSelection $lstSpineModel
            Clear-ListSelection $lstSpineFirmware
        }
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - process: Edge BGP ASN values"
        $goodAsn = 0
        foreach($sw in $borderObj)
        {
            $i = $sw.Hostname.Split('-') | Select-Object -Last 1
            if([int]$i -le [int]$validEdge)
            {
                switch ($i)
                {
                    '1'
                    {
                        $txtEdgeBGP1.Text = $sw.Asn
                        break
                    }
                    '2'
                    {
                        $txtEdgeBGP2.Text = $sw.Asn
                        break
                    }
                    '3'
                    {
                        $txtEdgeBGP3.Text = $sw.Asn
                        break
                    }
                    '4'
                    {
                        $txtEdgeBGP4.Text = $sw.Asn
                        break
                    }
                    default
                    {
                    }
                }
            }
            # Special case for old exports that might have duplicate Border names (find a good ASN)
            if(($goodAsn -eq 0) -and ($sw.Asn) -and ($i -le 2))
            {
                $goodAsn = $sw.Asn
            }
        }
        # Special case for old exports that might have duplicate Border names (set ASN of Border devices that did not get set correctly)
        for($b = 1; $b -le $borderObj.Count; $b++)
        {
            if([int]$b -le [int]$validEdge)
            {
                switch ($b)
                {
                    '1'
                    {
                        $val = $txtEdgeBGP1.Text
                        if((-not $val) -and ($goodAsn -ne 0))
                        {
                            $txtEdgeBGP1.Text = $goodAsn
                        }
                        break
                    }
                    '2'
                    {
                        $val = $txtEdgeBGP2.Text
                        if((-not $val) -and ($goodAsn -ne 0))
                        {
                            $txtEdgeBGP2.Text = $goodAsn
                        }
                        break
                    }
                    '3'
                    {
                        $val = $txtEdgeBGP3.Text
                        if((-not $val) -and ($goodAsn -ne 0))
                        {
                            $txtEdgeBGP3.Text = $goodAsn
                        }
                        break
                    }
                    '4'
                    {
                        $val = $txtEdgeBGP4.Text
                        if((-not $val) -and ($goodAsn -ne 0))
                        {
                            $txtEdgeBGP4.Text = $goodAsn
                        }
                        break
                    }
                    default
                    {
                    }
                }
            }
        }
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - process: Software MUX BGP ASN value"
        $muxObj = @($switchObj | Where-Object SwitchType -eq "MUX")
        $txtSoftwareBGP.Text = $muxObj[0].Asn
        $netObj = @($activeStamp.Supernets)
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - process: External Network"
        $extNet = $netObj | Where-Object Name -eq "CL01-External"
        $txtExternalSubnet.Text = $extNet.IPv4.Network.IPAddressToString
        $txtExternalSubnetSize.Text = $extNet.IPv4.Cidr
        $torObj = @($switchObj | Where-Object SwitchType -like "TOR*")
        $bmcObj = @($switchObj | Where-Object SwitchType -like "BMC*")
        if($validMake)
        {
            Write-Log -Message "$($MyInvocation.MyCommand.Name) - process: TOR Switch settings"
            $validTOR = Get-SwitchModels -Make $switchMake -Type 'TOR'
            if($torObj.Count -gt 0 -and $torObj[0].Model -ne $null)
            {
                Write-Log -Message "$($MyInvocation.MyCommand.Name) - TOR Model: $($torObj[0].Model)"
                if($validTOR -contains $torObj[0].Model)
                {
                    $firmwareTOR = Get-SwitchFirmware -Make $switchMake -Type 'TOR' -Model $torObj[0].Model
                    $lstTORModel.SelectedItem = $torObj[0].Model
                    if($torObj[0].FIRMWAREVERSION -ne $null)
                    {
                        Write-Log -Message "$($MyInvocation.MyCommand.Name) - TOR Firmware: $($torObj[0].FIRMWAREVERSION)"
                        if($firmwareTOR -contains $torObj[0].FIRMWAREVERSION)
                        {
                            $lstTORFirmware.SelectedItem = $torObj[0].FIRMWAREVERSION
                        }
                        else
                        {
                            Write-Log -Message "$($MyInvocation.MyCommand.Name) - WARNING: The imported TOR firmware version '$($torObj[0].FIRMWAREVERSION)' is no longer valid" -Force
                            $invalidSwitchImportData += "TOR Firmware: $($torObj[0].FIRMWAREVERSION)"
                            Clear-ListSelection $lstTORFirmware
                        }
                    }
                    else
                    {
                        Write-Log -Message "$($MyInvocation.MyCommand.Name) - WARNING: The imported TOR firmware version was null" -Force
                        $invalidSwitchImportData += "TOR Firmware: null"
                        Clear-ListSelection $lstTORFirmware
                    }
                }
                else
                {
                    Write-Log -Message "$($MyInvocation.MyCommand.Name) - WARNING: The imported TOR switch model '$($torObj[0].Model)' is no longer valid" -Force
                    $invalidSwitchImportData += "TOR Model: $($torObj[0].Model)"
                    Clear-ListSelection $lstTORModel
                }
            }
            else
            {
                Write-Log -Message "$($MyInvocation.MyCommand.Name) - WARNING: The imported TOR switch model was null" -Force
                $invalidSwitchImportData += "TOR Model: null"
                Clear-ListSelection $lstTORModel
            }
            if($switchMake -ne "CiscoUCS")
            {
                Write-Log -Message "$($MyInvocation.MyCommand.Name) - process: BMC Switch settings"
                $validBMC = Get-SwitchModels -Make $switchMake -Type 'BMC'
                if($bmcObj.Count -gt 0 -and $bmcObj[0].Model -ne $null)
                {
                    Write-Log -Message "$($MyInvocation.MyCommand.Name) - BMC Model: $($bmcObj[0].Model)"
                    if($validBMC -contains $bmcObj[0].Model)
                    {
                        $firmwareBMC = Get-SwitchFirmware -Make $switchMake -Type 'BMC' -Model $bmcObj[0].Model
                        $lstBMCModel.SelectedItem = $bmcObj[0].Model
                        if($bmcObj[0].FIRMWAREVERSION -ne $null)
                        {
                            Write-Log -Message "$($MyInvocation.MyCommand.Name) - BMC Firmware: $($bmcObj[0].FIRMWAREVERSION)"
                            if($firmwareBMC -contains $bmcObj[0].FIRMWAREVERSION)
                            {
                                $lstBMCFirmware.SelectedItem = $bmcObj[0].FIRMWAREVERSION
                            }
                            else
                            {
                                Write-Log -Message "$($MyInvocation.MyCommand.Name) - WARNING: The imported BMC switch firmware version '$($bmcObj[0].FIRMWAREVERSION)' is no longer valid" -Force
                                $invalidSwitchImportData += "BMC Firmware: $($bmcObj[0].FIRMWAREVERSION)"
                                Clear-ListSelection $lstBMCFirmware
                            }
                        }
                        else
                        {
                            Write-Log -Message "$($MyInvocation.MyCommand.Name) - WARNING: The imported BMC firmware version was null" -Force
                            $invalidSwitchImportData += "BMC Firmware: null"
                            Clear-ListSelection $lstBMCFirmware
                        }
                    }
                    else
                    {
                        Write-Log -Message "$($MyInvocation.MyCommand.Name) - WARNING: The imported BMC switch model '$($bmcObj[0].Model)' is no longer valid" -Force
                        $invalidSwitchImportData += "BMC Model: $($bmcObj[0].Model)"
                        Clear-ListSelection $lstBMCModel
                    }
                }
                else
                {
                    Write-Log -Message "$($MyInvocation.MyCommand.Name) - WARNING: The imported BMC switch model was null" -Force
                    $invalidSwitchImportData += "BMC Model: null"
                    Clear-ListSelection $lstBMCModel
                }
            }
        }
        if($scaleUnits.Count -gt 1)
        {
            if(($validMake) -and ($switchMake -ne "CiscoUCS"))
            {
                Write-Log -Message "$($MyInvocation.MyCommand.Name) - process: SPINE Switch settings"
                $spineObj = @($switchObj | Where-Object SwitchType -eq "Spine")
                $txtSpineCount.Text = $spineObj.Count
                $txtSpineBGP.Text = $spineObj[0].Asn
                $modelsSpine = Get-SwitchModels -Make $switchMake -Type 'SPINE'
                if($spineObj.Count -gt 0 -and $spineObj[0].Model -ne $null)
                {
                    if($modelsSpine -contains $spineObj[0].Model)
                    {
                        $lstSpineModel.SelectedItem = $spineObj[0].Model
                        $firmwareSpine = Get-SwitchFirmware -Make $switchMake -Type 'SPINE' -Model $spineObj[0].Model
                        if($spineObj[0].FIRMWAREVERSION -ne $null)
                        {
                            if($firmwareSpine -contains $spineObj[0].FIRMWAREVERSION)
                            {
                                $lstSpineFirmware.SelectedItem = $spineObj[0].FIRMWAREVERSION
                            }
                            else
                            {
                                Write-Log -Message "$($MyInvocation.MyCommand.Name) - WARNING: The imported SPINE switch firmware version '$($spineObj[0].FIRMWAREVERSION)' is no longer valid" -Force
                                $invalidSwitchImportData += "SPINE Firmware: $($spineObj[0].FIRMWAREVERSION)"
                                Clear-ListSelection $lstSpineFirmware
                            }
                        }
                        else
                        {
                            Write-Log -Message "$($MyInvocation.MyCommand.Name) - WARNING: The imported SPINE firmware version was null" -Force
                            $invalidSwitchImportData += "SPINE Firmware: null"
                            Clear-ListSelection $lstSpineFirmware
                        }
                    }
                    else
                    {
                        Write-Log -Message "$($MyInvocation.MyCommand.Name) - WARNING: The imported SPINE switch model '$($spineObj[0].Model)' is no longer valid" -Force
                        $invalidSwitchImportData += "SPINE Model: $($spineObj[0].Model)"
                        Clear-ListSelection $lstSpineModel
                    }
                }
                else
                {
                    Write-Log -Message "$($MyInvocation.MyCommand.Name) - WARNING: The imported SPINE switch model was null" -Force
                    $invalidSwitchImportData += "SPINE Model: null"
                    Clear-ListSelection $lstSpineModel
                }
                $spineNet = $netObj | Where-Object Name -eq "Rack01-SpineInfrastructure"
                $txtSpineSubnet.Text = $spineNet.IPv4.Network.IPAddressToString
            }
        }
        foreach($su in $scaleUnits)
        {
            [string]$suID = "SU{0:d2}" -f $su.Index
            [string]$rackId = $su.RackId
            [string]$rack = $activeStamp.Racks | Where-Object Name -eq $rackId
            [string]$suTab = "tabSU$($su.Index)"
            Write-Log -Message "$($MyInvocation.MyCommand.Name) - process: Scale-Unit '$($suID)' settings on tab '$($suTab)'"

            Write-Log -Message "$($MyInvocation.MyCommand.Name) - set '$($suID)' NodeCount"
            $scaleUnitTabs.$suTab.Objects.txtNodeCount.Text = $su.NodeCount

            Write-Log -Message "$($MyInvocation.MyCommand.Name) - set '$($suID)' Storage Network"
            $swSto = $netObj | Where-Object Name -match "$($suID)-Storage"
            $scaleUnitTabs.$suTab.Objects.txtStorageSubnet.Text = $swSto.IPv4.Network.IPAddressToString

            Write-Log -Message "$($MyInvocation.MyCommand.Name) - set '$($suID)' Infrastructure Network"
            $swInf = $netObj | Where-Object Name -match "$($suID)-Infrastructure"
            $scaleUnitTabs.$suTab.Objects.txtInfraSubnet.Text = $swInf.IPv4.Network.IPAddressToString

            $suSwitch = @($switchObj | Where-Object Hostname -like "*-$($suID)-*")
            if($suSwitch.Count -eq 0)
            {
                $suSwitch = @($switchObj | Where-Object SwitchType -like "*TOR*")
            }

            Write-Log -Message "$($MyInvocation.MyCommand.Name) - set '$($rackId)' BMC Network"
            $swBmc = $netObj | Where-Object Name -match "$($rackId)-BMC"
            $scaleUnitTabs.$suTab.Objects.txtBMCSubnet.Text = $swBmc.IPv4.Network.IPAddressToString

            $skipThis = $false
            if(($su.Index -gt 1) -and ($switchMake -eq "CiscoUCS"))
            {
                $skipThis = $true
            }
            if(-not $skipThis)
            {
                Write-Log -Message "$($MyInvocation.MyCommand.Name) - set '$($rackId)' TOR BGP ASN value"
                $tor1 = $suSwitch | Where-Object Hostname -like "*$($rackId)-TOR-1"
                $scaleUnitTabs.$suTab.Objects.txtTORASN.Text = $tor1.Asn

                Write-Log -Message "$($MyInvocation.MyCommand.Name) - set '$($rackId)' SwitchInfra Network"
                $swSwi = $netObj | Where-Object Name -match "$($rackId)-SwitchInfrastructure"
                $scaleUnitTabs.$suTab.Objects.txtSwitchInfraSubnet.Text = $swSwi.IPv4.Network.IPAddressToString
            }
        }
        Invoke-RefreshTotalNodeCount
        [string]$caption = $locString.capImportComplete
        [string]$message = $locString.msgImportComplete
        Close-WaitForm
        [System.Windows.Forms.MessageBox]::Show($message, $caption, [System.Windows.Forms.MessageBoxButtons]::OK)
    }
    catch
    {
        Close-WaitForm
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - ERROR: $($PSItem.Exception.Message)" -Force
        throw $PSItem
    }
    finally
    {
        if($invalidSwitchImportData.Count -ne 0)
        {
            $caption = $locString.capWarning
            $message = "$($locString.msgInvalidSwitchData):`n`n"
            foreach($item in $invalidSwitchImportData)
            {
                $message += "$($item)`n"
            }
            Close-WaitForm
            [System.Windows.Forms.MessageBox]::Show($message, $caption, [System.Windows.Forms.MessageBoxButtons]::OK)
        }
        Write-Log -Message "$($MyInvocation.MyCommand.Name) - completed"
    }
}

function Write-OpenDataToTab
{
    param
    (
        [Parameter(Mandatory=$true)]
        [string]$TabName
    )
    Write-Log -Message "$($MyInvocation.MyCommand.Name) - called for TabName '$($TabName)'"
    $thisTab = $tabControl.TabPages[$TabName]
    $orderList = @()
    if($TabName -eq "tabPartner")
    {
        ($openData.$TabName | Get-Member -Type NoteProperty).Name | ForEach-Object { $orderList += $PSItem }
    }
    else
    {
        $doFirst = @()
        $doSecond = @()
        $doLast = @()
        if($openData.$TabName)
        {
            ($openData.$TabName | Get-Member -Type NoteProperty).Name | ForEach-Object `
            {
                if($PSItem -like "*Model")
                {
                    $doSecond += $PSItem
                }
                elseif($PSItem -like "*Firmware")
                {
                    $doLast += $PSItem
                }
                else
                {
                    $doFirst += $PSItem
                }
            }
            $orderList += $doFirst
            $orderList += $doSecond
            $orderList += $doLast
        }
    }
    foreach($item in $orderList)
    {
        if($openData.$TabName.$item)
        {
            Write-Log -Message "$($MyInvocation.MyCommand.Name) - set control named '$($item)' to '$($openData.$TabName.$item)'"
            if(($thisTab.Controls[$item] -is [System.Windows.Forms.TextBox]) -or ($thisTab.Controls[$item] -is [System.Windows.Forms.RichTextBox]))
            {
                $thisTab.Controls[$item].Text = $openData.$TabName.$item
            }
            elseif($thisTab.Controls[$item] -is [System.Windows.Forms.ComboBox])
            {
                if($thisTab.Controls[$item].Items -contains $openData.$TabName.$item)
                {
                    $thisTab.Controls[$item].SelectedItem = $openData.$TabName.$item
                    if($item -like "*SwitchVendor")
                    {
                        Clear-ListSelection $lstTORFirmware
                        Clear-ListSelection $lstBMCFirmware
                        Clear-ListSelection $lstSpineFirmware
                        Clear-ListSelection $lstTORModel
                        Clear-ListSelection $lstBMCModel
                        Clear-ListSelection $lstSpineModel
                    }
                    elseif($item -like "*TORModel")
                    {
                        Clear-ListSelection $lstTORFirmware
                    }
                    elseif($item -like "*BMCModel")
                    {
                        Clear-ListSelection $lstBMCFirmware
                    }
                    elseif($item -like "*SpineModel")
                    {
                        Clear-ListSelection $lstSpineFirmware
                    }
                }
                else
                {
                    Write-Log -Message "$($MyInvocation.MyCommand.Name) - WARNING: '$($item)' does not contain '$($openData.$TabName.$item)'" -Force
                    Clear-ListSelection $thisTab.Controls[$item]
                    $script:invalidOpenData += $item
                }
            }
            else
            {
                Write-Log -Message "$($MyInvocation.MyCommand.Name) - WARNING: '$($item)' does not exist on the tab named '$($TabName)'" -Force
                $script:invalidOpenData += $item
            }
        }
    }
    Write-Log -Message "$($MyInvocation.MyCommand.Name) - completed"
}

function Export-NetMap
{
    param
    (
        [Parameter(Mandatory=$true)]
        [string]$InputFile,
        [Parameter(Mandatory=$true)]
        [string]$OutputDirectory,
        [switch]$Force
    )
    try
    {
        Invoke-LoadLocalizedData
        if(-not(Test-Path $InputFile))
        {
            $message = ($locString.msgFileNotFound -f $InputFile)
            throw $message
        }
        if(-not($InputFile.EndsWith('.json')))
        {
            $message = ($locString.msgFileNotValid -f $InputFile)
            throw $message
        }
        if(-not(Test-Path $OutputDirectory))
        {
            try
            {
                New-Item -ItemType Directory -Path $OutputDirectory | Out-Null
            }
            catch
            {
                throw $PSItem
            }
        }
        if(Test-Path (Join-Path $OutputDirectory 'ConfigurationData.json'))
        {
            if($Force)
            {
                Remove-Item $OutputDirectory -Recurse | Out-Null
                New-Item -ItemType Directory -Path $OutputDirectory | Out-Null
            }
            else
            {
                $message = ($locString.msgOverwriteConfigData -f $InputFile)
                Write-Error $message
                return
            }
        }
        $script:dllIPCalculator = 'Microsoft.AzureStack.PartnerToolkit.IPCalculator.dll'
        $script:dllJsonParser = 'Newtonsoft.Json.dll'
        $script:dllIPNetwork = 'System.Net.IPNetwork.dll'
        Add-Assembly (Join-Path $PSScriptRoot $dllIPCalculator)
        Add-Assembly (Join-Path $PSScriptRoot $dllJsonParser)
        Add-Assembly (Join-Path $PSScriptRoot $dllIPNetwork)
        $jsonDeserializer = New-Object Microsoft.AzureStack.PartnerToolkit.IPCalculator.Deserializer.JsonDeserializer
        $activeStamp = $jsonDeserializer::DeserializeJson((Resolve-Path $InputFile))
        $activeStamp.SetCurrentPath($PSScriptRoot)
        $ref = New-Object -TypeName "System.Text.StringBuilder"
        $script:errorText = [ref]$ref
        [bool]$isValid = $activeStamp.Validate($errorText)
        if($isValid)
        {
            $activeStamp.Generate()
            $combinedSerializer = New-Object Microsoft.AzureStack.PartnerToolkit.IPCalculator.Serializer.CombinedJsonDataSerializer
            $combinedSerializer.CurrentDirectory = $PSScriptRoot
            [bool]$success = $combinedSerializer.ProcessExport($activeStamp, (Resolve-Path $OutputDirectory))
            if(-not $success)
            {
                $message = $locString.errExportFailed
                throw $message
            }
        }
        else
        {
            $errList = @($errorText.Value.ToString().Split("`n"))
            [string[]]$message = @(Get-ValidationError -ErrorList $errList)
            [string]$errStr = ($locString.capError).ToUpper().Trim()
            foreach($msg in $message)
            {
                $msg = $msg.Trim()
                Write-Verbose -Message "$($errStr): $($msg)" -Verbose
            }
            throw $locString.msgValidationFailed
        }
    }
    catch
    {
        throw $PSItem
    }
}
# SIG # Begin signature block
# MIIjhgYJKoZIhvcNAQcCoIIjdzCCI3MCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCA9+5VM5H27b8Mt
# onmn2T6Y/hq6Y9QHZnQYrWYABPT6IqCCDYEwggX/MIID56ADAgECAhMzAAABUZ6N
# j0Bxow5BAAAAAAFRMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMTkwNTAyMjEzNzQ2WhcNMjAwNTAyMjEzNzQ2WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQCVWsaGaUcdNB7xVcNmdfZiVBhYFGcn8KMqxgNIvOZWNH9JYQLuhHhmJ5RWISy1
# oey3zTuxqLbkHAdmbeU8NFMo49Pv71MgIS9IG/EtqwOH7upan+lIq6NOcw5fO6Os
# +12R0Q28MzGn+3y7F2mKDnopVu0sEufy453gxz16M8bAw4+QXuv7+fR9WzRJ2CpU
# 62wQKYiFQMfew6Vh5fuPoXloN3k6+Qlz7zgcT4YRmxzx7jMVpP/uvK6sZcBxQ3Wg
# B/WkyXHgxaY19IAzLq2QiPiX2YryiR5EsYBq35BP7U15DlZtpSs2wIYTkkDBxhPJ
# IDJgowZu5GyhHdqrst3OjkSRAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUV4Iarkq57esagu6FUBb270Zijc8w
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU0MTM1MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAWg+A
# rS4Anq7KrogslIQnoMHSXUPr/RqOIhJX+32ObuY3MFvdlRElbSsSJxrRy/OCCZdS
# se+f2AqQ+F/2aYwBDmUQbeMB8n0pYLZnOPifqe78RBH2fVZsvXxyfizbHubWWoUf
# NW/FJlZlLXwJmF3BoL8E2p09K3hagwz/otcKtQ1+Q4+DaOYXWleqJrJUsnHs9UiL
# crVF0leL/Q1V5bshob2OTlZq0qzSdrMDLWdhyrUOxnZ+ojZ7UdTY4VnCuogbZ9Zs
# 9syJbg7ZUS9SVgYkowRsWv5jV4lbqTD+tG4FzhOwcRQwdb6A8zp2Nnd+s7VdCuYF
# sGgI41ucD8oxVfcAMjF9YX5N2s4mltkqnUe3/htVrnxKKDAwSYliaux2L7gKw+bD
# 1kEZ/5ozLRnJ3jjDkomTrPctokY/KaZ1qub0NUnmOKH+3xUK/plWJK8BOQYuU7gK
# YH7Yy9WSKNlP7pKj6i417+3Na/frInjnBkKRCJ/eYTvBH+s5guezpfQWtU4bNo/j
# 8Qw2vpTQ9w7flhH78Rmwd319+YTmhv7TcxDbWlyteaj4RK2wk3pY1oSz2JPE5PNu
# Nmd9Gmf6oePZgy7Ii9JLLq8SnULV7b+IP0UXRY9q+GdRjM2AEX6msZvvPCIoG0aY
# HQu9wZsKEK2jqvWi8/xdeeeSI9FN6K1w4oVQM4Mwggd6MIIFYqADAgECAgphDpDS
# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla
# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT
# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG
# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S
# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz
# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7
# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u
# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33
# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl
# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP
# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB
# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF
# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM
# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ
# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud
# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO
# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0
# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p
# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw
# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA
# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY
# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj
# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd
# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ
# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf
# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ
# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j
# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B
# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96
# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7
# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I
# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVWzCCFVcCAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAVGejY9AcaMOQQAAAAABUTAN
# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgD94oraMM
# l626ttjn+pnmw9eszScw5ICjJENXr3qiQ/YwQgYKKwYBBAGCNwIBDDE0MDKgFIAS
# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN
# BgkqhkiG9w0BAQEFAASCAQBTvWKpCvkTWrwI/ErjyIoK695VAcrT7M7/0oLErEcy
# +6zpLhvagfd3W628LkturMs3lvDWl+XtIMOjHTA8ABOpACPLxsrMeyH0/l7yFEM4
# WPuN7L2/zxmTTgSfzVXpo3n3GII95EB7vDzWAzSwc5zU0Ca43sw6H+doS66N7Niw
# 0mWf+PTniQ/1g+zkZtKOXXPMR1shRjnEHwGy8e5q2cw0N4Y1M5MfQNa71JhzwSNM
# qe9ZCpbkSpppBHGmqiG67v+AXdsLgQ/L3vVCO4yzjbxytyRWUGKo5hHROLm91hgo
# G+TxDtkp41MI7hvJJdj09ST5O3OKOJSJrI4989cLL90noYIS5TCCEuEGCisGAQQB
# gjcDAwExghLRMIISzQYJKoZIhvcNAQcCoIISvjCCEroCAQMxDzANBglghkgBZQME
# AgEFADCCAVEGCyqGSIb3DQEJEAEEoIIBQASCATwwggE4AgEBBgorBgEEAYRZCgMB
# MDEwDQYJYIZIAWUDBAIBBQAEICOcoRaarWLK1CpK8pSrSUZ3axtbeWLb/AF65aps
# wLKnAgZcyeF+LZIYEzIwMTkwNzI1MjAwODAzLjk3MlowBIACAfSggdCkgc0wgcox
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1p
# Y3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg
# RVNOOjEyQkMtRTNBRS03NEVCMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt
# cCBTZXJ2aWNloIIOPDCCBPEwggPZoAMCAQICEzMAAAD4wl8z0LWPFQQAAAAAAPgw
# DQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0
# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh
# dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcN
# MTgxMDI0MjExNDI5WhcNMjAwMTEwMjExNDI5WjCByjELMAkGA1UEBhMCVVMxEzAR
# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2Eg
# T3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046MTJCQy1FM0FFLTc0
# RUIxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggEiMA0G
# CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCWG4/+N8O+D28f14jQFJ4IelzVpAdF
# yEORF2758iUItiMKXth0ydPSWoBcQOEG43o5VZyAIwKwxDnIlusmJvrvej1qWpMb
# zw3x7DoZeiunALXxIUOXFDgUt7eMRG2LvAnpm8Df0Y3CNaKpWtBy5Loww48Fa0hs
# 9VhNog6pXOEPuvBUf2nDM6s6NB9yDNVzoRVy25PkgbDakOG9XDII1IyiOZM1w4o1
# KBK6Ury8orZx5n/az2kb+HLc6WkoSK/ewrqyfToM2qLeX8hxeaI8nLMUvfspRKLe
# q6tyUelFBZWBRE2Id42W03YHZpeuR5PGyU1jAeW1iXd2ZvQ03cfrzH4/AgMBAAGj
# ggEbMIIBFzAdBgNVHQ4EFgQU7Jd2aCn+E0/NdMYlGPIAfNp916cwHwYDVR0jBBgw
# FoAU1WM6XIoxkPNDe3xGG8UzaFqFbVUwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDov
# L2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljVGltU3RhUENB
# XzIwMTAtMDctMDEuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0
# cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNUaW1TdGFQQ0FfMjAx
# MC0wNy0wMS5jcnQwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEFBQcDCDAN
# BgkqhkiG9w0BAQsFAAOCAQEAqGTwfiwoXv5slFtdMIP9ESeh5btzNOZyD63KsYTd
# WnUE+P7pQvo1Geg6IHAONJrSBZ4lpttMf78MzoTItciD7GAeOgAoIPFjKWv7ambS
# WHN1bn3pmR1QYUpxar6Q5KoHyo1g24Lx+693JpRi98MFdEMmnLVTT3LA50mQZJXU
# Y6gRqqHhTbu/AD/px4Lof5tue0U7R9FD3PL4FYUJOl+bpuAugeWOtCFJTvIEZR3Q
# ym+xNTCimTu9VIYnkL1q9SRjjcN2os5ya6noSXf/frAGpbDIU7u6cFCeyPtJhjYC
# NGwu6Fv4KYJxNhS+2sTD7McIhAz9b2pSzzhs+ebv4vq6tDCCBnEwggRZoAMCAQIC
# CmEJgSoAAAAAAAIwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRp
# ZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTEwMDcwMTIxMzY1NVoXDTI1MDcwMTIx
# NDY1NVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV
# BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQG
# A1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggEiMA0GCSqGSIb3
# DQEBAQUAA4IBDwAwggEKAoIBAQCpHQ28dxGKOiDs/BOX9fp/aZRrdFQQ1aUKAIKF
# ++18aEssX8XD5WHCdrc+Zitb8BVTJwQxH0EbGpUdzgkTjnxhMFmxMEQP8WCIhFRD
# DNdNuDgIs0Ldk6zWczBXJoKjRQ3Q6vVHgc2/JGAyWGBG8lhHhjKEHnRhZ5FfgVSx
# z5NMksHEpl3RYRNuKMYa+YaAu99h/EbBJx0kZxJyGiGKr0tkiVBisV39dx898Fd1
# rL2KQk1AUdEPnAY+Z3/1ZsADlkR+79BL/W7lmsqxqPJ6Kgox8NpOBpG2iAg16Hgc
# sOmZzTznL0S6p/TcZL2kAcEgCZN4zfy8wMlEXV4WnAEFTyJNAgMBAAGjggHmMIIB
# 4jAQBgkrBgEEAYI3FQEEAwIBADAdBgNVHQ4EFgQU1WM6XIoxkPNDe3xGG8UzaFqF
# bVUwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud
# EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYD
# VR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwv
# cHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEB
# BE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9j
# ZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwgaAGA1UdIAEB/wSBlTCB
# kjCBjwYJKwYBBAGCNy4DMIGBMD0GCCsGAQUFBwIBFjFodHRwOi8vd3d3Lm1pY3Jv
# c29mdC5jb20vUEtJL2RvY3MvQ1BTL2RlZmF1bHQuaHRtMEAGCCsGAQUFBwICMDQe
# MiAdAEwAZQBnAGEAbABfAFAAbwBsAGkAYwB5AF8AUwB0AGEAdABlAG0AZQBuAHQA
# LiAdMA0GCSqGSIb3DQEBCwUAA4ICAQAH5ohRDeLG4Jg/gXEDPZ2joSFvs+umzPUx
# vs8F4qn++ldtGTCzwsVmyWrf9efweL3HqJ4l4/m87WtUVwgrUYJEEvu5U4zM9GAS
# inbMQEBBm9xcF/9c+V4XNZgkVkt070IQyK+/f8Z/8jd9Wj8c8pl5SpFSAK84Dxf1
# L3mBZdmptWvkx872ynoAb0swRCQiPM/tA6WWj1kpvLb9BOFwnzJKJ/1Vry/+tuWO
# M7tiX5rbV0Dp8c6ZZpCM/2pif93FSguRJuI57BlKcWOdeyFtw5yjojz6f32WapB4
# pm3S4Zz5Hfw42JT0xqUKloakvZ4argRCg7i1gJsiOCC1JeVk7Pf0v35jWSUPei45
# V3aicaoGig+JFrphpxHLmtgOR5qAxdDNp9DvfYPw4TtxCd9ddJgiCGHasFAeb73x
# 4QDf5zEHpJM692VHeOj4qEir995yfmFrb3epgcunCaw5u+zGy9iCtHLNHfS4hQEe
# gPsbiSpUObJb2sgNVZl6h3M7COaYLeqN4DMuEin1wC9UJyH3yKxO2ii4sanblrKn
# QqLJzxlBTeCG+SqaoxFmMNO7dDJL32N79ZmKLxvHIa9Zta7cRDyXUHHXodLFVeNp
# 3lfB0d4wwP3M5k37Db9dT+mdHhk4L7zPWAUu7w2gUDXa7wknHNWzfjUeCLraNtvT
# X4/edIhJEqGCAs4wggI3AgEBMIH4oYHQpIHNMIHKMQswCQYDVQQGEwJVUzETMBEG
# A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj
# cm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBP
# cGVyYXRpb25zMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjoxMkJDLUUzQUUtNzRF
# QjElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcG
# BSsOAwIaAxUA+nMNJAAghSaP1+UmH9+BnvMDG5mggYMwgYCkfjB8MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQg
# VGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIFAODkbgkwIhgPMjAx
# OTA3MjYwMjA2MDFaGA8yMDE5MDcyNzAyMDYwMVowdzA9BgorBgEEAYRZCgQBMS8w
# LTAKAgUA4ORuCQIBADAKAgEAAgIjsQIB/zAHAgEAAgIRujAKAgUA4OW/iQIBADA2
# BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIB
# AAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBALBALlDRychGSuD+GkEI4JwoERm4Y4Gd
# hNEdC6YiME6Ubp2EilbXJIryUsLUeUbDDu8QLplcik7PgmHlxRitNkbS1QLjLgSV
# 2cXPL1MqqVw3GnkkK0S4sLf7r6phpyMFyNM2Y1Mcw+xDWdk7wqxQm2thtO7zA6cM
# +DUAbshS52jGMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB
# IDIwMTACEzMAAAD4wl8z0LWPFQQAAAAAAPgwDQYJYIZIAWUDBAIBBQCgggFKMBoG
# CSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgj5kK2vo6
# fEFiQDobXK1XyL9WpNc32S2r/1jEieocjeUwgfoGCyqGSIb3DQEJEAIvMYHqMIHn
# MIHkMIG9BCB0JO0yGE8zWtffR/JEnox2OlsEDxZrOHB9U6eRbXOcDjCBmDCBgKR+
# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAA+MJfM9C1jxUEAAAA
# AAD4MCIEINFUiDqcruC/03ySExHTUTPqm5d9sYsfDSmA+4fx1pgKMA0GCSqGSIb3
# DQEBCwUABIIBAB6Gd46aRkwLftCS3Xio8iBHTgiLA72ZpcR7QyJyYj1JheIq6pzE
# kgwvsTUbMkkbEyJOw01aLxduv6bv8fQXpCGYP66I2VijuBp9NLceIhcJR52thgfd
# foak4gVTtl83oK1+GPbvtq7JcIOryX4ZV3UHmepsuHy3+wV2S7k4ce4EWR3ddSNb
# M2mRtS4X+AYgJDQ0V14M3Tk/05BI23HGKMJdgI04HdZq5d6CRDNEdhtF9mhh5b7A
# +vlEUzKJODdpGfWXyqdL0rF1QqGYBWZa4ghboUew8y8dSrzsAiam1ReLlW+bmjNi
# vxCIf41qw0OPx+weWvxytBJ98li0+V7X9Uo=
# SIG # End signature block