Bulk deploy Veeam Linux Proxies

In version 10 of Veeam Backup & Replication a lot of Linux love has been put in to the product. To me, one of the most interesting things is the new functionality where you can assign a Linux VM the role of a backup proxy. Historically the proxy functionality has been restricted to Windows OS only. The linux proxy does not come as a prebuilt Veeam appliance, just as with Windows proxies you still need to secure and patch the operating system. The rational behind it is easy to understand, most organizations already have some sort of patch management system in place to leverage for their production workload and you would want to have the same level of patching and security on your proxies as you have for all other workloads. I thought it would interesting to see if you could automate the deployment of proxy servers a bit. Sure enough it is a fairly simple process since Veeam Backup & Replication has a great PowerShell extension which will let you automate almost all of the tasks you can do in the GUI. As far as automating proxy servers go, my colleague Anthony Spiteri has a comprehensive project called “Project Ōtosukēru” based on Terraform which may be of interest to you as well.

As a side note, Veeam Backup & Replication version 10 contains a wide variety of new features and updates, the Veeam Vanguards made a list of their top features.

Mind you, the code below should serve as an indication of what you can do – as an example if you will. However, you use at at your own risk. I’ve put in an option to deploy to a test/lab environment (if you are lucky enough to have a lab environment) so you can start testing it out in a safe environment.

So why not combine this small project with the small Linux operating system VMware provides called PhotonOS? PhotonOS is a stripped down linux operating system we can use. Now you can certainly download an iso image and do a traditional install and set it up just the way you’d like and then convert it to a template in vSphere and make use of it that way. However I thought it would be more interesting to see how much you can automate. So instead I’m just downloading the prebuilt Linux appliance and using that as the source for my proxies.

The script performs 4 different tasks:

  • It starts by importing the settings you specified in the configuration file (config.json)
  • Download required components if they’re not available. Two components are needed: The PhotonOS OVA and a powershell script by William Lam called VMKeystrokes.ps1. VMKeystrokes is needed to change the initial password of the appliance.
  • Let’s you choose where to deploy the Linux appliance, you can pick a single host, all hosts in a specific cluster or all hosts in a specific datacenter. It will also let you choose if you’d like to deploy to a test/lab environment or to a production environment (settings fetched from config.json). The script will try to ping the IP range configured in config.json to verify that they are not being used. It then deploys the appliance to the selected target(s). Once deployed the appliance will be configured (Connecting to the specified network portgroup in vSphere, Set vCPU and vRAM, set Static IP address, firewall ports opened, configuring timezone).
  • The Linux server will then be added to Veeam Backup & Replication as a managed server. The managed server will then have the role Backup Proxy assigned to it, concurrent tasks will be set to whatever amount of vCPU’s you’ve assigned to the VM (from the configuration file).

Config.json explained
“location”: – Where files be downloaded and working directory
“ovasource”: – URL to the OVA
“ovaname”: – Name of the OVA
“ProxyBaseName”: – Base name of Proxy VM to be deployed
“ProxyvCPU”: – vCPUs to assign the Proxy VM
“ProxyvRAM”: – vRAM to assign the Proxy VM
“sshuser”: “root” – Logon account for the Proxy VM
“ovainitialpassword”: – Default password of the OVA is “changeme”
“newsshpassword”: – New password to apply for the root user on the Proxy VM

