Sample restore script

As an alternative to using Settings > Actions > Restore from backup from the appliance UI, you can write and run a script to automatically restore the appliance from a backup file.

NOTE:

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.

  1. Calls query-user() to get the appliance host name, user name and password, and backup file path.

  2. Calls login-appliance() to issue a REST request to get a session ID used to authorize restore REST calls.

  3. Calls uploadTo-appliance() to upload the backup to the appliance.

  4. Calls start-restore() to start the restore.

  5. 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:

  1. Calls recover-restoreID() to get the URI to verify the status of the last or an ongoing restore.

  2. 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
}