Sample restore script
As an alternative to using
from the appliance UI, you can write and run a script to automatically restore the appliance from a backup file.Only a user with Infrastructure administrator privileges can restore an appliance.
The Sample restore.ps1 script shown below provides a sample script that restores the appliance from a backup file or obtains progress about an ongoing restore process.
Sample script
If you do not pass parameters to the script, the script uploads and restores a backup file.
Calls
query-user()
to get the appliance host name, user name and password, and backup file path.Calls
login-appliance()
to issue a REST request to get a session ID used to authorize restore REST calls.Calls
uploadTo-appliance()
to upload the backup to the appliance.Calls
start-restore()
to start the restore.Calls
restore-status()
to periodically check the restore status until the restore completes.
If you pass the
-status
option to the script, the script verifies and reports the status of the last or an ongoing restore until the restore process is complete:
Calls
recover-restoreID()
to get the URI to verify the status of the last or an ongoing restore.Calls
restore-status()
to periodically verify the restore status until the restore completes.
Sample restore.ps1 script
#(C) Copyright 2012-2014 Hewlett Packard Enterprise Development LP
###########################################################################################################################
# Name: restore.ps1
# Usage: {directory}\restore.ps1 or {directory}\restore.ps1 -status https://{ipaddress}
# Purpose: Uploads a backup file to the appliance and then restores the appliance using the backup data
# Notes: To improve performance, this script uses the curl command if it is installed. The curl command
# must be installed with the SSL option.
# Windows PowerShell 3.0 must be installed to run the script
###########################################################################################################################
# tells the computer that this is a trusted source we are connecting to (brute force, could be refined)
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
# The scriptApiVersion is the default Api version (if the appliance supports this level
# or higher). This variable may be changed if the appliance is at a lower Api level.
$global:scriptApiVersion = 3
##### Obtain information from user #####
function query-user ()
{
<#
.DESCRIPTION
Obtains information needed to run the script by prompting the user for input.
.INPUTS
None, does not accept piping
.OUTPUTS
Outputs an object containing the obtained information.
.EXAMPLE
$userVals = query-user
#>
Write-Host "Restoring from backup is a destructive process, continue anyway?"
$continue = 0
do
{
$earlyExit = Read-Host
if ($earlyExit[0] -eq 'n')
{
return
}
elseif ($earlyExit[0] -ne 'y')
{
Write-Host "Please respond with a y or n"
}
else
{
$continue = 1
}
} while ($continue -eq 0)
do
{
Write-Host "Enter directory backup is located in (ie: C:\users\joe\)"
$backupDirectory = Read-Host
# Add trailing slash if needed
if (!$backupDirectory.EndsWith("\"))
{
$backupDirectory = $backupDirectory + "\"
}
Write-Host "Enter name of backup (ie: appliance_vm1_backup_2012-07-07_555555.bkp)"
$backupFile = Read-Host
# Check if file exists
$fullFilePath = $backupDirectory + $backupFile
if (! (Test-Path $fullFilePath))
{
Write-Host "Sorry the backup file $fullFilePath doesn't exist."
}
} while (! (Test-Path $fullFilePath))
Write-Host "Enter appliance IP address (ie: https://10.10.10.10)"
$hostname = Read-Host
# Correct some common errors
$hostname = $hostname.Trim().ToLower()
if (!$hostname.StartsWith("https://"))
{
if ($hostname.StartsWith("http://"))
{
$hostname = $hostname.Replace("http","https")
} else {
$hostname = "https://" + $hostname
}
}
Write-Host "Enter username"
$secUsername = Read-Host -AsSecureString
$username =
[Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($secUsername))
Write-Host "Enter password"
$secPassword = Read-Host -AsSecureString
$password =
[Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($secPassword))
$absolutePath = $backupDirectory + $backupFile
Write-Host "If using Active Directory, enter the Active Directory domain"
Write-Host " (Leave this field blank if not using Active Directory.)"
$ADName = Read-Host
$loginVals = @{ hostname = $hostname; userName = $username; password = $password; backupPath = $absolutePath;
backupFile = $backupFile; authLoginDomain = $ADName; }
return $loginVals
}
##### getApiVersion: Get X_API_Version #####
function getApiVersion ([int32] $currentApiVersion,[string]$hostname)
{
<#
.DESCRIPTION
Sends a web request to the appliance to obtain the current Api version.
Returns the lower of: Api version supported by the script and Api version
supported by the appliance.
.PARAMETER currentApiVersion
Api version that the script is currently using
.PARAMETER hostname
The appliance address to send the request to (in https://{ipaddress} format)
.INPUTS
None, does not accept piping
.OUTPUTS
Outputs the new active Api version
.EXAMPLE
$global:scriptApiVersion = getApiVersion()
#>
# the particular Uri on the Appliance to reqest the Api Version
$versionUri = "/rest/version"
# append the Uri to the end of the IP address to obtain a full Uri
$fullVersionUri = $hostname + $versionUri
# use setup-request to issue the REST request api version and get the response
try
{
$applianceVersionJson = setup-request -Uri $fullVersionUri -method "GET" -accept "application/json" -contentType "application/json"
if ($applianceVersionJson -ne $null)
{
$applianceVersion = $applianceVersionJson | convertFrom-Json
$currentApplianceVersion = $applianceVersion.currentVersion
if ($currentApplianceVersion -lt $currentApiVersion)
{
return $currentApplianceVersion
}
return $currentApiVersion
}
}
catch [System.Exception]
{
if ($global:interactiveMode -eq 1)
{
Write-Host $error[0].Exception.Message
}
else
{
Write-EventLog -EventId 100 -LogName Application -Source backup.ps1 -Message $error[0].Exception.Message
}
}
}
##### Send the login request to the appliance #####
function login-appliance ([string]$username,[string]$password,[string]$hostname,[string]$ADName)
{
<#
.DESCRIPTION
Attempts to send a web request to the appliance and obtain an authorized sessionID.
.PARAMETER username
The username to log into the remote appliance
.PARAMETER password
The correct password associated with username
.PARAMETER hostname
The appliance address to send the request to (in https://{ipaddress} format)
.PARAMETER ADName
The Active Directory name (optional)
.INPUTS
None, does not accept piping
.OUTPUTS
Outputs the response body containing the needed session ID.
.EXAMPLE
$authtoken = login-appliance $username $password $hostname $ADName
#>
# the particular URI on the Appliance to reqest an "auth token"
$loginURI = "/rest/login-sessions"
# append the URI to the end of the IP address to obtain a full URI
$fullLoginURI = $hostname + $loginURI
# create the request body as a hash table, then convert it to json format
if ($ADName)
{
$body = @{ userName = $username; password = $password; authLoginDomain = $ADName } | convertTo-json
}
else # null or empty
{
$body = @{ userName = $username; password = $password } | convertTo-json
}
try
{
# create a new webrequest object and give it the header values that will be accepted by the Appliance, get response
$loginRequest = setup-request -Uri $fullLoginURI -method "POST" -accept "application/json" -contentType "application/json" -Body $body
Write-Host "Login completed successfully."
}
catch [System.Exception]
{
Write-Host $_.Exception.message
Write-Host $error[0].Exception
return
}
#the output for the function, a hash table which contains a single value, "sessionID"
$loginRequest | convertFrom-Json
return
}
##### Upload the backup file to the appliance #####
function uploadTo-appliance ([string]$filepath,[string]$authinfo,[string]$hostname,[string]$backupFile)
{
<#
.DESCRIPTION
Attempts to upload a backup file to the appliance. Tries to use the curl command.
The curl command has significantly better performance especially for large backups.
If curl isn't installed, invokes uploadTo_appliance-without-curl to upload the file.
.PARAMETER filepath
The absolute filepath to the backup file.
.PARAMETER authinfo
The authorized session ID returned by the login request
.PARAMETER hostname
The appliance to connect to
.PARAMETER backupFile
The name of the file to upload. Only used to tell the server what file is contained in the post request.
.INPUTS
None, does not accept piping
.OUTPUTS
The response body to the upload post request.
.EXAMPLE
$uploadResponse = uploadTo-appliance $filePath $sessionID $hostname $fileName
#>
$uploadUri = "/rest/backups/archive"
$fullUploadUri = $hostname + $uploadUri
$curlUploadCommand = "curl -s -k -X POST " +
"-H 'content-type: multipart/form-data' " +
"-H 'accept: application/json' " +
"-H 'auth: " + $authinfo + "' " +
"-H 'X-API-Version: $global:scriptApiVersion' " +
"-F file=@" + $filepath + " " +
$fullUploadUri
Write-Host "Uploading backup file to appliance, this may take a few minutes..."
try
{
$testCurlSslOption = curl -V
if ($testCurlSslOption -match "SSL")
{
$rawUploadResponse = invoke-expression $curlUploadCommand
if ($rawUploadResponse -eq $null)
{
return
}
$uploadResponse = $rawUploadResponse | convertFrom-Json
if ($uploadResponse.status -eq "SUCCEEDED")
{
Write-Host "Upload complete."
return $uploadResponse
}
else
{
Write-Host $uploadResponse
return
}
}
else
{
Write-Host "Version of curl must support SSL to get improved upload performance."
return uploadTo-appliance-without-curl $filepath $authinfo $hostname $backupFile
}
}
catch [System.Management.Automation.CommandNotFoundException]
{
return uploadTo-appliance-without-curl $filepath $authinfo $hostname $backupFile
}
catch [System.Exception]
{
Write-Host "Not able to upload backup"
Write-Host $error[0].Exception
return
}
}
##### Upload the backup file to the appliance without using the curl command #####
function uploadTo-appliance-without-curl ([string]$filepath,[string]$authinfo,[string]$hostname,[string]$backupFile)
{
<#
.DESCRIPTION
Attempts to upload a backup to the appliance without using curl.
.PARAMETER filepath
The absolute filepath to the backup file.
.PARAMETER authinfo
The authorized session ID returned by the login request
.PARAMETER hostname
The appliance to connect to
.PARAMETER backupFile
The name of the file to upload. Only used to tell the server what file is contained in the post request.
.INPUTS
None, does not accept piping
.OUTPUTS
The response body to the upload post request.
.EXAMPLE
$uploadResponse = uploadTo-appliance $filePath $sessionID $hostname $fileName
#>
$uploadUri = "/rest/backups/archive"
$fullUploadUri = $hostname + $uploadUri
$uploadTimeout = 43200000 # 12 hours
$bufferSize = 65536 # bytes
try
{
[net.httpsWebRequest]$uploadRequest = [net.webRequest]::create($fullUploadUri)
$uploadRequest.method = "POST"
$uploadRequest.Timeout = $uploadTimeout
$uploadRequest.ReadWriteTimeout = $uploadTimeout
$uploadRequest.SendChunked = 1
$uploadRequest.AllowWriteStreamBuffering = 0
$uploadRequest.accept = "application/json"
$boundary = "----------------------------bac8d687982e"
$uploadRequest.ContentType = "multipart/form-data; boundary=----------------------------bac8d687982e"
$uploadRequest.Headers.Add("auth", $authinfo)
$uploadRequest.Headers.Add("X-API-Version", $global:scriptApiVersion)
$fs = New-Object IO.FileStream ($filepath,[System.IO.FileMode]::Open)
$rs = $uploadRequest.getRequestStream()
$rs.WriteTimeout = $uploadTimeout
$disposition = "Content-Disposition: form-data; name=""file""; filename=""encryptedBackup"""
$conType = "Content-Type: application/octet-stream"
[byte[]]$BoundaryBytes = [System.Text.Encoding]::UTF8.GetBytes("--" + $boundary + "`r`n")
$rs.write($BoundaryBytes,0,$BoundaryBytes.Length)
[byte[]]$contentDisp = [System.Text.Encoding]::UTF8.GetBytes($disposition + "`r`n")
$rs.write($contentDisp,0,$contentDisp.Length)
[byte[]]$contentType = [System.Text.Encoding]::UTF8.GetBytes($conType + "`r`n`r`n")
$rs.write($contentType,0,$contentType.Length)
$fs.CopyTo($rs,$bufferSize)
$fs.close()
[byte[]]$endBoundaryBytes = [System.Text.Encoding]::UTF8.GetBytes("`n`r`n--" + $boundary + "--`r`n")
$rs.write($endBoundaryBytes,0,$endBoundaryBytes.Length)
$rs.close()
}
catch [System.Exception]
{
Write-Host "Not able to send backup"
Write-Host $error[0].Exception
}
try
{
[net.httpsWebResponse]$response = $uploadRequest.getResponse()
$responseStream = $response.getResponseStream()
$responseStream.ReadTimeout = $uploadTimeout
$streamReader = New-Object IO.StreamReader ($responseStream)
$rawUploadResponse = $streamReader.readtoend()
$response.close()
if ($rawUploadResponse -eq $null)
{
return
}
$uploadResponse = $rawUploadResponse | convertFrom-Json
if ($uploadResponse.status -eq "SUCCEEDED")
{
Write-Host "Upload complete."
return $uploadResponse
}
else
{
Write-Host $rawUploadResponse
Write-Host $uploadResponse
return
}
}
catch [Net.WebException]
{
Write-Host $error[0]
$errorResponse = $error[0].Exception.InnerException.Response.getResponseStream()
$sr = New-Object IO.StreamReader ($errorResponse)
$frawErrorStream = $sr.readtoend()
$error[0].Exception.InnerException.Response.close()
$errorObject = $rawErrorStream | convertFrom-Json
Write-Host $errorObject.errorcode $errorObject.message $errorObject.resolution
return
}
}
##### Initiate the restore process #####
function start-restore ([string]$authinfo,[string]$hostname,[object]$uploadResponse)
{
<#
.DESCRIPTION
Sends a POST request to the restore resource to initiate a restore.
.PARAMETER authinfo
The authorized sessionID obtained from login.
.PARAMETER hostname
The appliance to connect to.
.PARAMETER uploadResponse
The response body from the upload request. Contains the backup URI needed for restore call.
.INPUTS
None, does not accept piping
.OUTPUTS
Outputs the response body from the POST restore call.
.EXAMPLE
$restoreResponse = start-restore $sessionID $hostname $uploadResponse
#>
# append the appropriate URI to the IP address of the Appliance
$backupUri = $uploadResponse.uri
$restoreUri = "/rest/restores"
$fullRestoreUri = $hostname + $restoreURI
$body = @{ type = "RESTORE"; uriOfBackupToRestore = $backupUri } | convertTo-json
# create a new webrequest and add the proper headers
try
{
$rawRestoreResponse = setup-request -uri $fullRestoreUri -method "POST" -accept "application/json" -contentType "application/json" -authValue $authinfo -Body $body
$restoreResponse = $rawRestoreResponse | convertFrom-Json
return $restoreResponse
}
catch [Net.WebException]
{
Write-Host $_.Exception.message
}
}
##### Check for the status of ongoing restore #####
function restore-status ([string]$authinfo = "foo",[string]$hostname,[object]$restoreResponse,[string]$recoveredUri = "")
{
<#
.DESCRIPTION
Uses GET requests to check the status of the restore process.
.PARAMETER authinfo
**to be removed once no longer a required header**
.PARAMETER hostname
The appliance to connect to
.PARAMETER restoreResponse
The response body from the restore initiation request.
.PARAMETER recoveredUri
In case of a interruption in the script or connection, the Uri for status is instead obtained through this parameter.
.INPUTS
None, does not accept piping
.OUTPUTS
None, end of script upon completion or fail.
.EXAMPLE
restore-status *$authinfo* -hostname $hostname -restoreResponse $restoreResponse
or
restore-status -hostname $hostname -recoveredUri $recoveredUri
#>
$retryCount = 0
$retryLimit = 5
$retryMode = 0
# append the appropriate URI to the IP address of the Appliance
if ($recoveredUri -ne "")
{
$fullStatusUri = $hostname + $recoveredUri
write-host $fullStatusUri
}
else
{
$fullStatusUri = $hostname + $restoreResponse.uri
}
do
{
try
{
# create a new webrequest and add the proper headers (new header, auth is needed for authorization
$rawStatusResp = setup-request -uri $fullStatusUri -method "GET" -accept "application/json" -contentType "application/json" -authValue $authinfo
$statusResponse = $rawStatusResp | convertFrom-Json
$trimmedPercent = ($statusResponse.percentComplete) / 5
$progressBar = "[" + "=" * $trimmedPercent + " " * (20 - $trimmedPercent) + "]"
Write-Host "`rRestore progress: $progressBar " $statusResponse.percentComplete "%" -NoNewline
}
catch [Net.WebException]
{
try
{
$errorResponse = $error[0].Exception.InnerException.Response.getResponseStream()
$sr = New-Object IO.StreamReader ($errorResponse)
$rawErrorStream = $sr.readtoend()
$error[0].Exception.InnerException.Response.close()
$errorObject = $rawErrorStream | convertFrom-Json
Write-Host $errorObject.message $errorObject.recommendedActions
}
catch [System.Exception]
{
Write-Host "`r`n" $error[1].Exception
}
# The error may be transient; retry several times. If it still fails, return with an error.
$retryCount++
$retryMode = 1
if ($retryCount -le $retryLimit)
{
Write-Host "In restore-status retrying GET on $fullStatusUri. retry count: $retryCount`r`n"
sleep 5
continue
}
else
{
Write-Host "`r`nRestore may have failed! Could not determine the status of the restore."
return
}
}
if ($statusResponse.status -eq "SUCCEEDED")
{
Write-Host "`r`nRestore complete!"
return
}
if ($statusResponse.status -eq "FAILED")
{
Write-Host "`r`nRestore failed! System should now undergo a reset to factory defaults."
}
Start-Sleep 10
} while (($statusResponse.status -eq "IN_PROGRESS") -or ($retryMode -eq 1))
return
}
##### Recovers Uri to the restore resource if connection lost #####
function recover-restoreID ([string]$hostname)
{
<#
.DESCRIPTION
Uses GET requests to check the status of the restore process.
.PARAMETER hostname
The appliance to end the request to.
.INPUTS
None, does not accept piping
.OUTPUTS
The Uri of the restore task in string form.
.EXAMPLE
$reacquiredUri = recover-restoredID $hostname
#>
$idUri = "/rest/restores/"
$fullIdUri = $hostname + $idUri
try
{
$rawIdResp = setup-request -uri $fullIdUri -method "GET" -contentType "application/json" -accept "application/json" -authValue "foo"
$idResponse = $rawIdResp | convertFrom-Json
}
catch [Net.WebException]
{
$_.Exception.message
return
}
return $idResponse.members[0].uri
}
function setup-request ([string]$uri,[string]$method,[string]$accept,[string]$contentType = "",[string]$authValue="0", [object]$body = $null)
{
<#
.DESCRIPTION
A function to handle the more generic web requests to avoid repeated code in every function.
.PARAMETER uri
The full address to send the request to (required)
.PARAMETER method
The type of request, namely POST and GET (required)
.PARAMETER accept
The type of response the request accepts (required)
.PARAMETER contentType
The type of the request body
.PARAMETER authValue
The session ID used to authenticate the request
.PARAMETER body
The message to put in the request body
.INPUTS
None
.OUTPUTS
The response from the appliance, typically in Json form.
.EXAMPLE
$responseBody = setup-request -uri https://10.10.10.10/rest/doThis -method "GET" -accept "application/json"
#>
try
{
[net.httpsWebRequest]$request = [net.webRequest]::create($uri)
$request.method = $method
$request.accept = $accept
$request.Headers.Add("Accept-Language: en-US")
if ($contentType -ne "")
{
$request.ContentType = $contentType
}
if ($authValue -ne "0")
{
$request.Headers.Item("auth") = $authValue
}
$request.Headers.Add("X-API-Version: $global:scriptApiVersion")
if ($body -ne $null)
{
#write-host $body
$requestBodyStream = New-Object IO.StreamWriter $request.getRequestStream()
$requestBodyStream.WriteLine($body)
$requestBodyStream.flush()
$requestBodyStream.close()
}
# attempt to connect to the Appliance and get a response
[net.httpsWebResponse]$response = $request.getResponse()
# response stored in a stream
$responseStream = $response.getResponseStream()
$sr = New-Object IO.StreamReader ($responseStream)
#the stream, which contains a json object is read into the storage variable
$rawResponseContent = $sr.readtoend()
$response.close()
return $rawResponseContent
}
catch [Net.WebException]
{
try
{
$errorResponse = $error[0].Exception.InnerException.Response.getResponseStream()
$sr = New-Object IO.StreamReader ($errorResponse)
$rawErrorStream = $sr.readtoend()
$error[0].Exception.InnerException.Response.close()
$errorObject = $rawErrorStream | convertFrom-Json
Write-Host "errorCode returned:" $errorObject.errorCode
Write-Host "when requesting a $method on $uri`r`n"
Write-Host $errorObject.message ";" $errorObject.recommendedActions
}
catch [System.Exception]
{
Write-Host $error[1].Exception.Message
}
throw
return
}
}
##### Begin main #####
#this checks to see if the user wants to just check a status of an existing restore
if ($args.count -eq 2)
{
foreach ($item in $args)
{
if ($item -eq "-status")
{
[void]$foreach.movenext()
$hostname = $foreach.current
# Correct some common errors in hostname
$hostname = $hostname.Trim().ToLower()
if (!$hostname.StartsWith("https://"))
{
if ($hostname.StartsWith("http://"))
{
$hostname = $hostname.Replace("http","https")
} else {
$hostname = "https://" + $hostname
}
}
}
else
{
Write-Host "Invalid arguments."
return
}
}
$reacquiredUri = recover-restoreID -hostname $hostname
if ($reacquiredUri -eq $null)
{
Write-Host "Error occurred when fetching active restore ID. No restore found."
return
}
restore-status -recoveredUri $reacquiredUri -hostname $hostname
return
}
elseif ($args.count -eq 0)
{
$loginVals = query-user
if ($loginVals -eq $null)
{
Write-Host "Error passing user login vals from function query-host, closing program."
return
}
#determines the active Api version
$global:scriptApiVersion = getApiVersion $global:scriptApiVersion $loginVals.hostname
if ($global:scriptApiVersion -eq $null)
{
Write-Host "Could not determine appliance Api version"
return
}
$authinfo = login-appliance $loginVals.userName $loginvals.password $loginVals.hostname $loginVals.authLoginDomain
if ($authinfo -eq $null)
{
Write-Host "Error getting authorized session from appliance, closing program."
return
}
$uploadResponse = uploadTo-appliance $loginVals.backupPath $authinfo.sessionID $loginVals.hostname $loginVals.backupFile
if ($uploadResponse -eq $null)
{
Write-Host "Error attempting to upload, closing program."
return
}
$restoreResponse = start-restore $authinfo.sessionID $loginVals.hostname $uploadResponse
if ($restoreResponse -eq $null)
{
Write-Host "Error obtaining response from Restore request, closing program."
return
}
restore-status -hostname $loginVals.hostname -restoreResponse $restoreResponse -authinfo $authinfo.sessionID
return
}
else
{
Write-Host "Usage: restore.ps1"
Write-Host "or"
Write-Host "restore.ps1 -status https://{ipaddress}"
return
}