AGMPowerCLIConnectFunctions.ps1
# Copyright 2022 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. function psfivecerthandler { if (-not ([System.Management.Automation.PSTypeName]'ServerCertificateValidationCallback').Type) { $certCallback = @" using System; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; public class ServerCertificateValidationCallback { public static void Ignore() { if(ServicePointManager.ServerCertificateValidationCallback ==null) { ServicePointManager.ServerCertificateValidationCallback += delegate ( Object obj, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors ) { return true; }; } } } "@ Add-Type $certCallback } [ServerCertificateValidationCallback]::Ignore() # ensure TLS12 is in use. We set it back when disconnect-act is run $env:CUR_PROTS = [System.Net.ServicePointManager]::SecurityProtocol [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12; } function Connect-AGM { <# .SYNOPSIS Connects to AGM to create a Session ID .DESCRIPTION The Connect-AGM connects to AGM to get a session ID to use on all subsequent calls .NOTES Written by Anthony Vandewerdt .EXAMPLE Google Cloud Backup and DR only: Connect-AGM -agmip agm-12345678.backupdr.actifiogo.com -agmuser apiuser@project1.iam.gserviceaccount.com -oauth2ClientId 123456789-fimdb0rbeamc17l3akilabcdefgh.apps.googleusercontent.com Connects to a Google Cloud Backup and DR Management Console. The key difference is that rather than a password, an oauth2ClientId is specified instead Note the AGMIP is not a URL. It is the host name portion of the endpoint without either https:// at the start or /actifio at the end .EXAMPLE Actifio only: Connect-AGM -agmip 172.24.1.117 -agmuser admin This will connect to AGM with a username of "admin" to the IP address 172.24.1.117. The prompt will request a secure password. .EXAMPLE Actifio only: Connect-AGM -agmip 172.24.1.117 -agmuser admin -i This will connect to AGM with a username of "admin" to the IP address 172.24.1.117. The prompt will securely request a password. Because -i is specified certificate validation of the AGM is ignored .EXAMPLE Actifio only: Connect-AGM -agmip 172.24.1.117 -agmuser admin -passwordfile av.key This will connect to AGM with a username of "admin" to the IP address 172.24.1.117. The password will be provided by using a previously created password file using Save-AGMPassword #> Param([String]$agmip,[String]$agmuser,[String]$agmpassword,[String]$oauth2ClientId,[String]$passwordfile,[switch][alias("q")]$quiet, [switch][alias("p")]$printsession,[switch][alias("i")]$ignorecerts,[int]$actmaxapilimit,[int]$agmtimeout) # max objects returned will be unlimited. Otherwise user can supply a limit if (!($agmmaxapilimit)) { $agmmaxapilimit = 0 } $GLOBAL:agmmaxapilimit = $agmmaxapilimit if (!($agmip)) { $agmip = Read-Host "IP or Name of AGM" } if ($agmip | select-string "/") { Get-AGMErrorMessage -messagetoprint "AGMIP is possibly a URL. Use the FQDN portion of the URL without either https:// or /actifio" return } if (!($agmuser)) { $agmuser = Read-Host "AGM user" } if (!($agmtimeout)) { [int]$agmtimeout = 300 } $agmipsniff = $agmip.Substring(0,4) if ($agmipsniff -eq "bmc-") { $accesstoken = $true } if ((!($agmpassword)) -and (!($passwordfile)) -and (!($oauth2ClientId)) -and (!($accesstoken))) { if ($agmipsniff -eq "agm-") { $oauth2ClientId = Read-Host "oauth2ClientId" } } # based on the action, do the right thing. if ( $certaction -eq "i" -or $certaction -eq "I" ) { $hostVersionInfo = (get-host).Version.Major if ( $hostVersionInfo -lt "6" ) { psfivecerthandler } else { # set IGNOREAGMCERTS so that we ignore self-signed certs $GLOBAL:IGNOREAGMCERTS = "y" } } # OAUTH handling if (($oauth2ClientId) -or ($accesstoken)) { if (((get-host).Version.Major -eq 7) -and ((get-host).Version.Minor -eq 3)) { $PSNativeCommandArgumentPassing = "Legacy" } # first we get a token if ($oauth2ClientId) { $Url = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/$agmuser" +":generateIdToken" $body = '{"audience": "' +$oauth2ClientId +'", "includeEmail":"true"}' } else { $Url = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/$agmuser" +":generateAccessToken" $body = '{"scope":["https://www.googleapis.com/auth/cloud-platform","https://www.googleapis.com/auth/userinfo.email"],"lifetime":"3600s"}' } $RestError = $null Try { $resp = Invoke-RestMethod -Method POST -Headers @{ Authorization = "Bearer $(gcloud auth print-access-token)" } -body $body -ContentType "application/json" -Uri $url } Catch { $RestError = $_ } if ($RestError) { $loginfailedsniff = Test-AGMJSON $RestError $loginfailedsniff return } elseif ($resp.token) { $token = $resp.token } elseif ($resp.accessToken) { $token = $resp.accessToken } else { Get-AGMErrorMessage -messagetoprint "Failed to get a token" return } # now we get a sessionID $Url = "https://" +$agmip +"/actifio/session" Try { $resp = Invoke-RestMethod -Method POST -Headers @{ Authorization = "Bearer $token" } -Uri $Url } Catch { $RestError = $_ } if ($RestError) { $loginfailedsniff = Test-AGMJSON $RestError $loginfailedsniff return } elseif ($resp.id) { $GLOBAL:AGMSESSIONID = $resp.id $GLOBAL:AGMIP = $agmip $GLOBAL:AGMTimezone = "local" $GLOBAL:AGMToken = $token $GLOBAL:AGMTIMEOUT = $agmtimeout if ($quiet) { return } elseif ($printsession) { Write-Host "$agmsessionid" return } else { Write-Host "Login Successful!" return } } else { Get-AGMErrorMessage -messagetoprint "Failed to get a sessionid" return } } # start 10.x AGM from below if ($ignorecerts) { $hostVersionInfo = (get-host).Version.Major if ( $hostVersionInfo -lt "6" ) { psfivecerthandler } else { $GLOBAL:IGNOREAGMCERTS = "y" } } else { Try { $resp = Invoke-RestMethod -Uri https://$agmip -TimeoutSec 60 } Catch { $RestError = $_ } if ($RestError -like "The operation was canceled.") { Get-AGMErrorMessage -messagetoprint "No response was received from $agmip after $agmtimeout seconds" return; } elseif ($RestError -like "Connection refused") { Get-AGMErrorMessage -messagetoprint "Connection refused received from $agmip" return; } elseif ($RestError) { Write-Host -ForeGroundColor Yellow "The SSL certificate from https://$agmip is not trusted. Please choose one of the following options"; Write-Host -ForeGroundColor Yellow "(I)gnore & continue"; Write-Host -ForeGroundColor Yellow "(C)ancel"; $validresp = ("i", "I", "c", "C"); $certaction = $null # prompt until we get a proper response. while ( $validresp.Contains($certaction) -eq $false ) { $certaction = Read-Host "Please select an option"; } # based on the action, do the right thing. if ( $certaction -eq "i" -or $certaction -eq "I" ) { $hostVersionInfo = (get-host).Version.Major if ( $hostVersionInfo -lt "6" ) { psfivecerthandler } else { $GLOBAL:IGNOREAGMCERTS = "y" } } elseif ( $certaction -eq "c" -or $certaction -eq "C" ) { # just exit return; } } } if (!($passwordfile)) { if (!($agmpassword)) { # prompt for a password [SecureString]$passwordenc = Read-Host -AsSecureString "Password"; } else { [SecureString]$passwordenc = (ConvertTo-SecureString $agmpassword -AsPlainText -Force) } } else { # if the password file provided is relative or absolute doesn't matter. Test for it first if ( Test-Path $passwordfile ) { [SecureString]$passwordenc = Get-Content $passwordfile | ConvertTo-SecureString; } else { Get-AGMErrorMessage -messagetoprint "Password file: $passwordfile could not be opened." return; } } $Url = "https://$agmip/actifio/session" $creds = New-Object System.Management.Automation.PSCredential ("$agmuser", $passwordenc) $RestError = $null Try { $hostVersionInfo = (get-host).Version.Major if ( $hostVersionInfo -lt "6" ) { $resp = Invoke-RestMethod -Method POST -Uri $Url -Credential $creds -TimeoutSec 60 } else { $resp = Invoke-RestMethod -SkipCertificateCheck -Method POST -Uri $Url -Credential $creds -TimeoutSec 60 } } Catch { $RestError = $_ } if ($RestError -like "The operation was canceled.") { Get-AGMErrorMessage -messagetoprint "No response was received from $agmip after 60 seconds" return; } elseif ($RestError -like "Connection refused") { Get-AGMErrorMessage -messagetoprint "Connection refused received from $agmip" return; } elseif ($RestError) { $loginfailedsniff = Test-AGMJSON $RestError if ($loginfailedsniff.err_code -eq "10011") { $agmerror = @() $agmerrorcol = "" | Select-Object err_code,errormessage [int]$agmerrorcol.err_code = "10011" $agmerrorcol.errormessage = "Login failed. Check your username and password." $agmerror = $agmerror + $agmerrorcol $agmerror return } elseif ($loginfailedsniff.errorcode -eq "10017") { $agmerror = @() $agmerrorcol = "" | Select-Object err_code,errormessage [int]$agmerrorcol.err_code = "10017" $agmerrorcol.errormessage = "Login failed. You appear to be logging into a VDP Appliance, rather than an AGM." $agmerror = $agmerror + $agmerrorcol $agmerror return } else { $loginfailedsniff return } } else { $GLOBAL:AGMSESSIONID = $resp.session_id $GLOBAL:AGMIP = $agmip $GLOBAL:AGMTimezone = "local" $GLOBAL:AGMTIMEOUT = $agmtimeout if ($quiet) { return } elseif ($printsession) { Write-Host "$agmsessionid" return } else { Write-Host "Login Successful!" return } } } function Disconnect-AGM { <# .SYNOPSIS Connects to AGM to delete a Session ID .DESCRIPTION The Disconnect-AGM connects to AGM to delete a session ID .NOTES Written by Anthony Vandewerdt .EXAMPLE Disconnect-AGM #> Param([switch][alias("q")]$quiet,[switch][alias("p")]$printsession) if ( (!($AGMSESSIONID)) -or (!($AGMIP)) ) { Get-AGMErrorMessage -messagetoprint "Not logged in or session expired. Please login using Connect-AGM" return } $RestError = $null Try { if ($GLOBAL:IGNOREAGMCERTS) { $resp = Invoke-RestMethod -Method DELETE -SkipCertificateCheck -Headers @{ Authorization = "Actifio $AGMSESSIONID" } -Uri "https://$AGMIP/actifio/session/$AGMSESSIONID" } else { if ($AGMToken) { $resp = Invoke-RestMethod -Method DELETE -Headers @{ Authorization = "Bearer $AGMToken"; "backupdr-management-session" = "Actifio $AGMSESSIONID" } -Uri "https://$AGMIP/actifio/session/$AGMSESSIONID" } else { $resp = Invoke-RestMethod -Method DELETE -Headers @{ Authorization = "Actifio $AGMSESSIONID" } -Uri "https://$AGMIP/actifio/session/$AGMSESSIONID" } } } Catch { $RestError = $_ } if ($RestError) { Test-AGMJSON "$RestError" } else { if ($quiet) { $GLOBAL:AGMSESSIONID = "" return } elseif ($printsession) { Write-Host "Successfully deleted session ID $AGMSESSIONID" $GLOBAL:AGMSESSIONID = "" return } else { Write-Host "Success!" $GLOBAL:AGMSESSIONID = "" return } } } Function Save-AGMPassword([string]$filename,[string]$password) { <# .SYNOPSIS Save credentials so that scripting is easy and interactive login is no longer needed. .EXAMPLE Save-AGMPassword -filename admin-pass.key Save the password for use later. .EXAMPLE Save-AGMPassword -filename ./5b-admin-pass -password "passw0rd" Save the specified plaintext password to the specified file name .DESCRIPTION Store the credentials in a file which can be used to login to AGM. Providing a AGM IP and a AGM User will prompt for a password which will then be stored in the file location provided. To change the credentials, simply re-run the cmdlet. .PARAMETER filename Required. Absolute or relative location where the file should be saved. example: .\actpass example: C:\Users\admin\actpass #> # if no file is provided, prompt for one if (!($filename)) { $filename = Read-Host "Filename"; } # if the filename already exists. don't overwrite it. error and exit. if ( Test-Path $filename ) { Get-AGMErrorMessage -messagetoprint "The file: $filename already exists. Please delete it first."; return; } # prompt for password if (!($password)) { $passwordenc = Read-Host -AsSecureString "Password" $passwordenc | ConvertFrom-SecureString | Out-File $filename } else { $passwordenc = $password | ConvertTo-SecureString -AsPlainText -Force $passwordenc | ConvertFrom-SecureString | Out-File $filename } if ( $? ) { write-host "Password saved to $filename." write-host "You may now use -passwordfile with Connect-AGM to provide a saved password file." } else { Get-AGMErrorMessage -messagetoprint "An error occurred in saving the password"; } } # offer a way to limit the maximum number of results in a single lookup function Set-AGMAPILimit([Parameter(Mandatory = $true)] [ValidateRange(0, [int]::MaxValue)][int]$userapilimit ) { <# .SYNOPSIS Offers a way to globally limit the number of objects returned by any API get request. .DESCRIPTION The AGM GUI by default displays a fixed number of objects per page, limiting the amount of data fetched when a page is displayed. By default the PowerShell module will get every object available for the Get being used, unless the user specifies a limit with that get command. For object types like job history this can result in possibly millions of objects (jobs) being returned. So if you are exploring the API then setting a global limit can allow you to issue gets without concern about how many objects will be fetched. .NOTES Written by Anthony Vandewerdt .EXAMPLE Set-AGMAPILimit 10 This means that every Get command supplied by the base module will only return 10 objects maxium, unless the -limit option is used .EXAMPLE Set-AGMAPILimit 0 This resets the global limit to 0 which is unlimited, meaning AGM will return every object that it has for the relevant Get. #> $GLOBAL:agmmaxapilimit = $userapilimit } function Get-AGMAPILimit { $agmmaxapilimit } # offer a way to control timezone used in output. By default we use User local time for all data function Set-AGMTimeZoneHandling ([switch][alias("l")]$local,[switch][alias("u")]$utc) { <# .SYNOPSIS Offers a way to change which timezone timestamps are shown in. .DESCRIPTION By default the PowerShell module shows all timestamp in the local timezone of the powershell session. You can validate which timezone that is with: Get-TimeZone You can validate whether the AGM Module is using local or UTC with: Get-AGMTimeZoneHandling .NOTES Written by Anthony Vandewerdt .EXAMPLE Set-AGMTimeZoneHandling -l Show all timestamps in the local timezone of the PowerShell session. .EXAMPLE Set-AGMTimeZoneHandling -u Show all timestamps in UTC (GMT). #> if ((!($local)) -and (!($utc))) { Get-AGMErrorMessage -messagetoprint "Please specify either -local or -utc" } if ($utc) { $GLOBAL:AGMTimezone = "UTC" } if ($local) { $GLOBAL:AGMTimezone = "local" } } function Get-AGMTimeZoneHandling { <# .SYNOPSIS Offers a way to display how timezones are being handled. .DESCRIPTION By default the PowerShell module shows all timestamp in the local timezone of the powershell session. You can validate which timezone that is with: Get-TimeZone You can change whether the AGM Module is using local or UTC with: Set-AGMTimeZoneHandling .NOTES Written by Anthony Vandewerdt .EXAMPLE Get-AGMTimeZoneHandling Show whether the AGM Module is using local or UTC #> if (($AGMTimezone -eq "local") -or (!($AGMTimezone))) { $currentlocal = Get-TimeZone Write-Host "Currently timezone in use is local timezone which is $currentlocal" } else { Write-Host "Currently timezone in use is $GLOBAL:AGMTimezone" } } function Get-GoogleCloudBackupDRConsole ([string]$project,[string]$location) { <# .SYNOPSIS Displays details of Google Cloud Backup and DR Management Console .DESCRIPTION The user needs to specify a project ID and region .NOTES Written by Anthony Vandewerdt .EXAMPLE Get-GoogleCloudBackupDRConsole -project project1 -location asia-southeast1 #> if (!($project)) { Get-AGMErrorMessage -messagetoprint "Please specify project with -project" } if (!($location)) { Get-AGMErrorMessage -messagetoprint "Please specify -location" } Try { $resp = Invoke-RestMethod -Method GET -Headers @{ Authorization = "Bearer $(gcloud auth print-access-token)" } -Uri "https://backupdr.googleapis.com/v1/projects/$project/locations/$location/managementServers" } Catch { $RestError = $_ } if ($RestError) { Test-AGMJSON "$RestError" } elseif ($resp.managementServers) { $resp.managementServers } } <# .SYNOPSIS Log into the vCenter. This function will read the password from the masked standard input or a specified password file. You can use `Save-vCenterPassword` before calling this function. If the specified password file does not exist, it will prompt to ask the password and save the inputed password into the password file. .EXAMPLE Connect-vCenter -vCenterHostName abcd-112233.e123abc45.southamerica-east1.abc.com -User user-01@abc.com -PassFilePath '.vcenter_pass' # If .vcenter_pass does not exists, will prompt Password: ************ .EXAMPLE Save-vCenterPassword -FileName '.vcenter_pass' Connect-vCenter -vCenterHostName abcd-112233.e123abc45.southamerica-east1.abc.com -User user-01@abc.com -PassFilePath '.vcenter_pass' .EXAMPLE Connect-vCenter -vCenterHostName abcd-112233.e123abc45.southamerica-east1.abc.com -User user-01@abc.com .EXAMPLE Connect-vCenter -vCenterHostId 6880886 -User user-01@abc.com #> function Connect-vCenter { [CmdletBinding()] param ( # The host name of the vCenter, e.g. abcd-112233.e123abc45.southamerica-east1.abc.com [Parameter(Mandatory = $true, ParameterSetName = "LogInByHostName")] [string] $vCenterHostName, # The `id` of the vCenter host, you can find the `id` by `(Get-AGMHost -filtervalue "isvcenterhost=true") | Select-Object id,name` [Parameter(Mandatory = $true, ParameterSetName = "LogInByHostId")] [int] $vCenterId, # The user name for logging into the vCenter [Parameter(Mandatory = $true, ParameterSetName = "LogInByHostName")] [Parameter(Mandatory = $true, ParameterSetName = "LogInByHostId")] [string] $UserName, # File that saves the encrypted password [Parameter(Mandatory = $false)] [string] $PassFilePath ) # If specified vCenterId, we will try to find the host name by this parameter if ($vCenterId) { $vCenterHostName = Find-vCenterHostName $vCenterId } Write-Output "Connecting vCenter, hostname: $vCenterHostName, user: $UserName" # Clean up the stale server configuration # `Invoke-CreateSession` will fail if we don't perform this step since the user credentials will be cleared after # retrieving the api session. Disconnect-vCenter try { # Check if the user passes -PassFilePath option, # If uses, read from the password file if exists, otherwise prompt for password and save it into the `PassFilePath` # If not uses, read the password from the standard input if ($PassFilePath) { if (Test-Path $PassFilePath) { $password_enc = Get-Content $PassFilePath | ConvertTo-SecureString; } else { $password_enc = Save-vCenterPassword -FileName $PassFilePath } } else { # Read credentials from the standard input $password_enc = Read-Host -AsSecureString -Prompt "Password" } # Create vSphere Server Configuration with the provided Credentials. $serverConfiguration = New-vSphereServerConfiguration -Server $vCenterHostName -User $UserName -Password $password_enc # Creates a Session with the vSphere API if we don't have a session. $apiSession = Invoke-CreateSession -WithHttpInfo -ErrorAction Stop # Set the API Key in the vSphere Server Configuration, received with the API Session. # This step will celar the user credentials and will only keep the API Session ID $serverConfiguration = $serverConfiguration | Set-vSphereServerConfigurationApiKey -SessionResponse $apiSession Write-Output "vCenter connected" } catch { Write-Error "Failed to connect to the vCenter, please double check the credentials." } } <# .SYNOPSIS Disconnect from vCenter and delete the API session. You will need to call `Connect-vCenter` next time. #> function Disconnect-vCenter { $serverConfiguration = Get-vSphereServerConfiguration if ($null -ne $serverConfiguration) { Remove-vSphereServerConfiguration $serverConfiguration } } <# .SYNOPSIS Encrypt and save the password of the vCenter into a file. #> function Save-vCenterPassword { [CmdletBinding()] param ( # Absolute or relative location where the file should be saved. [Parameter(Mandatory = $true)] [string] $FileName, # Encrypted password [Parameter(Mandatory = $false, ValueFromPipeline = $true)] [SecureString] $PasswordEnc ) # if no file is provided, prompt for one if (!$FileName) { $FileName = Read-Host "File Name"; } # if the filename already exists. don't overwrite it. error and exit. if ( Test-Path $FileName ) { Get-AGMErrorMessage -messagetoprint "The file: $FileName already exists. Please delete it first."; return; } # prompt for password if (!($PasswordEnc)) { $PasswordEnc = Read-Host -AsSecureString "Password" } try { $PasswordEnc | ConvertFrom-SecureString | Out-File $FileName -ErrorAction Stop Write-Host "Password saved to $FileName." Write-Host "You may now use -PassFilePath with `Connect-vCenter` to provide a saved password file." return $PasswordEnc } catch { Get-AGMErrorMessage -messagetoprint "An error occurred in saving the password" } } |