# Deploy_Veeam_Linux_Proxy.ps1 # Version: 1.0 # Author: Niclas Borgstrom, Veeam Cloud Solution Architect EMEA # # Synopsis: # This script will download a linux appliance (based on VMware PhotonOS) # and deploy the appliance to a host, cluster or datacenter of your choice. # It will also download VMKeystrokes.ps1 by William Lam, VMware in order to # do the initial configuration of PhotonOS. # When the OVA is deployed it will be configured with a static IP address, # firewall will be opened for Veeam Backup Proxy specific ports, vCPU and # vRAM will be changed and the VM will be added to Veeam Backup & Replication # as a managed server and then be assigned the proxy role. Concurrent tasks # will be set according to the vCPUs assigned to the VM. # # You can choose to deploy either to a production- or a test environment, if # available. The OVA will only be deployed to powered on hosts. # # Disclaimer: # The script is for demonstration purpose only, run it at your own risk # # Credits # 'My-Logger' function by William Lam, VMware # # Prereqs: # - VMware environment managed by vCenter # - ESXi 6.5 (if using the default HW13 PhotonOS appliance) # - ESXi 6.0 (if using optional HW11 PhotonOS appliance) # http://dl.bintray.com/vmware/photon/3.0/Rev2/ova/photon-hw11-3.0-9355405.ova # - Veeam Backup & Replication already installed on a server. # - Internet access to download PhotonOS and VMKeystrokes.ps1 to change the default # password on the PhotonOS appliance. # # Limitations: # - Linux proxy can't be used as a 'Guest Interaction Proxy' # - Linux proxy can't be used as a 'File Backup Proxy' # - Linux Proxy only supports Hot-Add/Virtual appliance mode # # Changelog: # v 1.0: Initial release $global:BaseDirectory = "C:\temp\" $global:BaseConfig = "config.json" $verboseLogFile = $global:BaseDirectory + "Veeam_Linux_Proxy_Deployment_" + $(Get-Date -Format "yyyy-MM-dd_HH-mm") + ".log" Function My-Logger { param( [Parameter(Mandatory=$true)] [String]$message ) $timeStamp = Get-Date -Format "yyyy-MM-dd_HH:mm:ss" Write-Host -NoNewline -ForegroundColor White "[$timestamp]" Write-Host -ForegroundColor Green " $message" $logMessage = "[$timeStamp] $message" $logMessage | Out-File -Append -LiteralPath $verboseLogFile } # Load and parse the JSON configuration file My-Logger "Loading JSON configuration file $BaseDirectory$BaseConfig" try { $global:Config = Get-Content "$BaseDirectory$BaseConfig" -RAW -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | ConvertFrom-Json -ErrorAction SilentlyContinue -WarningAction SilentlyContinue } catch { My-Logger "[ERROR] The configuration files is missing!" Break } # Check the configuration if (!($Config)) { My-Logger "[ERROR] The configuration file is missing!" Break } $location = $Config.General.location $ovasource = $Config.General.ovasource $ovaname = $Config.General.ovaname $proxybasename = $Config.General.ProxyBaseName $proxyvcpu = $Config.General.ProxyvCPU $proxyvram = $Config.General.ProxyvRAM $sshuser = $Config.General.sshuser $ovainitialpassword = $Config.General.ovainitialpassword $newsshpassword = $Config.General.newsshpassword # Set temp location Set-Location $location try { Add-PSSnapin -Name VeeamPSSnapin -ErrorAction Stop Import-Module -Name VMware.VimAutomation.Core -ErrorAction Stop } catch { My-Logger "[ERROR] PowerCLI or VeeamPSSnapin not installed." Break } # Verify that VMKeystrokes.ps1 is available My-Logger "Checking VMkeystrokes.ps1..." Clear-Host try { # Dot source VMKeystrokes.ps1 if it's available . .\VMKeystrokes.ps1 } catch { # If VMKeystrokes.ps1 isn't available, ask if it should be downloaded My-Logger " [WARNING] Unable to find 'VMKeystrokes.ps1' in $location" $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} if ($downloadvmkeystrokes.ToLower() -eq "y") { My-Logger " Downloading 'VMKeystrokes.ps1'" Invoke-WebRequest -Uri "https://raw.githubusercontent.com/lamw/vghetto-scripts/master/powershell/VMKeystrokes.ps1" -OutFile ".\VMKeystrokes.ps1" . .\VMKeystrokes.ps1 } else { My-Logger " [ERROR] Can't continue without VMkeystrokes.ps1, exiting..." Break } } $targethosts = $null # Verify that PhotonOS it available My-Logger "Checking PhotonOS OVA..." Clear-Host if(!(Test-Path ($location + "\" + $ovaname))) { My-Logger " [WARNING] Unable to find PhotonOS OVA: $($location + "\" + $ovaname)" $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} if ($downloadova.ToLower() -eq "y") { My-logger " Downloading $($location + "\" + $ovaname)" Invoke-WebRequest -Uri $ovasource -OutFile $($BaseDirectory + "\" + $ovaname) } else { My-Logger " [ERROR] PhotonOS OVA not available!" Break } } # Select where to deploy Linux Proxies Clear-Host Write-Output "`nWould you like to deploy to prod or test:`n" Write-Output "1. Production environment - $($Config.Prod.vcserver)" Write-Output "2. Test environment - $($Config.Test.vcserver)" $selectenvironment = if(($selectenvironment = Read-Host "`nSelect item or press enter to accept default value [1]") -eq '') {"1"} else {$selectenvironment} # Populate strings based on selected deployment type if ($selectenvironment -eq "1") { My-Logger "Deploying to Production environment $($Config.Prod.vcserver)" $vbrserver = $Config.Prod.vbrserver $vcserver = $Config.Prod.vcserver $network = $Config.Prod.network $startipaddress = $Config.Prod.startipaddress $endipaddress = $Config.Prod.endipaddress $vbruser = $Config.Prod.vbruser $vcuser = $Config.Prod.vcuser $vcdatacenter = $Config.Prod.vcdatacenter $gateway = $Config.Prod.gateway $dns = $Config.Prod.dns $domains = $Config.Prod.domains $ntpserver = $Config.Prod.ntpserver } elseif ($selectenvironment -eq "2"){ My-Logger "Deploying to Test environment $($Config.Test.vcserver)" $vbrserver = $Config.Test.vbrserver $vcserver = $Config.Test.vcserver $network = $Config.Test.network $startipaddress = $Config.Test.startipaddress $endipaddress = $Config.Test.endipaddress $vbruser = $Config.Test.vbruser $vcuser = $Config.Test.vcuser $vcdatacenter = $Config.Test.vcdatacenter $gateway = $Config.Test.gateway $dns = $Config.Test.dns $domains = $Config.Test.domains $ntpserver = $Config.Test.ntpserver } else { My-Logger "[ERROR] Wrong selection..." } # Function to build a dynamic menu for infrastructure components (Datacenter, Cluster or Host) Function Get-HostList { Param ( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] $ListItem, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] $GetItem ) 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 "`nSelect item or press enter to accept default value [1]") -eq '') {"1"} else {$ans} $Selection = $menu.Item($ans); Invoke-Expression $($GetItem) } catch { Write-Host "The selection you made is unavailable, please make a valid selection between 1 and $($i-1)..." } } # Connect to VMware vCenter Server and Veeam Backup & Replication #Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false My-Logger "Connecting to VMware vCenter Server $vcserver" $vicredential = Get-Credential -UserName $vcuser -Message "Enter password for VMware vCenter server: $vcserver" Connect-VIServer $vcserver -Credential $vicredential -WarningAction SilentlyContinue My-Logger "Connecting to Veeam Backup & Replication Server $vbrserver" $vbrcredential = Get-Credential -UserName $vbruser -Message "Enter password for Veeam Backup & Replication server: $vbrserver" Connect-VBRServer -Server $vbrserver -Credential $vbrcredential -WarningAction SilentlyContinue # Select if scope of deployment Clear-Host Write-Output "`nWould you like to deploy a proxy to:`n" Write-Output "1. A Single Host" Write-Output "2. A Cluster (to all hosts in a specific cluster)" Write-Output "3. A Datacenter (to all hosts in a specific datacenter)" [int]$option = if(($option = Read-Host "`nSelect item or press enter to accept default value [2]") -eq '') {"2"} else {$option} if ($option -eq "1") { $MyQuery = (Get-Datacenter $vcdatacenter | Get-VMHost) $MyList = "Get-VMHost `$Selection | Where-Object -FilterScript { `$_.PowerState -eq 'PoweredOn' }" My-Logger "Deploying to Host" } elseif ($option -eq "2"){ $MyQuery = (Get-Datacenter $vcdatacenter | Get-Cluster) $MyList = "Get-Cluster `$Selection | Get-VMHost | Where-Object -FilterScript { `$_.PowerState -eq 'PoweredOn' }" My-Logger "Deploying to Cluster" } elseif ($option -eq "3"){ $MyQuery = (Get-Datacenter) $MyList = "Get-Datacenter `$Selection | Get-Cluster | Get-VMHost | Where-Object -FilterScript { `$_.PowerState -eq 'PoweredOn' }" My-Logger "Deploying to Datacenter" } else { My-Logger "Wrong selection..." } Clear-Host Write-Output "Select where to deploy Linux Proxies:`n" $targethosts = (Get-HostList -ListItem $MyQuery -GetItem $MyList) # Verify the selected object actually contains powered on hosts if ($null -eq $targethosts) { My-Logger "No powered on hosts found..." Disconnect-VBRServer Disconnect-VIServer $vcserver -Confirm:$false Break } # Check and verify assigned IP range from config.json and verify correct IP format $deployedproxies = [ordered]@{} $iplistunverified = @() $iplistverified = @() try { [IPADDRESS]$startip = $startipaddress [IPADDRESS]$endip = $endipaddress $iplistunverified += $($startip.GetAddressBytes()[3])..$($endip.GetAddressBytes()[3]) | ForEach-Object {$($startip.GetAddressBytes()[0],$startip.GetAddressBytes()[1],$startip.GetAddressBytes()[2], $_ -join '.')} # Verify if IP addresses are available My-Logger "Available IP addresses in range $startip to $endip :" Clear-Host Write-Output "Checking available IP addresses..." foreach ($checkedip in $iplistunverified) { if (!($(Test-Connection -BufferSize 2 -TTL 5 -ComputerName $checkedip -quiet -count 1))) { $iplistverified += $checkedip My-Logger " $checkedip - available" } else { My-Logger " $checkedip - in use" } } if ($iplistverified.Count -lt $targethosts.count) { My-Logger "[ERROR] Not enough IP addresses assigned, $($targethosts.count) IP addresses are needed. Please update config.json" Disconnect-VBRServer Disconnect-VIServer $vcserver -Confirm:$false Break } } catch { My-Logger "[ERROR] IP address assignment is incorrect, verify in config.json" Disconnect-VBRServer Disconnect-VIServer $vcserver -Confirm:$false Break } # Deploy PhotonOS, change root password and assign IP settings, add VM as Managed Server and assign Proxy Role $ipincrement = 0 foreach ($targethost in $targethosts) { My-Logger "Processing host: $targethost" $sw = [Diagnostics.Stopwatch]::StartNew() # Verify if a proxy is already present on host or deploy proxy if it's missing if (Get-VMHost $targethost | Get-VM | Where-Object { $_.Name -like $($ProxyBaseName + "_*") }) { My-Logger " A Veeam Proxy is already deployed to host: $targethost" } else { $ProxyName = $ProxyBaseName + "_" + ($targethost.name).replace(".","-") My-Logger " Importing PhotonOS OVA $ProxyName" $vm = Import-VApp -Source $ovaname -Name $ProxyName -VMHost $targethost -DiskStorageFormat thin # "VM Netork" will be used to attach NIC, must be present on host My-Logger " Assigning resources to $ProxyName" $vm | Set-VM -NumCpu $proxyvcpu -MemoryGB $proxyvram -Confirm:$false $vm | Get-NetworkAdapter -Name "Network adapter 1" | Set-NetworkAdapter -Portgroup $network -Confirm:$false $vm | New-AdvancedSetting -Name disk.enableUUID -Value TRUE -Confirm:$false -Force:$true Start-VM $vm Wait-Tools -VM $vm Start-Sleep 10 My-Logger " VMKeystrokes: Sending user account to $vm" Set-VMKeystrokes -VMName $VM -StringInput $sshuser -ReturnCarriage $true Start-Sleep 2 My-Logger " VMKeystrokes: Sending inital OVA password to $vm" Set-VMKeystrokes -VMName $VM -StringInput $ovainitialpassword -ReturnCarriage $true Start-Sleep 2 My-Logger " VMKeystrokes: Sending initial OVA password to $vm" Set-VMKeystrokes -VMName $VM -StringInput $ovainitialpassword -ReturnCarriage $true Start-Sleep 2 My-Logger " VMKeystrokes: Sending new password to $vm" Set-VMKeystrokes -VMName $VM -StringInput $newsshpassword -ReturnCarriage $true Start-Sleep 2 My-Logger " VMKeystrokes: Sending new password to $vm" Set-VMKeystrokes -VMName $VM -StringInput $newsshpassword -ReturnCarriage $true # Install and update PhotonOS # https://github.com/Vanarga/vmware/wiki/2a.-Configuring-Photon-OS # insert 'yum update -y' last in the $cmd section if an automatic update should be performed Start-Sleep 10 $ipofvm = $iplistverified[$ipincrement] My-Logger " Configuring $ProxyName with IP: $ipofvm" $cmd = @" yum install nano perl mlocate -y hostnamectl set-hostname $ProxyName iptables -A INPUT -p tcp --match multiport --dports 2500:3300 -j ACCEPT iptables -A OUTPUT -p icmp -j ACCEPT iptables -A INPUT -p icmp -j ACCEPT iptables-save >/etc/systemd/scripts/ip4save timedatectl set-timezone Europe/Stockholm echo "[Match]" > /etc/systemd/network/10-static-en.network echo "Name=eth0" >> /etc/systemd/network/10-static-en.network echo " " >> /etc/systemd/network/10-static-en.network echo "[Network]" >> /etc/systemd/network/10-static-en.network echo "Address=$ipofvm/24" >> /etc/systemd/network/10-static-en.network echo "Gateway=$gateway" >> /etc/systemd/network/10-static-en.network echo "DNS=$dns" >> /etc/systemd/network/10-static-en.network echo "Domains=$domains" >> /etc/systemd/network/10-static-en.network echo "NTP=$ntpserver" >> /etc/systemd/network/10-static-en.network echo "LinkLocalAddressing=no" >> /etc/systemd/network/10-static-en.network echo "IPv6AcceptRA=no" >> /etc/systemd/network/10-static-en.network chmod 644 /etc/systemd/network/10-static-en.network systemctl restart systemd-networkd.service systemctl daemon-reload "@ My-Logger " Invoking script on $ProxyName" Invoke-VMScript -VM $VM -ScriptText $cmd -GuestUser $sshuser -GuestPassword $newsshpassword -ScriptType Bash # Add Proxy VM to VBR as managed server and add proxy role if ((Get-PSSnapin VeeamPSSnapin).Version.Major -ge 10) { My-Logger " Adding $ipofvm as a Veeam Managed Server" Add-VBRLinux -Name $ipofvm -SSHUser $sshuser -SSHPassword $newsshpassword -Description "Linux Server based on PhotonOS 3.0 Revision 2" My-Logger " Adding Backup Proxy Role" Add-VBRViLinuxProxy -Server (Get-VBRServer -Name $ipofvm -Type Linux) -Description "Linux Backup Proxy based on PhotonOS 3.0 Revision 2" -MaxTasks $proxyvcpu -Force My-Logger " Veeam Proxy was deployed to host: $targethost" } else { My-Logger " [ERROR] You need at least Veeam Backup & Replication version 10 to add a Linux Proxy" Disconnect-VBRServer Disconnect-VIServer $vcserver -Confirm:$false Break } # Collect information on Proxies in hashtable for future use $deployedproxies.add($proxyname,$ipofvm) $ipincrement++ $ipofvm = $null My-Logger " Deployment of proxy took: $($sw.Elapsed.Minutes) minutes and $($sw.Elapsed.Seconds) seconds" } } Clear-Host if (($deployedproxies).Count -eq 0) { My-Logger "[ERROR] Something went wrong, no proxies were deployed!" My-Logger "Check log file $verboseLogFile" Disconnect-VBRServer Disconnect-VIServer $vcserver -Confirm:$false } else { My-Logger "The following proxies were deployed:" My-Logger $deployedproxies.Keys Disconnect-VBRServer Disconnect-VIServer $vcserver -Confirm:$false }