common_utils.ps1
Set-StrictMode -Version 3.0 $global:ZVM_VM_NAME = "ZVML" $global:VRA_VM_PATTERN = "Z-VRA*" $global:ZERTO_FOLDER_ON_HOST = "/var/zerto" $global:SDDC_RESOURCE_ID_PATTERN = "/subscriptions/(?<AvsSubscriptionId>[^/]+)/resourceGroups/(?<AvsResourceGroup>[^/]+)/providers/Microsoft\.AVS/privateClouds/(?<AvsCloudName>[^/]+)" function Get-StrictMode { try { $a = @(1); ($null -eq $a[2]) | Out-Null } catch { return 3 } try { ($null -eq "Not-a-Date".Year) | Out-Null } catch { return 2 } try { ($undefined -gt 1) | Out-Null } catch { return 1 } return 0 } function Start-ZVM { try { Write-Host "Starting $($MyInvocation.MyCommand)..." $ZVM = Get-VM -Name $ZVM_VM_NAME if ($null -eq $ZVM) { throw "$ZVM_VM_NAME VM does not exist." } else { if ($ZVM.PowerState -eq 'PoweredOff') { Write-Host "$ZVM_VM_NAME is powered off, going to power it on" Start-VM $ZVM -ErrorAction Stop | Out-Null } else { Write-Host "$ZVM_VM_NAME is up and running" } } } catch { throw "Failed to start $ZVM_VM_NAME. Problem: $_" } } function Stop-ZVM { try { Write-Host "Starting $($MyInvocation.MyCommand)..." $ZVM = Get-VM -Name $ZVM_VM_NAME if ($null -eq $ZVM) { throw "$ZVM_VM_NAME VM does not exist." } else { if ($ZVM.PowerState -eq 'PoweredOn') { Write-Host "$ZVM_VM_NAME is powered on, going to power it off" Stop-VM -VM $ZVM -Confirm:$False -ErrorAction Stop | Out-Null } else { Write-Host "$ZVM_VM_NAME is off" } } } catch { throw "Failed to stop $ZVM_VM_NAME. Problem: $_" } } function New-RandomPassword { # Generate a password with at least 4 uppercase, 4 lowercase, 3 digits and 3 special characters from !@#%^*() # The first character must be a letter # The password length will vary between 14 and 18 symbols Write-Host "Starting $($MyInvocation.MyCommand)..." $extlen = { 0, 1 | Get-SecureRandom } $firstRand = 'a'..'z' | Get-SecureRandom $lowerRand = 'a'..'z' | Get-SecureRandom -Count (3 + (& $extlen)) # Eventually, the password will have at least 4 lowercase letters when firstRand is added $upperRand = 'A'..'Z' | Get-SecureRandom -Count (4 + (& $extlen)) $numericRand = '0'..'9' | Get-SecureRandom -Count (3 + (& $extlen)) $specialRand = [char[]]'!@#%^*()' | Get-SecureRandom -Count (3 + (& $extlen)) $allRand = $upperRand + $lowerRand + $numericRand + $specialRand $shuffledRand = $allRand | Get-SecureRandom -Count $allRand.Length $password = $firstRand + ($shuffledRand -join '') return $password #TODO: return secure string } function New-ZertoFolderOnHost { param( [Parameter(Mandatory = $true, HelpMessage = "Host Name to connect with SSH")] [string]$HostName ) process { Write-Host "Starting $($MyInvocation.MyCommand)..." $Command = "mkdir -p $ZERTO_FOLDER_ON_HOST" $Res = Invoke-SSHCommands -HostName $HostName -Commands $Command $ExitStatus = $Res["0_exitStatus"]; if ( $ExitStatus -ne '0' ) { throw "failed to create $ZERTO_FOLDER_ON_HOST on host $HostName. Exit status for ""$Command"" is $ExitStatus" } else { Write-Host "Zerto folder ($ZERTO_FOLDER_ON_HOST) was created on $HostName." } } } function Invoke-Retry { param ( [Parameter(Mandatory = $true)] [ScriptBlock]$Action, [Parameter(Mandatory = $true)] [string]$ActionName, [Parameter(Mandatory = $true)] [int]$RetryCount, [Parameter(Mandatory = $true)] [int]$RetryIntervalSeconds ) #TODO: This method must be reworked to get rid of Write-Output, use Write-Host but ensure it is no too verbose for ($i = 1; $i -le $RetryCount; $i++) { try { Write-Output "Attempt $i of $RetryCount executing $ActionName." & $Action Write-Output "$ActionName succeeded." return } catch { Write-Output "Attempt $i of $RetryCount on $ActionName wasn't successful." if ($i -ne $RetryCount) { Write-Output "Waiting for $RetryIntervalSeconds seconds before retrying $ActionName..." Start-Sleep -Seconds $RetryIntervalSeconds } else { Write-Output "Error: All attempts to execute $ActionName failed. Exiting." throw } } } } function Get-DatastoreUUID ($DatastoreName) { Write-Host "Starting $($MyInvocation.MyCommand)..." $datastore = Get-Datastore -Name $DatastoreName $uuid = ($datastore.ExtensionData.Info.Url -replace '.*volumes/', '').TrimEnd('/') # Url format is 'ds:///vmfs/volumes/vsan:529ecf79732d3104-202d349462b20b76/' Write-Host "Datastore '$DatastoreName' UUID: $uuid" return $uuid } function Invoke-SSHCommands { param( [Parameter(Mandatory = $true, HelpMessage = "Host Name to connect with SSH")] [string]$HostName, [Parameter(Mandatory = $true, HelpMessage = "Commands to execute")] [String[]]$Commands, [Parameter(Mandatory = $false, HelpMessage = "Action on exitStatus 1")] [string]$ExitStatusAction = "Stop" ) process { Write-Host "Starting $($MyInvocation.MyCommand)..." $NamedOutputs = @{} Set-Variable -Name NamedOutputs -Value $NamedOutputs -Scope Global $i = 0 foreach ($Command in $Commands) { $SSH = Invoke-SSHCommand -SSHSession $SSH_Sessions[$HostName].Value -Command $Command if (-not $SSH) { throw "Failed to Invoke-SSHCommand '$Command' on host $HostName" } $ExitStatus = $SSH.ExitStatus $SshError = $SSH.Error $Output = ($SSH.Output -join ";") if ($ExitStatus -ne 0 -or $SshError) { if (($ExitStatus -eq 1) -and (!$SshError) -and ($ExitStatusAction -eq "Skip")) { Write-Host "ExitStatus of '$Command' is 1, while ExitStatusAction = Skip. Skipping..." } else { throw "Failed to run '$Command' on host $HostName, ExitStatus: $ExitStatus, Output: $Output, Error: $SshError, ExitStatusAction: $ExitStatusAction" } } Write-Host "Command was completed successfully '$Command' on host $HostName, ExitStatus: $ExitStatus, Output: $Output, Error: $SshError" $NamedOutputs["$($i)_cmd"] = $Command $NamedOutputs["$($i)_exitStatus"] = $ExitStatus $NamedOutputs["$($i)_output"] = $Output $NamedOutputs["$($i)_error"] = $SshError $i++; } return $NamedOutputs } } function Set-KeystrokesToZVMA { <# .SYNOPSIS Sends a series of character keystrokse to ZVMA VM #> param ( [Parameter(Mandatory)] [string]$KeystrokesInput ) Write-Host "Starting $($MyInvocation.MyCommand)..." $hidCharMap = @{ "a" = "0x04"; "b" = "0x05"; "c" = "0x06"; "d" = "0x07"; "e" = "0x08"; "f" = "0x09"; "g" = "0x0a"; "h" = "0x0b"; "i" = "0x0c"; "j" = "0x0d"; "k" = "0x0e"; "l" = "0x0f"; "m" = "0x10"; "n" = "0x11"; "o" = "0x12"; "p" = "0x13"; "q" = "0x14"; "r" = "0x15"; "s" = "0x16"; "t" = "0x17"; "u" = "0x18"; "v" = "0x19"; "w" = "0x1a"; "x" = "0x1b"; "y" = "0x1c"; "z" = "0x1d"; "1" = "0x1e"; "2" = "0x1f"; "3" = "0x20"; "4" = "0x21"; "5" = "0x22"; "6" = "0x23"; "7" = "0x24"; "8" = "0x25"; "9" = "0x26"; "0" = "0x27"; "!" = "0x1e"; "@" = "0x1f"; "#" = "0x20"; "$" = "0x21"; "%" = "0x22"; "^" = "0x23"; "&" = "0x24"; "*" = "0x25"; "(" = "0x26"; ")" = "0x27"; "_" = "0x2d"; "+" = "0x2e"; "{" = "0x2f"; "}" = "0x30"; "|" = "0x31"; ":" = "0x33"; "~" = "0x35"; "<" = "0x36"; ">" = "0x37"; "?" = "0x38"; "-" = "0x2d"; "=" = "0x2e"; "[" = "0x2f"; "]" = "0x30"; "\" = "0x31"; ";" = "0x33"; "," = "0x36"; "." = "0x37"; "/" = "0x38"; " " = "0x2c"; "`'" = "0x34"; "`"" = "0x34"; "`n" = "0x28"; } $ZVM = Get-View -ViewType VirtualMachine -Filter @{"Name" = "^$($ZVM_VM_NAME)$" } if ($null -eq $ZVM) { throw "$ZVM_VM_NAME VM does not exist." } $hidKeyEvents = @() foreach ($char in $KeystrokesInput.ToCharArray()) { if (-not $hidCharMap.ContainsKey([string]$char)) { throw "Character $char is not supported." } $hidCodeHexStr = $hidCharMap[[string]$char] $hidCodeHexToInt = [Convert]::ToInt64($hidCodeHexStr, "16") $hidCodeValue = ($hidCodeHexToInt -shl 16) -bor 0007 # Expected value for UsbHidCode of 'a' is '0x04 0007' $hidEvent = New-Object VMware.Vim.UsbScanCodeSpecKeyEvent # Add LeftShift for capital letters and ~!@#$%^&*()_+{}:"<>? characters if ( ($char -cmatch "[A-Z]") -or ($char -match '[~!@#$%^&*()_+{}:"<>?]') ) { $modifier = New-Object Vmware.Vim.UsbScanCodeSpecModifierType $modifier.LeftShift = $true $hidEvent.Modifiers = $modifier } $hidEvent.UsbHidCode = $hidCodeValue $hidKeyEvents += $hidEvent } $spec = New-Object Vmware.Vim.UsbScanCodeSpec $spec.KeyEvents = $hidKeyEvents $len = $ZVM.PutUsbScanCodes($spec) return $len } function Change-ExpiredConsolePasswordIfRequired { <# .SYNOPSIS Changes the ZVM VM console password if it is locked out due to expiration username ⟶ current pass ⟶ current pass ⟶ new pass ⟶ new pass #> Write-Host "Starting $($MyInvocation.MyCommand)..." $ZAPPLIANCE_USER = "zadmin" $testBefore = Test-ZertoPasswordResult if ($testBefore -eq [PasswordsValidationResult]::ConsolePasswordInvalidOrExpired) { Write-Host "Attempting to change a possibly expired password..." $oldPassword = $PersistentSecrets.ZappliancePassword $newPassword = New-RandomPassword $len = Set-KeystrokesToZVMA -KeystrokesInput "$ZAPPLIANCE_USER`n" $len = Set-KeystrokesToZVMA -KeystrokesInput "$oldPassword`n" $len = Set-KeystrokesToZVMA -KeystrokesInput "$oldPassword`n" $len = Set-KeystrokesToZVMA -KeystrokesInput "$newPassword`n" $len = Set-KeystrokesToZVMA -KeystrokesInput "$newPassword`n" $PersistentSecrets.ZappliancePassword = $newPassword $testAfter = Test-ZertoPasswordResult if ($testAfter -eq [PasswordsValidationResult]::PasswordsValid) { Write-Host "Expired console password was changed successfully." return $true } else { throw "Failed to change the possibly expired password." } } return $false } |