├── README.md ├── screenshots ├── screenshot-1.png ├── screenshot-2.png ├── screenshot-3.png ├── screenshot-4.png ├── screenshot-5.png ├── screenshot-6.png └── screenshot-7.png └── vsphere-with-tanzu-nsx-advanced-lb-lab-deployment.ps1 /README.md: -------------------------------------------------------------------------------- 1 | # Automated vSphere with Tanzu using NSX Advanced Load Balancer Lab Deployment 2 | 3 | ## Table of Contents 4 | 5 | * [Description](#description) 6 | * [Changelog](#changelog) 7 | * [Requirements](#requirements) 8 | * [FAQ](#faq) 9 | * [Configuration](#configuration) 10 | * [Logging](#logging) 11 | * [Sample Execution](#sample-execution) 12 | * [Lab Deployment Script](#lab-deployment-script) 13 | * [Enable Workload Management](#enable-workload-management) 14 | 15 | ## Description 16 | 17 | Similar to previous "Automated Lab Deployment Scripts" (such as [here](https://www.williamlam.com/2016/11/vghetto-automated-vsphere-lab-deployment-for-vsphere-6-0u2-vsphere-6-5.html), [here](https://www.williamlam.com/2017/10/vghetto-automated-nsx-t-2-0-lab-deployment.html), [here](https://www.williamlam.com/2018/06/vghetto-automated-pivotal-container-service-pks-lab-deployment.html), [here](https://www.williamlam.com/2020/04/automated-vsphere-7-and-vsphere-with-kubernetes-lab-deployment-script.html) and [here](https://www.williamlam.com/2020/10/automated-vsphere-with-tanzu-lab-deployment-script.html)), this script makes it very easy for anyone to deploy a vSphere 7.0 Update 2 environment setup with [vSphere with Tanzu and the new NSX Advanced Load Balancer (NSX ALB)](https://core.vmware.com/blog/vsphere-tanzu-nsx-advanced-load-balancer-essentials) in a Nested Lab environment for learning and educational purposes. All required VMware components (ESXi, vCenter Server and NSX ALB VMs) are automatically deployed and configured to allow for the enablement of vSphere with Tanzu. For more details about vSphere with Tanzu, please refer to the official VMware documentation [here](https://docs.vmware.com/en/VMware-vSphere/7.0/vmware-vsphere-with-tanzu/GUID-152BE7D2-E227-4DAA-B527-557B564D9718.html). 18 | 19 | Below is a diagram of what is deployed as part of the solution and you simply need to have an existing vSphere environment running that is managed by vCenter Server and with enough resources (CPU, Memory and Storage) to deploy this "Nested" lab. For workload management enablement (post-deployment operation), please have a look at the [Sample Execution](#sample-execution) section below. 20 | 21 | You are now ready to get your K8s on! 😁 22 | 23 | ![](screenshots/screenshot-1.png) 24 | 25 | ## Changelog 26 | 27 | * **04/05/2021** 28 | * Initial Release 29 | 30 | ## Requirements 31 | * vCenter Server running at least vSphere 6.7 or later 32 | * If your physical storage is vSAN, please ensure you've applied the following setting as mentioned [here](https://www.williamlam.com/2013/11/how-to-run-nested-esxi-on-top-of-vsan.html) 33 | * ESXi Networking 34 | * Enable either [MAC Learning](https://williamlam.com/2018/04/native-mac-learning-in-vsphere-6-7-removes-the-need-for-promiscuous-mode-for-nested-esxi.html) or [Promiscuous Mode](https://kb.vmware.com/kb/1004099) on your physical ESXi host networking to ensure proper network connectivity for Nested ESXi workloads 35 | * Resource Requirements 36 | * Compute 37 | * Ability to provision VMs with up to 8 vCPU 38 | * Ability to provision up to 108 GB of memory 39 | * DRS-enabled Cluster (not required but vApp creation will not be possible) 40 | * Network 41 | * 1 x Standard or Distributed Portgroup (routable) to deploy all VMs (vSphere Management, NSX ALB + Supervisor Management) 42 | * 5 x IP Addresses for VCSA, ESXi and NSX ALB VM 43 | * 5 x Consecutive IP Addresses for Kubernetes Control Plane VMs 44 | * 8 x IP Addresses for NSX ALB Service Engine VMs 45 | * 1 x Standard or Distributed Portgroup (routable) for combined Load Balancer IPs + Workload Network (e.g. 172.17.32.128/26) 46 | * IP Range for NSX ALB VIP/Load Balancer addresses (e.g. 172.17.32.152-172.17.32.159) 47 | * IP Range for Workload Network (e.g. 172.17.32.160-172.17.32.179) 48 | * Storage 49 | * Ability to provision up to 1TB of storage 50 | 51 | **Note:** For detailed requirements, plesae refer to the official document [here](https://docs.vmware.com/en/VMware-vSphere/7.0/vmware-vsphere-with-tanzu/GUID-EE236215-DA4D-4579-8BEB-A693D1882C77.html) 52 | 53 | * Desktop (Windows, Mac or Linux) with latest PowerShell Core and PowerCLI 12.1 Core installed. See [ instructions here](https://blogs.vmware.com/PowerCLI/2018/03/installing-powercli-10-0-0-macos.html) for more details 54 | * vSphere 7 Update 2 & NSX ALB OVAs: 55 | * [vCenter Server Appliance 7.0 Update 2 Build 17694817](https://my.vmware.com/web/vmware/downloads/details?downloadGroup=VC70U2&productId=974&rPId=61844) 56 | * [NSX ALB 20.1.4-9087 OVA](https://my.vmware.com/web/vmware/downloads/details?downloadGroup=NSX-ALB-10&productId=1092&rPId=55618) 57 | * [Nested ESXi 7.0 Update 2 OVA](https://community.broadcom.com/flings) 58 | 59 | ## FAQ 60 | 61 | 1) Can I reduce the default CPU, Memory and Storage resources? 62 | 63 | * You can, see this [blog post](https://www.williamlam.com/2020/04/deploying-a-minimal-vsphere-with-kubernetes-environment.html) for more details. I have not personally tested reducing CPU and Memory resources for NSX ALB, YMMV. 64 | 65 | 3) Can I just deploy vSphere (VCSA, ESXi) and vSAN without NSX ALB and vSphere with Tanzu? 66 | 67 | * Yes, simply search for the following variables and change their values to `0` to not deploy Tanzu components or run through the configurations 68 | 69 | ``` 70 | $deployNSXAdvLB = 0 71 | $setupTanzuStoragePolicy = 0 72 | $setupTanzu = 0 73 | $setupNSXAdvLB = 0 74 | ``` 75 | 6) How do I enable Workload Management after the script has completed? 76 | 77 | * Please see [Enable Workload Management](#enable-workload-management) section for instructions 78 | 79 | 7) How do I troubleshoot enabling or consuming vSphere with Tanzu? 80 | 81 | * Please refer to this [troubleshooting tips for vSphere with Kubernetes](https://www.williamlam.com/2020/05/troubleshooting-tips-for-configuring-vsphere-with-kubernetes.html) blog post 82 | 83 | 8) Is there a way to automate the enablement of Workload Management to a vSphere Cluster? 84 | 85 | * Yes, the [Workload Management PowerCLI Module for automating vSphere with Tanzu](https://www.williamlam.com/2020/05/workload-management-powercli-module-for-automating-vsphere-with-kubernetes.html) can be used. Please see [Enable Workload Management](#enable-workload-management) section for instructions 86 | 87 | 9) Can I deploy vSphere with Tanzu using NSX-T instead of NSX ALB? 88 | * Yes, you will need to use the previous version of the [Automated vSphere with Kubernetes deployment script](https://www.williamlam.com/2020/04/automated-vsphere-7-and-vsphere-with-kubernetes-lab-deployment-script.html) and substituting the vSphere 7.0 Update 2 images 89 | 90 | ## Configuration 91 | 92 | Before you can run the script, you will need to edit the script and update a number of variables to match your deployment environment. Details on each section is described below including actual values used in my home lab environment. 93 | 94 | This section describes the credentials to your physical vCenter Server in which the vSphere with Tanzu lab environment will be deployed to: 95 | ```console 96 | $VIServer = "FILL-ME-IN" 97 | $VIUsername = "FILL-ME-IN" 98 | $VIPassword = "FILL-ME-IN" 99 | ``` 100 | 101 | This section describes the required TKG Content Library needed for deploying TKG Clusters. 102 | 103 | ```console 104 | $TKGContentLibraryName = "TKG-Content-Library" 105 | $TKGContentLibraryURL = "https://wp-content.vmware.com/v2/latest/lib.json" 106 | ``` 107 | 108 | This section describes the location of the files required for deployment. 109 | 110 | ```console 111 | $NestedESXiApplianceOVA = "C:\Users\william\Desktop\tanzu\Nested_ESXi7.0u2_Appliance_Template_v1.ova" 112 | $VCSAInstallerPath = "C:\Users\william\Desktop\tanzu\VMware-VCSA-all-7.0.2-17694817" 113 | $NSXAdvLBOVA = "C:\Users\william\Desktop\tanzu\controller-20.1.4-9087.ova" 114 | ``` 115 | **Note:** The path to the VCSA Installer must be the extracted contents of the ISO 116 | 117 | This section describes the Tanzu Kubernetes Grid vSphere Content Library to subscribed from. This should be left alone and just ensure your environment has outbound connectivity to this endpoint 118 | 119 | ```console 120 | $TKGContentLibraryName = "TKG-Content-Library" 121 | $TKGContentLibraryURL = "https://wp-content.vmware.com/v2/latest/lib.json" 122 | ``` 123 | 124 | This section defines the number of Nested ESXi VMs to deploy along with their associated IP Address(s). The names are merely the display name of the VMs when deployed. At a minimum, you should deploy at least three hosts, but you can always add additional hosts and the script will automatically take care of provisioning them correctly. 125 | ```console 126 | $NestedESXiHostnameToIPs = @{ 127 | "tanzu-esxi-1" = "172.17.33.4" 128 | "tanzu-esxi-2" = "172.17.33.5" 129 | "tanzu-esxi-3" = "172.17.33.6" 130 | } 131 | ``` 132 | 133 | This section describes the resources allocated to each of the Nested ESXi VM(s). Depending on your usage, you may need to increase the resources. For Memory and Disk configuration, the unit is in GB. 134 | ```console 135 | $NestedESXivCPU = "4" 136 | $NestedESXivMEM = "24" #GB 137 | $NestedESXiCachingvDisk = "8" #GB 138 | $NestedESXiCapacityvDisk = "200" #GB 139 | ``` 140 | 141 | This section describes the VCSA deployment configuration such as the VCSA deployment size, Networking & SSO configurations. If you have ever used the VCSA CLI Installer, these options should look familiar. 142 | ```console 143 | $VCSADeploymentSize = "tiny" 144 | $VCSADisplayName = "tanzu-vcsa-1" 145 | $VCSAIPAddress = "172.17.33.3" 146 | $VCSAHostname = "tanzu-vcsa-1.tshirts.inc" #Change to IP if you don't have valid DNS 147 | $VCSAPrefix = "24" 148 | $VCSASSODomainName = "vsphere.local" 149 | $VCSASSOPassword = "VMware1!" 150 | $VCSARootPassword = "VMware1!" 151 | $VCSASSHEnable = "true" 152 | ``` 153 | 154 | This section describes the NSX ALB VM configurations 155 | 156 | ```console 157 | $NSXAdvLBDisplayName = "tanzu-nsx-alb" 158 | $NSXAdvLByManagementIPAddress = "172.17.33.9" 159 | $NSXAdvLBHostname = "tanzu-nsx-alb.tshirts.inc" 160 | $NSXAdvLBAdminPassword = "VMware1!" 161 | $NSXAdvLBvCPU = "8" #GB 162 | $NSXAdvLBvMEM = "24" #GB 163 | $NSXAdvLBPassphrase = "VMware" 164 | $NSXAdvLBIPAMName = "Tanzu-Default-IPAM" 165 | ``` 166 | 167 | This section describes the Service Engine Network Configuration 168 | 169 | ```console 170 | $NSXAdvLBManagementNetwork = "172.17.33.0" 171 | $NSXAdvLBManagementNetworkPrefix = "24" 172 | $NSXAdvLBManagementNetworkStartRange = "172.17.33.180" 173 | $NSXAdvLBManagementNetworkEndRange = "172.17.33.187" 174 | ```` 175 | 176 | This section describes the combined VIP/Frontend and Workload Network Configuration 177 | 178 | ```console 179 | $NSXAdvLBCombinedVIPWorkloadNetwork = "Nested-Tanzu-Workload" 180 | $NSXAdvLBWorkloadNetwork = "172.17.32.128" 181 | $NSXAdvLBWorkloadNetworkPrefix = "26" 182 | $NSXAdvLBWorkloadNetworkStartRange = "172.17.32.152" 183 | $NSXAdvLBWorkloadNetworkEndRange = "172.17.32.159" 184 | ``` 185 | 186 | This section describes the self-sign TLS certificate to generate for NSX ALB 187 | 188 | ```console 189 | $NSXAdvLBSSLCertName = "nsx-alb" 190 | $NSXAdvLBSSLCertExpiry = "365" # Days 191 | $NSXAdvLBSSLCertEmail = "admini@primp-industries.local" 192 | $NSXAdvLBSSLCertOrganizationUnit = "R&D" 193 | $NSXAdvLBSSLCertOrganization = "primp-industries" 194 | $NSXAdvLBSSLCertLocation = "Palo Alto" 195 | $NSXAdvLBSSLCertState = "CA" 196 | $NSXAdvLBSSLCertCountry = "US" 197 | ``` 198 | 199 | This section describes the location as well as the generic networking settings applied to Nested ESXi VCSA & NSX VMs 200 | 201 | ```console 202 | $VMDatacenter = "San Jose" 203 | $VMCluster = "Compute Cluster" 204 | $VMNetwork = "Nested-Tanzu-Mgmt" 205 | $VMDatastore = "comp-vsanDatastore" 206 | $VMNetmask = "255.255.255.0" 207 | $VMGateway = "172.17.33.1" 208 | $VMDNS = "172.17.31.2" 209 | $VMNTP = "45.87.78.35" 210 | $VMPassword = "VMware1!" 211 | $VMDomain = "tshirts.inc" 212 | $VMSyslog = "172.17.33.3" 213 | $VMFolder = "Tanzu" 214 | # Applicable to Nested ESXi only 215 | $VMSSH = "true" 216 | $VMVMFS = "false" 217 | ``` 218 | 219 | This section describes the configuration of the new vCenter Server from the deployed VCSA. **Default values are sufficient.** 220 | 221 | ```console 222 | $NewVCDatacenterName = "Tanzu-Datacenter" 223 | $NewVCVSANClusterName = "Workload-Cluster" 224 | $NewVCVDSName = "Tanzu-VDS" 225 | $NewVCMgmtPortgroupName = "DVPG-Supervisor-Management-Network" 226 | $NewVCWorkloadPortgroupName = "DVPG-Workload-Network" 227 | ``` 228 | 229 | This section describes the Tanzu Configurations. **Default values are sufficient.** 230 | 231 | ```console 232 | $StoragePolicyTagCategory = "tanzu-demo-tag-category" 233 | $StoragePolicyTagName = "tanzu-demo-storage" 234 | $DevOpsUsername = "devops" 235 | $DevOpsPassword = "VMware1!" 236 | ``` 237 | 238 | Once you have saved your changes, you can now run the PowerCLI script as you normally would. 239 | 240 | ## Logging 241 | 242 | There is additional verbose logging that outputs as a log file in your current working directory **tanzu-nsx-adv-lb-lab-deployment.log** 243 | 244 | ## Sample Execution 245 | 246 | In this example below, I will be using a two /24 VLANs (172.17.33.0/24 and 172.17.32.0/24). The first network will be used to provision all VMs and place them under typical vSphere Management network configuration and 5 IPs will be allocated from this range for the Supervisor Control Plane and 8 IPs for the NSX ALB Service Engine. The second network will combine both IP ranges for the NSX ALB VIP/Frontend function as well as the IP ranges for Workloads. See the table below for the explicit network mappings and it is expected that you have a setup similar to what has been outlined. 247 | 248 | | Hostname | IP Address | Function | 249 | |---------------------------|-----------------------------|--------------------------------------------------------| 250 | | tanzu-vcsa-1.tshirts.inc | 172.17.33.3 | vCenter Server | 251 | | tanzu-esxi-1.tshirts.inc | 172.17.33.4 | ESXi-1 | 252 | | tanzu-esxi-2.tshirts.inc | 172.17.33.5 | ESXi-2 | 253 | | tanzu-esxi-3.tshirts.inc | 172.17.33.6 | ESXi-3 | 254 | | tanzu-nsx-alb.tshirts.inc | 172.17.33.9 | NSX ALB | 255 | | N/A | 172.17.33.180-172.17.33.187 | Service Engine (8 IPs from Mgmt Network) | 256 | | N/A | 172.17.33.190-172.17.33.195 | Supervisor Control Plane (5 IPs from Mgmt Network) | 257 | | N/A | 172.17.32.152-172.17.32.159 | NSX ALB VIP/Load Balancer IP Range | 258 | | N/A | 172.17.32.160-172.17.32.179 | Workload IP Range | 259 | 260 | ### Lab Deployment Script 261 | 262 | Here is a screenshot of running the script if all basic pre-reqs have been met and the confirmation message before starting the deployment: 263 | 264 | ![](screenshots/screenshot-2.png) 265 | 266 | Here is an example output of a complete deployment: 267 | 268 | ![](screenshots/screenshot-3.png) 269 | 270 | **Note:** Deployment time will vary based on underlying physical infrastructure resources. In my lab, this took ~29min to complete. 271 | 272 | Once completed, you will end up with your deployed vSphere with Kubernetes Lab which is placed into a vApp 273 | 274 | ![](screenshots/screenshot-4.png) 275 | 276 | ### Enable Workload Management 277 | 278 | To consume the vSphere with Tanzu capability in vSphere 7.0 Update 2, you must enable workload management on a specific vSphere Cluster, which is not part of this automation script. 279 | 280 | You have two options: 281 | 282 | 1) You can enable workload management by using the vSphere UI. For more details, please refer to the official VMware documentation [here](https://docs.vmware.com/en/VMware-vSphere/7.0/vmware-vsphere-with-kubernetes/GUID-21ABC792-0A23-40EF-8D37-0367B483585E.html). 283 | 284 | Here is an example output of what the deployment configuration should look like for my network setup: 285 | 286 | ![](screenshots/screenshot-5.png) 287 | 288 | 2) You can use my community [PowerCLI module (VMware.WorkloadManagement)](https://www.powershellgallery.com/packages/VMware.WorkloadManagement/1.0.9) to automate the enablement of workload management: 289 | 290 | Here is an example using the `New-WorkloadManagement3` function and the same configuration as shown in the UI above to enable workload management: 291 | 292 | ```console 293 | Import-Module VMware.WorkloadManagement 294 | 295 | $vSphereWithTanzuParams = @{ 296 | TanzuvCenterServer = "tanzu-vcsa-1.tshirts.inc"; 297 | TanzuvCenterServerUsername = "administrator@vsphere.local"; 298 | TanzuvCenterServerPassword = "VMware1!"; 299 | ClusterName = "Workload-Cluster"; 300 | TanzuContentLibrary = "TKG-Content-Library"; 301 | ControlPlaneSize = "TINY"; 302 | MgmtNetworkStartIP = "172.17.33.190"; 303 | MgmtNetworkSubnet = "255.255.255.0"; 304 | MgmtNetworkGateway = "172.17.33.1"; 305 | MgmtNetworkDNS = @("172.17.31.2"); 306 | MgmtNetworkDNSDomain = "tshirts.inc"; 307 | MgmtNetworkNTP = @("5.199.135.170"); 308 | WorkloadNetworkStartIP = "172.17.32.160"; 309 | WorkloadNetworkIPCount = 8; 310 | WorkloadNetworkSubnet = "255.255.255.0"; 311 | WorkloadNetworkGateway = "172.17.32.1"; 312 | WorkloadNetworkDNS = @("172.17.31.2"); 313 | WorkloadNetworkServiceCIDR = "10.96.0.0/24"; 314 | StoragePolicyName = "tanzu-gold-storage-policy"; 315 | NSXALBIPAddress = "172.17.33.9"; 316 | NSXALBPort = "443"; 317 | NSXALBCertName = "nsx-alb" 318 | NSXALBUsername = "admin"; 319 | NSXALBPassword = "VMware1!"; 320 | } 321 | New-WorkloadManagement3 @vSphereWithTanzuParams 322 | ``` 323 | 324 | Here is an example output of running the above snippet: 325 | 326 | ![](screenshots/screenshot-6.png) 327 | 328 | Upon a successful enablement of workload management, you should have two NSX ALB Service Engine VMs deployed: 329 | 330 | ![](screenshots/screenshot-7.png) 331 | -------------------------------------------------------------------------------- /screenshots/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lamw/vsphere-with-tanzu-nsx-advanced-lb-automated-lab-deployment/95ca314eacbe172b305d808e4c6b248bc677cc6f/screenshots/screenshot-1.png -------------------------------------------------------------------------------- /screenshots/screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lamw/vsphere-with-tanzu-nsx-advanced-lb-automated-lab-deployment/95ca314eacbe172b305d808e4c6b248bc677cc6f/screenshots/screenshot-2.png -------------------------------------------------------------------------------- /screenshots/screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lamw/vsphere-with-tanzu-nsx-advanced-lb-automated-lab-deployment/95ca314eacbe172b305d808e4c6b248bc677cc6f/screenshots/screenshot-3.png -------------------------------------------------------------------------------- /screenshots/screenshot-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lamw/vsphere-with-tanzu-nsx-advanced-lb-automated-lab-deployment/95ca314eacbe172b305d808e4c6b248bc677cc6f/screenshots/screenshot-4.png -------------------------------------------------------------------------------- /screenshots/screenshot-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lamw/vsphere-with-tanzu-nsx-advanced-lb-automated-lab-deployment/95ca314eacbe172b305d808e4c6b248bc677cc6f/screenshots/screenshot-5.png -------------------------------------------------------------------------------- /screenshots/screenshot-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lamw/vsphere-with-tanzu-nsx-advanced-lb-automated-lab-deployment/95ca314eacbe172b305d808e4c6b248bc677cc6f/screenshots/screenshot-6.png -------------------------------------------------------------------------------- /screenshots/screenshot-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lamw/vsphere-with-tanzu-nsx-advanced-lb-automated-lab-deployment/95ca314eacbe172b305d808e4c6b248bc677cc6f/screenshots/screenshot-7.png -------------------------------------------------------------------------------- /vsphere-with-tanzu-nsx-advanced-lb-lab-deployment.ps1: -------------------------------------------------------------------------------- 1 | # Author: William Lam 2 | # Website: www.williamlam.com 3 | 4 | # vCenter Server used to deploy vSphere with Tanzu with NSX Advanced Load Balancer Lab 5 | $VIServer = "FILL-ME-IN" 6 | $VIUsername = "FILL-ME-IN" 7 | $VIPassword = "FILL-ME-IN" 8 | 9 | # Full Path to both the Nested ESXi 7.0 VA, Extracted VCSA 7.0 ISO & NSX Advanced 20.1.4 OVA 10 | $NestedESXiApplianceOVA = "C:\Users\william\Desktop\tanzu\Nested_ESXi7.0u2_Appliance_Template_v1.ova" 11 | $VCSAInstallerPath = "C:\Users\william\Desktop\tanzu\VMware-VCSA-all-7.0.2-17694817" 12 | $NSXAdvLBOVA = "C:\Users\william\Desktop\tanzu\controller-20.1.4-9087.ova" 13 | 14 | # TKG Content Library URL 15 | $TKGContentLibraryName = "TKG-Content-Library" 16 | $TKGContentLibraryURL = "https://wp-content.vmware.com/v2/latest/lib.json" 17 | 18 | # Nested ESXi VMs to deploy 19 | $NestedESXiHostnameToIPs = @{ 20 | "tanzu-esxi-1" = "172.17.33.4" 21 | "tanzu-esxi-2" = "172.17.33.5" 22 | "tanzu-esxi-3" = "172.17.33.6" 23 | } 24 | 25 | # Nested ESXi VM Resources 26 | $NestedESXivCPU = "4" 27 | $NestedESXivMEM = "24" #GB 28 | $NestedESXiCachingvDisk = "8" #GB 29 | $NestedESXiCapacityvDisk = "200" #GB 30 | 31 | # VCSA Deployment Configuration 32 | $VCSADeploymentSize = "tiny" 33 | $VCSADisplayName = "tanzu-vcsa-1" 34 | $VCSAIPAddress = "172.17.33.3" 35 | $VCSAHostname = "tanzu-vcsa-1.tshirts.inc" #Change to IP if you don't have valid DNS 36 | $VCSAPrefix = "24" 37 | $VCSASSODomainName = "vsphere.local" 38 | $VCSASSOPassword = "VMware1!" 39 | $VCSARootPassword = "VMware1!" 40 | $VCSASSHEnable = "true" 41 | 42 | # NSX Advanced LB Configuration 43 | $NSXAdvLBDisplayName = "tanzu-nsx-alb" 44 | $NSXAdvLByManagementIPAddress = "172.17.33.9" 45 | $NSXAdvLBHostname = "tanzu-nsx-alb.tshirts.inc" 46 | $NSXAdvLBAdminPassword = "VMware1!" 47 | $NSXAdvLBvCPU = "8" #GB 48 | $NSXAdvLBvMEM = "24" #GB 49 | $NSXAdvLBPassphrase = "VMware" 50 | $NSXAdvLBIPAMName = "Tanzu-Default-IPAM" 51 | 52 | # Service Engine Management Network Configuration 53 | $NSXAdvLBManagementNetwork = "172.17.33.0" 54 | $NSXAdvLBManagementNetworkPrefix = "24" 55 | $NSXAdvLBManagementNetworkStartRange = "172.17.33.180" 56 | $NSXAdvLBManagementNetworkEndRange = "172.17.33.187" 57 | 58 | # VIP/Workload Network Configuration 59 | $NSXAdvLBCombinedVIPWorkloadNetwork = "Nested-Tanzu-Workload" 60 | $NSXAdvLBWorkloadNetwork = "172.17.32.128" 61 | $NSXAdvLBWorkloadNetworkPrefix = "26" 62 | $NSXAdvLBWorkloadNetworkStartRange = "172.17.32.152" 63 | $NSXAdvLBWorkloadNetworkEndRange = "172.17.32.159" 64 | 65 | # Self-Sign TLS Certificate 66 | $NSXAdvLBSSLCertName = "nsx-alb" 67 | $NSXAdvLBSSLCertExpiry = "365" # Days 68 | $NSXAdvLBSSLCertEmail = "admini@primp-industries.local" 69 | $NSXAdvLBSSLCertOrganizationUnit = "R&D" 70 | $NSXAdvLBSSLCertOrganization = "primp-industries" 71 | $NSXAdvLBSSLCertLocation = "Palo Alto" 72 | $NSXAdvLBSSLCertState = "CA" 73 | $NSXAdvLBSSLCertCountry = "US" 74 | 75 | # General Deployment Configuration for Nested ESXi, VCSA & NSX Adv LB VM 76 | $VMDatacenter = "San Jose" 77 | $VMCluster = "Compute Cluster" 78 | $VMNetwork = "Nested-Tanzu-Mgmt" 79 | $VMDatastore = "comp-vsanDatastore" 80 | $VMNetmask = "255.255.255.0" 81 | $VMGateway = "172.17.33.1" 82 | $VMDNS = "172.17.31.2" 83 | $VMNTP = "45.87.78.35" 84 | $VMPassword = "VMware1!" 85 | $VMDomain = "tshirts.inc" 86 | $VMSyslog = "172.17.33.3" 87 | $VMFolder = "Tanzu" 88 | # Applicable to Nested ESXi only 89 | $VMSSH = "true" 90 | $VMVMFS = "false" 91 | 92 | # Name of new vSphere Datacenter/Cluster when VCSA is deployed 93 | $NewVCDatacenterName = "Tanzu-Datacenter" 94 | $NewVCVSANClusterName = "Workload-Cluster" 95 | $NewVCVDSName = "Tanzu-VDS" 96 | $NewVCMgmtPortgroupName = "DVPG-Supervisor-Management-Network" 97 | $NewVCWorkloadPortgroupName = "DVPG-Workload-Network" 98 | 99 | # Tanzu Configuration 100 | $StoragePolicyName = "tanzu-gold-storage-policy" 101 | $StoragePolicyTagCategory = "tanzu-demo-tag-category" 102 | $StoragePolicyTagName = "tanzu-demo-storage" 103 | $DevOpsUsername = "devops" 104 | $DevOpsPassword = "VMware1!" 105 | 106 | # Advanced Configurations 107 | # Set to 1 only if you have DNS (forward/reverse) for ESXi hostnames 108 | $addHostByDnsName = 1 109 | 110 | #### DO NOT EDIT BEYOND HERE #### 111 | 112 | $verboseLogFile = "tanzu-nsx-adv-lb-lab-deployment.log" 113 | $random_string = -join ((65..90) + (97..122) | Get-Random -Count 8 | % {[char]$_}) 114 | $VAppName = "Nested-Tanzu-NSX-Adv-LB-Lab-$random_string" 115 | 116 | $preCheck = 1 117 | $confirmDeployment = 1 118 | $deployNSXAdvLB = 1 119 | $deployNestedESXiVMs = 1 120 | $deployVCSA = 1 121 | $setupNewVC = 1 122 | $addESXiHostsToVC = 1 123 | $configureVSANDiskGroup = 1 124 | $configureVDS = 1 125 | $clearVSANHealthCheckAlarm = 1 126 | $setupTanzuStoragePolicy = 1 127 | $setupTanzu = 1 128 | $setupNSXAdvLB = 1 129 | $moveVMsIntovApp = 1 130 | 131 | $vcsaSize2MemoryStorageMap = @{ 132 | "tiny"=@{"cpu"="2";"mem"="12";"disk"="415"}; 133 | "small"=@{"cpu"="4";"mem"="19";"disk"="480"}; 134 | "medium"=@{"cpu"="8";"mem"="28";"disk"="700"}; 135 | "large"=@{"cpu"="16";"mem"="37";"disk"="1065"}; 136 | "xlarge"=@{"cpu"="24";"mem"="56";"disk"="1805"} 137 | } 138 | 139 | $esxiTotalCPU = 0 140 | $vcsaTotalCPU = 0 141 | $esxiTotalMemory = 0 142 | $vcsaTotalMemory = 0 143 | $esxiTotalStorage = 0 144 | $vcsaTotalStorage = 0 145 | $nsxalbTotalStorage = 128 146 | 147 | $StartTime = Get-Date 148 | 149 | Function Get-SSLThumbprint { 150 | param( 151 | [Parameter( 152 | Position=0, 153 | Mandatory=$true, 154 | ValueFromPipeline=$true, 155 | ValueFromPipelineByPropertyName=$true) 156 | ] 157 | [Alias('FullName')] 158 | [String]$URL 159 | ) 160 | 161 | $Code = @' 162 | using System; 163 | using System.Collections.Generic; 164 | using System.Net.Http; 165 | using System.Net.Security; 166 | using System.Security.Cryptography.X509Certificates; 167 | namespace CertificateCapture 168 | { 169 | public class Utility 170 | { 171 | public static Func ValidationCallback = 172 | (message, cert, chain, errors) => { 173 | var newCert = new X509Certificate2(cert); 174 | var newChain = new X509Chain(); 175 | newChain.Build(newCert); 176 | CapturedCertificates.Add(new CapturedCertificate(){ 177 | Certificate = newCert, 178 | CertificateChain = newChain, 179 | PolicyErrors = errors, 180 | URI = message.RequestUri 181 | }); 182 | return true; 183 | }; 184 | public static List CapturedCertificates = new List(); 185 | } 186 | public class CapturedCertificate 187 | { 188 | public X509Certificate2 Certificate { get; set; } 189 | public X509Chain CertificateChain { get; set; } 190 | public SslPolicyErrors PolicyErrors { get; set; } 191 | public Uri URI { get; set; } 192 | } 193 | } 194 | '@ 195 | if ($PSEdition -ne 'Core'){ 196 | Add-Type -AssemblyName System.Net.Http 197 | if (-not ("CertificateCapture" -as [type])) { 198 | Add-Type $Code -ReferencedAssemblies System.Net.Http 199 | } 200 | } else { 201 | if (-not ("CertificateCapture" -as [type])) { 202 | Add-Type $Code 203 | } 204 | } 205 | 206 | $Certs = [CertificateCapture.Utility]::CapturedCertificates 207 | 208 | $Handler = [System.Net.Http.HttpClientHandler]::new() 209 | $Handler.ServerCertificateCustomValidationCallback = [CertificateCapture.Utility]::ValidationCallback 210 | $Client = [System.Net.Http.HttpClient]::new($Handler) 211 | $Result = $Client.GetAsync($Url).Result 212 | 213 | $sha1 = [Security.Cryptography.SHA1]::Create() 214 | $certBytes = $Certs[-1].Certificate.GetRawCertData() 215 | $hash = $sha1.ComputeHash($certBytes) 216 | $thumbprint = [BitConverter]::ToString($hash).Replace('-',':') 217 | return $thumbprint.toLower() 218 | } 219 | 220 | Function My-Logger { 221 | param( 222 | [Parameter(Mandatory=$true)][String]$message, 223 | [Parameter(Mandatory=$false)][String]$color="green" 224 | ) 225 | 226 | $timeStamp = Get-Date -Format "MM-dd-yyyy_hh:mm:ss" 227 | 228 | Write-Host -NoNewline -ForegroundColor White "[$timestamp]" 229 | Write-Host -ForegroundColor $color " $message" 230 | $logMessage = "[$timeStamp] $message" 231 | $logMessage | Out-File -Append -LiteralPath $verboseLogFile 232 | } 233 | 234 | if($preCheck -eq 1) { 235 | if(!(Test-Path $NestedESXiApplianceOVA)) { 236 | Write-Host -ForegroundColor Red "`nUnable to find $NestedESXiApplianceOVA ...`n" 237 | exit 238 | } 239 | 240 | if(!(Test-Path $VCSAInstallerPath)) { 241 | Write-Host -ForegroundColor Red "`nUnable to find $VCSAInstallerPath ...`n" 242 | exit 243 | } 244 | 245 | if( !(Test-Path $NSXAdvLBOVA) -and $deployNSXAdvLB -eq 1) { 246 | Write-Host -ForegroundColor Red "`nUnable to find $NSXAdvLBOVA ...`n" 247 | exit 248 | } 249 | 250 | if($PSVersionTable.PSEdition -ne "Core") { 251 | Write-Host -ForegroundColor Red "`tPowerShell Core was not detected, please install that before continuing ... `n" 252 | exit 253 | } 254 | } 255 | 256 | if($confirmDeployment -eq 1) { 257 | Write-Host -ForegroundColor Magenta "`nPlease confirm the following configuration will be deployed:`n" 258 | 259 | Write-Host -ForegroundColor Yellow "---- vSphere with Tanzu Basic Automated Lab Deployment Configuration ---- " 260 | Write-Host -NoNewline -ForegroundColor Green "Nested ESXi Image Path: " 261 | Write-Host -ForegroundColor White $NestedESXiApplianceOVA 262 | Write-Host -NoNewline -ForegroundColor Green "VCSA Image Path: " 263 | Write-Host -ForegroundColor White $VCSAInstallerPath 264 | Write-Host -NoNewline -ForegroundColor Green "HA Proxy Image Path: " 265 | Write-Host -ForegroundColor White $NSXAdvLBOVA 266 | 267 | Write-Host -ForegroundColor Yellow "`n---- vCenter Server Deployment Target Configuration ----" 268 | Write-Host -NoNewline -ForegroundColor Green "vCenter Server Address: " 269 | Write-Host -ForegroundColor White $VIServer 270 | Write-Host -NoNewline -ForegroundColor Green "VM Network: " 271 | Write-Host -ForegroundColor White $VMNetwork 272 | 273 | Write-Host -NoNewline -ForegroundColor Green "VM Storage: " 274 | Write-Host -ForegroundColor White $VMDatastore 275 | Write-Host -NoNewline -ForegroundColor Green "VM Cluster: " 276 | Write-Host -ForegroundColor White $VMCluster 277 | Write-Host -NoNewline -ForegroundColor Green "VM vApp: " 278 | Write-Host -ForegroundColor White $VAppName 279 | 280 | Write-Host -ForegroundColor Yellow "`n---- vESXi Configuration ----" 281 | Write-Host -NoNewline -ForegroundColor Green "# of Nested ESXi VMs: " 282 | Write-Host -ForegroundColor White $NestedESXiHostnameToIPs.count 283 | Write-Host -NoNewline -ForegroundColor Green "vCPU: " 284 | Write-Host -ForegroundColor White $NestedESXivCPU 285 | Write-Host -NoNewline -ForegroundColor Green "vMEM: " 286 | Write-Host -ForegroundColor White "$NestedESXivMEM GB" 287 | Write-Host -NoNewline -ForegroundColor Green "Caching VMDK: " 288 | Write-Host -ForegroundColor White "$NestedESXiCachingvDisk GB" 289 | Write-Host -NoNewline -ForegroundColor Green "Capacity VMDK: " 290 | Write-Host -ForegroundColor White "$NestedESXiCapacityvDisk GB" 291 | Write-Host -NoNewline -ForegroundColor Green "IP Address(s): " 292 | Write-Host -ForegroundColor White $NestedESXiHostnameToIPs.Values 293 | Write-Host -NoNewline -ForegroundColor Green "Netmask " 294 | Write-Host -ForegroundColor White $VMNetmask 295 | Write-Host -NoNewline -ForegroundColor Green "Gateway: " 296 | Write-Host -ForegroundColor White $VMGateway 297 | Write-Host -NoNewline -ForegroundColor Green "DNS: " 298 | Write-Host -ForegroundColor White $VMDNS 299 | Write-Host -NoNewline -ForegroundColor Green "NTP: " 300 | Write-Host -ForegroundColor White $VMNTP 301 | Write-Host -NoNewline -ForegroundColor Green "Syslog: " 302 | Write-Host -ForegroundColor White $VMSyslog 303 | Write-Host -NoNewline -ForegroundColor Green "Enable SSH: " 304 | Write-Host -ForegroundColor White $VMSSH 305 | Write-Host -NoNewline -ForegroundColor Green "Create VMFS Volume: " 306 | Write-Host -ForegroundColor White $VMVMFS 307 | 308 | Write-Host -ForegroundColor Yellow "`n---- VCSA Configuration ----" 309 | Write-Host -NoNewline -ForegroundColor Green "Deployment Size: " 310 | Write-Host -ForegroundColor White $VCSADeploymentSize 311 | Write-Host -NoNewline -ForegroundColor Green "SSO Domain: " 312 | Write-Host -ForegroundColor White $VCSASSODomainName 313 | Write-Host -NoNewline -ForegroundColor Green "Enable SSH: " 314 | Write-Host -ForegroundColor White $VCSASSHEnable 315 | Write-Host -NoNewline -ForegroundColor Green "Hostname: " 316 | Write-Host -ForegroundColor White $VCSAHostname 317 | Write-Host -NoNewline -ForegroundColor Green "IP Address: " 318 | Write-Host -ForegroundColor White $VCSAIPAddress 319 | Write-Host -NoNewline -ForegroundColor Green "Netmask " 320 | Write-Host -ForegroundColor White $VMNetmask 321 | Write-Host -NoNewline -ForegroundColor Green "Gateway: " 322 | Write-Host -ForegroundColor White $VMGateway 323 | 324 | Write-Host -ForegroundColor Yellow "`n---- NSX Advanced LB Configuration ----" 325 | Write-Host -NoNewline -ForegroundColor Green "Hostname: " 326 | Write-Host -ForegroundColor White $NSXAdvLBHostname 327 | Write-Host -NoNewline -ForegroundColor Green "Management IP Address: " 328 | Write-Host -ForegroundColor White $NSXAdvLByManagementIPAddress 329 | Write-Host -ForegroundColor Green "Service Engine: " 330 | Write-Host -NoNewline -ForegroundColor Green " Portgroup: " 331 | Write-Host -ForegroundColor White $VMNetwork 332 | Write-Host -NoNewline -ForegroundColor Green " Network: " 333 | Write-Host -ForegroundColor White $NSXAdvLBManagementNetwork/$NSXAdvLBManagementNetworkPrefix 334 | Write-Host -NoNewline -ForegroundColor Green " Range: " 335 | Write-Host -ForegroundColor White "$NSXAdvLBManagementNetworkStartRange to $NSXAdvLBManagementNetworkEndRange" 336 | Write-Host -ForegroundColor Green "Combined VIP/Workload: " 337 | Write-Host -NoNewline -ForegroundColor Green " Portgroup: " 338 | Write-Host -ForegroundColor White $NSXAdvLBCombinedVIPWorkloadNetwork 339 | Write-Host -NoNewline -ForegroundColor Green " Network: " 340 | Write-Host -ForegroundColor White $NSXAdvLBWorkloadNetwork/$NSXAdvLBWorkloadNetworkPrefix 341 | Write-Host -NoNewline -ForegroundColor Green " Range: " 342 | Write-Host -ForegroundColor White "$NSXAdvLBWorkloadNetworkStartRange to $NSXAdvLBWorkloadNetworkEndRange" 343 | 344 | $esxiTotalCPU = $NestedESXiHostnameToIPs.count * [int]$NestedESXivCPU 345 | $esxiTotalMemory = $NestedESXiHostnameToIPs.count * [int]$NestedESXivMEM 346 | $esxiTotalStorage = ($NestedESXiHostnameToIPs.count * [int]$NestedESXiCachingvDisk) + ($NestedESXiHostnameToIPs.count * [int]$NestedESXiCapacityvDisk) 347 | $vcsaTotalCPU = $vcsaSize2MemoryStorageMap.$VCSADeploymentSize.cpu 348 | $vcsaTotalMemory = $vcsaSize2MemoryStorageMap.$VCSADeploymentSize.mem 349 | $vcsaTotalStorage = $vcsaSize2MemoryStorageMap.$VCSADeploymentSize.disk 350 | 351 | Write-Host -ForegroundColor Yellow "`n---- Resource Requirements ----" 352 | Write-Host -NoNewline -ForegroundColor Green "ESXi VM CPU: " 353 | Write-Host -NoNewline -ForegroundColor White $esxiTotalCPU 354 | Write-Host -NoNewline -ForegroundColor Green " ESXi VM Memory: " 355 | Write-Host -NoNewline -ForegroundColor White $esxiTotalMemory "GB " 356 | Write-Host -NoNewline -ForegroundColor Green "ESXi VM Storage: " 357 | Write-Host -ForegroundColor White $esxiTotalStorage "GB" 358 | Write-Host -NoNewline -ForegroundColor Green "VCSA VM CPU: " 359 | Write-Host -NoNewline -ForegroundColor White $vcsaTotalCPU 360 | Write-Host -NoNewline -ForegroundColor Green " VCSA VM Memory: " 361 | Write-Host -NoNewline -ForegroundColor White $vcsaTotalMemory "GB " 362 | Write-Host -NoNewline -ForegroundColor Green "VCSA VM Storage: " 363 | Write-Host -ForegroundColor White $vcsaTotalStorage "GB" 364 | Write-Host -NoNewline -ForegroundColor Green "NSX ALB VM CPU: " 365 | Write-Host -NoNewline -ForegroundColor White $NSXAdvLBvCPU 366 | Write-Host -NoNewline -ForegroundColor Green " NSX ALB VM Memory: " 367 | Write-Host -NoNewline -ForegroundColor White $NSXAdvLBvMEM "GB " 368 | Write-Host -NoNewline -ForegroundColor Green "NSX ALB VM Storage: " 369 | Write-Host -ForegroundColor White $nsxalbTotalStorage "GB" 370 | 371 | Write-Host -ForegroundColor White "---------------------------------------------" 372 | Write-Host -NoNewline -ForegroundColor Green "Total CPU: " 373 | Write-Host -ForegroundColor White ($esxiTotalCPU + $vcsaTotalCPU + $nsxManagerTotalCPU + $NSXAdvLBvCPU) 374 | Write-Host -NoNewline -ForegroundColor Green "Total Memory: " 375 | Write-Host -ForegroundColor White ($esxiTotalMemory + $vcsaTotalMemory + $NSXAdvLBvMEM) "GB" 376 | Write-Host -NoNewline -ForegroundColor Green "Total Storage: " 377 | Write-Host -ForegroundColor White ($esxiTotalStorage + $vcsaTotalStorage + $nsxalbTotalStorage) "GB" 378 | 379 | Write-Host -ForegroundColor Magenta "`nWould you like to proceed with this deployment?`n" 380 | $answer = Read-Host -Prompt "Do you accept (Y or N)" 381 | if($answer -ne "Y" -or $answer -ne "y") { 382 | exit 383 | } 384 | Clear-Host 385 | } 386 | 387 | if( $deployNestedESXiVMs -eq 1 -or $deployVCSA -eq 1 -or $deployNSXAdvLB -eq 1) { 388 | My-Logger "Connecting to Management vCenter Server $VIServer ..." 389 | $viConnection = Connect-VIServer $VIServer -User $VIUsername -Password $VIPassword -WarningAction SilentlyContinue 390 | 391 | $datastore = Get-Datastore -Server $viConnection -Name $VMDatastore | Select -First 1 392 | $cluster = Get-Cluster -Server $viConnection -Name $VMCluster 393 | $datacenter = $cluster | Get-Datacenter 394 | $vmhost = $cluster | Get-VMHost | Select -First 1 395 | } 396 | 397 | if($deployNestedESXiVMs -eq 1) { 398 | $NestedESXiHostnameToIPs.GetEnumerator() | Sort-Object -Property Value | Foreach-Object { 399 | $VMName = $_.Key 400 | $VMIPAddress = $_.Value 401 | 402 | $ovfconfig = Get-OvfConfiguration $NestedESXiApplianceOVA 403 | $networkMapLabel = ($ovfconfig.ToHashTable().keys | where {$_ -Match "NetworkMapping"}).replace("NetworkMapping.","").replace("-","_").replace(" ","_") 404 | $ovfconfig.NetworkMapping.$networkMapLabel.value = $VMNetwork 405 | 406 | $ovfconfig.common.guestinfo.hostname.value = $VMName 407 | $ovfconfig.common.guestinfo.ipaddress.value = $VMIPAddress 408 | $ovfconfig.common.guestinfo.netmask.value = $VMNetmask 409 | $ovfconfig.common.guestinfo.gateway.value = $VMGateway 410 | $ovfconfig.common.guestinfo.dns.value = $VMDNS 411 | $ovfconfig.common.guestinfo.domain.value = $VMDomain 412 | $ovfconfig.common.guestinfo.ntp.value = $VMNTP 413 | $ovfconfig.common.guestinfo.syslog.value = $VMSyslog 414 | $ovfconfig.common.guestinfo.password.value = $VMPassword 415 | if($VMSSH -eq "true") { 416 | $VMSSHVar = $true 417 | } else { 418 | $VMSSHVar = $false 419 | } 420 | $ovfconfig.common.guestinfo.ssh.value = $VMSSHVar 421 | 422 | My-Logger "Deploying Nested ESXi VM $VMName ..." 423 | $vm = Import-VApp -Source $NestedESXiApplianceOVA -OvfConfiguration $ovfconfig -Name $VMName -Location $cluster -VMHost $vmhost -Datastore $datastore -DiskStorageFormat thin 424 | 425 | My-Logger "Adding vmnic2/vmnic3 for `"$VMNetwork`" and `"$NSXAdvLBCombinedVIPWorkloadNetwork`" to passthrough to Nested ESXi VMs ..." 426 | New-NetworkAdapter -VM $vm -Type Vmxnet3 -NetworkName $VMNetwork -StartConnected -confirm:$false | Out-File -Append -LiteralPath $verboseLogFile 427 | New-NetworkAdapter -VM $vm -Type Vmxnet3 -NetworkName $NSXAdvLBCombinedVIPWorkloadNetwork -StartConnected -confirm:$false | Out-File -Append -LiteralPath $verboseLogFile 428 | 429 | $vm | New-AdvancedSetting -name "ethernet2.filter4.name" -value "dvfilter-maclearn" -confirm:$false -ErrorAction SilentlyContinue | Out-File -Append -LiteralPath $verboseLogFile 430 | $vm | New-AdvancedSetting -Name "ethernet2.filter4.onFailure" -value "failOpen" -confirm:$false -ErrorAction SilentlyContinue | Out-File -Append -LiteralPath $verboseLogFile 431 | 432 | $vm | New-AdvancedSetting -name "ethernet3.filter4.name" -value "dvfilter-maclearn" -confirm:$false -ErrorAction SilentlyContinue | Out-File -Append -LiteralPath $verboseLogFile 433 | $vm | New-AdvancedSetting -Name "ethernet3.filter4.onFailure" -value "failOpen" -confirm:$false -ErrorAction SilentlyContinue | Out-File -Append -LiteralPath $verboseLogFile 434 | 435 | My-Logger "Updating vCPU Count to $NestedESXivCPU & vMEM to $NestedESXivMEM GB ..." 436 | Set-VM -Server $viConnection -VM $vm -NumCpu $NestedESXivCPU -MemoryGB $NestedESXivMEM -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile 437 | 438 | My-Logger "Updating vSAN Cache VMDK size to $NestedESXiCachingvDisk GB & Capacity VMDK size to $NestedESXiCapacityvDisk GB ..." 439 | Get-HardDisk -Server $viConnection -VM $vm -Name "Hard disk 2" | Set-HardDisk -CapacityGB $NestedESXiCachingvDisk -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile 440 | Get-HardDisk -Server $viConnection -VM $vm -Name "Hard disk 3" | Set-HardDisk -CapacityGB $NestedESXiCapacityvDisk -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile 441 | 442 | My-Logger "Powering On $vmname ..." 443 | $vm | Start-Vm -RunAsync | Out-Null 444 | } 445 | } 446 | 447 | if($deployNSXAdvLB -eq 1) { 448 | $ovfconfig = Get-OvfConfiguration $NSXAdvLBOVA 449 | 450 | $ovfconfig.NetworkMapping.Management.value = $VMNetwork 451 | $ovfconfig.avi.CONTROLLER.mgmt_ip.value = $NSXAdvLByManagementIPAddress 452 | $ovfconfig.avi.CONTROLLER.default_gw.value = $VMGateway 453 | 454 | My-Logger "Deploying NSX Advanced LB VM $NSXAdvLBDisplayName ..." 455 | $vm = Import-VApp -Source $NSXAdvLBOVA -OvfConfiguration $ovfconfig -Name $NSXAdvLBDisplayName -Location $cluster -VMHost $vmhost -Datastore $datastore -DiskStorageFormat thin 456 | 457 | My-Logger "Updating vCPU Count to $NSXAdvLBvCPU & vMEM to $NSXAdvLBvMEM GB ..." 458 | Set-VM -Server $viConnection -VM $vm -NumCpu $NSXAdvLBvCPU -MemoryGB $NSXAdvLBvMEM -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile 459 | 460 | My-Logger "Powering On $NSXAdvLBDisplayName ..." 461 | $vm | Start-Vm -RunAsync | Out-Null 462 | } 463 | 464 | if($deployVCSA -eq 1) { 465 | if($IsWindows) { 466 | $config = (Get-Content -Raw "$($VCSAInstallerPath)\vcsa-cli-installer\templates\install\embedded_vCSA_on_VC.json") | convertfrom-json 467 | } else { 468 | $config = (Get-Content -Raw "$($VCSAInstallerPath)/vcsa-cli-installer/templates/install/embedded_vCSA_on_VC.json") | convertfrom-json 469 | } 470 | 471 | $config.'new_vcsa'.vc.hostname = $VIServer 472 | $config.'new_vcsa'.vc.username = $VIUsername 473 | $config.'new_vcsa'.vc.password = $VIPassword 474 | $config.'new_vcsa'.vc.deployment_network = $VMNetwork 475 | $config.'new_vcsa'.vc.datastore = $datastore 476 | $config.'new_vcsa'.vc.datacenter = $datacenter.name 477 | $config.'new_vcsa'.vc.target = $VMCluster 478 | $config.'new_vcsa'.appliance.thin_disk_mode = $true 479 | $config.'new_vcsa'.appliance.deployment_option = $VCSADeploymentSize 480 | $config.'new_vcsa'.appliance.name = $VCSADisplayName 481 | $config.'new_vcsa'.network.ip_family = "ipv4" 482 | $config.'new_vcsa'.network.mode = "static" 483 | $config.'new_vcsa'.network.ip = $VCSAIPAddress 484 | $config.'new_vcsa'.network.dns_servers[0] = $VMDNS 485 | $config.'new_vcsa'.network.prefix = $VCSAPrefix 486 | $config.'new_vcsa'.network.gateway = $VMGateway 487 | $config.'new_vcsa'.os.ntp_servers = $VMNTP 488 | $config.'new_vcsa'.network.system_name = $VCSAHostname 489 | $config.'new_vcsa'.os.password = $VCSARootPassword 490 | if($VCSASSHEnable -eq "true") { 491 | $VCSASSHEnableVar = $true 492 | } else { 493 | $VCSASSHEnableVar = $false 494 | } 495 | $config.'new_vcsa'.os.ssh_enable = $VCSASSHEnableVar 496 | $config.'new_vcsa'.sso.password = $VCSASSOPassword 497 | $config.'new_vcsa'.sso.domain_name = $VCSASSODomainName 498 | 499 | if($IsWindows) { 500 | My-Logger "Creating VCSA JSON Configuration file for deployment ..." 501 | $config | ConvertTo-Json | Set-Content -Path "$($ENV:Temp)\jsontemplate.json" 502 | 503 | My-Logger "Deploying the VCSA ..." 504 | Invoke-Expression "$($VCSAInstallerPath)\vcsa-cli-installer\win32\vcsa-deploy.exe install --no-esx-ssl-verify --accept-eula --acknowledge-ceip $($ENV:Temp)\jsontemplate.json"| Out-File -Append -LiteralPath $verboseLogFile 505 | } elseif($IsMacOS) { 506 | My-Logger "Creating VCSA JSON Configuration file for deployment ..." 507 | $config | ConvertTo-Json | Set-Content -Path "$($ENV:TMPDIR)jsontemplate.json" 508 | 509 | My-Logger "Deploying the VCSA ..." 510 | Invoke-Expression "$($VCSAInstallerPath)/vcsa-cli-installer/mac/vcsa-deploy install --no-esx-ssl-verify --accept-eula --acknowledge-ceip $($ENV:TMPDIR)jsontemplate.json"| Out-File -Append -LiteralPath $verboseLogFile 511 | } elseif ($IsLinux) { 512 | My-Logger "Creating VCSA JSON Configuration file for deployment ..." 513 | $config | ConvertTo-Json | Set-Content -Path "/tmp/jsontemplate.json" 514 | 515 | My-Logger "Deploying the VCSA ..." 516 | Invoke-Expression "$($VCSAInstallerPath)/vcsa-cli-installer/lin64/vcsa-deploy install --no-esx-ssl-verify --accept-eula --acknowledge-ceip /tmp/jsontemplate.json"| Out-File -Append -LiteralPath $verboseLogFile 517 | } 518 | } 519 | 520 | if($moveVMsIntovApp -eq 1) { 521 | # Check whether DRS is enabled as that is required to create vApp 522 | if((Get-Cluster -Server $viConnection $cluster).DrsEnabled) { 523 | My-Logger "Creating vApp $VAppName ..." 524 | $VApp = New-VApp -Name $VAppName -Server $viConnection -Location $cluster 525 | 526 | if(-Not (Get-Folder $VMFolder -ErrorAction Ignore)) { 527 | My-Logger "Creating VM Folder $VMFolder ..." 528 | $folder = New-Folder -Name $VMFolder -Server $viConnection -Location (Get-Datacenter $VMDatacenter | Get-Folder vm) 529 | } 530 | 531 | if($deployNestedESXiVMs -eq 1) { 532 | My-Logger "Moving Nested ESXi VMs into $VAppName vApp ..." 533 | $NestedESXiHostnameToIPs.GetEnumerator() | Sort-Object -Property Value | Foreach-Object { 534 | $vm = Get-VM -Name $_.Key -Server $viConnection 535 | Move-VM -VM $vm -Server $viConnection -Destination $VApp -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile 536 | } 537 | } 538 | 539 | if($deployVCSA -eq 1) { 540 | $vcsaVM = Get-VM -Name $VCSADisplayName -Server $viConnection 541 | My-Logger "Moving $VCSADisplayName into $VAppName vApp ..." 542 | Move-VM -VM $vcsaVM -Server $viConnection -Destination $VApp -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile 543 | } 544 | 545 | if($deployNSXAdvLB -eq 1) { 546 | $nsxAdvLBVM = Get-VM -Name $NSXAdvLBDisplayName -Server $viConnection 547 | My-Logger "Moving $NSXAdvLBDisplayName into $VAppName vApp ..." 548 | Move-VM -VM $nsxAdvLBVM -Server $viConnection -Destination $VApp -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile 549 | } 550 | 551 | My-Logger "Moving $VAppName to VM Folder $VMFolder ..." 552 | Move-VApp -Server $viConnection $VAppName -Destination (Get-Folder -Server $viConnection $VMFolder) | Out-File -Append -LiteralPath $verboseLogFile 553 | } else { 554 | My-Logger "vApp $VAppName will NOT be created as DRS is NOT enabled on vSphere Cluster ${cluster} ..." 555 | } 556 | } 557 | 558 | if( $deployNestedESXiVMs -eq 1 -or $deployVCSA -eq 1 -or $deployNSXAdvLB -eq 1) { 559 | My-Logger "Disconnecting from $VIServer ..." 560 | Disconnect-VIServer -Server $viConnection -Confirm:$false 561 | } 562 | 563 | if($setupNewVC -eq 1) { 564 | My-Logger "Connecting to the new VCSA ..." 565 | $vc = Connect-VIServer $VCSAIPAddress -User "administrator@$VCSASSODomainName" -Password $VCSASSOPassword -WarningAction SilentlyContinue 566 | 567 | $d = Get-Datacenter -Server $vc $NewVCDatacenterName -ErrorAction Ignore 568 | if( -Not $d) { 569 | My-Logger "Creating Datacenter $NewVCDatacenterName ..." 570 | New-Datacenter -Server $vc -Name $NewVCDatacenterName -Location (Get-Folder -Type Datacenter -Server $vc) | Out-File -Append -LiteralPath $verboseLogFile 571 | } 572 | 573 | $c = Get-Cluster -Server $vc $NewVCVSANClusterName -ErrorAction Ignore 574 | if( -Not $c) { 575 | My-Logger "Creating VSAN Cluster $NewVCVSANClusterName ..." 576 | New-Cluster -Server $vc -Name $NewVCVSANClusterName -Location (Get-Datacenter -Name $NewVCDatacenterName -Server $vc) -DrsEnabled -HAEnabled -VsanEnabled | Out-File -Append -LiteralPath $verboseLogFile 577 | 578 | (Get-Cluster $NewVCVSANClusterName) | New-AdvancedSetting -Name "das.ignoreRedundantNetWarning" -Type ClusterHA -Value $true -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile 579 | } 580 | 581 | if($addESXiHostsToVC -eq 1) { 582 | $NestedESXiHostnameToIPs.GetEnumerator() | Sort-Object -Property Value | Foreach-Object { 583 | $VMName = $_.Key 584 | $VMIPAddress = $_.Value 585 | 586 | $targetVMHost = $VMIPAddress 587 | if($addHostByDnsName -eq 1) { 588 | $targetVMHost = $VMName 589 | } 590 | My-Logger "Adding ESXi host $targetVMHost to Cluster ..." 591 | Add-VMHost -Server $vc -Location (Get-Cluster -Name $NewVCVSANClusterName) -User "root" -Password $VMPassword -Name $targetVMHost -Force | Out-File -Append -LiteralPath $verboseLogFile 592 | } 593 | 594 | $haRuntime = (Get-Cluster $NewVCVSANClusterName).ExtensionData.RetrieveDasAdvancedRuntimeInfo 595 | $totalHaHosts = $haRuntime.TotalHosts 596 | $totalHaGoodHosts = $haRuntime.TotalGoodHosts 597 | while($totalHaGoodHosts -ne $totalHaHosts) { 598 | My-Logger "Waiting for vSphere HA configuration to complete ..." 599 | Start-Sleep -Seconds 60 600 | $haRuntime = (Get-Cluster $NewVCVSANClusterName).ExtensionData.RetrieveDasAdvancedRuntimeInfo 601 | $totalHaHosts = $haRuntime.TotalHosts 602 | $totalHaGoodHosts = $haRuntime.TotalGoodHosts 603 | } 604 | } 605 | 606 | if($configureVSANDiskGroup -eq 1) { 607 | My-Logger "Enabling VSAN & disabling VSAN Health Check ..." 608 | Get-VsanClusterConfiguration -Server $vc -Cluster $NewVCVSANClusterName | Set-VsanClusterConfiguration -HealthCheckIntervalMinutes 0 | Out-File -Append -LiteralPath $verboseLogFile 609 | 610 | foreach ($vmhost in Get-Cluster -Server $vc | Get-VMHost) { 611 | $luns = $vmhost | Get-ScsiLun | select CanonicalName, CapacityGB 612 | 613 | My-Logger "Querying ESXi host disks to create VSAN Diskgroups ..." 614 | foreach ($lun in $luns) { 615 | if(([int]($lun.CapacityGB)).toString() -eq "$NestedESXiCachingvDisk") { 616 | $vsanCacheDisk = $lun.CanonicalName 617 | } 618 | if(([int]($lun.CapacityGB)).toString() -eq "$NestedESXiCapacityvDisk") { 619 | $vsanCapacityDisk = $lun.CanonicalName 620 | } 621 | } 622 | My-Logger "Creating VSAN DiskGroup for $vmhost ..." 623 | New-VsanDiskGroup -Server $vc -VMHost $vmhost -SsdCanonicalName $vsanCacheDisk -DataDiskCanonicalName $vsanCapacityDisk | Out-File -Append -LiteralPath $verboseLogFile 624 | } 625 | } 626 | 627 | if($configureVDS -eq 1) { 628 | # vmnic0 = Management on VSS 629 | # vmnic1 = unused 630 | # vmnic2 = Management on VDS (uplink1) 631 | # vmnic3 = Wrokload on VDS (uplink2) 632 | 633 | $vds = New-VDSwitch -Server $vc -Name $NewVCVDSName -Location (Get-Datacenter -Name $NewVCDatacenterName) -Mtu 1600 -NumUplinkPorts 2 634 | 635 | My-Logger "Creating VDS Management Network Portgroup" 636 | New-VDPortgroup -Server $vc -Name $NewVCMgmtPortgroupName -Vds $vds | Out-File -Append -LiteralPath $verboseLogFile 637 | Get-VDPortgroup -Server $vc $NewVCMgmtPortgroupName | Get-VDUplinkTeamingPolicy | Set-VDUplinkTeamingPolicy -ActiveUplinkPort @("dvUplink1") -UnusedUplinkPort @("dvUplink2") | Out-File -Append -LiteralPath $verboseLogFile 638 | 639 | My-Logger "Creating VDS Supervisor Cluster Management Network Portgroup" 640 | New-VDPortgroup -Server $vc -Name $NewVCWorkloadPortgroupName -Vds $vds | Out-File -Append -LiteralPath $verboseLogFile 641 | Get-VDPortgroup -Server $vc $NewVCWorkloadPortgroupName | Get-VDUplinkTeamingPolicy | Set-VDUplinkTeamingPolicy -ActiveUplinkPort @("dvUplink2") -UnusedUplinkPort @("dvUplink1") | Out-File -Append -LiteralPath $verboseLogFile 642 | 643 | foreach ($vmhost in Get-Cluster -Server $vc | Get-VMHost) { 644 | My-Logger "Adding $vmhost to $NewVCVDSName" 645 | $vds | Add-VDSwitchVMHost -VMHost $vmhost | Out-Null 646 | 647 | $vmhostNetworkAdapter = Get-VMHost $vmhost | Get-VMHostNetworkAdapter -Physical -Name vmnic2,vmnic3 648 | $vds | Add-VDSwitchPhysicalNetworkAdapter -VMHostNetworkAdapter $vmhostNetworkAdapter -Confirm:$false 649 | } 650 | } 651 | 652 | if($clearVSANHealthCheckAlarm -eq 1) { 653 | My-Logger "Clearing default VSAN Health Check Alarms, not applicable in Nested ESXi env ..." 654 | $alarmMgr = Get-View AlarmManager -Server $vc 655 | Get-Cluster -Server $vc | where {$_.ExtensionData.TriggeredAlarmState} | %{ 656 | $cluster = $_ 657 | $Cluster.ExtensionData.TriggeredAlarmState | %{ 658 | $alarmMgr.AcknowledgeAlarm($_.Alarm,$cluster.ExtensionData.MoRef) 659 | } 660 | } 661 | $alarmSpec = New-Object VMware.Vim.AlarmFilterSpec 662 | $alarmMgr.ClearTriggeredAlarms($alarmSpec) 663 | } 664 | 665 | # Final configure and then exit maintanence mode in case patching was done earlier 666 | foreach ($vmhost in Get-Cluster -Server $vc | Get-VMHost) { 667 | # Disable Core Dump Warning 668 | Get-AdvancedSetting -Entity $vmhost -Name UserVars.SuppressCoredumpWarning | Set-AdvancedSetting -Value 1 -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile 669 | 670 | # Enable vMotion traffic 671 | $vmhost | Get-VMHostNetworkAdapter -VMKernel | Set-VMHostNetworkAdapter -VMotionEnabled $true -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile 672 | 673 | if($vmhost.ConnectionState -eq "Maintenance") { 674 | Set-VMHost -VMhost $vmhost -State Connected -RunAsync -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile 675 | } 676 | } 677 | 678 | if($setupTanzuStoragePolicy) { 679 | $datastoreName = "vsanDatastore" 680 | 681 | My-Logger "Creating Tanzu Storage Policies and attaching to $datastoreName ..." 682 | New-TagCategory -Server $vc -Name $StoragePolicyTagCategory -Cardinality single -EntityType Datastore | Out-File -Append -LiteralPath $verboseLogFile 683 | New-Tag -Server $vc -Name $StoragePolicyTagName -Category $StoragePolicyTagCategory | Out-File -Append -LiteralPath $verboseLogFile 684 | Get-Datastore -Server $vc -Name $datastoreName | New-TagAssignment -Server $vc -Tag $StoragePolicyTagName | Out-File -Append -LiteralPath $verboseLogFile 685 | New-SpbmStoragePolicy -Server $vc -Name $StoragePolicyName -AnyOfRuleSets (New-SpbmRuleSet -Name "tanzu-ruleset" -AllOfRules (New-SpbmRule -AnyOfTags (Get-Tag $StoragePolicyTagName))) | Out-File -Append -LiteralPath $verboseLogFile 686 | } 687 | 688 | My-Logger "Disconnecting from new VCSA ..." 689 | Disconnect-VIServer $vc -Confirm:$false 690 | } 691 | 692 | if($setupTanzu -eq 1) { 693 | My-Logger "Connecting to Management vCenter Server $VIServer for enabling Tanzu ..." 694 | Connect-VIServer $VIServer -User $VIUsername -Password $VIPassword -WarningAction SilentlyContinue | Out-Null 695 | 696 | My-Logger "Creating local $DevOpsUsername User in vCenter Server ..." 697 | $devopsUserCreationCmd = "/usr/lib/vmware-vmafd/bin/dir-cli user create --account $DevOpsUsername --first-name `"Dev`" --last-name `"Ops`" --user-password `'$DevOpsPassword`' --login `'administrator@$VCSASSODomainName`' --password `'$VCSASSOPassword`'" 698 | Invoke-VMScript -ScriptText $devopsUserCreationCmd -vm (Get-VM -Name $VCSADisplayName) -GuestUser "root" -GuestPassword "$VCSARootPassword" | Out-File -Append -LiteralPath $verboseLogFile 699 | 700 | My-Logger "Disconnecting from Management vCenter ..." 701 | Disconnect-VIServer * -Confirm:$false | Out-Null 702 | 703 | $vc = Connect-VIServer $VCSAIPAddress -User "administrator@$VCSASSODomainName" -Password $VCSASSOPassword -WarningAction SilentlyContinue 704 | 705 | My-Logger "Creating TKG Subscribed Content Library $TKGContentLibraryName ..." 706 | $clScheme = ([System.Uri]$TKGContentLibraryURL).scheme 707 | $clHost = ([System.Uri]$TKGContentLibraryURL).host 708 | $clPort = ([System.Uri]$TKGContentLibraryURL).port 709 | $clThumbprint = Get-SSLThumbprint -Url "${clScheme}://${clHost}:${clPort}" 710 | 711 | New-ContentLibrary -Server $vc -Name $TKGContentLibraryName -Description "Subscribed TKG Content Library" -Datastore (Get-Datastore -Server $vc "vsanDatastore") -AutomaticSync -SubscriptionUrl $TKGContentLibraryURL -SslThumbprint $clThumbprint | Out-File -Append -LiteralPath $verboseLogFile 712 | 713 | Disconnect-VIServer $vc -Confirm:$false | Out-Null 714 | } 715 | 716 | if($setupNSXAdvLB -eq 1) { 717 | # NSX ALB can take up to several minutes to initialize upon initial power on 718 | while(1) { 719 | try { 720 | $response = Invoke-WebRequest -Uri http://${NSXAdvLByManagementIPAddress} -SkipCertificateCheck 721 | if($response.StatusCode -eq 200) { 722 | My-Logger "$NSXAdvLBDisplayName is now ready for configuration ..." 723 | break 724 | } 725 | } catch { 726 | My-Logger "$NSXAdvLBDisplayName is not ready, sleeping for 2 minutes ..." 727 | Start-Sleep -Seconds 120 728 | } 729 | } 730 | 731 | # Assumes Basic Auth has been enabled per automation below 732 | $pair = "admin:VMware1!" 733 | $bytes = [System.Text.Encoding]::ASCII.GetBytes($pair) 734 | $base64 = [System.Convert]::ToBase64String($bytes) 735 | 736 | $newPassbasicAuthHeaders = @{ 737 | "Authorization"="basic $base64"; 738 | "Content-Type"="application/json"; 739 | "Accept"="application/json"; 740 | "x-avi-version"="20.1.4"; 741 | } 742 | 743 | $enableBasicAuth=1 744 | $updateAdminPassword=1 745 | $updateBackupPassphrase=1 746 | $updateDnsAndSMTPSettings=1 747 | $updateWelcomeWorkflow=1 748 | $createSSLCertificate=1 749 | $updateSSlCertificate=1 750 | $registervCenter=1 751 | $updateVCMgmtNetwork=1 752 | $updateVCWorkloadNetwork=1 753 | $createDefaultIPAM=1 754 | $updateDefaultIPAM=1 755 | 756 | if($enableBasicAuth -eq 1) { 757 | $headers = @{ 758 | "Content-Type"="application/json" 759 | "Accept"="application/json" 760 | } 761 | 762 | $payload = @{ 763 | username="admin"; 764 | password="58NFaGDJm(PJH0G"; 765 | } 766 | 767 | $defaultPasswordBody = $payload | ConvertTo-Json 768 | 769 | $response = Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/login -Body $defaultPasswordBody -Method POST -Headers $headers -SessionVariable WebSession -SkipCertificateCheck 770 | $csrfToken = $WebSession.Cookies.GetCookies("https://${NSXAdvLByManagementIPAddress}/login")["csrftoken"].value 771 | 772 | $headers = @{ 773 | "Content-Type"="application/json" 774 | "Accept"="application/json" 775 | "x-avi-version"="20.1.4" 776 | "x-csrftoken"=$csrfToken 777 | "referer"="https://${NSXAdvLByManagementIPAddress}/login" 778 | } 779 | 780 | $json = (Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/systemconfiguration -Method GET -Headers $headers -WebSession $WebSession -SkipCertificateCheck).Content | ConvertFrom-Json 781 | $json.portal_configuration.allow_basic_authentication = $true 782 | $systemConfigBody = $json | ConvertTo-Json -Depth 10 783 | 784 | try { 785 | My-Logger "Enabling bacic auth ..." 786 | $response = Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/systemconfiguration -Body $systemConfigBody -Method PUT -Headers $headers -WebSession $WebSession -SkipCertificateCheck 787 | } catch { 788 | My-Logger "Failed to update basic auth" "red" 789 | Write-Error "`n($_.Exception.Message)`n" 790 | break 791 | } 792 | 793 | if($response.Statuscode -eq 200) { 794 | My-Logger "Successfully enabled basic auth for $NSXAdvLBDisplayName ..." 795 | } else { 796 | My-Logger "Something went wrong enabling basic auth" "yellow" 797 | $response 798 | break 799 | } 800 | } 801 | 802 | if($updateAdminPassword -eq 1) { 803 | $pair = "admin:58NFaGDJm(PJH0G" 804 | $bytes = [System.Text.Encoding]::ASCII.GetBytes($pair) 805 | $base64 = [System.Convert]::ToBase64String($bytes) 806 | 807 | $basicAuthHeaders = @{ 808 | "Authorization"="basic $base64" 809 | "Content-Type"="application/json" 810 | "Accept"="application/json" 811 | } 812 | 813 | $payload = @{ 814 | old_password = "58NFaGDJm(PJH0G"; 815 | password = $NSXAdvLBAdminPassword; 816 | username = "admin" 817 | } 818 | 819 | $newPasswordBody = $payload | ConvertTo-Json 820 | 821 | try { 822 | My-Logger "Changing default admin password" 823 | $response = Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/useraccount -Body $newPasswordBody -Method PUT -Headers $basicAuthHeaders -SkipCertificateCheck 824 | } catch { 825 | My-Logger "Failed to change admin password" "red" 826 | Write-Error "`n($_.Exception.Message)`n" 827 | break 828 | } 829 | 830 | if($response.Statuscode -eq 200) { 831 | My-Logger "Successfully changed default admin password ..." 832 | } else { 833 | My-Logger "Something went wrong changing default admin password" "yellow" 834 | $response 835 | break 836 | } 837 | } 838 | 839 | if($updateBackupPassphrase -eq 1) { 840 | $backupJsonResult = ((Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/backupconfiguration -Method GET -Headers $newPassbasicAuthHeaders -SkipCertificateCheck).Content | ConvertFrom-Json).results[0] 841 | 842 | $passPhraseJson = @{ 843 | "add" = @{ 844 | "backup_passphrase" = $nsxAdvLBPassphrase; 845 | } 846 | } 847 | $newBackupJsonBody = ($passPhraseJson | ConvertTo-json) 848 | 849 | try { 850 | My-Logger "Configuring backup passphrase ..." 851 | $response = Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/backupconfiguration/$($backupJsonResult.uuid) -body $newBackupJsonBody -Method PATCH -Headers $newPassbasicAuthHeaders -SkipCertificateCheck 852 | } catch { 853 | My-Logger "Failed to update backup passphrase" "red" 854 | Write-Error "`n($_.Exception.Message)`n" 855 | break 856 | } 857 | 858 | if($response.Statuscode -eq 200) { 859 | My-Logger "Successfully updated backup passphrase ..." 860 | } else { 861 | My-Logger "Something went wrong updating backup passphrase" "yellow" 862 | $response 863 | break 864 | } 865 | } 866 | 867 | if($updateDnsAndSMTPSettings -eq 1) { 868 | $dnsResults = (Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/systemconfiguration -Method GET -Headers $newPassbasicAuthHeaders -SkipCertificateCheck).Content | ConvertFrom-Json 869 | 870 | $dnsResults.dns_configuration.search_domain = "$VMDomain" 871 | $dnsResults.email_configuration.smtp_type = "SMTP_NONE" 872 | 873 | $dnsConfig = @{ 874 | "addr" = "$VMDNS"; 875 | "type" = "V4"; 876 | } 877 | 878 | $dnsResults.dns_configuration | Add-Member -MemberType NoteProperty -Name server_list -Value @($dnsConfig) 879 | $newDnsJsonBody = ($dnsResults | ConvertTo-json -Depth 4) 880 | 881 | try { 882 | My-Logger "Configuring DNS and SMTP settings" 883 | $response = Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/systemconfiguration -body $newDnsJsonBody -Method PUT -Headers $newPassbasicAuthHeaders -SkipCertificateCheck 884 | } catch { 885 | My-Logger "Failed to update DNS and SMTP settings" "red" 886 | Write-Error "`n($_.Exception.Message)`n" 887 | break 888 | } 889 | 890 | if($response.Statuscode -eq 200) { 891 | My-Logger "Successfully updated DNS and SMTP settings ..." 892 | } else { 893 | My-Logger "Something went wrong with updating DNS and SMTP settings" "yellow" 894 | $response 895 | break 896 | } 897 | } 898 | 899 | if($updateWelcomeWorkflow -eq 1) { 900 | $welcomeWorkflowJson = @{ 901 | "replace" = @{ 902 | "welcome_workflow_complete" = "true"; 903 | } 904 | } 905 | 906 | $welcomeWorkflowBody = ($welcomeWorkflowJson | ConvertTo-json) 907 | 908 | try { 909 | My-Logger "Disabling initial welcome message ..." 910 | $response = Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/systemconfiguration -body $welcomeWorkflowBody -Method PATCH -Headers $newPassbasicAuthHeaders -SkipCertificateCheck 911 | } catch { 912 | My-Logger "Failed to disable welcome workflow message" "red" 913 | Write-Error "`n($_.Exception.Message)`n" 914 | break 915 | } 916 | 917 | if($response.Statuscode -eq 200) { 918 | My-Logger "Successfully disabled welcome workflow message ..." 919 | } else { 920 | My-Logger "Something went wrong disabling welcome workflow message" "yellow" 921 | $response 922 | break 923 | } 924 | } 925 | 926 | if($createSSLCertificate -eq 1) { 927 | 928 | $selfSignCertPayload = @{ 929 | "certificate" = @{ 930 | "expiry_status" = "SSL_CERTIFICATE_GOOD"; 931 | "days_until_expire" = $NSXAdvLBSSLCertExpiry; 932 | "self_signed" = "true" 933 | "subject" = @{ 934 | "common_name" = $NSXAdvLBHostname; 935 | "email_address" = $NSXAdvLBSSLCertEmail; 936 | "organization_unit" = $NSXAdvLBSSLCertOrganizationUnit; 937 | "organization" = $NSXAdvLBSSLCertOrganization; 938 | "locality" = $NSXAdvLBSSLCertLocation; 939 | "state" = $NSXAdvLBSSLCertState; 940 | "country" = $NSXAdvLBSSLCertCountry; 941 | }; 942 | "subject_alt_names" = @($NSXAdvLByManagementIPAddress); 943 | }; 944 | "key_params" = @{ 945 | "algorithm" = "SSL_KEY_ALGORITHM_RSA"; 946 | "rsa_params" = @{ 947 | "key_size" = "SSL_KEY_2048_BITS"; 948 | "exponent" = "65537"; 949 | }; 950 | }; 951 | "status" = "SSL_CERTIFICATE_FINISHED"; 952 | "format" = "SSL_PEM"; 953 | "certificate_base64" = "true"; 954 | "key_base64" = "true"; 955 | "type" = "SSL_CERTIFICATE_TYPE_SYSTEM"; 956 | "name" = $NSXAdvLBSSLCertName; 957 | } 958 | 959 | $selfSignCertBody = ($selfSignCertPayload | ConvertTo-Json -Depth 8) 960 | 961 | try { 962 | My-Logger "Creating self-sign TLS certificate ..." 963 | $response = Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/sslkeyandcertificate -body $selfSignCertBody -Method POST -Headers $newPassbasicAuthHeaders -SkipCertificateCheck 964 | } catch { 965 | My-Logger "Error in creating self-sign TLS certificate" "red" 966 | Write-Error "`n($_.Exception.Message)`n" 967 | break 968 | } 969 | 970 | if($response.Statuscode -eq 201) { 971 | My-Logger "Successfully created self-sign TLS certificate ..." 972 | } else { 973 | My-Logger "Something went wrong creating self-sign TLS certificate" "yellow" 974 | $response 975 | break 976 | } 977 | } 978 | 979 | if($updateSSlCertificate -eq 1) { 980 | $certJsonResults = ((Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/sslkeyandcertificate?include_name -Method GET -Headers $newPassbasicAuthHeaders -SkipCertificateCheck).Content | ConvertFrom-Json).results | where {$_.name -eq $NSXAdvLBSSLCertName} 981 | 982 | $systemConfigJsonResults = (Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/systemconfiguration -Method GET -Headers $newPassbasicAuthHeaders -SkipCertificateCheck).Content | ConvertFrom-Json 983 | 984 | $systemConfigJsonResults.portal_configuration.sslkeyandcertificate_refs = @(${certJsonResults}.url) 985 | 986 | $updateSSLCertBody = $systemConfigJsonResults | ConvertTo-Json -Depth 4 987 | 988 | try { 989 | My-Logger "Updating NSX ALB to new self-sign TLS ceretificate ..." 990 | $response = Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/systemconfiguration -body $updateSSLCertBody -Method PUT -Headers $newPassbasicAuthHeaders -SkipCertificateCheck 991 | } catch { 992 | My-Logger "Error in updating self-sign TLS certificate" "red" 993 | Write-Error "`n($_.Exception.Message)`n" 994 | break 995 | } 996 | 997 | if($response.Statuscode -eq 200) { 998 | My-Logger "Successfully updated to new self-sign TLS certificate ..." 999 | } else { 1000 | My-Logger "Something went wrong updating to new self-sign TLS certificate" "yellow" 1001 | $response 1002 | break 1003 | } 1004 | } 1005 | 1006 | if($registervCenter -eq 1) { 1007 | $cloudConfigResult = ((Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/cloud -Method GET -Headers $newPassbasicAuthHeaders -SkipCertificateCheck).Content | ConvertFrom-Json).results[0] 1008 | 1009 | $cloudConfigResult.vtype = "CLOUD_VCENTER" 1010 | 1011 | $vcConfig = @{ 1012 | "username" = "administrator@vsphere.local" 1013 | "password" = "$VCSASSOPassword"; 1014 | "vcenter_url" = "$VCSAHostname"; 1015 | "privilege" = "WRITE_ACCESS"; 1016 | "datacenter" ="$NewVCDatacenterName"; 1017 | "management_ip_subnet" = @{ 1018 | "ip_addr" = @{ 1019 | "addr" = "$NSXAdvLBManagementNetwork"; 1020 | "type" = "V4"; 1021 | }; 1022 | "mask" = "$NSXAdvLBManagementNetworkPrefix"; 1023 | } 1024 | } 1025 | 1026 | $cloudConfigResult | Add-Member -MemberType NoteProperty -Name vcenter_configuration -Value $vcConfig 1027 | 1028 | $newCloudConfigBody = ($cloudConfigResult | ConvertTo-Json -Depth 4) 1029 | 1030 | try { 1031 | My-Logger "Register Tanzu vCenter Server $VCSAHostname to NSX ALB ..." 1032 | $response = Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/cloud/$($cloudConfigResult.uuid) -body $newCloudConfigBody -Method PUT -Headers $newPassbasicAuthHeaders -SkipCertificateCheck 1033 | } catch { 1034 | My-Logger "Failed to register Tanzu vCenter Server" "red" 1035 | Write-Error "`n($_.Exception.Message)`n" 1036 | break 1037 | } 1038 | 1039 | if($response.Statuscode -eq 200) { 1040 | My-Logger "Successfully registered Tanzu vCenter Server ..." 1041 | } else { 1042 | My-Logger "Something went wrong registering Tanzu vCenter Server" "yellow" 1043 | $response 1044 | break 1045 | } 1046 | } 1047 | 1048 | if($updateVCMgmtNetwork -eq 1) { 1049 | Start-Sleep -Seconds 20 1050 | 1051 | $cloudNetworkResult = ((Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/network -Method GET -Headers $newPassbasicAuthHeaders -SkipCertificateCheck).Content | ConvertFrom-Json).results | where {$_.name -eq $NewVCMgmtPortgroupName} 1052 | 1053 | $mgmtNetworkConfig = @{ 1054 | "prefix" = @{ 1055 | "ip_addr" = @{ 1056 | "addr" = "$NSXAdvLBManagementNetwork"; 1057 | "type" = "V4"; 1058 | }; 1059 | "mask" = "$NSXAdvLBManagementNetworkPrefix"; 1060 | }; 1061 | "static_ip_ranges" = @( 1062 | @{ 1063 | "range" = @{ 1064 | "begin" = @{ 1065 | "addr" = $NSXAdvLBManagementNetworkStartRange; 1066 | "type" = "V4"; 1067 | }; 1068 | "end" = @{ 1069 | "addr" = $NSXAdvLBManagementNetworkEndRange; 1070 | "type" = "V4"; 1071 | } 1072 | }; 1073 | "type" = "STATIC_IPS_FOR_VIP_AND_SE"; 1074 | } 1075 | ) 1076 | } 1077 | 1078 | $cloudNetworkResult | Add-Member -MemberType NoteProperty -Name configured_subnets -Value @($mgmtNetworkConfig) 1079 | 1080 | $newCloudMgmtNetworkBody = ($cloudNetworkResult | ConvertTo-Json -Depth 10) 1081 | 1082 | # Create Subnet mapping 1083 | try { 1084 | My-Logger "Creating subnet mapping for Service Engine Network ..." 1085 | $response = Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/network/$($cloudNetworkResult.uuid) -body $newCloudMgmtNetworkBody -Method PUT -Headers $newPassbasicAuthHeaders -SkipCertificateCheck 1086 | } catch { 1087 | My-Logger "Failed to create subnet mapping for $NewVCMgmtPortgroupName" "red" 1088 | Write-Error "`n($_.Exception.Message)`n" 1089 | break 1090 | } 1091 | 1092 | if($response.Statuscode -eq 200) { 1093 | My-Logger "Successfully created subnet mapping for $NewVCMgmtPortgroupName ..." 1094 | } else { 1095 | My-Logger "Something went wrong creating subnet mapping for $NewVCMgmtPortgroupName" "yellow" 1096 | $response 1097 | break 1098 | } 1099 | 1100 | # Add default Gateway 1101 | $vrfContextResult = ((Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/vrfcontext -Method GET -Headers $newPassbasicAuthHeaders -SkipCertificateCheck).Content | ConvertFrom-Json).results | where {$_.name -eq "management"} 1102 | 1103 | $staticRouteConfig = @{ 1104 | "next_hop" = @{ 1105 | "addr" = $VMGateway; 1106 | "type" = "V4"; 1107 | }; 1108 | "route_id" = "1"; 1109 | "prefix" = @{ 1110 | "ip_addr" = @{ 1111 | "addr" = "0.0.0.0"; 1112 | "type" = "V4"; 1113 | }; 1114 | "mask" = "0" 1115 | } 1116 | } 1117 | 1118 | $vrfContextResult | Add-Member -MemberType NoteProperty -Name static_routes -Value @($staticRouteConfig) 1119 | 1120 | $newvrfContextkBody = ($vrfContextResult | ConvertTo-Json -Depth 10) 1121 | 1122 | try { 1123 | My-Logger "Updating VRF Context for default gateway ..." 1124 | $response = Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/vrfcontext/$(${vrfContextResult}.uuid) -body $newvrfContextkBody -Method PUT -Headers $newPassbasicAuthHeaders -SkipCertificateCheck 1125 | } catch { 1126 | My-Logger "Failed to update VRF context" "red" 1127 | Write-Error "`n($_.Exception.Message)`n" 1128 | break 1129 | } 1130 | 1131 | if($response.Statuscode -eq 200) { 1132 | My-Logger "Successfully updated VRF context ..." 1133 | } else { 1134 | My-Logger "Something went wrong updating VRF context" "yellow" 1135 | $response 1136 | break 1137 | } 1138 | 1139 | # Associtae Tanzu Management Network to vCenter 1140 | $cloudNetworkResult = ((Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/network -Method GET -Headers $newPassbasicAuthHeaders -SkipCertificateCheck).Content | ConvertFrom-Json).results | where {$_.name -eq $NewVCMgmtPortgroupName} 1141 | 1142 | $cloudConfigResult = ((Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/cloud -Method GET -Headers $newPassbasicAuthHeaders -SkipCertificateCheck).Content | ConvertFrom-Json).results[0] 1143 | 1144 | 1145 | $cloudConfigResult.vcenter_configuration | Add-Member -MemberType NoteProperty -Name management_network -Value $(${cloudNetworkResult}.vimgrnw_ref) 1146 | $newCloudConfigBody = ($cloudConfigResult | ConvertTo-Json -Depth 4) 1147 | 1148 | try { 1149 | My-Logger "Associating Service Engine network to Tanzu vCenter Server ..." 1150 | $response = Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/cloud/$(${cloudConfigResult}.uuid) -body $newCloudConfigBody -Method PUT -Headers $newPassbasicAuthHeaders -SkipCertificateCheck 1151 | } catch { 1152 | My-Logger "Failed to associate service engine network to Tanzu vCenter Server" "red" 1153 | Write-Error "`n($_.Exception.Message)`n" 1154 | break 1155 | } 1156 | 1157 | if($response.Statuscode -eq 200) { 1158 | My-Logger "Successfully associated service engine network to Tanzu vCenter Server ..." 1159 | } else { 1160 | My-Logger "Something went wrong associating service engine network to Tanzu vCenter Server" "yellow" 1161 | $response 1162 | break 1163 | } 1164 | } 1165 | 1166 | if($updateVCWorkloadNetwork -eq 1) { 1167 | $cloudNetworkResult = ((Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/network -Method GET -Headers $newPassbasicAuthHeaders -SkipCertificateCheck).Content | ConvertFrom-Json).results | where {$_.name -eq $NewVCWorkloadPortgroupName} 1168 | 1169 | $workloadNetworkConfig = @{ 1170 | "prefix" = @{ 1171 | "ip_addr" = @{ 1172 | "addr" = "$NSXAdvLBWorkloadNetwork"; 1173 | "type" = "V4"; 1174 | }; 1175 | "mask" = "$NSXAdvLBWorkloadNetworkPrefix"; 1176 | }; 1177 | "static_ip_ranges" = @( 1178 | @{ 1179 | "range" = @{ 1180 | "begin" = @{ 1181 | "addr" = $NSXAdvLBWorkloadNetworkStartRange; 1182 | "type" = "V4"; 1183 | }; 1184 | "end" = @{ 1185 | "addr" = $NSXAdvLBWorkloadNetworkEndRange; 1186 | "type" = "V4"; 1187 | } 1188 | }; 1189 | "type" = "STATIC_IPS_FOR_VIP_AND_SE"; 1190 | } 1191 | ) 1192 | } 1193 | 1194 | $cloudNetworkResult | Add-Member -MemberType NoteProperty -Name configured_subnets -Value @($workloadNetworkConfig) 1195 | 1196 | $newCloudWorkloadNetworkBody = ($cloudNetworkResult | ConvertTo-Json -Depth 10) 1197 | 1198 | # Create Subnet mapping 1199 | try { 1200 | My-Logger "Creating subnet mapping for Workload Network ..." 1201 | $response = Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/network/$($cloudNetworkResult.uuid) -body $newCloudWorkloadNetworkBody -Method PUT -Headers $newPassbasicAuthHeaders -SkipCertificateCheck 1202 | } catch { 1203 | My-Logger "Failed to create subnet mapping for $NewVCWorkloadPortgroupName" "red" 1204 | Write-Error "`n($_.Exception.Message)`n" 1205 | break 1206 | } 1207 | 1208 | if($response.Statuscode -eq 200) { 1209 | My-Logger "Successfully created subnet mapping for $NewVCWorkloadPortgroupName ..." 1210 | } else { 1211 | My-Logger "Something went wrong creating subnet mapping for $NewVCWorkloadPortgroupName" "yellow" 1212 | $response 1213 | break 1214 | } 1215 | } 1216 | 1217 | if($createDefaultIPAM -eq 1) { 1218 | $cloudNetworkResult = ((Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/network -Method GET -Headers $newPassbasicAuthHeaders -SkipCertificateCheck).Content | ConvertFrom-Json).results | where {$_.name -eq $NewVCWorkloadPortgroupName} 1219 | 1220 | $ipamConfig = @{ 1221 | "name" = $NSXAdvLBIPAMName; 1222 | "tenant_ref" = "https://${NSXAdvLByManagementIPAddress}/tenant/admin"; 1223 | "type" = "IPAMDNS_TYPE_INTERNAL"; 1224 | "internal_profile" = @{ 1225 | "ttl" = "30"; 1226 | "usable_networks" = @( 1227 | @{ 1228 | "nw_ref" = "$(${cloudNetworkResult}.url)" 1229 | } 1230 | ); 1231 | }; 1232 | "allocate_ip_in_vrf" = "true" 1233 | } 1234 | 1235 | $ipamBody = $ipamConfig | ConvertTo-Json -Depth 4 1236 | 1237 | try { 1238 | My-Logger "Creating new IPAM Default Profile ..." 1239 | $response = Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/ipamdnsproviderprofile -body $ipamBody -Method POST -Headers $newPassbasicAuthHeaders -SkipCertificateCheck 1240 | } catch { 1241 | My-Logger "Failed to create IPAM default profile" "red" 1242 | Write-Error "`n($_.Exception.Message)`n" 1243 | break 1244 | } 1245 | 1246 | if($response.Statuscode -eq 201) { 1247 | My-Logger "Successfully created IPAM default profile ..." 1248 | } else { 1249 | My-Logger "Something went wrong creating IPAM default profile" "yellow" 1250 | $response 1251 | break 1252 | } 1253 | } 1254 | 1255 | if($updateDefaultIPAM -eq 1) { 1256 | $ipamResult = ((Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/ipamdnsproviderprofile -Method GET -Headers $newPassbasicAuthHeaders -SkipCertificateCheck).Content | ConvertFrom-Json).results | where {$_.name -eq $NSXAdvLBIPAMName} 1257 | 1258 | $cloudConfigResult = ((Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/cloud -Method GET -Headers $newPassbasicAuthHeaders -SkipCertificateCheck).Content | ConvertFrom-Json).results[0] 1259 | 1260 | $cloudConfigResult | Add-Member -MemberType NoteProperty -Name ipam_provider_ref -Value $ipamResult.url 1261 | 1262 | $newClouddConfigBody = ($cloudConfigResult | ConvertTo-Json -Depth 10) 1263 | 1264 | try { 1265 | My-Logger "Updating Default Cloud to new IPAM Profile ..." 1266 | $response = Invoke-WebRequest -Uri https://${NSXAdvLByManagementIPAddress}/api/cloud/$($cloudConfigResult.uuid) -body $newClouddConfigBody -Method PUT -Headers $newPassbasicAuthHeaders -SkipCertificateCheck 1267 | } catch { 1268 | My-Logger "Failed to update default IPAM profile" "red" 1269 | Write-Error "`n($_.Exception.Message)`n" 1270 | break 1271 | } 1272 | 1273 | if($response.Statuscode -eq 200) { 1274 | My-Logger "Successfully updated default IPAM profile ..." 1275 | } else { 1276 | My-Logger "Something went wrong updating default IPAM profile" "yellow" 1277 | $response 1278 | break 1279 | } 1280 | } 1281 | } 1282 | 1283 | $EndTime = Get-Date 1284 | $duration = [math]::Round((New-TimeSpan -Start $StartTime -End $EndTime).TotalMinutes,2) 1285 | 1286 | My-Logger "vSphere with Tanzu NSX Advanced LB Lab Deployment Complete!" 1287 | My-Logger "StartTime: $StartTime" 1288 | My-Logger " EndTime: $EndTime" 1289 | My-Logger " Duration: $duration minutes" 1290 | --------------------------------------------------------------------------------