“Prod”: {
“vbrserver”: – IP adress to Veeam Backup & Replication server
“vcserver”: – IP adress to VMware vCenter Server
“network”: – Portgroup the Proxy VM will be connected to
“startipaddress”: – Start of IP pool the Proxy VMs will use
“endipaddress”: – End of IP pool the Proxy VMs till use
“vbruser”: – User to connect to Veeam Backup & Replication
“vcuser”: – User to connect to VMware vCenter Server
“vcdatacenter”: – Datacenter in vCenter to use
“gateway”: – IP address of gateway for the Proxy VM
“dns”: – IP address of DNS for the Proxy VM
“domains”: – DNS Search Domains for the Proxy VM
“ntpserver”: – NTP Server for the Proxy VM to use

The Proxy VM will be named with ProxyBaseName + IP address of the target host to where the VM is deployed with all dots replaced by dashes, i.e “VeeamProxy_10-10-50-38”.

Save the two files to c:\temp and then you’re good to go!
No more bits needed to be downloaded, the script will download what it’s required. The only thing you need to change is the settings in config.json to reflect your environment.

Download Deploy_Veeam_Linux_Proxy.ps1
Download config.json

The script will add proxies based on IP addresses to Veeam Backup & Replication in this version, but per Veeam recommendation FQDNs should be used.


  1. # Deploy_Veeam_Linux_Proxy.ps1
  2. # Version: 1.0
  3. # Author: Niclas Borgstrom, Veeam Cloud Solution Architect EMEA
  4. #
  5. # Synopsis: 
  6. # This script will download a linux appliance (based on VMware PhotonOS)
  7. # and deploy the appliance to a host, cluster or datacenter of your choice.
  8. # It will also download VMKeystrokes.ps1 by William Lam, VMware in order to 
  9. # do the initial configuration of PhotonOS.
  10. # When the OVA is deployed it will be configured with a static IP address,
  11. # firewall will be opened for Veeam Backup Proxy specific ports, vCPU and
  12. # vRAM will be changed and the VM will be added to Veeam Backup & Replication
  13. # as a managed server and then be assigned the proxy role. Concurrent tasks
  14. # will be set according to the vCPUs assigned to the VM.
  15. #
  16. # You can choose to deploy either to a production- or a test environment, if 
  17. # available. The OVA will only be deployed to powered on hosts.
  18. #
  19. # Disclaimer:
  20. # The script is for demonstration purpose only, run it at your own risk
  21. #
  22. # Credits
  23. # 'My-Logger' function by William Lam, VMware
  24. #
  25. # Prereqs:
  26. # - VMware environment managed by vCenter
  27. # - Veeam Backup & Replication already installed on a server.
  28. # - Internet access to download PhotonOS and VMKeystrokes.ps1 to change the default 
  29. #    password on the PhotonOS appliance.
  30. #
  31. # Limitations:
  32. # - Linux proxy can't be used as a 'Guest Interaction Proxy'
  33. # - Linux proxy can't be used as a 'File Backup Proxy'
  34. # - Linux Proxy only supports Hot-Add/Virtual appliance mode
  35. #
  36. # Changelog:
  37. # v 1.0: Initial release
  39. $global:BaseDirectory = "C:\temp\"
  40. $global:BaseConfig = "config.json"
  41. $verboseLogFile = $global:BaseDirectory + "Veeam_Linux_Proxy_Deployment_" + $(Get-Date -Format "yyyy-MM-dd_HH-mm") + ".log"
  43. Function My-Logger {
  44.     param(
  45.     [Parameter(Mandatory=$true)]
  46.     [String]$message
  47.     )
  49.     $timeStamp = Get-Date -Format "yyyy-MM-dd_HH:mm:ss"
  51.     Write-Host -NoNewline -ForegroundColor White "[$timestamp]"
  52.     Write-Host -ForegroundColor Green " $message"
  53.     $logMessage = "[$timeStamp] $message"
  54.     $logMessage | Out-File -Append -LiteralPath $verboseLogFile
  55. }
  57. # Load and parse the JSON configuration file 
  58. My-Logger "Loading JSON configuration file $BaseDirectory$BaseConfig"
  59. try { 
  60. 	$global:Config = Get-Content "$BaseDirectory$BaseConfig" -RAW -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | ConvertFrom-Json -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
  62. } catch { 
  63. 	My-Logger "[ERROR] The configuration files is missing!"
  64. 	Break
  65. } 
  67. # Check the configuration
  68. if (!($Config)) {
  69.     My-Logger "[ERROR] The configuration file is missing!"
  70.     Break
  71. }
  73. $location = $Config.General.location
  74. $ovasource = $Config.General.ovasource
  75. $ovaname = $Config.General.ovaname
  76. $proxybasename = $Config.General.ProxyBaseName
  77. $proxyvcpu = $Config.General.ProxyvCPU
  78. $proxyvram = $Config.General.ProxyvRAM
  79. $sshuser = $Config.General.sshuser
  80. $ovainitialpassword = $Config.General.ovainitialpassword
  81. $newsshpassword = $Config.General.newsshpassword
  83. # Set temp location
  84. Set-Location $location
  86. try {
  87. 	Add-PSSnapin -Name VeeamPSSnapin -ErrorAction Stop
  88. 	Import-Module -Name VMware.VimAutomation.Core -ErrorAction Stop
  89. } catch {
  90. 	My-Logger "[ERROR] PowerCLI or VeeamPSSnapin not installed."
  91. 	Break
  92. }
  94. # Verify that VMKeystrokes.ps1 is available
  95. My-Logger "Checking VMkeystrokes.ps1..."
  96. Clear-Host
  97. try { 
  98. 	# Dot source VMKeystrokes.ps1 if it's available
  99. 	. .\VMKeystrokes.ps1
  101. } catch {
  102. 	# If VMKeystrokes.ps1 isn't available, ask if it should be downloaded
  103. 	My-Logger "  [WARNING] Unable to find 'VMKeystrokes.ps1' in $location"
  104. 	$downloadvmkeystrokes = if(($downloadvmkeystrokes = Read-Host "`nWould you like to download it (Y/N) or press enter to accept default value [Y]") -eq '') {"Y"} else {$downloadvmkeystrokes}
  105. 	if ($downloadvmkeystrokes.ToLower() -eq "y") {
  106. 		My-Logger "  Downloading 'VMKeystrokes.ps1'"
  107. 		Invoke-WebRequest -Uri "https://raw.githubusercontent.com/lamw/vghetto-scripts/master/powershell/VMKeystrokes.ps1" -OutFile ".\VMKeystrokes.ps1"
  108. 		. .\VMKeystrokes.ps1
  109. 	} else {
  110. 		My-Logger "  [ERROR] Can't continue without VMkeystrokes.ps1, exiting..."
  111. 		Break
  112. 	}
  113. }
  115. $targethosts = $null
  117. # Verify that PhotonOS it available
  118. My-Logger "Checking PhotonOS OVA..."
  119. Clear-Host
  120. if(!(Test-Path ($location + "\" + $ovaname))) {
  121. 	My-Logger "  [WARNING] Unable to find PhotonOS OVA: $($location + "\" + $ovaname)"
  122. 	$downloadova = if(($downloadova = Read-Host "`nWould you like to download it (Y/N) or press enter to accept default value [Y]") -eq '') {"Y"} else {$downloadova}
  123. 	if ($downloadova.ToLower() -eq "y") {
  124. 		My-logger "  Downloading $($location + "\" + $ovaname)"	
  125. 		Invoke-WebRequest -Uri $ovasource -OutFile $($BaseDirectory + "\" + $ovaname)
  126. 	} else {
  127. 		My-Logger "  [ERROR] PhotonOS OVA not available!"
  128. 		Break
  129. 	}
  130. }
  132. # Select where to deploy Linux Proxies
  133. Clear-Host
  134. Write-Output "`nWould you like to deploy to prod or test:`n"
  135. Write-Output "1. Production environment - $($Config.Prod.vcserver)"
  136. Write-Output "2. Test environment - $($Config.Test.vcserver)"
  137. $selectenvironment = if(($selectenvironment = Read-Host "`nSelect item or press enter to accept default value [1]") -eq '') {"1"} else {$selectenvironment}
  139. # Populate strings based on selected deployment type
  140. if ($selectenvironment -eq "1") {
  141. 	My-Logger "Deploying to Production environment $($Config.Prod.vcserver)"
  142. 	$vbrserver = $Config.Prod.vbrserver
  143. 	$vcserver = $Config.Prod.vcserver
  144. 	$network = $Config.Prod.network
  145. 	$startipaddress = $Config.Prod.startipaddress
  146. 	$endipaddress = $Config.Prod.endipaddress
  147. 	$vbruser = $Config.Prod.vbruser
  148. 	$vcuser = $Config.Prod.vcuser
  149. 	$vcdatacenter = $Config.Prod.vcdatacenter
  150. 	$gateway = $Config.Prod.gateway
  151. 	$dns = $Config.Prod.dns
  152. 	$domains = $Config.Prod.domains
  153. 	$ntpserver = $Config.Prod.ntpserver
  154. } elseif ($selectenvironment -eq "2"){
  155. 	My-Logger "Deploying to Test environment $($Config.Test.vcserver)"
  156. 	$vbrserver = $Config.Test.vbrserver
  157. 	$vcserver = $Config.Test.vcserver
  158. 	$network = $Config.Test.network
  159. 	$startipaddress = $Config.Test.startipaddress
  160. 	$endipaddress = $Config.Test.endipaddress
  161. 	$vbruser = $Config.Test.vbruser
  162. 	$vcuser = $Config.Test.vcuser
  163. 	$vcdatacenter = $Config.Test.vcdatacenter
  164. 	$gateway = $Config.Test.gateway
  165. 	$dns = $Config.Test.dns
  166. 	$domains = $Config.Test.domains
  167. 	$ntpserver = $Config.Test.ntpserver
  168. } else {
  169. 	My-Logger "[ERROR] Wrong selection..."
  170. }
  172. # Function to build a dynamic menu for infrastructure components (Datacenter, Cluster or Host)
  173. Function Get-HostList {
  174.     Param (
  175.         [Parameter(Mandatory)]
  176.         [ValidateNotNullOrEmpty()]
  177.         $ListItem,
  179.         [Parameter(Mandatory)]
  180.         [ValidateNotNullOrEmpty()]
  181.         $GetItem
  182.     )
  184.     try {
  185.         $menu = @{}
  186.         For ($i=1;$i -le $ListItem.count; $i++) { 
  187.             Write-Host "$i. $($ListItem[$i-1].Name)"
  188.             $menu.Add($i,($ListItem[$i-1].Name)) 
  189.         }
  190.         [int]$ans = if (($ans = Read-Host "`nSelect item or press enter to accept default value [1]") -eq '') {"1"} else {$ans}
  191.         $Selection = $menu.Item($ans); Invoke-Expression $($GetItem)
  192.     } catch {
  193.         Write-Host "The selection you made is unavailable, please make a valid selection between 1 and $($i-1)..."
  194.     }
  195. }
  197. # Connect to VMware vCenter Server and Veeam Backup & Replication
  198. #Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false
  199. My-Logger "Connecting to VMware vCenter Server $vcserver"
  200. $vicredential = Get-Credential -UserName $vcuser -Message "Enter password for VMware vCenter server: $vcserver"
  201. Connect-VIServer $vcserver -Credential $vicredential -WarningAction SilentlyContinue
  203. My-Logger "Connecting to Veeam Backup & Replication Server $vbrserver"
  204. $vbrcredential = Get-Credential -UserName $vbruser -Message "Enter password for Veeam Backup & Replication server: $vbrserver"
  205. Connect-VBRServer -Server $vbrserver -Credential $vbrcredential -WarningAction SilentlyContinue
  207. # Select if scope of deployment
  208. Clear-Host
  209. Write-Output "`nWould you like to deploy a proxy to:`n"
  210. Write-Output "1. A Single Host"
  211. Write-Output "2. A Cluster (to all hosts in a specific cluster)"
  212. Write-Output "3. A Datacenter (to all hosts in a specific datacenter)"
  213. [int]$option = if(($option = Read-Host "`nSelect item or press enter to accept default value [2]") -eq '') {"2"} else {$option}
  215. if ($option -eq "1") {
  216. 	$MyQuery = (Get-Datacenter $vcdatacenter | Get-VMHost)
  217. 	$MyList = "Get-VMHost `$Selection | Where-Object -FilterScript { `$_.PowerState -eq 'PoweredOn' }"
  218. 	My-Logger "Deploying to Host"
  219. } elseif ($option -eq "2"){
  220. 	$MyQuery = (Get-Datacenter $vcdatacenter | Get-Cluster)
  221. 	$MyList = "Get-Cluster `$Selection | Get-VMHost | Where-Object -FilterScript { `$_.PowerState -eq 'PoweredOn' }"
  222. 	My-Logger "Deploying to Cluster"
  223. } elseif ($option -eq "3"){
  224. 	$MyQuery = (Get-Datacenter)
  225. 	$MyList = "Get-Datacenter `$Selection | Get-Cluster | Get-VMHost | Where-Object -FilterScript { `$_.PowerState -eq 'PoweredOn' }"
  226. 	My-Logger "Deploying to Datacenter"
  227. } else {
  228. 	My-Logger "Wrong selection..."
  229. }
  231. Clear-Host
  232. Write-Output "Select where to deploy Linux Proxies:`n"	
  233. $targethosts = (Get-HostList -ListItem $MyQuery -GetItem $MyList)
  235. # Verify the selected object actually contains powered on hosts
  236. if ($null -eq $targethosts) {
  237. 	My-Logger "No powered on hosts found..."
  238. 	Disconnect-VBRServer
  239. 	Disconnect-VIServer $vcserver -Confirm:$false
  240. 	Break
  241. }
  243. # Check and verify assigned IP range from config.json and verify correct IP format
  244. $deployedproxies = [ordered]@{}
  245. $iplistunverified = @()
  246. $iplistverified = @()
  247. try {
  248.     [IPADDRESS]$startip = $startipaddress
  249.     [IPADDRESS]$endip = $endipaddress
  250.     $iplistunverified += $($startip.GetAddressBytes()[3])..$($endip.GetAddressBytes()[3]) | ForEach-Object {$($startip.GetAddressBytes()[0],$startip.GetAddressBytes()[1],$startip.GetAddressBytes()[2], $_ -join '.')}
  252.     # Verify if IP addresses are available
  253.     My-Logger "Available IP addresses in range $startip to $endip :"
  254.     Clear-Host
  255.     Write-Output "Checking available IP addresses..."
  256.     foreach ($checkedip in $iplistunverified) {
  257.         if (!($(Test-Connection -BufferSize 2 -TTL 5 -ComputerName $checkedip -quiet -count 1))) {
  258.             $iplistverified += $checkedip
  259.             My-Logger "  $checkedip - available"
  260.         } else {
  261.             My-Logger "  $checkedip - in use"
  262.         }
  263.     }
  265.     if ($iplistverified.Count -lt $targethosts.count) {
  266.         My-Logger "[ERROR] Not enough IP addresses assigned, $($targethosts.count) IP addresses are needed. Please update config.json"
  267.         Disconnect-VBRServer
  268.         Disconnect-VIServer $vcserver -Confirm:$false
  269.         Break
  270.     }
  271. } catch {
  272. 	#Clear-Host
  273. 	My-Logger "[ERROR] IP address assignment is incorrect, verify in config.json"
  274. 	Disconnect-VBRServer
  275. 	Disconnect-VIServer $vcserver -Confirm:$false
  276. 	Break
  277. }
  279. # Deploy PhotonOS, change root password and assign IP settings, add VM as Managed Server and assign Proxy Role
  280. $ipincrement = 0
  281. foreach ($targethost in $targethosts) {
  282. 	My-Logger "Processing host: $targethost"
  283. 	$sw = [Diagnostics.Stopwatch]::StartNew()
  285. 	# Verify if a proxy is already present on host or deploy proxy if it's missing
  286. 	if (Get-VMHost $targethost | Get-VM | Where-Object { $_.Name -like $($ProxyBaseName + "_*") }) {
  287. 		My-Logger "  A Veeam Proxy is already deployed to host: $targethost"
  288. 	} else {	
  289. 		$ProxyName = $ProxyBaseName + "_" + ($targethost.name).replace(".","-")
  290. 		My-Logger "  Importing PhotonOS OVA $ProxyName"
  291. 		$vm = Import-VApp -Source $ovaname -Name $ProxyName -VMHost $targethost -DiskStorageFormat thin
  292. 		# "VM Netork" will be used to attach NIC, must be present on host
  293. 		My-Logger "  Assigning resources to $ProxyName"
  294. 		$vm | Set-VM -NumCpu $proxyvcpu -MemoryGB $proxyvram -Confirm:$false
  295. 		$vm | Get-NetworkAdapter -Name "Network adapter 1" | Set-NetworkAdapter -Portgroup $network -Confirm:$false
  296. 		$vm | New-AdvancedSetting -Name disk.enableUUID -Value TRUE -Confirm:$false -Force:$true
  298. 		Start-VM $vm
  300. 		Wait-Tools -VM $vm
  301. 		Start-Sleep 10
  302. 		My-Logger "  VMKeystrokes: Sending user account to $vm"
  303. 		Set-VMKeystrokes -VMName $VM -StringInput $sshuser -ReturnCarriage $true
  304. 		Start-Sleep 2
  305. 		My-Logger "  VMKeystrokes: Sending inital OVA password to $vm"
  306. 		Set-VMKeystrokes -VMName $VM -StringInput $ovainitialpassword -ReturnCarriage $true
  307. 		Start-Sleep 2
  308. 		My-Logger "  VMKeystrokes: Sending initial OVA password to $vm"
  309. 		Set-VMKeystrokes -VMName $VM -StringInput $ovainitialpassword -ReturnCarriage $true
  310. 		Start-Sleep 2
  311. 		My-Logger "  VMKeystrokes: Sending new password to $vm"
  312. 		Set-VMKeystrokes -VMName $VM -StringInput $newsshpassword -ReturnCarriage $true
  313. 		Start-Sleep 2
  314. 		My-Logger "  VMKeystrokes: Sending new password to $vm"
  315. 		Set-VMKeystrokes -VMName $VM -StringInput $newsshpassword -ReturnCarriage $true
  317. 		# Install and update PhotonOS
  318. 		# https://github.com/Vanarga/vmware/wiki/2a.-Configuring-Photon-OS
  319. 		# insert 'yum update -y' last in the $cmd section if an automatic update should be performed
  320. 		Start-Sleep 10
  321. 		$ipofvm = $iplistverified[$ipincrement]
  322. 		My-Logger "  Configuring $ProxyName with IP: $ipofvm"
  324. 		$cmd = @"
  325. 		yum install nano perl mlocate -y
  326. 		hostnamectl set-hostname $ProxyName
  327. 		iptables -A INPUT -p tcp --match multiport --dports 2500:3300 -j ACCEPT
  328. 		iptables -A OUTPUT -p icmp -j ACCEPT
  329. 		iptables -A INPUT  -p icmp  -j ACCEPT
  330. 		iptables-save >/etc/systemd/scripts/ip4save
  331. 		timedatectl set-timezone Europe/Stockholm
  332. 		echo "[Match]" > /etc/systemd/network/10-static-en.network
  333. 		echo "Name=eth0" >> /etc/systemd/network/10-static-en.network
  334. 		echo " " >> /etc/systemd/network/10-static-en.network
  335. 		echo "[Network]" >> /etc/systemd/network/10-static-en.network
  336. 		echo "Address=$ipofvm/24" >> /etc/systemd/network/10-static-en.network
  337. 		echo "Gateway=$gateway" >> /etc/systemd/network/10-static-en.network
  338. 		echo "DNS=$dns" >> /etc/systemd/network/10-static-en.network
  339. 		echo "Domains=$domains" >> /etc/systemd/network/10-static-en.network
  340. 		echo "NTP=$ntpserver" >> /etc/systemd/network/10-static-en.network
  341. 		echo "LinkLocalAddressing=no" >> /etc/systemd/network/10-static-en.network
  342. 		echo "IPv6AcceptRA=no" >> /etc/systemd/network/10-static-en.network
  343. 		chmod 644 /etc/systemd/network/10-static-en.network
  344. 		systemctl restart systemd-networkd.service
  345. 		systemctl daemon-reload
  346. "@
  348. 		My-Logger "  Invoking script on $ProxyName"
  349. 		Invoke-VMScript -VM $VM -ScriptText $cmd -GuestUser $sshuser -GuestPassword $newsshpassword -ScriptType Bash
  351. 		# Add Proxy VM to VBR as managed server and add proxy role
  352. 		if ((Get-PSSnapin VeeamPSSnapin).Version.Major -ge 10) {
  353. 			My-Logger "  Adding $ipofvm as a Veeam Managed Server"
  354. 			Add-VBRLinux -Name $ipofvm -SSHUser $sshuser -SSHPassword $newsshpassword -Description "Linux Server based on PhotonOS 3.0 Revision 2"
  355. 			My-Logger "  Adding Backup Proxy Role"
  356. 			Add-VBRViLinuxProxy -Server (Get-VBRServer -Name $ipofvm -Type Linux) -Description "Linux Backup Proxy based on PhotonOS 3.0 Revision 2" -MaxTasks $proxyvcpu -Force
  357. 			My-Logger "  Veeam Proxy was deployed to host: $targethost"
  358. 		} else {
  359. 			My-Logger "  [ERROR] You need at least Veeam Backup & Replication version 10 to add a Linux Proxy"
  360. 			Disconnect-VBRServer
  361. 			Disconnect-VIServer $vcserver -Confirm:$false
  362. 			Break
  363. 		}
  366. 		# Collect information on Proxies in hashtable for future use
  367. 		$deployedproxies.add($proxyname,$ipofvm)
  368. 		$ipincrement++
  369. 		$ipofvm = $null
  370. 		My-Logger "  Deployment of proxy took: $($sw.Elapsed.Minutes) minutes and $($sw.Elapsed.Seconds) seconds"
  371. 	}	
  372. }
  374. Clear-Host
  375. if (($deployedproxies).Count -eq 0) {
  376. 	My-Logger "[ERROR] Something went wrong, no proxies were deployed!"
  377. 	My-Logger "Check log file $verboseLogFile"
  378. 	Disconnect-VBRServer
  379. 	Disconnect-VIServer $vcserver -Confirm:$false
  380. } else {
  381. 	My-Logger "The following proxies were deployed:" 
  382. 	My-Logger $deployedproxies
  383. 	Disconnect-VBRServer
  384. 	Disconnect-VIServer $vcserver -Confirm:$false
  385. }


  1. {
  2.   "General": {
  3.     "location": "C:\\temp",
  4.     "ovasource": "http://dl.bintray.com/vmware/photon/3.0/Rev2/ova/photon-hw13_uefi-3.0-9355405.ova",
  5.     "ovaname": "photon-hw13_uefi-3.0-9355405.ova",
  6.     "ProxyBaseName": "VeeamProxy",
  7.     "ProxyvCPU": "4",
  8.     "ProxyvRAM": "8",
  9.     "sshuser": "root",
  10.     "ovainitialpassword": "changeme",
  11.     "newsshpassword": "newsecretpassword"
  12.   },
  13.   "Test": {
  14. 	"vbrserver": "",
  15. 	"vcserver": "",
  16. 	"network": "VM Network",
  17. 	"startipaddress": "",
  18. 	"endipaddress": "",
  19. 	"vbruser": "Administrator",
  20. 	"vcuser": "administrator@vsphere.local",
  21. 	"vcdatacenter": "Datacenter",
  22. 	"gateway": "",
  23. 	"dns": "",
  24. 	"domains": "mydomain.local",
  25. 	"ntpserver": "se.pool.ntp.org"
  26.   },
  27.   "Prod": {
  28. 	"vbrserver": "",
  29. 	"vcserver": "",
  30. 	"network": "Prod",
  31. 	"startipaddress": "",
  32. 	"endipaddress": "",
  33. 	"vbruser": "Administrator",
  34. 	"vcuser": "administrator@vsphere.local",
  35. 	"vcdatacenter": "Datacenter",
  36. 	"gateway": "",
  37. 	"dns": "",
  38. 	"domains": "mydomain.local",
  39. 	"ntpserver": "se.pool.ntp.org"
  40.   }
  41. }

PowerShell function for menu creation revisited

Sometimes when creating blog posts you just get excited to publish a post as soon as it’s finished, but then you realize you can do something better. So yesterday I published a post on how to use a function in PowerShell to create an interactive dynamic menu but in hindsight it could have been more “generic” – so today I’m back with a new post on how you can create that generic function by providing the query in a separate string instead as part of the function itself. This allows you to merely change the query (the input for creating the menu) without having to change anything inside the function. Now it’s a “real” function! 🙂

I’ve added two mandatory parameters to the function, the first is the query to retrieve data and the second parameter is the task that you’d like to perform on the selected object. I’m calling the parameters -ListItem and -GetItem but the actual query is set in the $MyQuery and $MyTask string and they are then passed on to the function to perform the task. So if I’d like to get a list of VMs I just change the $MyQuery to something like “(Get-VM | Sort-Object)” and if I’d like to get a list of Datastores I set $MyQuery = “(Get-Datastore | Where {$_.Type -eq ‘VMFS’} | Sort-Object -Descending FreeSpaceGB)”. Once the list is presented and you select an object you then pass on what’s going to happen with it with the $MyTask – so if I want to retrieve a specific VM I set it to “Get-VM” but it could be anything (like Remove-VM but there’s no warning so be careful!)

Get a list of VMs
Get a list of VMFS datastores and sort them based on free space

Calling the function is then done using this syntax:
Set-Menu -ListItem $MyQuery -GetItem $MyTask

$VIServer = "vcsa.mydomain.com"
$VIUsername = "administrator@vsphere.local"
$VIPassword = "MyPassword"

Connect-VIServer $VIServer -User $VIUsername -Password $VIPassword -WarningAction SilentlyContinue

$MyQuery = (Get-Datastore | Where {$_.Type -eq 'VMFS'} |Sort-Object -Descending FreeSpaceGB)
$MyTask = "Get-Datastore"

Function Set-Menu {
    Param (


    Try {
    $menu = @{}
    For ($i=1;$i -le $ListItem.count; $i++)
        { Write-Host "$i. $($ListItem[$i-1].Name)"
        $menu.Add($i,($ListItem[$i-1].Name)) }
    [int]$ans = if(($ans = Read-Host "Select item or press enter to accept default value [1]") -eq '') {"1"} else {$ans}
    $Selection = $menu.Item($ans); Invoke-Expression $($GetItem +  " " + $Selection)

    Catch {
    Write-Host "The selection you made is unavailable, please make a valid selection between 1 and $($i-1)..."


Set-Menu -ListItem $MyQuery -GetItem $MyTask

Disconnect-VIServer $VIServer -Confirm:$false
Set-Menu waiting for user input
Set-Menu has executed the task defined in $MyTask

The way you can use the function is up to you but I use it in William Lams vghetto-vsphere-automated-lab-deployment script

Simplify your life with dynamic menus in PowerShell

If you are anything like me you’re setting up test or demo environments and then tearing them down a few hours later when you’re done with what ever testing you were doing. While setting up a VMware vSphere test environment is super easy using powershell/powercli (if you haven’t already visited William Lams web page, I highly recommend it and while you’re there grab the PowerShell scripts to deploy vSphere/vSAN/NSX environments. Kudos to William for everything you do for the vCommunity!!), I use his powershell scripts almost on a daily basis!

Now while setting up a single lab environment is usually not a big problem using the scripts provided by Willam but when you start setting up multiple labs (I typically have like 3 or 4 labs set up on any given time for different purposes) you might run out of some resources statically configured in the script, for instance your datastore configured doesn’t have enough capacity. So why not enhance the experience with dynamic selection of for instance datastore (or in my example: DatastoreCluster). Turns out it’s really easy to build an interactive dynamic menu of your datastoresclusters and use the menu to select where to install the lab environment.

I’m using a function to call the datastoreclusters I can use. I have a basic error handling included that you might need to extend. The code below includes 2 examples, the first function is calling available DatastoreClusters from a vCenter server and ordering them based on available free space and the second example gets a list of port groups available on my distributed switch. I’ve also assigned a default selection/value for faster deployments, just press enter to select the default value.

$VIServer = "vcsa.mydomain.com"
$VIUsername = "administrator@vsphere.local"
$VIPassword = "MyPassword"

Connect-VIServer $VIServer -User $VIUsername -Password $VIPassword -WarningAction SilentlyContinue

Function Show-DatastoreClusters ($Title = 'My Datastores'){
Try {
$AvailDatastores = (Get-Datastore | Where {$_.Type -eq "VMFS"} |Sort-Object -Descending FreeSpaceGB)
$menuds = @{}
For ($i=1;$i -le $AvailDatastores.count; $i++)
{ Write-Host "$i. $($AvailDatastores[$i-1].Name) - $([math]::Round($AvailDatastores[$i-1].FreespaceGB,1)) GB free space"
$menuds.Add($i,($AvailDatastores[$i-1].Name)) }
[int]$ansds = if(($ansds = Read-Host "Select Datastore or press enter to accept default value [1]") -eq ''){"1"} else {$ansds}
$DatastoreSelection = $menuds.Item($ansds) ; Get-Datastore $DatastoreSelection
Catch { Write-Host "The selection you made is unavailable, please make a valid selection between 1 and $($i-1)..." Start-Sleep 2 }

Function Show-PortGroups ($Title = 'My Portgroups'){
Try {
$AvailPortgroups = (Get-VDSwitch -Name "DSwitch" | Get-VDPortgroup)
$menupg = @{}
For ($i=1;$i -le $AvailPortgroups.count; $i++)
{ Write-Host "$i. $($AvailPortgroups[$i-1].Name)"
$menupg.Add($i,($AvailPortgroups[$i-1].Name)) }
[int]$anspg = if(($anspg = Read-Host "Select portgroup or press enter to accept default value [7]") -eq ''){"7"}else{$anspg}
$PortgroupSelection = $menupg.Item($anspg) ; Get-VDPortgroup $PortgroupSelection
Catch { Write-Host "The selection you made is unavailable, please make a valid selection between 1 and $($i-1)..." Start-Sleep 2 }
Write-Host `n
$VMDatastore = Show-DatastoreClusters
Write-Host `n
$VMNetwork = Show-Portgroups
Write-Host `n

Disconnect-VIServer $VIServer -Confirm:$false

The function above can then be included in the script from William Lam. When calling the function the output will look something like this (the menus being produced are highlighted in red):

Function to build dynamic menu

If you’d like you can add some extra granularity when enumerating the datastore, expand the “Where” statement to just get the datastores with a specific name:

{$_.Type -eq "VMFS" -and $_.Name -like "VMFS-ESXi7*"}

Once you got you script fetching the desired data you can add the function to any script, I’ve added it to William’s script for some added flexibility.

PowerShell for the win!

Who doesn’t love PowerShell and PoweCLI? I use it to automate as much as I can. Building demo environments, upgrading stuff or just playing around. The list is just a few examples of available modules from the Microsoft PowerShell Gallery, you can spend hours exploring interesting modules there. The list below is mostly a reminder for myself but feel free to explore!





















Log Insight






Pure Storage



Amazon Web Services
















The power of PowerCLI – Labautomation

Har du någonsin behövt skapa identiska labbmiljöer till flera deltagare, i till exempel utbildningssyfte eller för en workshop? Då vet du att det inte bara är att högerklicka på en template och välja deploy from template. Det behövs kanske flera olika templates som ska rullas ut, separata virtuella switchar och portgrupper behöver skapas för varje deltagare osv. Det blir fort en väldigt stor administrativ belastning. Det finns lite olika sätta att lösa problemet, exempelvis via vApps i vCloud Director.

Men om du nu inte har vCloud Director i din miljö, du kanske bara har en eller två hostar i miljön. Vad göra? Då kommer VMware PowerCLI till din räddning! PowerCLI är VMwares utökning av Microsoft Powershell som låter dig scripta i stort sätt allt du kan tänkas vilja göra. Du behöver en windows maskin samt hämta PowerCLI som finns här.

I mitt dagliga jobb behöver jag ofta sätta upp färdiga miljöer som innehåller mellan 5-10 virtuella maskiner som jag har tillgängliga som templates. För att enkelt kunna skapa unika labbmiljöer för flera personer/deltagare så har jag skapat ett skript som automatiserar utrullningen åt mig. Det enda jag behöver göra är att ange hur många kit jag vill skapa samt var jag vill skapa dom (på vilken host och vilket datastore). Sen har jag lagt till möjligheten att ange vilket ”startnummer” jag vill använda. Exempelvis om jag vill skapa 10 kit men det datastore jag angett finns det bara plats för 5 stycken kit. Då kan jag dela upp 5 kit på datastore1 med startummer 1 därefter skapar jag ytterligare 5 kit på datastore 2 fast med startnummer 6. Då har jag 10 kit med en logisk namnstandard – från 1 till 10.

En kort förklaring vad scriptet gör (förutom att skapa en meny där man kan välja lite olika alternativ som att skapa vApps, starta / stänga vApps osv):
1. Skapa en vApp för varje deltagare
2. Rulla ut VMs från templates till vAppen
3. Skapa isolerat nätverk kopplat till varje deltagare
4. Ändra säkerhetsnivån på utvalda portgrupper till promiscous mode eftersom det är nested ESXi
5. Koppla de virtuella maskinerna i en vApp till studentens egna nätverk
6. Ändra så att de virtuella maskinerna stängs ner gracefully när vAppen stängs (från Power off till Guest Shutdown)

När kiten är skapade kan jag via skriptet starta samtliga kit eller stänga ner dom om jag vill det. I mitt exempel har jag även en VM som är tillgänglig från klientnätet för att ge deltagarna tillgång till den skyddade labbmiljön, en Control Center. Jag kan via skriptet hämta in samtliga IP adresser till dessa Control Center maskinerna så jag kan dela ut ett kit till varje deltagare som därefter loggar in på maskinen via RDP och kommer åt resten av labbkitet.

Det är ett basic skript utan felhantering och så vidare men använd det om du vill men använd det på egen risk och endast i en skydad miljö.

# ===============================================================
#     NAME: VMware_lab_setup.ps1
#     AUTHOR: Niclas Borgström, Viridis IT
#     DATE: 2014-03-12 #
#     VERSION: 0.9
# ===============================================================

# Explain what the script does
Write-Host "This script will allow for set up and configure"
Write-Host "a lab environment for VMware vSphere 5.5 using"
Write-Host "existing templates."

# Ask for the Host name or IP
$VCENTER = Read-Host "Enter vCenter Server Name or IP"
Connect-VIServer -server $VCENTER

# Setup Our MENU
do {
     # Select environment MENU
     Write-Host "*** Config Menu for VMW55 ***"
     Write-Host "-----------------------------"
     # Print out the MENU
     Write-Host "What task would you like to perform?"
     Write-Host " "
     Write-Host " 1. Set up a new lab environment"
     Write-Host " 2. Power on vApps"
     Write-Host " 3. Get IP addresses of Desktop and ControlCenter VMs"
     Write-Host " 4. Get IP addresses of all VMs in vApps"
     Write-Host " 5. Power off (gracefully) vApps"
     Write-Host " "
     Write-Host " 0. Quit"
     Write-Host " "
     $response = Read-Host "Select 1-5"
     Write-Host " "

     switch ($response)
1 {

    # Show a welcome message
    Write-Host "VMware vSphere 5.5 lab environment set up v1.03"
    Write-Host "-----------------------------------------------"
    Write-Host " "

    # Select amount of Labkits to set up and what number the studentkit wil be assigned
    [int]$labkit = Read-Host "How many student kits will be needed? (1-12)"
    [int]$start = Read-Host "What startnumber would you like to use? (1-100)"
    [int]$end = $start + $labkit -1

    # Select host to install Lab kits on
    Write-Host " "
    Write-Host "Choose one of the hosts below from the Rescoures_Cluster:"
    Get-Cluster "Resources_Cluster" | Get-VMhost | Select Name
    $labhost = Read-Host "Type FQDN/IP address of host do you want to use"
    Get-VMhost $labhost | select Name

    # Select what datastore to use with enough capacity for thin vmdk and vswp
    Get-VMhost $labhost | Get-Datastore | Where-Object {$_.freespaceGB -gt ($labkit*(40+30))} | Select Name
    $datastore = Read-Host "What datastore would you like to use? (only showing datastores with enough free space)"
    Get-VMhost $labhost | Get-Datastore $datastore | Select Name
    Write-Host "Datastore" $datastore "will be used to install the vApp(s)"

    # Print summary
    Write-Host "Summary of lab environment set up:"
    Write-Host "---------------------------------"
    Write-Host "Lab kits to set up: " $labkit
    Write-Host "First kit name:     " "VMW55_Student$start"
    Write-Host "Last kit name:      " "VMW55_Student$end"
    Write-Host "Startnumber:        " $start
    Write-Host "Host:               " $labhost
    Write-Host "Datastore:          " $datastore
    Write-Host "                    "
    Write-Host "Required resources: "
    Write-Host "RAM:                " ($labkit*30) "GB"
    Write-Host "CPU:                " ($labkit*11) "vCPU"
    Write-Host "Storage (intial):   " ($labkit*40) "GB"
    Write-Host "Storage (max):      " ($labkit*316) "GB"
    $choice = @()
    $choice = Read-Host "Is this correct (Y/N)?"
    if ($choice -eq "y")
        # Start setting up the lab environment for each student
        $start..$end | Foreach {
        Write-Host "Setting up environment for student" $start "..."
        $studentid = "VMW55_Student$start"

        # Create a vSwitch for each student
        Write-Host "Creating Network environment..."
        New-VirtualSwitch -VMHost $labhost -Name $studentid

        # Create Portgroups for 3 use cases
        New-VirtualPortGroup -Name $studentid"_Production" -VirtualSwitch $studentid
        New-VirtualPortGroup -Name $studentid"_Management" -VirtualSwitch $studentid
        New-VirtualPortGroup -Name $studentid"_vMotion" -VirtualSwitch $studentid

        # Set the Management Portgroup to allow PromiscousMode
        $netsys = Get-View (Get-VMHost $labhost | Get-View).configmanager.networksystem
        $portgroupspec = New-Object VMWare.Vim.HostPortGroupSpec
        $portgroupspec.vswitchname = $studentid
        $portgroupname = $studentid
        $portgroupname = ($portgroupname += "_Management")
        $portgroupspec.Name = $portgroupname
        $portgroupspec.policy = New-object vmware.vim.HostNetworkPolicy
        $portgroupspec.policy.Security = New-object vmware.vim.HostNetworkSecurityPolicy
        $portgroupspec.policy.Security.AllowPromiscuous = $true

        # Create a vApp for each student
        Write-Host "Creating vApp..."
        New-VApp -Name $studentid -Location "Resources_Cluster"

        # Start deploying virtual machines
        Write-Host "Deploying virtual machines..."
        New-VM -VMHost $labhost -Name ControlCenter -Template VMW55_ControlCenter -Datastore $datastore -DiskStorageFormat Thin -ResourcePool $studentid
        New-VM -VMHost $labhost -Name iscsi -Template VMW55_iscsi -Datastore $datastore -DiskStorageFormat Thin -ResourcePool $studentid
        New-VM -VMHost $labhost -Name nfs -Template VMW55_nfs -Datastore $datastore -DiskStorageFormat Thin -ResourcePool $studentid
        New-VM -VMHost $labhost -Name esxi01 -Template VMW55_esxi01 -Datastore $datastore -DiskStorageFormat Thin -ResourcePool $studentid
        New-VM -VMHost $labhost -Name esxi02 -Template VMW55_esxi02 -Datastore $datastore -DiskStorageFormat Thin -ResourcePool $studentid
        New-VM -VMHost $labhost -Name vcva01 -Template VMW55_vcva01 -Datastore $datastore -DiskStorageFormat Thin -ResourcePool $studentid
        New-VM -VMHost $labhost -Name Desktop01 -Template VMW55_Desktop01 -Datastore $datastore -DiskStorageFormat Thin -ResourcePool $studentid

        # Change networking to dedicated portgroups
        Write-Host "Change portgroups on all virtual machines in the vApp..."
        Get-VApp $studentid | Get-VM | Get-NetworkAdapter | Where {$_.NetworkName -eq "Production_template" } | Set-NetworkAdapter -NetworkName $studentid"_Production" -Confirm:$false
        Get-VApp $studentid | Get-VM | Get-NetworkAdapter | Where {$_.NetworkName -eq "vMotion_template" } | Set-NetworkAdapter -NetworkName $studentid"_vMotion" -Confirm:$false
        Get-VApp $studentid | Get-VM | Get-NetworkAdapter | Where {$_.NetworkName -eq "Management_template" } | Set-NetworkAdapter -NetworkName $studentid"_Management" -Confirm:$false

        # Change StopAction for all VMs in the vApp
        Write-Host "Change settings for vApp" $studentid "..."
        $AppView = Get-VApp $studentid | Get-View
        Foreach ($Entity in $AppView.VAppconfig.EntityConfig)
            If ($Entity.Stop.Action -ne "guestShutdown")
                $VAppConfigSpec = New-Object VMware.Vim.VAppConfigSpec
                $EntityConfig = New-Object VMware.Vim.VAppEntityConfigInfo
                $EntityConfig.Key = (Get-View $Entity.Key).MoRef
                #$EntityConfig.startDelay = 120
                $EntityConfig.waitingForGuest = $true
                #$EntityConfig.stopDelay = 120
                $EntityConfig.StopAction = "guestShutdown"
                $VAppConfigSpec.EntityConfig = $EntityConfig

    [int]$start = $start + 1
2 {

    # Power on all VMs in vApp
    # To power on all lab kits just type the first letters, i.e to power on
    # all lab kits called VMW55_student1, VMW55_student2, and so on you only
    # need to typ "VMW55"
    Write-Host "Power on all vApps for VMW55 lab"
    Write-Host "--------------------------------"
    Write-Host " "
    Get-vApp | Select-Object -property Name
    $vappinput = Read-Host "What lab should power on? (case sensitive)"
    Get-VApp | Where {$_.Name -like "$vappinput*"} | Start-VApp
3 {

    # List Ip address of all Desktop01 & ControlCenter VMs in selected vApps
    Write-Host "IP address of all Desktop01 & ControlCenter VMs for VMW55 class"
    Write-Host "-------------------------------------------------------------------"
    Write-Host " "
    Get-vApp | Select-Object -property Name
    $vappinput = Read-Host "What Class should we list? (case sensitive)"
    Write-Host "Saving result to file 'IPAddress_of_Desktop.txt'"
    $FencedVMIP = Get-VApp | Where {$_.Name -like "$vappinput*"} | Get-VM | Where {$_.Name -like "Desktop*" -or $_.Name -eq "ControlCenter"} | Select {$_.VApp},Name,{$_.Guest.IpAddress[0]}
    $FencedVMIP | Out-File C:\PS_Scripts\VMware\VMW55\IPAddress_Of_Desktop.txt
4 {

    # List Ip address of all VMs in selected vApps
    Write-Host "IP address of all VMs for VMW55 lab"
    Write-Host "-------------------------------------"
    Write-Host " "
    Get-vApp | Select-Object -property Name
    $vappinput = Read-Host "What Class should we list? (case sensitive)"
    Write-Host "Saving result to file 'IPAddress_All_VMs.txt'"
    $AllVMIP = Get-VApp | Where {$_.Name -like "$vappinput*"} | Get-VM | Select Name,{$_.VApp},{$_.Guest.IpAddress}
    $AllVMIP | Out-File C:\PS_Scripts\VMware\VMW55\IPAddress_All_VMs.txt

5 {

    # Shutdown all VMs in the selected vApps
    Write-Host "Power off all vApps for VMW55 class"
    Write-Host "-----------------------------------"
    Write-Host " "
    Get-vApp | Select-Object -property Name

$vappinput = Read-Host "What Class should power off? (case sensitive)"
    $choice = Read-Host "Are you sure you want to stop all vApps (Y/N)?"
    if ($choice -eq "y")
        Get-VApp | Where {$_.Name -like "$vappinput*"} | Stop-VApp


0 {
    "** Exiting Script **";
    Write-Host "Quit";
    Disconnect-viserver -server $VCENTER -confirm:$false

default {
    "** The selection could not be determined **";
    Start-Sleep 1

while ($response -ne "0")