├── .gitignore ├── LICENSE ├── README.md ├── pod-deploy.json.template └── pod-deploy.ps1 /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Delete-Pod.ps1 3 | *.log 4 | *.json 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Sam McGeown 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pod-Deploy 2 | Deploy a 3-node nested vSphere, VSAN and NSX lab 3 | 4 | ## 14-Nov-2017 5 | * Updated to deploy ESXi hosts in parallel 6 | * Modified the VSAN config to use automatic disk claim mode 7 | * Tweaked VDS uplink config to reduce errors 8 | * Template JSON will deploy a separate PSC and VC -------------------------------------------------------------------------------- /pod-deploy.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "__version": "0.2", 3 | "__comments": "Configuration for pod-deploy.ps1 - www.definit.co.uk - https://github.com/sammcgeown/Pod-Deploy", 4 | "physical": { 5 | "server": "vcsa.definit.local", 6 | "user": "administrator@vsphere.local", 7 | "password": "VMware1!", 8 | "datacenter": "Lab", 9 | "cluster": "Workload", 10 | "datastore": "vsanDatastore", 11 | "folder": "Nested Labs/Pod203", 12 | "portgroup": "203-Pod-203", 13 | "network": { 14 | "netmask": "255.255.255.0", 15 | "gateway": "192.168.203.1", 16 | "prefix": "24", 17 | "dns": "192.168.1.1", 18 | "domain": "definit.local", 19 | "ntp": "192.168.1.1" 20 | } 21 | }, 22 | "sources": { 23 | "VCSAInstaller": "/Users/smcgeown/Documents/SCRIPTS/Pod-Deploy-Source/vSphere/VMware-VCSA-all-6.5.0-5973321", 24 | "ESXiAppliance": "/Users/smcgeown/Documents/SCRIPTS/Pod-Deploy-Source/ESXi/Nested_ESXi6.5d_Appliance_Template_v1.0.ova", 25 | "NSXAppliance" : "/Users/smcgeown/Documents/SCRIPTS/Pod-Deploy-Source/NSX/VMware-NSX-Manager-6.4.0-7564187.ova" 26 | }, 27 | "esxi": { 28 | "hosts": [ 29 | { 30 | "name": "pod-203-esxi-1", 31 | "ip": "192.168.203.20" 32 | }, 33 | { 34 | "name": "pod-203-esxi-2", 35 | "ip": "192.168.203.21" 36 | }, 37 | { 38 | "name": "pod-203-esxi-3", 39 | "ip": "192.168.203.22" 40 | } 41 | ], 42 | "cpu": "4", 43 | "ram": "16", 44 | "cacheDisk": "0", 45 | "capacityDisk": "0", 46 | "createVMFS": false 47 | }, 48 | "vcsa": { 49 | "deploymentSize": "tiny", 50 | "name": "pod-203-vcsa", 51 | "ip": "192.168.203.10", 52 | "hostname": "192.168.203.10", 53 | "rootPassword": "VMware1!", 54 | "sso": { 55 | "domain": "vsphere.local", 56 | "site": "Pod203-Site", 57 | "password": "VMware1!", 58 | "replicationPartner": "" 59 | }, 60 | "datacenter": "Pod203-Datacenter", 61 | "cluster": "Pod203-Cluster-1", 62 | "distributedSwitch": "Pod-203-VDS", 63 | "portgroup": "VLAN203" 64 | }, 65 | "general": { 66 | "password": "VMware1!", 67 | "syslog": "192.168.1.26", 68 | "ssh": true, 69 | "log": "pod-203-deploy.log" 70 | }, 71 | "license": { 72 | "vcenter": "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX", 73 | "vsphere": "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX", 74 | "vsan": "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX", 75 | "nsx": "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX" 76 | }, 77 | "nfs": [ 78 | { 79 | "name": "NFS-01", 80 | "path": "/volume1/SYN-NFS-01", 81 | "server": "192.168.4.10" 82 | } 83 | ] 84 | } -------------------------------------------------------------------------------- /pod-deploy.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [Parameter(Mandatory=$true)] [String]$configFile, 3 | [switch]$deployESXi, 4 | [switch]$deployVCSA, 5 | [switch]$configureVCSA, 6 | [switch]$licenseVCSA, 7 | [switch]$configureHosts, 8 | [switch]$configureVDSwitch, 9 | [switch]$configureVSAN, 10 | [switch]$deployNSXManager, 11 | [switch]$configureNSX, 12 | [switch]$deployvRAAppliance 13 | ) 14 | try { 15 | Get-Module -ListAvailable VMware.PowerCLI,PowerNSX | Import-Module -ErrorAction SilentlyContinue 16 | } 17 | catch {} 18 | finally {} 19 | 20 | if ( !(Get-Module -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue) ) { 21 | throw "PowerCLI must be installed" 22 | } 23 | # Hat tips and thanks go to... 24 | # William Lam http://www.virtuallyghetto.com/2016/11/vghetto-automated-vsphere-lab-deployment-for-vsphere-6-0u2-vsphere-6-5.html 25 | # Rawlinson Riviera http://www.punchingclouds.com/2016/03/24/vmware-virtual-san-automated-deployments-powercli/ 26 | # Brian Graf http://www.vtagion.com/automatically-deploy-nsx-connect-vcenter/ 27 | # Anthony Burke https://networkinferno.net/license-nsx-via-automation-with-powercli 28 | 29 | # Get the folder location 30 | #$ScriptLocation = Split-Path -Parent $PSCommandPath 31 | 32 | # Import the JSON Config File 33 | $podConfig = (get-content $($configFile) -Raw) | ConvertFrom-Json 34 | 35 | 36 | # Log File 37 | $verboseLogFile = $podConfig.general.log 38 | 39 | $StartTime = Get-Date 40 | 41 | # Platform independant temp directory... 42 | if($env:TMPDIR) { $temp = $env:TMPDIR } elseif ($env:TMP) { $temp = $env:TMP } else { $temp = "/tmp" } 43 | 44 | # There must be an easier way than this... 45 | switch ($PSVersionTable.PSEdition) { 46 | "Core" { 47 | # Mac, Linux or Windows 48 | if($PSVersionTable.OS -match "Darwin") { 49 | # Mac 50 | $vcsaDeploy = "$($podConfig.sources.VCSAInstaller)/vcsa-cli-installer/mac/vcsa-deploy" 51 | } elseif ($PSVersionTable.OS -match "Linux") { 52 | # Linux 53 | $vcsaDeploy = "$($podConfig.sources.VCSAInstaller)/vcsa-cli-installer/lin64/vcsa-deploy" 54 | } else { 55 | # Windows 56 | $vcsaDeploy = "$($podConfig.sources.VCSAInstaller)\vcsa-cli-installer\win32\vcsa-deploy.exe" 57 | } 58 | } 59 | "Desktop" { 60 | # Windows 61 | $vcsaDeploy = "$($podConfig.sources.VCSAInstaller)\vcsa-cli-installer\win32\vcsa-deploy.exe" 62 | } 63 | } 64 | $externalPSCconfig = "$($podConfig.sources.VCSAInstaller)\vcsa-cli-installer\templates\install\PSC_first_instance_on_VC.json" 65 | $externalVCSAconfig = "$($podConfig.sources.VCSAInstaller)\vcsa-cli-installer\templates\install\vCSA_on_VC.json" 66 | $embeddedVCSAconfig = "$($podConfig.sources.VCSAInstaller)\vcsa-cli-installer\templates\install\embedded_vCSA_on_VC.json" 67 | 68 | Function Write-Log { 69 | param( 70 | [Parameter(Mandatory=$true)] 71 | [String]$Message, 72 | [switch]$Warning, 73 | [switch]$Info 74 | ) 75 | $timeStamp = Get-Date -Format "dd-MM-yyyy hh:mm:ss" 76 | Write-Host -NoNewline -ForegroundColor White "[$timestamp]" 77 | if($Warning){ 78 | Write-Host -ForegroundColor Yellow " WARNING: $message" 79 | } elseif($Info) { 80 | Write-Host -ForegroundColor White " $message" 81 | }else { 82 | Write-Host -ForegroundColor Green " $message" 83 | } 84 | "[$timeStamp] $message" | Out-File -Append -LiteralPath $verboseLogFile 85 | } 86 | 87 | function Get-VcConnection { 88 | param( 89 | [string]$vcsaName, 90 | [string]$vcsaUser, 91 | [string]$vcsaPassword 92 | ) 93 | #Write-Log "Getting connection for $($vcsaName)" 94 | $existingConnection = $global:DefaultVIServers | Where-Object -Property Name -eq -Value $vcsaName 95 | if($existingConnection -ne $null) { 96 | return $existingConnection; 97 | } else { 98 | $connection = Connect-VIServer -Server $vcsaName -User $vcsaUser -Password $vcsaPassword -WarningAction SilentlyContinue; 99 | return $connection; 100 | } 101 | } 102 | 103 | function Close-VcConnection { 104 | param( 105 | [string]$vcsaName 106 | ) 107 | if($vcsaName.Length -le 0) { 108 | if($Global:DefaultVIServers.count -ge 0) { 109 | Write-Log -Message "Disconnecting from all vCenter Servers" 110 | Disconnect-VIServer -Server $Global:DefaultVIServers -Confirm:$false 111 | } 112 | } else { 113 | $existingConnection = $global:DefaultVIServers | where-object -Property Name -eq -Value $vcsaName 114 | if($existingConnection -ne $null) { 115 | Write-Log -Message "Disconnecting from $($vcsaName)" 116 | Disconnect-VIServer -Server $existingConnection -Confirm:$false; 117 | } else { 118 | Write-Log -Message "Could not find an existing connection named $($vcsaName)" -Warning 119 | } 120 | } 121 | } 122 | 123 | function Get-PodFolder { 124 | param( 125 | $vcsaConnection, 126 | [string]$folderPath 127 | ) 128 | $folderArray = $folderPath.split("/") 129 | $parentFolder = Get-Folder -Server $vcsaConnection -Name vm 130 | foreach($folder in $folderArray) { 131 | $folderExists = Get-Folder -Server $vcsaConnection | Where-Object -Property Name -eq -Value $folder 132 | if($folderExists -ne $null) { 133 | $parentFolder = $folderExists 134 | } else { 135 | $parentFolder = New-Folder -Name $folder -Location $parentFolder 136 | } 137 | } 138 | return $parentFolder 139 | } 140 | 141 | Write-Log "#### Validating Configuration ####" 142 | Write-Log "### Testing Sources" 143 | if(Test-Path -Path $podConfig.sources.VCSAInstaller) { Write-Log "VCSA Source: OK" -Info } else { Write-Log "VCSA Source: Failed" -Warning; $preflightFailure = $true } 144 | if(Test-Path -Path $podConfig.sources.ESXiAppliance) { Write-Log "ESXi Source: OK" -Info } else { Write-Log "ESXi Source: Failed" -Warning; $preflightFailure = $true } 145 | if(Test-Path -Path $podConfig.sources.NSXAppliance) { Write-Log "NSX Source: OK" -Info } else { Write-Log "NSX Source: Failed" -Warning; $preflightFailure = $true } 146 | Write-Log "### Validating Target" 147 | $pVCSA = Get-VcConnection -vcsaName $podConfig.physical.server -vcsaUser $podConfig.physical.user -vcsaPassword $podConfig.physical.password -ErrorAction SilentlyContinue 148 | if($pVCSA) { Write-Log "Physical VCSA: OK" -Info } else { Write-Log "Physical VCSA: Failed" -Warning; $preflightFailure = $true } 149 | $pCluster = Get-Cluster -Name $podConfig.physical.cluster -Server $pVCSA -ErrorAction SilentlyContinue 150 | if($pCluster) { Write-Log "Physical Cluster: OK" -Info } else { Write-Log "Physical Cluster: Failed" -Warning; $preflightFailure = $true } 151 | $pDatastore = Get-Datastore -Name $podConfig.physical.datastore -Server $pVCSA -ErrorAction SilentlyContinue 152 | if($pDatastore) { Write-Log "Physical Datastore: OK" -Info } else { Write-Log "Physical Datastore: Failed" -Warning; $preflightFailure = $true } 153 | $pPortGroup = Get-VDPortgroup -Name $podConfig.physical.portgroup -Server $pVCSA -ErrorAction SilentlyContinue 154 | if($pPortGroup) { Write-Log "Physical Portgroup: OK" -Info } else { Write-Log "Physical Portgroup: Failed" -Warning; $preflightFailure = $true } 155 | if($pPortGroup.ExtensionData.Config.DefaultPortConfig.SecurityPolicy.AllowPromiscuous.Value -eq "Accept") { Write-Log "Physical Portgroup (Promiscuous mode): OK" -Info } else { Write-Log "Physical Portgroup: Promiscuous mode denied" -Warning; $preflightFailure = $true } 156 | if($pPortGroup.ExtensionData.Config.DefaultPortConfig.SecurityPolicy.ForgedTransmits.Value -eq "Accept") { Write-Log "Physical Portgroup (Forged transmits): OK" -Info } else { Write-Log "Physical Portgroup: Forged transmits denied" -Warning; $preflightFailure = $true } 157 | $pFolder = Get-PodFolder -vcsaConnection $pVCSA -folderPath $podConfig.physical.folder -ErrorAction SilentlyContinue 158 | if($pFolder) { Write-Log "Physical Folder: OK" -Info } else { Write-Log "Physical Folder: Failed" -Warning; $preflightFailure = $true } 159 | $pHost = $pCluster | Get-VMHost -Server $pVCSA -ErrorAction SilentlyContinue | Where-Object { $_.ConnectionState -eq "Connected" } | Get-Random 160 | if($pHost) { Write-Log "Physical Host: OK" -Info } else { Write-Log "Physical Host: Failed" -Warning; $preflightFailure = $true } 161 | 162 | 163 | if($preflightFailure) { 164 | Write-Log "#### Aborting - please fix pre-flight configuration errors ####" -Warning 165 | Close-VcConnection 166 | return; 167 | } 168 | 169 | if($deployESXi) { 170 | Write-Log "#### Deploying Nested ESXi VMs ####" 171 | $pVCSA = Get-VcConnection -vcsaName $podConfig.physical.server -vcsaUser $podConfig.physical.user -vcsaPassword $podConfig.physical.password 172 | $pCluster = Get-Cluster -Name $podConfig.physical.cluster -Server $pVCSA 173 | $pDatastore = Get-Datastore -Name $podConfig.physical.datastore -Server $pVCSA 174 | $pPortGroup = Get-VDPortgroup -Name $podConfig.physical.portgroup -Server $pVCSA 175 | $pFolder = Get-PodFolder -vcsaConnection $pVCSA -folderPath $podConfig.physical.folder 176 | 177 | if ($pDatastore.Type -eq "vsan") { 178 | Write-Log "VSAN Datastore detected, checking Fake SCSI Reservations" 179 | $pHosts = Get-VMHost -Location $pCluster 180 | foreach($pHost in $pHosts) { 181 | $Setting = Get-AdvancedSetting -Entity $pHost -Name "VSAN.FakeSCSIReservations" 182 | if($Setting.Value -ne 1) { 183 | Write-Log "Setting FakeSCSIReservations on $($pHost)" 184 | Get-AdvancedSetting -Entity $pHost -Name "VSAN.FakeSCSIReservations" | Set-AdvancedSetting -Value 1 -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile 185 | } 186 | } 187 | } 188 | 189 | $deployTasks = @() 190 | 191 | $podConfig.esxi.hosts | ForEach-Object { 192 | $pESXi = $pCluster | Get-VMHost -Server $pVCSA | Where-Object { $_.ConnectionState -eq "Connected" } | Get-Random 193 | 194 | $nestedESXiName = $_.name 195 | $nestedESXiIPAddress = $_.ip 196 | 197 | if((Get-VM | Where-Object -Property Name -eq -Value $nestedESXiName) -eq $null) { 198 | $ovfConfig = Get-ovfConfiguration -Ovf $podConfig.sources.ESXiAppliance 199 | $ovfConfig.Common.guestinfo.hostname.Value = $nestedESXiName 200 | $ovfConfig.Common.guestinfo.ipaddress.Value = $nestedESXiIPAddress 201 | $ovfConfig.Common.guestinfo.netmask.Value = $podConfig.physical.network.netmask 202 | $ovfConfig.Common.guestinfo.gateway.Value = $podConfig.physical.network.gateway 203 | $ovfConfig.Common.guestinfo.dns.Value = $podConfig.physical.network.dns 204 | $ovfConfig.Common.guestinfo.domain.Value = $podConfig.physical.network.domain 205 | $ovfConfig.Common.guestinfo.ntp.Value = $podConfig.physical.network.ntp 206 | $ovfConfig.Common.guestinfo.syslog.Value = $podConfig.general.syslog 207 | $ovfConfig.Common.guestinfo.password.Value = $podConfig.general.password 208 | $ovfConfig.Common.guestinfo.ssh.Value = $podConfig.general.ssh 209 | $ovfConfig.Common.guestinfo.createvmfs.Value = $podConfig.esxi.createVMFS 210 | $ovfConfig.NetworkMapping.VM_Network.Value = $pPortGroup 211 | 212 | Write-Log "Deploying Nested ESXi VM $($nestedESXiName) to $($pESXi)" 213 | $task = Import-VApp -Server $pVCSA -VMHost $pESXi -Source $podConfig.sources.ESXiAppliance -ovfConfiguration $ovfConfig -Name $nestedESXiName -Location $pCluster -Datastore $pDatastore -DiskStorageFormat thin -RunAsync -ErrorAction SilentlyContinue 214 | $deployTasks += $task 215 | } else { 216 | Write-Log "Nested ESXi host $($nestedESXiName) exists, skipping" -Warning 217 | } 218 | } 219 | 220 | $taskCount = $deployTasks.Count 221 | while($taskCount -gt 0) { 222 | $deployTasks | ForEach-Object { 223 | #Write-Log -Message "Task $($_.Id) - $($_.State) - $($_.PercentComplete)%" 224 | Write-Progress -Activity "Deploying ESXi ($($_.Id))" -Status "$($_.PercentComplete)% Complete" -PercentComplete $_.PercentComplete 225 | if($_.State -eq "Success") { 226 | # Deployment Completed 227 | Write-Progress -Activity "Deploying ESXi ($($_.Result))" -Completed 228 | $nestedESXiVM = Get-VM -Name $_.Result -Server $pVCSA 229 | $nestedESXiName = $nestedESXiVM.Name 230 | 231 | Write-Log "$($nestedESXiName): Updating vCPU ($($podConfig.esxi.cpu)) & RAM ($($podConfig.esxi.ram)GB)" 232 | $nestedESXiVM | Set-VM -NumCpu $podConfig.esxi.cpu -MemoryGB $podConfig.esxi.ram -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile 233 | 234 | Write-Log "$($nestedESXiName): Updating vSAN Caching disk to $($podConfig.esxi.cacheDisk)GB" 235 | # Work around for VSAN issue with not enough disk space - delete and add new disk 236 | Get-HardDisk -VM $nestedESXiVM | Where-Object -Property "CapacityGB" -eq -Value 4 | Remove-HardDisk -DeletePermanently -Confirm:$false 237 | if($podConfig.esxi.capacityDisk > 0) { 238 | New-HardDisk -VM $nestedESXiVM -Persistence persistent -SizeGB $podConfig.esxi.cacheDisk -StorageFormat Thin | Out-File -Append -LiteralPath $verboseLogFile 239 | New-AdvancedSetting -Entity $nestedESXiVM -Name 'scsi0:1.virtualSSD' -Value $true -Confirm:$false -Force | Out-File -Append -LiteralPath $verboseLogFile 240 | } 241 | 242 | Write-Log "$($nestedESXiName): Updating vSAN Capacity disk to $($podConfig.esxi.capacityDisk) GB" 243 | # Work around for VSAN issue with not enough disk space - delete and add new disk 244 | Get-HardDisk -VM $nestedESXiVM | Where-Object -Property "CapacityGB" -eq -Value 8 | Remove-HardDisk -DeletePermanently -Confirm:$false 245 | if($podConfig.esxi.capacityDisk > 0) { 246 | New-HardDisk -VM $nestedESXiVM -Persistence persistent -SizeGB $podConfig.esxi.capacityDisk -StorageFormat Thin | Out-File -Append -LiteralPath $verboseLogFile 247 | New-AdvancedSetting -Entity $nestedESXiVM -Name 'scsi0:2.virtualSSD' -Value $true -Confirm:$false -Force | Out-File -Append -LiteralPath $verboseLogFile 248 | } 249 | 250 | # Ensure the disks are marked as SSD 251 | New-AdvancedSetting -Entity $nestedESXiVM -Name 'scsi0:0.virtualSSD' -Value $true -Confirm:$false -Force | Out-File -Append -LiteralPath $verboseLogFile 252 | 253 | Write-Log "Moving $nestedESXiName to $($pFolder.Name) folder" 254 | Move-VM -VM $nestedESXiVM -InventoryLocation $pFolder | Out-File -Append -LiteralPath $verboseLogFile 255 | 256 | Write-Log "Powering On $nestedESXiName" 257 | Start-VM -VM $nestedESXiVM -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile 258 | 259 | $successTask = $_ 260 | $deployTasks = $deployTasks | Where-Object $_.Id -ne ($successTask.Id) 261 | $taskCount-- 262 | 263 | } elseif($_.State -eq "Error") { 264 | Write-Progress -Activity "Deploying ESXi ($($_.Result))" -Completed 265 | Write-Log -Message " failed to deploy" -Warning 266 | $failedTask = $_ 267 | $deployTasks = $deployTasks | Where-Object $_.Id -ne ($failedTask.Id) 268 | $taskCount-- 269 | } 270 | } 271 | Start-Sleep 5 272 | } 273 | Close-VcConnection -vcsaName $podConfig.physical.server 274 | Write-Log "#### Nested ESXi VMs Deployed ####" 275 | } 276 | 277 | if($deployVCSA) { 278 | Write-Log "#### Deploying vCenter Server Appliance(s) ####" 279 | $pVCSA = Get-VcConnection -vcsaName $podConfig.physical.server -vcsaUser $podConfig.physical.user -vcsaPassword $podConfig.physical.password 280 | $pCluster = Get-Cluster -Name $podConfig.physical.cluster -Server $pVCSA 281 | $pDatastore = Get-Datastore -Name $podConfig.physical.datastore -Server $pVCSA 282 | $pPortGroup = Get-VDPortgroup -Name $podConfig.physical.portgroup -Server $pVCSA 283 | $pFolder = Get-PodFolder -vcsaConnection $pVCSA -folderPath $podConfig.physical.folder 284 | 285 | Write-Log "Disabling DRS on $($podConfig.physical.cluster)" 286 | $pCluster | Set-Cluster -DrsEnabled:$false -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile 287 | 288 | if($podConfig.psc -ne $null) { 289 | Write-Log "##### Deploying external PSC #####" 290 | $config = (Get-Content -Raw $externalPSCconfig) | convertfrom-json 291 | $config.'new.vcsa'.vc.hostname = $podConfig.physical.server 292 | $config.'new.vcsa'.vc.username = $podConfig.physical.user 293 | $config.'new.vcsa'.vc.password = $podConfig.physical.password 294 | $config.'new.vcsa'.vc.datacenter = @($podConfig.physical.datacenter) 295 | $config.'new.vcsa'.vc.datastore = $podConfig.physical.datastore 296 | $config.'new.vcsa'.vc.target = @($podConfig.physical.cluster) 297 | $config.'new.vcsa'.vc.'deployment.network' = $podConfig.physical.portgroup 298 | $config.'new.vcsa'.appliance.'thin.disk.mode' = $true 299 | $config.'new.vcsa'.appliance.'deployment.option' = $podConfig.psc.deploymentSize 300 | $config.'new.vcsa'.appliance.name = $podConfig.psc.name 301 | $config.'new.vcsa'.network.'system.name' = $podConfig.psc.hostname 302 | $config.'new.vcsa'.network.'ip.family' = "ipv4" 303 | $config.'new.vcsa'.network.mode = "static" 304 | $config.'new.vcsa'.network.ip = $podConfig.psc.ip 305 | $config.'new.vcsa'.network.'dns.servers'[0] = $podConfig.physical.network.dns 306 | $config.'new.vcsa'.network.prefix = $podConfig.physical.network.prefix 307 | $config.'new.vcsa'.network.gateway = $podConfig.physical.network.gateway 308 | $config.'new.vcsa'.os.'ssh.enable' = $podConfig.general.ssh 309 | $config.'new.vcsa'.os.password = $podConfig.psc.rootPassword 310 | $config.'new.vcsa'.sso.password = $podConfig.psc.sso.password 311 | $config.'new.vcsa'.sso.'domain-name' = $podConfig.psc.sso.domain 312 | $config.'new.vcsa'.sso.'site-name' = $podConfig.psc.sso.site 313 | if($podConfig.psc.sso.replicationPartner.length -gt 0) { 314 | # Join existing domain 315 | Write-Log "PSC will join replicate to $($podConfig.psc.sso.replicationPartner) " 316 | $config.'new.vcsa'.sso | Add-Member -Name "first-instance" -Value $false -MemberType NoteProperty 317 | $config.'new.vcsa'.sso | Add-Member -Name "sso.port" -Value "443" -MemberType NoteProperty 318 | $config.'new.vcsa'.sso | Add-Member -Name "replication-partner-hostname" -Value $podConfig.psc.sso.replicationPartner -MemberType NoteProperty 319 | } 320 | Write-Log "Creating PSC JSON Configuration file for deployment" 321 | $config | ConvertTo-Json | Set-Content -Path "$($temp)\psctemplate.json" 322 | 323 | if((Get-VM | Where-Object -Property Name -eq -Value $podConfig.psc.name) -eq $null) { 324 | Write-Log "Deploying OVF, this may take a while..." 325 | Invoke-Expression "$($vcsaDeploy) install --no-esx-ssl-verify --accept-eula --acknowledge-ceip $($temp)\psctemplate.json"| Out-File -Append -LiteralPath $verboseLogFile 326 | $vcsaDeployOutput | Out-File -Append -LiteralPath $verboseLogFile 327 | Write-Log "Moving $($podConfig.psc.name) to $($podConfig.physical.folder)" 328 | if((Get-VM | Where-Object {$_.name -eq $podConfig.psc.name}) -eq $null) { 329 | throw "Could not find VCSA VM. The script was unable to find the deployed VCSA" 330 | } 331 | Get-VM -Name $podConfig.psc.name | Move-VM -InventoryLocation $pFolder | Out-File -Append -LiteralPath $verboseLogFile 332 | } else { 333 | Write-Log "PSC exists, skipping" -Warning 334 | } 335 | 336 | } 337 | if($podConfig.vcsa -ne $null) { 338 | if($podConfig.psc -ne $null) { 339 | Write-Log "##### Deploying VCSA with external PSC #####" 340 | $config = (Get-Content -Raw $externalVCSAconfig) | convertfrom-json 341 | # External PSC Specific config 342 | $config.'new.vcsa'.sso.'sso.port' = "443" 343 | $config.'new.vcsa'.sso.'platform.services.controller' = $podConfig.psc.ip 344 | } else { 345 | Write-Log "##### Deploying VCSA with embedded PSC #####" 346 | $config = (Get-Content -Raw $embeddedVCSAconfig) | convertfrom-json 347 | # Embedded PSC Specific config 348 | $config.'new.vcsa'.sso.'site-name' = $podConfig.vcsa.sso.site 349 | } 350 | $config.'new.vcsa'.vc.hostname = $podConfig.physical.server 351 | $config.'new.vcsa'.vc.username = $podConfig.physical.user 352 | $config.'new.vcsa'.vc.password = $podConfig.physical.password 353 | $config.'new.vcsa'.vc.datacenter = @($podConfig.physical.datacenter) 354 | $config.'new.vcsa'.vc.datastore = $podConfig.physical.datastore 355 | $config.'new.vcsa'.vc.target = @($podConfig.physical.cluster) 356 | $config.'new.vcsa'.vc.'deployment.network' = $podConfig.physical.portgroup 357 | $config.'new.vcsa'.os.'ssh.enable' = $podConfig.general.ssh 358 | $config.'new.vcsa'.os.password = $podConfig.vcsa.rootPassword 359 | $config.'new.vcsa'.appliance.'thin.disk.mode' = $true 360 | $config.'new.vcsa'.appliance.'deployment.option' = $podConfig.vcsa.deploymentSize 361 | $config.'new.vcsa'.appliance.name = $podConfig.vcsa.name 362 | $config.'new.vcsa'.network.'system.name' = $podConfig.vcsa.hostname 363 | $config.'new.vcsa'.network.'ip.family' = "ipv4" 364 | $config.'new.vcsa'.network.mode = "static" 365 | $config.'new.vcsa'.network.ip = $podConfig.vcsa.ip 366 | $config.'new.vcsa'.network.'dns.servers'[0] = $podConfig.physical.network.dns 367 | $config.'new.vcsa'.network.prefix = $podConfig.physical.network.prefix 368 | $config.'new.vcsa'.network.gateway = $podConfig.physical.network.gateway 369 | $config.'new.vcsa'.sso.password = $podConfig.vcsa.sso.password 370 | $config.'new.vcsa'.sso.'domain-name' = $podConfig.vcsa.sso.domain 371 | Write-Log "Creating VCSA JSON Configuration file for deployment" 372 | $config | ConvertTo-Json | Set-Content -Path "$($temp)vctemplate.json" 373 | if((Get-VM | Where-Object -Property Name -eq -Value $podConfig.vcsa.name) -eq $null) { 374 | Write-Log "Deploying OVF, this may take a while..." 375 | Write-Log "$($vcsaDeploy) install --no-esx-ssl-verify --accept-eula --acknowledge-ceip $($temp)vctemplate.json" -Warning 376 | Invoke-Expression "$($vcsaDeploy) install --no-esx-ssl-verify --accept-eula --acknowledge-ceip $($temp)vctemplate.json" | Out-File -Append -LiteralPath $verboseLogFile 377 | $vcsaDeployOutput | Out-File -Append -LiteralPath $verboseLogFile 378 | Write-Log "Moving $($podConfig.vcsa.name) to $($podConfig.physical.folder)" 379 | if((Get-VM | Where-Object {$_.name -eq $podConfig.vcsa.name}) -eq $null) { 380 | throw "Could not find VCSA VM. The script was unable to find the deployed VCSA" 381 | } 382 | Get-VM -Name $podConfig.vcsa.name | Move-VM -InventoryLocation $pFolder | Out-File -Append -LiteralPath $verboseLogFile 383 | } else { 384 | Write-Log "VCSA exists, skipping" -Warning 385 | } 386 | Write-Log "Enabling DRS on $($podConfig.physical.cluster)" 387 | $pCluster | Set-Cluster -DrsEnabled:$true -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile 388 | } 389 | Close-VcConnection -vcsaName $podConfig.physical.server 390 | } 391 | 392 | 393 | if($configureVCSA) { 394 | Write-Log "#### Configuring VCSA ####" 395 | $nVCSA = Get-VcConnection -vcsaName $podConfig.vcsa.ip -vcsaUser "administrator@$($podConfig.vcsa.sso.domain)" -vcsaPassword $podConfig.vcsa.sso.password 396 | 397 | Write-Log "## Configuring Datacenter and Cluster ##" 398 | Write-Log "Creating Datacenter $($podConfig.vcsa.datacenter)" -Info 399 | $nDatacenter = (Get-Datacenter -Server $nVCSA | Where-Object -Property Name -eq -Value $podConfig.vcsa.datacenter) 400 | if($nDatacenter -eq $null) { 401 | $nDatacenter = New-Datacenter -Server $nVCSA -Name $podConfig.vcsa.datacenter -Location (Get-Folder -Type Datacenter -Server $nVCSA) 402 | } else { 403 | Write-Log "Datacenter exists, skipping" -Warning 404 | } 405 | Write-Log "Creating Cluster $($podConfig.vcsa.cluster)" -Info 406 | $nCluster = Get-Cluster -Server $nVCSA | Where-object -Property Name -eq -Value $podConfig.vcsa.cluster 407 | if($nCluster -eq $null) { 408 | $nCluster = New-Cluster -Server $nVCSA -Name $podConfig.vcsa.cluster -Location $nDatacenter -DrsEnabled 409 | } else { 410 | Write-Log "Cluster exists, skipping" -Warning 411 | } 412 | Write-Log "Enabling VMotion on vmkernel ports" -Info 413 | Get-VMHostNetworkAdapter -Server $nVCSA -VMkernel | Set-VMHostNetworkAdapter -VMotionEnabled:$true -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile 414 | 415 | if($licenseVCSA) { 416 | Write-Log "Licensing vSphere" 417 | $serviceInstance = Get-View ServiceInstance -Server $nVCSA 418 | $licenseManagerRef=$serviceInstance.Content.LicenseManager 419 | $licenseManager=Get-View $licenseManagerRef 420 | $licenseManager.AddLicense($podConfig.license.vcenter,$null) | Out-File -Append -LiteralPath $verboseLogFile 421 | $licenseManager.AddLicense($podConfig.license.vsphere,$null) | Out-File -Append -LiteralPath $verboseLogFile 422 | $licenseManager.AddLicense($podConfig.license.vsan,$null) | Out-File -Append -LiteralPath $verboseLogFile 423 | $licenseManager.AddLicense($podConfig.license.nsx,$null) | Out-File -Append -LiteralPath $verboseLogFile 424 | $licenseAssignmentManager = Get-View $licenseManager.LicenseAssignmentManager 425 | Write-Log "Assigning vCenter Server License" -Info 426 | try { 427 | $licenseAssignmentManager.UpdateAssignedLicense($nVCSA.InstanceUuid, $podConfig.license.vcenter, $null) | Out-File -Append -LiteralPath $verboseLogFile 428 | } 429 | catch { 430 | $ErrorMessage = $_.Exception.Message 431 | Write-Log $ErrorMessage -Warning 432 | } 433 | } 434 | 435 | Write-Log "## Adding hosts to cluster ##" 436 | $nCluster = Get-Cluster -Name $podConfig.vcsa.cluster -Server $nVCSA 437 | $podConfig.esxi.hosts | ForEach-Object { 438 | $nestedESXiIPAddress = $_.ip 439 | Write-Log "Adding ESXi host $($_.name) to Cluster" -Info 440 | if((Get-VMHost -Server $nVCSA | Where-Object -Property Name -eq -Value $nestedESXiIPAddress) -eq $null) { 441 | Add-VMHost -Server $nVCSA -Location $nCluster -User "root" -Password $podConfig.general.password -Name $nestedESXiIPAddress -Force | Set-VMHost -LicenseKey $podConfig.license.vsphere -State "Maintenance" | Out-File -Append -LiteralPath $verboseLogFile 442 | } else { 443 | Write-Log "Host exists, skipping" -Warning 444 | } 445 | } 446 | 447 | if($podConfig.nfs.count -gt 0) { 448 | Write-Log "## Adding NFS shares to hosts ##" 449 | $nHosts = Get-VMHost -Server $nVCSA -Location $nCluster 450 | foreach($nfs in $podConfig.nfs) { 451 | Write-Log "Adding NFS share $($nfs.name)" -Info 452 | if(Get-Datastore -Name $nfs.name -ErrorAction SilentlyContinue) { 453 | Write-Log "Datastore $($nfs.name) exists, skipping" -Warning 454 | } else { 455 | $nHosts | New-Datastore -Nfs -Name $nfs.name -NfsHost $nfs.server -Path $nfs.path | Out-File -Append -LiteralPath $verboseLogFile 456 | } 457 | } 458 | } 459 | 460 | if($configureVSAN) { 461 | Write-Log "## Configuring VSAN ##" 462 | $VSANCluster = Get-Cluster -Name $podConfig.vcsa.cluster -Server $nVCSA | Out-File -Append -LiteralPath $verboseLogFile 463 | if($VSANCluster.VsanEnabled) { 464 | Write-Log "VSAN is enabled, skipping" -Warning 465 | } else { 466 | Set-Cluster -Cluster $podConfig.vcsa.cluster -VsanEnabled:$true -VsanDiskClaimMode Automatic -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile 467 | Write-Log "Assigning VSAN License" -Info 468 | $serviceInstance = Get-View ServiceInstance -Server $nVCSA 469 | $licenseManagerRef=$serviceInstance.Content.LicenseManager 470 | $licenseManager=Get-View $licenseManagerRef 471 | $licenseAssignmentManager = Get-View $licenseManager.LicenseAssignmentManager 472 | $clusterRef = (Get-Cluster -Server $nVCSA -Name $podConfig.vcsa.cluster | get-view).MoRef 473 | try { 474 | $licenseAssignmentManager.UpdateAssignedLicense(($clusterRef.value), $podConfig.license.vsan, $null) | Out-File -Append -LiteralPath $verboseLogFile 475 | } 476 | catch { 477 | $ErrorMessage = $_.Exception.Message 478 | Write-Log $ErrorMessage -Warning 479 | } 480 | } 481 | 482 | # $nHosts = Get-VMHost -Server $nVCSA -Location $podConfig.vcsa.cluster 483 | # foreach ($nHost in $nHosts) { 484 | # $luns = $nHost | Get-ScsiLun | Select-Object CanonicalName, CapacityGB 485 | # if((Get-VsanDiskGroup -VMHost $nHost) -eq $null) { 486 | # Write-Log "Querying ESXi host disks to create VSAN Diskgroups" 487 | # foreach ($lun in $luns) { 488 | # if(([int]($lun.CapacityGB)).toString() -eq "$($podConfig.esxi.cacheDisk)") { 489 | # $vsanCacheDisk = $lun.CanonicalName 490 | # } 491 | # if(([int]($lun.CapacityGB)).toString() -eq "$($podConfig.esxi.capacityDisk)") { 492 | # $vsanCapacityDisk = $lun.CanonicalName 493 | # } 494 | # } 495 | # Write-Log "Creating VSAN DiskGroup for $nHost" 496 | # New-VsanDiskGroup -Server $nVCSA -VMHost $nHost -SsdCanonicalName $vsanCacheDisk -DataDiskCanonicalName $vsanCapacityDisk | Out-File -Append -LiteralPath $verboseLogFile 497 | # } 498 | # } 499 | } 500 | 501 | if($configureVDSwitch) { 502 | Write-Log "## Configuring Distributed Switching ##" 503 | $nHosts = Get-VMHost -Location $podConfig.vcsa.cluster -Server $nVCSA 504 | $nDatacenter = Get-Datacenter -Name $podConfig.vcsa.datacenter -Server $nVCSA 505 | $distributedSwitch = Get-VDSwitch -Server $nVCSA | Where-Object -Property Name -eq -Value $podConfig.vcsa.distributedSwitch 506 | Write-Log "Creating distributed switch" -Info 507 | if($distributedSwitch -eq $null) { 508 | $distributedSwitch = New-VDSwitch -Name $podConfig.vcsa.distributedSwitch -Location $nDatacenter -Server $nVCSA -NumUplinkPorts 2 509 | Start-Sleep -Seconds 3 510 | } else { 511 | Write-Log "Distributed switch exists, skipping" -Warning 512 | } 513 | Write-Log "Adding hosts to distributed switch" -Info 514 | foreach ($nHost in $nHosts) { 515 | if(($distributedSwitch | Get-VMHost | Where-Object {$_.Name -eq $nHost.Name}) -eq $null) { 516 | Add-VDSwitchVMHost -VDSwitch $distributedSwitch -VMHost $nHost 517 | #Start-Sleep -Seconds 10 518 | } else { 519 | Write-Log "$($nHost) is already added to VDS" -Warning 520 | } 521 | } 522 | $dvPortGroup = Get-VDPortgroup | Where-Object -Property Name -eq -Value $podConfig.vcsa.portgroup 523 | Write-Log "Creating distributed port group" -Info 524 | if($dvPortGroup -eq $null) { 525 | $dvPortGroup = New-VDPortgroup -Name $podConfig.vcsa.portgroup -NumPorts 1000 -VDSwitch $distributedSwitch 526 | Start-Sleep -Seconds 3 527 | } else { 528 | Write-Log "Distributed port group exists, skipping" -Warning 529 | } 530 | 531 | foreach($nHost in $nHosts) { 532 | $networkAdapters = Get-VMHostNetworkAdapter -VMHost $nHost -Physical 533 | $vmkernelAdapter = Get-VMHostNetworkAdapter -VMHost $nHost -VMKernel 534 | Write-Log "Migrating NICs and VMkernel adapter to distributed switch" -Info 535 | if($vmkernelAdapter.PortGroupName -ne $podConfig.vcsa.portgroup) { 536 | Add-VDSwitchPhysicalNetworkAdapter -DistributedSwitch $distributedSwitch -VMHostPhysicalNic $networkAdapters -VMHostVirtualNic $vmkernelAdapter -VirtualNicPortgroup $dvPortGroup -Confirm:$false 537 | } else { 538 | Write-Log "NICs and VMKernel adapter are already assigned to the distributed switch, skipping" -Warning 539 | } 540 | 541 | } 542 | Start-Sleep -Seconds 5 543 | Write-Log "Removing standard vSwitches" -Info 544 | Get-VirtualSwitch -Server $nVCSA -Standard | Remove-VirtualSwitch -Confirm:$false 545 | } 546 | } 547 | 548 | Close-VcConnection 549 | 550 | $EndTime = Get-Date 551 | $duration = [math]::Round((New-TimeSpan -Start $StartTime -End $EndTime).TotalMinutes,2) 552 | 553 | Write-Log "Pod Deployment Tasks Completed in $($duration) minutes" --------------------------------------------------------------------------------