# 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
# - 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 {
#Clear-Host
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
Disconnect-VBRServer
Disconnect-VIServer $vcserver -Confirm:$false
}
You must be logged in to post a comment.