scripts/labdeploy.ps1 (882 lines of code) (raw):

# Credits to: # William Lam # Roberto Canton & Husam Hilal & Ludovic Rivallain # Website: https://aka.ms/AVSLabs param ( # Description: "Group number for the set of nested labs to deploy." [Parameter(Mandatory)] [ValidateRange(1, 50)] [Alias("GroupNumber")] [Int] $group, # Description: "Number of nested labs to be deployed." [Parameter(Mandatory)] [ValidateRange(1, 50)] [Alias("LabNumber")] [Int] $lab, # Description: "Is deployment in fully automated mode. Default to $false." [Parameter()] [Alias("IsAutomated")] [switch] $automated = $false, # Description: "AVS configuration hashtable." [Parameter()] [Alias("Credentials")] [hashtable] $AVSInfo ) #Examples: # labdeploy.ps1 -group 1 -lab 1 # labdeploy.ps1 -group 1 -lab 2 -automated # labdeploy.ps1 -group 1 -lab 2 -automated -AVSInfo $AVSInfo $ErrorActionPreference = "Stop" $timeStamp = Get-Date -Format "MM-dd-yyyy_hh:mm:ss" $timeStamp = $timeStamp.replace(':', '.') $verboseLogFile = "nested-lab-${group}-${lab}-${timeStamp}.log" function Write-Log { param( [Parameter(Mandatory = $true)] [String]$message ) $timeStamp = Get-Date -Format "MM-dd-yyyy_hh:mm:ss" Write-Host -NoNewline -ForegroundColor White "[$timestamp]" Write-Host -ForegroundColor Green " $message" $logMessage = "[$timeStamp] $message" $logMessage | Out-File -Force -LiteralPath .\$verboseLogFile -Append } Write-Log "Reading argurments, starting build process ........" Write-Log " - Group Number is $group" Write-Log " - Lab Number is $lab" Write-Log " - Automation is $automated" $groupNumber = $group $labNumber = $lab # Local Directory Information $mypath = Get-Location Write-Log "Local path is $mypath" # Reading credentials if ( $AVSInfo.Count -eq 0) { # Reading from nestedlabs.yml, setting variables for easier identification Write-Log "Reading from nestedlabs.yml file" Import-Module powershell-yaml [string]$fileContent = Get-Content -Raw 'nestedlabs.yml' $config = ConvertFrom-YAML $fileContent } else { Write-Log "Getting AVS SDDC Credentials through Parameter" $config = $AVSInfo } # vCenter Server Variables $VIServer = $config.AVSvCenter.IP $VIUsername = $config.AVSvCenter.Username $VIPassword = $config.AVSvCenter.Password Write-Log "vCenter Host: $VIServer" # NSX-T Server Variables $nsxtHost = $config.AVSNSXT.IP $nsxtUser = $config.AVSNSXT.Username $nsxtPass = $config.AVSNSXT.Password Write-Log "NSX-T Host: $nsxtHost" # AVS NSX-T Configurations $VMNetwork = "Group-${groupNumber}-${labNumber}-NestedLab" $VMNetworkCIDR = "10.${groupNumber}.${labNumber}.1/24" # Full Path to both the Nested ESXi VA and Extracted VCSA ISO $NestedESXiApplianceOVA = "${mypath}\Templates\Nested_ESXi7.0u3p_Appliance_Template_v1.ova" $VCSAInstallerPath = "${mypath}\Templates\VCSA7-Install" $PhotonNFSOVA = "${mypath}\Templates\PhotonOS_NFS_Appliance_0.1.0.ova" $PhotonOSOVA = "${mypath}\Templates\app-a-standalone.ova" $RouterOVA = "${mypath}\Templates\jammy-server-cloudimg-amd64.ova" $RouterUserDataPath = "${mypath}\router-userdata.yaml" # Nested ESXi VMs to deploy $NestedESXiHostnameToIPs = @{ "esxi-${groupNumber}-${labNumber}" = "10.${groupNumber}.${labNumber}.3" #"esxi-${groupNumber}-${labNumber}" = "10.${groupNumber}.${labNumber}.4" #"esxi-${groupNumber}-${labNumber}" = "10.${groupNumber}.${labNumber}.5" } # Nested ESXi VM Resources $NestedESXivCPU = "16" #Cores $NestedESXivMEM = "48" #GB # Defaults $defaultPassword = "MSFTavs1!" $defaultDomain = "avs.lab" # Nested NFS Information $NFSVMDisplayName = "nfs-${groupNumber}-${labNumber}" $NFSVMHostname = "nfs-${groupNumber}-${labNumber}.${defaultDomain}" $NFSVMIPAddress = "10.${groupNumber}.${labNumber}.7" $NFSVMPrefix = "24" $NFSVMVolumeLabel = "nfs" $NFSVMCapacity = "500" #GB $NFSVMRootPassword = $defaultPassword # If Routing for VM segment is needed $RouterVMDisplayName = "router-${groupNumber}-${labNumber}" $RouterVMHostname = "router-${groupNumber}-${labNumber}.avs.lab" $RouterVMIPAddress = "10.${groupNumber}.${labNumber}.8" $RouterVMPrefix = "24" $RouterVMPassword = $defaultPassword # VCSA Deployment Configuration $VCSADeploymentSize = "small" $VCSADisplayName = "vcsa-${groupNumber}-${labNumber}" $VCSAIPAddress = "10.${groupNumber}.${LabNumber}.2" $VCSAHostname = "10.${groupNumber}.${LabNumber}.2" #Change to IP if you don't have valid DNS $VCSAPrefix = "24" $VCSASSODomainName = $defaultDomain $VCSASSOPassword = $defaultPassword $VCSARootPassword = $defaultPassword $VCSASSHEnable = $true # General Deployment Configuration for Nested ESXi, VCSA & NSX VMs $VMDatacenter = "SDDC-Datacenter" $VMCluster = "Cluster-1" $VMResourcePool = "NestedLabs" $VMFolder = "NestedLabs" $VMDatastore = "vsanDatastore" $VMNetmask = "255.255.255.0" $VMGateway = "10.${groupNumber}.${labNumber}.1" $VMDNS = "1.1.1.1" $VMNTP = "pool.ntp.org" $VMPassword = $defaultPassword $VMDomain = $defaultDomain #$VMSyslog = "192.168.1.10" # Applicable to Nested ESXi only $ESXiVMSSH = $true # Name of new vSphere Datacenter/Cluster when VCSA is deployed $NewVCDatacenterName = "OnPrem-SDDC-Datacenter-${groupNumber}-${labNumber}" $NewVCVSANClusterName = "OnPrem-SDDC-Cluster-${groupNumber}-${labNumber}" $NewVCVDSName = "OnPrem-SDDC-VDS-${groupNumber}-${labNumber}" $NewVCMgmtDVPGName = "OnPrem-management-${groupNumber}-${labNumber}" $NewVCvMotionDVPGName = "OnPrem-vmotion-${groupNumber}-${labNumber}" $NewVCUplinkDVPGName = "OnPrem-uplink-${groupNumber}-${labNumber}" $NewVCReplicationDVPGName = "OnPrem-replication-${groupNumber}-${labNumber}" $NewVCWorkloadDVPGName = "OnPrem-workload-${groupNumber}-${labNumber}" $NewVCWorkloadVMFormat = "Workload-${groupNumber}-${labNumber}-" # workload-01,02,03,etc $NewVcWorkloadVMCount = 2 $NewVcVAppName = "Nested-SDDC-Lab-${groupNumber}-${labNumber}" # Advanced Configurations # Set to $true only if you have DNS (forward/reverse) for ESXi hostnames $addHostByDnsName = $false #### DO NOT EDIT BEYOND HERE #### $preCheck = $true $confirmDeployment = $true $deployNFSVM = $true $deployNestedESXiVMs = $true $deployVCSA = $true $setupNewVC = $true $addESXiHostsToVC = $true $configureESXiStorage = $true $configureVDS = $true $moveVMsIntovApp = $true $deployWorkload = $true $deployRouting = $true $vcsaSize2MemoryStorageMap = @{ "tiny" = @{"cpu" = "2"; "mem" = "12"; "disk" = "415" }; "small" = @{"cpu" = "4"; "mem" = "19"; "disk" = "480" }; "medium" = @{"cpu" = "8"; "mem" = "28"; "disk" = "700" }; "large" = @{"cpu" = "16"; "mem" = "37"; "disk" = "1065" }; "xlarge" = @{"cpu" = "24"; "mem" = "56"; "disk" = "1805" } } $esxiTotalCPU = 12 $vcsaTotalCPU = 0 $esxiTotalMemory = 48 $vcsaTotalMemory = 0 $esxiTotalStorage = 0 $StartTime = Get-Date if ($preCheck) { if (!(Test-Path $NestedESXiApplianceOVA)) { Write-Host -ForegroundColor Red "`nUnable to find $NestedESXiApplianceOVA ...`n" exit } if (!(Test-Path $VCSAInstallerPath)) { Write-Host -ForegroundColor Red "`nUnable to find $VCSAInstallerPath ...`n" exit } if (!(Test-Path $PhotonNFSOVA)) { Write-Host -ForegroundColor Red "`nUnable to find $PhotonNFSOVA ...`n" exit } if ($deployWorkload) { if (!(Test-Path $PhotonOSOVA)) { Write-Host -ForegroundColor Red "`nUnable to find $PhotonOSOVA ...`n" exit } } if ($deployRouting) { if (!(Test-Path $RouterOVA)) { Write-Host -ForegroundColor Red "`nUnable to find $RouterOVA ...`n" exit } } if ($PSVersionTable.PSEdition -ne "Core") { Write-Host -ForegroundColor Red "`tPowerShell Core was not detected, please install that before continuing ... `n" exit } } if ($confirmDeployment) { Write-Host -ForegroundColor Magenta "`nPlease confirm the following configuration will be deployed:`n" Write-Host -ForegroundColor Yellow "---- Nested SDDC Automated Lab Deployment Configuration ---- " Write-Host -NoNewline -ForegroundColor Green "SDDC Provider: " Write-Host -ForegroundColor White "Microsoft" Write-Host -NoNewline -ForegroundColor Green "VMware Cloud Service: " Write-Host -ForegroundColor White "Azure VMware Solution (AVS)" Write-Host -NoNewline -ForegroundColor Green "`nNested ESXi Image Path: " Write-Host -ForegroundColor White $NestedESXiApplianceOVA Write-Host -NoNewline -ForegroundColor Green "VCSA Image Path: " Write-Host -ForegroundColor White $VCSAInstallerPath Write-Host -NoNewline -ForegroundColor Green "NFS Image Path: " Write-Host -ForegroundColor White $PhotonNFSOVA if ($deployWorkload) { Write-Host -NoNewline -ForegroundColor Green "PhotonOS Image Path: " Write-Host -ForegroundColor White $PhotonOSOVA } Write-Host -ForegroundColor Yellow "`n---- vCenter Server Deployment Target Configuration ----" Write-Host -NoNewline -ForegroundColor Green "vCenter Server Address: " Write-Host -ForegroundColor White $VIServer Write-Host -NoNewline -ForegroundColor Green "VM Network: " Write-Host -ForegroundColor White $VMNetwork Write-Host -NoNewline -ForegroundColor Green "VM Cluster: " Write-Host -ForegroundColor White $VMCluster Write-Host -NoNewline -ForegroundColor Green "VM Resource Pool: " Write-Host -ForegroundColor White $VMResourcePool Write-Host -NoNewline -ForegroundColor Green "VM Storage: " Write-Host -ForegroundColor White $VMDatastore Write-Host -NoNewline -ForegroundColor Green "VM vApp: " Write-Host -ForegroundColor White $NewVcVAppName Write-Host -ForegroundColor Yellow "`n---- vESXi Configuration ----" Write-Host -NoNewline -ForegroundColor Green "# of Nested ESXi VMs: " Write-Host -ForegroundColor White $NestedESXiHostnameToIPs.count Write-Host -NoNewline -ForegroundColor Green "vCPU: " Write-Host -ForegroundColor White $NestedESXivCPU Write-Host -NoNewline -ForegroundColor Green "vMEM: " Write-Host -ForegroundColor White "$NestedESXivMEM GB" Write-Host -NoNewline -ForegroundColor Green "NFS Storage: " Write-Host -ForegroundColor White "$NFSVMCapacity GB" Write-Host -NoNewline -ForegroundColor Green "IP Address(s): " Write-Host -ForegroundColor White $NestedESXiHostnameToIPs.Values Write-Host -NoNewline -ForegroundColor Green "Netmask " Write-Host -ForegroundColor White $VMNetmask Write-Host -NoNewline -ForegroundColor Green "Gateway: " Write-Host -ForegroundColor White $VMGateway Write-Host -NoNewline -ForegroundColor Green "DNS: " Write-Host -ForegroundColor White $VMDNS Write-Host -NoNewline -ForegroundColor Green "NTP: " Write-Host -ForegroundColor White $VMNTP Write-Host -NoNewline -ForegroundColor Green "Syslog: " Write-Host -ForegroundColor White $VMSyslog Write-Host -NoNewline -ForegroundColor Green "Enable SSH: " Write-Host -ForegroundColor White $ESXiVMSSH Write-Host -ForegroundColor Yellow "`n---- VCSA Configuration ----" Write-Host -NoNewline -ForegroundColor Green "Deployment Size: " Write-Host -ForegroundColor White $VCSADeploymentSize Write-Host -NoNewline -ForegroundColor Green "SSO Domain: " Write-Host -ForegroundColor White $VCSASSODomainName Write-Host -NoNewline -ForegroundColor Green "Enable SSH: " Write-Host -ForegroundColor White $VCSASSHEnable Write-Host -NoNewline -ForegroundColor Green "Hostname: " Write-Host -ForegroundColor White $VCSAHostname Write-Host -NoNewline -ForegroundColor Green "IP Address: " Write-Host -ForegroundColor White $VCSAIPAddress Write-Host -NoNewline -ForegroundColor Green "Netmask " Write-Host -ForegroundColor White $VMNetmask Write-Host -NoNewline -ForegroundColor Green "Gateway: " Write-Host -ForegroundColor White $VMGateway $esxiTotalCPU = $NestedESXiHostnameToIPs.count * [int]$NestedESXivCPU $esxiTotalMemory = $NestedESXiHostnameToIPs.count * [int]$NestedESXivMEM $esxiTotalStorage = [int]$NFSCapacity $vcsaTotalCPU = $vcsaSize2MemoryStorageMap.$VCSADeploymentSize.cpu $vcsaTotalMemory = $vcsaSize2MemoryStorageMap.$VCSADeploymentSize.mem $vcsaTotalStorage = $vcsaSize2MemoryStorageMap.$VCSADeploymentSize.disk Write-Host -ForegroundColor Yellow "`n---- Resource Requirements ----" Write-Host -NoNewline -ForegroundColor Green "ESXi VM CPU: " Write-Host -NoNewline -ForegroundColor White $esxiTotalCPU Write-Host -NoNewline -ForegroundColor Green " ESXi VM Memory: " Write-Host -NoNewline -ForegroundColor White $esxiTotalMemory "GB " Write-Host -NoNewline -ForegroundColor Green "ESXi VM Storage: " Write-Host -ForegroundColor White $esxiTotalStorage "GB" Write-Host -NoNewline -ForegroundColor Green "VCSA VM CPU: " Write-Host -NoNewline -ForegroundColor White $vcsaTotalCPU Write-Host -NoNewline -ForegroundColor Green " VCSA VM Memory: " Write-Host -NoNewline -ForegroundColor White $vcsaTotalMemory "GB " Write-Host -NoNewline -ForegroundColor Green "VCSA VM Storage: " Write-Host -ForegroundColor White $vcsaTotalStorage "GB" $nfsCPU = 2 $nfsMemory = 4 $nfsStorage = $NFSCapacity Write-Host -NoNewline -ForegroundColor Green "NFS VM CPU: " Write-Host -NoNewline -ForegroundColor White $nfsCPU Write-Host -NoNewline -ForegroundColor Green " NFS VM Memory: " Write-Host -NoNewline -ForegroundColor White $nfsMemory " GB " Write-Host -NoNewline -ForegroundColor Green "NFS VM Storage: " Write-Host -ForegroundColor White $NFSVMCapacity " GB" $routerCPU = 2 $routerMemory = 1 $routerStorage = 10 Write-Host -NoNewline -ForegroundColor Green "Router VM CPU: " Write-Host -NoNewline -ForegroundColor White $routerCPU Write-Host -NoNewline -ForegroundColor Green " Router VM Memory: " Write-Host -NoNewline -ForegroundColor White $routerMemory " GB " Write-Host -NoNewline -ForegroundColor Green "Router VM Storage: " Write-Host -ForegroundColor White $routerStorage " GB" Write-Host -ForegroundColor White "---------------------------------------------" Write-Host -NoNewline -ForegroundColor Green "Total CPU: " Write-Host -ForegroundColor White ($esxiTotalCPU + $vcsaTotalCPU + $nsxManagerTotalCPU + $nsxEdgeTotalCPU + $nfsCPU + $routerCPU) Write-Host -NoNewline -ForegroundColor Green "Total Memory: " Write-Host -ForegroundColor White ($esxiTotalMemory + $vcsaTotalMemory + $nsxManagerTotalMemory + $nsxEdgeTotalMemory + $nfsMemory + $routerMemory) "GB" Write-Host -NoNewline -ForegroundColor Green "Total Storage: " Write-Host -ForegroundColor White ($esxiTotalStorage + $vcsaTotalStorage + $nsxManagerTotalStorage + $nsxEdgeTotalStorage + $nfsStorage + $routerStorage) "GB" Write-Host -ForegroundColor White "---------------------------------------------" Write-Host -ForegroundColor Magenta "`nWould you like to proceed with this deployment?`n" if (-Not $automated) { $answer = Read-Host -Prompt "Do you accept (Y or N)" if ($answer -ne "Y" -or $answer -ne "y") { exit } } else { Write-Host -ForegroundColor Green "`nAutomated Deployment!`n" } Write-Host -ForegroundColor Green "`n========= ============= =========`n" #Clear-Host // commenting this cmdlet to make script eligible to be invoked using ForEach-Object -Parallel } # Import the PowerCLI module Write-Log "Importing PowerCLI PowerShell Module" Import-Module VMware.PowerCLI Write-Log "Imported PowerCLI PowerShell Module Successfully" if ( $deployNFSVM -or $deployNestedESXiVMs -or $deployVCSA) { # Connecting to vCenter Server try { Write-Log "Connecting to Management vCenter Server $VIServer ..." $viConnection = Connect-VIServer -Server $VIServer -User $VIUsername -Password $VIPassword -WarningAction SilentlyContinue -ErrorAction Stop Write-Log "Connected to vCenter Server" }catch { Write-Log "Failed to connect to $VIServer. Error: $_" exit } # Connecting to NSX-T Manager Write-Log "Connecting to NSX-T Server $nsxtHost ..." $nsxtConnection = Connect-NsxtServer -Server ${nsxtHost} -User ${nsxtUser} -Password ${nsxtPass} Write-Log "Connected to NSX-T Server" # Create Resource Pool Write-Log "Creating $VMResourcePool if it does not exist ......" if (-Not (Get-ResourcePool -Name $VMResourcePool -Server $viConnection -ErrorAction Ignore)) { $newrp = New-ResourcePool -Server $viConnection -Location 'Cluster-1' -Name $VMResourcePool Write-Log "Creation of $VMResourcePool completed." } # Get Transport Zone ID: Transport Zone Overlay = $tzoneOverlay, Transport Zone Overlay ID = $tzoneOverlayID, tzPath Write-Log "Getting Transport Zone Overlay ID from NSX-T" $tzSvc = Get-NsxtService -Name com.vmware.nsx.transport_zones $tzones = $tzSvc.list() $tzoneOverlay = $tzones.results | Where-Object { $_.display_name -like 'TNT**-OVERLAY-TZ' } #TODO: Test if commenting the following line will cause any problem #$tzoneOverlayID = $tzoneOverlay.id $tzoneOverlay = $tzoneOverlay.display_name #TODO: Get-NsxtPolicyService is depricated, need to find alternative #Solution is as below but need to test switching from Connect-NsxtServer to Connect-NsxServer <# #References: https://blogs.vmware.com/networkvirtualization/2022/05/navigating-nsx-module-in-powercli-12-6.html/ # https://github.com/vmware-samples/nsx-t/tree/master/powercli Connect-NsxServer -Server $nsxtHost -User $nsxtUser -Password $nsxtPass $tzs = Invoke-ListTransportZonesForEnforcementPoint -EnforcementpointId "default" -SiteId "default" $tzPath = ($tzs.Results | Where-Object { $_.DisplayName -match 'TNT\d{2}-OVERLAY-TZ' }).Path | Select-Object -First 1 #> #TODO: Test if commenting the following line will cause any problem #$transportZonePolicyService = Get-NsxtPolicyService -Name "com.vmware.nsx_policy.infra.sites.enforcement_points.transport_zones" #$tzPath = ($transportZonePolicyService.list("default", "default").results | where { $_.display_name -like "TNT**-OVERLAY-TZ" }).path # Get Default T1 Gateway Write-Log "Getting NSX-T Default T1 Gateway" $t1svc = Get-NsxtService -Name com.vmware.nsx.logical_routers $t1list = $t1Svc.list() $t1result = $t1list.results | Where-Object { $_.display_name -like 'TNT**-T1' } #TODO: Test if commenting the following line will cause any problem #$t1ID = $t1result.id $t1Name = $t1result.display_name # Create Segment Profiles $getswitchprof = Get-NsxtService -Name com.vmware.nsx.switching_profiles $getswitchproflist = $getswitchprof.list() $getswitchprofresult = $getswitchproflist.results | Where-Object { $_.display_name -like 'Group${groupNumber}*' } $switchprofName = $getswitchprofresult.display_name # Create IP Discovery Segment Profile $IPProfileName = "Group${groupNumber}-IPDiscoveryProfile" if ($switchprofName -contains "$IPProfileName") { Write-Log "$IPProfileName already exists, will use it." } else { Write-Log "Creating $IPProfileName......" $uri = "https://$nsxtHost/policy/api/v1/infra/ip-discovery-profiles/$IPProfileName" $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($nsxtUser):$($nsxtPass)")) $Header = @{ Authorization = "Basic $base64AuthInfo" } $Body = @" { "resource_type": "IPDiscoveryProfile", "display_name": "$IPProfileName", "description": "", "ip_v4_discovery_options": { "arp_snooping_config": { "arp_snooping_enabled": true, "arp_binding_limit": 100 }, "dhcp_snooping_enabled": true, "vmtools_enabled": true }, "ip_v6_discovery_options": { "nd_snooping_config": { "nd_snooping_enabled": false, "nd_snooping_limit": 3 }, "dhcp_snooping_v6_enabled": false, "vmtools_v6_enabled": false }, "tofu_enabled": true, "arp_nd_binding_timeout": 10, "duplicate_ip_detection": { "duplicate_ip_detection_enabled": false } } "@ $ipprofile = Invoke-RestMethod -Uri $uri -Headers $Header -Method Patch -Body $Body -ContentType "application/json" -SkipCertificateCheck } ### Create MAC Discovery Segment Profile $MACProfileName = "Group${groupNumber}-MACDiscoveryProfile" if ($switchprofName -contains "$MACProfileName") { Write-Log "$MACProfileName already exists, will use it." } else { Write-Log "Creating $MACProfileName......" $uri = "https://$nsxtHost/policy/api/v1/infra/mac-discovery-profiles/$MACProfileName" $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($nsxtUser):$($nsxtPass)")) $Header = @{ Authorization = "Basic $base64AuthInfo" } $Body = @" { "resource_type":"MacDiscoveryProfile", "display_name": "${MacProfileName}", "description": "", "mac_change_enabled": true, "mac_learning_enabled": true, "unknown_unicast_flooding_enabled": true, "mac_limit_policy": "ALLOW", "mac_limit": 4096 } "@ $macprofile = Invoke-RestMethod -Uri $uri -Headers $Header -Method Patch -Body $Body -ContentType "application/json" -SkipCertificateCheck } # Create Segment Security Segment Profile $SegSecProfileName = "Group${groupNumber}-SegmentSecurityProfile" if ($switchprofName -contains "$SegSecProfileName") { Write-Log "$SegSecProfileName already exists, will use it." } else { Write-Log "Creating $SegSecProfileName......" $uri = "https://$nsxtHost/policy/api/v1/infra/segment-security-profiles/$SegSecProfileName" $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($nsxtUser):$($nsxtPass)")) $Header = @{ Authorization = "Basic $base64AuthInfo" } $Body = @" { "resource_type": "SegmentSecurityProfile", "id": "${SegSecProfileName}", "display_name": "${SegSecProfileName}", "description": "", "bpdu_filter_enable": false, "dhcp_server_block_enabled": false, "dhcp_client_block_enabled": false, "non_ip_traffic_block_enabled": false, "dhcp_server_block_v6_enabled": false, "dhcp_client_block_v6_enabled": false, "ra_guard_enabled": true } "@ $secprofile = Invoke-RestMethod -Uri $uri -Headers $Header -Method Patch -Body $Body -ContentType "application/json" -SkipCertificateCheck } ## Create Network Segment for Nested Lab Write-Log "Creating Network in AVS NSX-T for Nested Lab ${labNumber}" $segmentName = $VMNetwork $gatewayaddress = $VMNetworkCIDR Write-Log "Creating $segmentName....." $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($nsxtUser):$($nsxtPass)")) $Header = @{ Authorization = "Basic $base64AuthInfo" } $Body = @" { "display_name":"$segmentName", "subnets": [ { "gateway_address":"$gatewayaddress" } ], "connectivity_path": "/infra/tier-1s/$t1Name" } "@ $segmentURL = "https://$nsxtHost/policy/api/v1/infra/tier-1s/$t1Name/segments/" + $segmentName $existingSegment = Invoke-WebRequest -Uri $segmentURL -Headers $Header -Method GET -SkipCertificateCheck -SkipHttpErrorCheck if ($existingSegment.StatusCode -eq 200) { Write-Log "A segment $segmentName already exists, will reuse it" } else { $segmentCreation = Invoke-RestMethod -Uri $segmentURL -Headers $Header -Method Patch -Body $Body -ContentType "application/json" -SkipCertificateCheck Write-Log "Segment $segmentName created....." sleep 15 } ## Adding Security Segment Profile Write-Log "Adding Security Segment Profile to $segmentName ....." $bindingName = "Lab${groupNumber}-segment_security_binding_map" $uri = "https://$nsxtHost/policy/api/v1/infra/tier-1s/$t1Name/segments/${segmentName}/segment-security-profile-binding-maps/${bindingName}" $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($nsxtUser):$($nsxtPass)")) $Header = @{ Authorization = "Basic $base64AuthInfo" } $Body = @" { "resource_type": "SegmentSecurityProfileBindingMap", "id": "${bindingName}", "display_name": "${bindingName}", "path": "/infra/segments/${segmentName}/segment-security-profile-binding-maps/${bindingName}", "parent_path": "/infra/tier-1s/$t1Name/segments/${segmentName}", "relative_path": "${bindingName}", "marked_for_delete": false, "segment_security_profile_path": "/infra/segment-security-profiles/Group${groupNumber}-SegmentSecurityProfile" } "@ $secProfAdd = Invoke-RestMethod -Uri $uri -Headers $Header -Method Put -Body $Body -ContentType "application/json" -SkipCertificateCheck ## Adding Discovery Segment Profiles Write-Log "Adding Discovery Segment Profile to $segmentName ....." $bindingName = "Lab${groupNumber}-segment_discovery_binding_map" $uri = "https://$nsxtHost/policy/api/v1/infra/tier-1s/$t1Name/segments/${segmentName}/segment-discovery-profile-binding-maps/${bindingName}" $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($nsxtUser):$($nsxtPass)")) $Header = @{ Authorization = "Basic $base64AuthInfo" } $Body = @" { "resource_type":" SegmentDiscoveryProfileBindingMap", "display_name": "${bindingName}", "description":"", "mac_discovery_profile_path":"/infra/mac-discovery-profiles/Group${groupNumber}-MACDiscoveryProfile", "ip_discovery_profile_path":"/infra/ip-discovery-profiles/Group${groupNumber}-IPDiscoveryProfile" } "@ $discProfAdd = Invoke-RestMethod -Uri $uri -Headers $Header -Method Patch -Body $Body -ContentType "application/json" -SkipCertificateCheck # Get Logical Switch Information Write-Log "Getting Logical Switch Information for $segmentName" #TODO: Test if commenting the following line will cause any problem #$lssvc = Get-NsxtService -Name com.vmware.nsx.logical_switches #$lslist = $lsSvc.list() #$lsresult = $lslist.results | Where-Object { $_.display_name -eq "$network" } #$lsID = $lsresult.id #$lsName = $lsresult.display_name # Gather AVS vCenter Information $datastore = Get-Datastore -Server $viConnection -Name $VMDatastore | Select -First 1 $resourcepool = Get-ResourcePool -Server $viConnection -Name $VMResourcePool $cluster = Get-Cluster -Server $viConnection -Name $VMCluster $datacenter = $cluster | Get-Datacenter $vmhost = $cluster | Get-VMHost | Select -First 1 } if ($deployNestedESXiVMs) { $NestedESXiHostnameToIPs.GetEnumerator() | Sort-Object -Property Value | Foreach-Object { $VMName = $_.Key $VMIPAddress = $_.Value $ovfconfig = Get-OvfConfiguration $NestedESXiApplianceOVA $ovfNetworkLabel = ($ovfconfig.NetworkMapping | Get-Member -MemberType Properties).Name $ovfconfig.NetworkMapping.$ovfNetworkLabel.value = $VMNetwork Start-Sleep 15 $ovfconfig.common.guestinfo.hostname.value = $VMName $ovfconfig.common.guestinfo.ipaddress.value = $VMIPAddress $ovfconfig.common.guestinfo.netmask.value = $VMNetmask $ovfconfig.common.guestinfo.gateway.value = $VMGateway $ovfconfig.common.guestinfo.dns.value = $VMDNS $ovfconfig.common.guestinfo.domain.value = $VMDomain $ovfconfig.common.guestinfo.ntp.value = $VMNTP $ovfconfig.common.guestinfo.password.value = $VMPassword $ovfconfig.common.guestinfo.ssh.value = $ESXiVMSSH Write-Log "Deploying Nested ESXi VM $VMName ..." $vm = Import-VApp -Source $NestedESXiApplianceOVA -OvfConfiguration $ovfconfig -Name $VMName -Location $resourcepool -VMHost $vmhost -Datastore $datastore -DiskStorageFormat thin -Force Write-Log "Adding vmnic2/vmnic3 to $VMNetwork ..." #TODO: Specifying a distributed port group name as network name is no longer supported. Use the -Portgroup parameter. #https://developer.broadcom.com/powercli/latest/vmware.vimautomation.core/commands/new-networkadapter/ New-NetworkAdapter -VM $vm -Type Vmxnet3 -NetworkName $VMNetwork -StartConnected -confirm:$false | Out-File -Append -LiteralPath $verboseLogFile New-NetworkAdapter -VM $vm -Type Vmxnet3 -NetworkName $VMNetwork -StartConnected -confirm:$false | Out-File -Append -LiteralPath $verboseLogFile Write-Log "Updating vCPU Count to $NestedESXivCPU & vMEM to $NestedESXivMEM GB ..." Set-VM -Server $viConnection -VM $vm -NumCpu $NestedESXivCPU -MemoryGB $NestedESXivMEM -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile Write-Log "Powering On $vmname ..." $vm | Start-Vm -RunAsync | Out-Null } } if ($deployNFSVM) { $ovfconfig = Get-OvfConfiguration $PhotonNFSOVA $ovfNetworkLabel = ($ovfconfig.NetworkMapping | Get-Member -MemberType Properties).Name $ovfconfig.NetworkMapping.$ovfNetworkLabel.value = $VMNetwork $ovfconfig.common.guestinfo.hostname.value = $NFSVMHostname $ovfconfig.common.guestinfo.ipaddress.value = $NFSVMIPAddress $ovfconfig.common.guestinfo.netmask.value = $NFSVMPrefix $ovfconfig.common.guestinfo.gateway.value = $VMGateway $ovfconfig.common.guestinfo.dns.value = $VMDNS $ovfconfig.common.guestinfo.domain.value = $VMDomain $ovfconfig.common.guestinfo.root_password.value = $NFSVMRootPassword $ovfconfig.common.guestinfo.nfs_volume_name.value = $NFSVMVolumeLabel $ovfconfig.Common.disk2size.value = $NFSVMCapacity Write-Log "Deploying PhotonOS NFS VM $NFSVMDisplayName ..." $vm = Import-VApp -Source $PhotonNFSOVA -OvfConfiguration $ovfconfig -Name $NFSVMDisplayName -Location $resourcepool -VMHost $vmhost -Datastore $datastore -DiskStorageFormat thin -Force Write-Log "Powering On $NFSVMDisplayName ..." $vm | Start-Vm -RunAsync | Out-Null } if ($deployVCSA) { if ($IsWindows) { $config = (Get-Content -Raw "$($VCSAInstallerPath)\vcsa-cli-installer\templates\install\embedded_vCSA_on_VC.json") | ConvertFrom-Json } else { $config = (Get-Content -Raw "$($VCSAInstallerPath)/vcsa-cli-installer/templates/install/embedded_vCSA_on_VC.json") | ConvertFrom-Json } $config.'new_vcsa'.vc.hostname = $VIServer $config.'new_vcsa'.vc.username = $VIUsername $config.'new_vcsa'.vc.password = $VIPassword $config.'new_vcsa'.vc.deployment_network = $VMNetwork $config.'new_vcsa'.vc.datastore = $datastore $config.'new_vcsa'.vc.datacenter = $datacenter.name $config.'new_vcsa'.appliance.thin_disk_mode = $true $config.'new_vcsa'.appliance.deployment_option = $VCSADeploymentSize $config.'new_vcsa'.appliance.name = $VCSADisplayName $config.'new_vcsa'.network.ip_family = "ipv4" $config.'new_vcsa'.network.mode = "static" $config.'new_vcsa'.network.ip = $VCSAIPAddress $config.'new_vcsa'.network.dns_servers[0] = $VMDNS $config.'new_vcsa'.network.prefix = $VCSAPrefix $config.'new_vcsa'.network.gateway = $VMGateway $config.'new_vcsa'.os.ntp_servers = $VMNTP #TODO: #add: $config.'new_vcsa'.os.time_tools_sync = $true #remove: ntp_servers entry $config.'new_vcsa'.network.system_name = $VCSAHostname $config.'new_vcsa'.os.password = $VCSARootPassword $config.'new_vcsa'.os.ssh_enable = $VCSASSHEnable $config.'new_vcsa'.sso.password = $VCSASSOPassword $config.'new_vcsa'.sso.domain_name = $VCSASSODomainName # Hack due to JSON depth issue $config.'new_vcsa'.vc.psobject.Properties.Remove("target") $config.'new_vcsa'.vc | Add-Member NoteProperty -Name target -Value "REPLACE-ME" if ($IsWindows) { Write-Log "Creating VCSA JSON Configuration file for deployment ..." $config | ConvertTo-Json -WarningAction SilentlyContinue | Set-Content -Path "$($ENV:Temp)\jsontemplate.json" $target = "[`"$VMCluster`",`"Resources`",`"$VMResourcePool`"]" (Get-Content -path "$($ENV:Temp)\jsontemplate.json" -Raw) -replace '"REPLACE-ME"', $target | Set-Content -path "$($ENV:Temp)\jsontemplate.json" Write-Log "Deploying the VCSA ..." Invoke-Expression "$($VCSAInstallerPath)\vcsa-cli-installer\win32\vcsa-deploy.exe install --no-ssl-certificate-verification --accept-eula --acknowledge-ceip $($ENV:Temp)\jsontemplate.json" | Out-File -Append -LiteralPath $verboseLogFile } elseif ($IsMacOS) { Write-Log "Creating VCSA JSON Configuration file for deployment ..." $config | ConvertTo-Json -WarningAction SilentlyContinue | Set-Content -Path "$($ENV:TMPDIR)jsontemplate.json" Write-Log "Deploying the VCSA ..." Invoke-Expression "$($VCSAInstallerPath)/vcsa-cli-installer/mac/vcsa-deploy install --no-ssl-certificate-verification --accept-eula --acknowledge-ceip $($ENV:TMPDIR)jsontemplate.json" | Out-File -Append -LiteralPath $verboseLogFile } elseif ($IsLinux) { Write-Log "Creating VCSA JSON Configuration file for deployment ..." $config | ConvertTo-Json -WarningAction SilentlyContinue | Set-Content -Path "/tmp/jsontemplate.json" Write-Log "Deploying the VCSA ..." Invoke-Expression "$($VCSAInstallerPath)/vcsa-cli-installer/lin64/vcsa-deploy install --no-ssl-certificate-verification --accept-eula --acknowledge-ceip /tmp/jsontemplate.json" | Out-File -Append -LiteralPath $verboseLogFile } } if ($viConnection) { Write-Log "Disconnecting from $VIServer ..." Disconnect-VIServer -Server $viConnection -Confirm:$false } Write-Log "Reconnecting $VIServer" $viConnection = Connect-VIServer $VIServer -User $VIUsername -Password $VIPassword -WarningAction SilentlyContinue if ($moveVMsIntovApp) { Write-Log "Creating vApp $NewVcVAppName ..." $VApp = New-VApp -Name $NewVcVAppName -Server $viConnection -Location $resourcepool if (-Not (Get-Folder $VMFolder -ErrorAction Ignore)) { Write-Log "Creating VM Folder $VMFolder ..." $folder = New-Folder -Name $VMFolder -Server $viConnection -Location (Get-Datacenter $VMDatacenter | Get-Folder vm) } if ($deployNFSVM) { $vcsaVM = Get-VM -Name $NFSVMDisplayName -Server $viConnection Write-Log "Moving $NFSVMDisplayName into $NewVcVAppName vApp ..." Move-VM -VM $vcsaVM -Server $viConnection -Destination $VApp -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile } if ($deployNestedESXiVMs) { Write-Log "Moving Nested ESXi VMs into $NewVcVAppName vApp ..." $NestedESXiHostnameToIPs.GetEnumerator() | Sort-Object -Property Value | Foreach-Object { $vm = Get-VM -Name $_.Key -Server $viConnection Move-VM -VM $vm -Server $viConnection -Destination $VApp -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile } } if ($deployVCSA) { $vcsaVM = Get-VM -Name $VCSADisplayName -Server $viConnection Write-Log "Moving $VCSADisplayName into $NewVcVAppName vApp ..." Move-VM -VM $vcsaVM -Server $viConnection -Destination $VApp -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile } Write-Log "Moving $NewVcVAppName to VM Folder $VMFolder ..." Move-VApp -Server $viConnection $NewVcVAppName -Destination (Get-Folder -Server $viConnection $VMFolder) | Out-File -Append -LiteralPath $verboseLogFile } if ( $deployNFSVM -or $deployNestedESXiVMs -or $deployVCSA) { Write-Log "Disconnecting from $VIServer ..." Disconnect-VIServer -Server $viConnection -Confirm:$false } if ($setupNewVC) { Write-Log "Connecting to the new VCSA ..." $vc = Connect-VIServer $VCSAIPAddress -User "administrator@$VCSASSODomainName" -Password $VCSASSOPassword -WarningAction SilentlyContinue -Force $d = Get-Datacenter -Server $vc $NewVCDatacenterName -ErrorAction Ignore if ( -Not $d) { Write-Log "Creating Datacenter $NewVCDatacenterName ..." New-Datacenter -Server $vc -Name $NewVCDatacenterName -Location (Get-Folder -Type Datacenter -Server $vc) | Out-File -Append -LiteralPath $verboseLogFile } $c = Get-Cluster -Server $vc $NewVCVSANClusterName -ErrorAction Ignore if ( -Not $c) { Write-Log "Creating vSphere Cluster $NewVCVSANClusterName ..." New-Cluster -Server $vc -Name $NewVCVSANClusterName -Location (Get-Datacenter -Name $NewVCDatacenterName -Server $vc) -DrsEnabled | Out-File -Append -LiteralPath $verboseLogFile } if ($addESXiHostsToVC) { $NestedESXiHostnameToIPs.GetEnumerator() | Sort-Object -Property Value | Foreach-Object { $VMName = $_.Key $VMIPAddress = $_.Value $targetVMHost = $VMIPAddress if ($addHostByDnsName) { $targetVMHost = $VMName } Write-Log "Adding ESXi host $targetVMHost to Cluster ..." Add-VMHost -Server $vc -Location (Get-Cluster -Name $NewVCVSANClusterName) -User "root" -Password $VMPassword -Name $targetVMHost -Force | Out-File -Append -LiteralPath $verboseLogFile } } if ($configureESXiStorage) { $labDatastore = "LabDatastore" Write-Log "Adding NFS Storage ..." foreach ($vmhost in Get-Cluster -Server $vc | Get-VMHost) { $vmhost | New-Datastore -Nfs -Name $labDatastore -Path /mnt/${NFSVMVolumeLabel} -NfsHost $NFSVMIPAddress | Out-File -Append -LiteralPath $verboseLogFile } } if ($configureVDS) { $vds = New-VDSwitch -Server $vc -Name $NewVCVDSName -Location (Get-Datacenter -Name $NewVCDatacenterName) -Mtu 1600 $workloadVLANid = "${labNumber}00" New-VDPortgroup -Server $vc -Name $NewVCMgmtDVPGName -Vds $vds | Out-File -Append -LiteralPath $verboseLogFile New-VDPortgroup -Server $vc -Name $NewVCvMotionDVPGName -Vds $vds | Out-File -Append -LiteralPath $verboseLogFile New-VDPortgroup -Server $vc -Name $NewVCUplinkDVPGName -Vds $vds | Out-File -Append -LiteralPath $verboseLogFile New-VDPortgroup -Server $vc -Name $NewVCReplicationDVPGName -Vds $vds | Out-File -Append -LiteralPath $verboseLogFile New-VDPortgroup -Server $vc -Name $NewVCWorkloadDVPGName -VLanId $workloadVLANid -Vds $vds | Out-File -Append -LiteralPath $verboseLogFile foreach ($vmhost in Get-Cluster -Server $vc | Get-VMHost) { Write-Log "Adding $vmhost to $NewVCVDSName" $vds | Add-VDSwitchVMHost -VMHost $vmhost | Out-Null $vmhostNetworkAdapter = Get-VMHost $vmhost | Get-VMHostNetworkAdapter -Physical -Name vmnic1 $vds | Add-VDSwitchPhysicalNetworkAdapter -VMHostNetworkAdapter $vmhostNetworkAdapter -Confirm:$false } } # Final configure and then exit maintanence mode in case patching was done earlier foreach ($vmhost in Get-Cluster -Server $vc | Get-VMHost) { # Disable Core Dump Warning Get-AdvancedSetting -Entity $vmhost -Name UserVars.SuppressCoredumpWarning | Set-AdvancedSetting -Value 1 -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile # Enable vMotion traffic $vmhost | Get-VMHostNetworkAdapter -VMKernel | Set-VMHostNetworkAdapter -VMotionEnabled $true -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile if ($vmhost.ConnectionState -eq "Maintenance") { Set-VMHost -VMhost $vmhost -State Connected -RunAsync -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile } } if ($deployWorkload) { Write-Log "Deploying Workload VMs ..." $vmhost = Get-Cluster -Server $vc | Get-VMHost | Select-Object -First 1 $vcdatastore = Get-Datastore -Server $vc $appVMdns = "1.1.1.1" $appVMGateway = "10.${groupNumber}.1${labNumber}.129" $appVMIP = "10.${groupNumber}.1${labNumber}.128" $ovfconfig = Get-OvfConfiguration $PhotonOSOVA $ovfNetworkLabel = ($ovfconfig.NetworkMapping | Get-Member -MemberType Properties).Name $ovfconfig.NetworkMapping.$ovfNetworkLabel.value = $NewVCWorkloadDVPGName foreach ($i in 1..$NewVcWorkloadVMCount) { $VMName = "$NewVCWorkloadVMFormat$i" $a, $b, $c, $d = $appVMIP.Split(".") $d = [int]$d + $i+1 $newappVMIP = "${a}.${b}.${c}.${d}" $ovfconfig.Common.guestinfo.dns.value = "$appVMdns" $ovfconfig.Common.guestinfo.gateway.value = "$appVMgateway" $ovfconfig.Common.guestinfo.ipaddress.value = "$newappVMip" $ovfconfig.Common.guestinfo.netmask.value = "27" Write-Log "Deploying $VMName with IP $newappVMIP ..." $vm = Import-VApp -Server $vc -Source $PhotonOSOVA -OvfConfiguration $ovfconfig -Name $VMName -VMHost $vmhost -Datastore $vcdatastore -DiskStorageFormat thin -Force $vm | Start-VM -Server $vc -Confirm:$false | Out-Null Write-Log "$VMName deployed successfully and was powered on ..." } } if ($deployRouting) { $vmhost = Get-Cluster -Server $vc | Get-VMHost | Select-Object -First 1 $vcdatastore = Get-Datastore -Server $vc $ovfconfig = Get-OvfConfiguration $RouterOVA # Primary network mapping $ovfNetworkLabel = ($ovfconfig.NetworkMapping | Get-Member -MemberType Properties).Name $ovfconfig.NetworkMapping.$ovfNetworkLabel.value = $NewVCMgmtDVPGName # Cloud Init $routerUserData = Get-Content -Path $RouterUserDataPath | Out-String $routerUserData = $routerUserData.Replace("__PRIMARY_IPADDRESS__", $RouterVMIPAddress) $routerUserData = $routerUserData.Replace("__PRIMARY_NETMASK__", $RouterVMPrefix) $routerUserData = $routerUserData.Replace("__PRIMARY_GATEWAY__", $VMGateway) $routerUserData = $routerUserData.Replace("__SECONDIP_ADDRESS__", "10.${groupNumber}.1${labNumber}.129") $routerUserData = $routerUserData.Replace("__SECONDARY_NETMASK__", "27") # Prepare string for b64 encoding $routerUserDataBytes = [System.Text.Encoding]::UTF8.GetBytes($routerUserData) # OVF properties $ovfconfig.Common.hostname.value = $RouterVMHostname $ovfconfig.Common.instance_id.value = New-Guid $ovfconfig.Common.password.value = $RouterVMPassword $ovfconfig.Common.user_data.value = [Convert]::ToBase64String($routerUserDataBytes) Write-Log "Deploying Routing VM $RouterVMDisplayName ..." $vm = Import-VApp -Server $vc -Source $RouterOVA -OvfConfiguration $ovfconfig -Name $RouterVMHostname -VMHost $VMhost -Datastore $vcdatastore -DiskStorageFormat thin -Force Write-Log "Attaching Routing VM $RouterVMDisplayName to workload segment..." New-NetworkAdapter -VM $vm -Portgroup $NewVCWorkloadDVPGName -StartConnected | Out-File -Append -LiteralPath $verboseLogFile Write-Log "Powering On $RouterVMDisplayName ..." $vm | Start-Vm | Out-Null # wait for tools # Create static route on NSX segment to reach VM segment # Connecting to NSX-T Manager Write-Log "Connecting to NSX-T Server $nsxtHost ..." $nsxtConnection = Connect-NsxServer -Server ${nsxtHost} -User ${nsxtUser} -Password ${nsxtPass} Write-Log "Connected to NSX-T Server" # Get Default T1 Gateway Write-Log "Getting NSX-T Default T1 Gateway" $t1result = (Invoke-ListTier1 -Server $nsxtConnection).results | Where-Object { $_.DisplayName -like 'TNT**-T1' } $t1Name = $t1result.DisplayName $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($nsxtUser):$($nsxtPass)")) $Header = @{ Authorization = "Basic $base64AuthInfo" } # Remove existing route with same name $existingRoute = (Invoke-ListTier1StaticRoutes -Server $nsxtConnection -Tier1Id $t1Name).Results | Where-Object { $_.network -eq "10.${groupNumber}.1${labNumber}.128/27" } if ($existingRoute) { Write-Log "Removing an exisitng route with same network target" $sRouteURL = "https://$nsxtHost/policy/api/v1/infra/tier-1s/$t1Name/static-routes/" + $existingRoute.Id Invoke-RestMethod -Uri $sRouteURL -Headers $Header -Method DELETE -ContentType "application/json" -SkipCertificateCheck Start-Sleep 15 } $Body = @" { "display_name": "$NewVcVAppName", "network": "10.${groupNumber}.1${labNumber}.128/27", "next_hops":[ { "admin_distance":1, "ip_address": "$RouterVMIPAddress" } ], "id":"$NewVcVAppName" } "@ $sRouteURL = "https://$nsxtHost/policy/api/v1/infra/tier-1s/$t1Name/static-routes/$NewVcVAppName" $sRoute = Invoke-RestMethod -Uri $sRouteURL -Headers $Header -Method PUT -Body $Body -ContentType "application/json" -SkipCertificateCheck Start-Sleep 15 Write-Log "Static route $NewVcVAppName created....." Write-Log "Disconnecting from NSX ..." if ($nsxtConnection) { Disconnect-NsxServer -Connection $nsxtConnection #-ErrorAction SilentlyContinue -WarningAction SilentlyContinue } } Write-Log "Disconnecting from new VCSA ..." Disconnect-VIServer $vc -Confirm:$false } $EndTime = Get-Date $duration = [math]::Round((New-TimeSpan -Start $StartTime -End $EndTime).TotalMinutes, 2) Write-Log " " Write-Log " " Write-Log "Nested SDDC Lab Deployment Complete!" Write-Log " " Write-Log "Nested Lab Info:" Write-Log " - Group Number is $group" Write-Log " - Lab Number is $lab" Write-Log " - vCenter IP: $VCSAIPAddress" Write-Log " - vCenter Admin: administrator@$VCSASSODomainName" Write-Log " " Write-Log "Execution Summary" Write-Log "-StartTime: $StartTime" Write-Log "-EndTime: $EndTime" Write-Log "-Duration: $duration minutes"