From 392048afce073e5b4949ba91fc82c9f8974ad782 Mon Sep 17 00:00:00 2001 From: Justin Paul Date: Sun, 9 Feb 2025 21:01:17 -0500 Subject: [PATCH] Update Invoke-ZertoRestRequest.ps1 reverting to non-LLM code --- .../Public/Invoke-ZertoRestRequest.ps1 | 263 ++++++++++-------- 1 file changed, 141 insertions(+), 122 deletions(-) diff --git a/ZertoApiWrapper/Public/Invoke-ZertoRestRequest.ps1 b/ZertoApiWrapper/Public/Invoke-ZertoRestRequest.ps1 index 00e1160..f19c415 100644 --- a/ZertoApiWrapper/Public/Invoke-ZertoRestRequest.ps1 +++ b/ZertoApiWrapper/Public/Invoke-ZertoRestRequest.ps1 @@ -1,151 +1,170 @@ function Invoke-ZertoRestRequest { [cmdletbinding()] param( - [Parameter(HelpMessage = "API method to be used. GET, PUT, POST, or DELETE. If unspecified, defaults to GET.")] + # Parameter help description + [Parameter( + Helpmessage = "API method to be used. GET, PUT, POST, or DELETE. Refer to documentation for the API endpoint to ensure the correct method is being used. If unspecified, defaults to GET" + )] [ValidateSet("GET", "PUT", "POST", "DELETE")] [string]$method = "GET", - - [Parameter(Mandatory, HelpMessage = "URI endpoint to be utilized. Only the endpoint needs to be submitted.")] + [Parameter( + Mandatory, + Helpmessage = "URI endpoint to be utilized. When submitting the URI, only the endpoint needs to be submitted. Please review the help documentation for examples." + )] [ValidateNotNullOrEmpty()] [string]$uri, - - [Parameter(HelpMessage = "Body to be submitted to the REST API endpoint in JSON format.")] - [string]$body = "", - - [Parameter(HelpMessage = "PSCredential object, used only when authenticating with the ZVM.")] + [Parameter( + Helpmessage = "Body to be submitted to the REST API endpoint. This needs to be submitted in JSON format" + )] + [ValidateNotNullOrEmpty()] + [string]$body, + [Parameter( + Helpmessage = "PSCredential object. This is ONLY used when authenticating with the ZVM. No other endpoints require this and generally is not used." + )] [PSCredential]$credential, - - [Parameter(HelpMessage = "Use this switch to return request headers along with the response body.")] + [Parameter( + Helpmessage = "Use this switch if you would like the request headers returned along with the body. Useful for troubleshooting to get HTTP error codes." + )] [switch]$returnHeaders ) - + # API version. Currently this is locked at v1 in all versions of Zerto. Should this change, will look + # at making this as parameter to be selected during function call. $apiVersion = "v1" + # While the API can use XML or JSON, this module is built on JSON functionality. Currently forcing all + # content types and language to JSON. + $contentType = "application/json" $callerErrorActionPreference = $ErrorActionPreference - - # Ensure required script variables exist - if (-not (Test-Path variable:script:zvmServer) -or -not (Test-Path variable:script:zvmPort)) { - Throw "Zerto connection does not exist. Run Connect-ZertoServer first." + # If the ZVM server and Port not defined, Stop Call + if ( -not ((Test-Path variable:script:zvmServer) -and (Test-Path variable:script:zvmPort)) ) { + Throw "Zerto Connection does not Exist. Please run Connect-ZertoServer first to establish a connection" } - # Ensure $Script:Reconnect is defined - if (-not (Test-Path variable:script:Reconnect)) { $Script:Reconnect = $false } - - # Check for expired session - if ((Test-Path variable:script:AuthExpiresAt) -and $([datetime]$script:AuthExpiresAt) -lt (Get-Date)) { + # If the Headers exist and the Last action was more than 30 minutes ago, Session is Expired + if ( (Test-Path variable:script:zvmHeaders) -and (Test-Path variable:script:AuthExpiresAt) -and $([datetime]$script:AuthExpiresAt) -lt $(Get-Date) -and $Script:Reconnect -eq $False ) { Remove-Variable -Name AuthExpiresAt -Scope Script - if ($Script:Reconnect) { - Write-Verbose "Authorization expired. Reauthorizing." - Connect-ZertoServer -zertoServer $Script:zvmServer -zertoPort $script:zvmPort -credential $Script:CachedCredential - } else { - Throw "Authorization token expired. Please reauthorize." - } - } + Throw "Authorization Token has Expired. Please re-authorize to the Zerto Virtual Manager" + } elseif (( (Test-Path variable:script:zvmHeaders) -and (Test-Path variable:script:AuthExpiresAt) -and $([datetime]$script:AuthExpiresAt) -lt $(Get-Date) -and $Script:Reconnect -eq $True )) { + Write-Verbose "Authorization had expired. Attempting Reauthorization." + Remove-Variable -Name AuthExpiresAt -Scope Script + Connect-ZertoServer -zertoServer $Script:zvmServer -zertoPort $script:zvmPort -credential $Script:CachedCredential + }# else { + # Build the URI to be submitted $submittedURI = "https://{0}:{1}/{2}/{3}" -f $script:zvmServer, $script:zvmPort, $apiVersion, $uri - $script:zvmLastAction = (Get-Date).Ticks - $responseHeaders = @{} - $apiRequestResults = $null - try { - # Set default headers - $headers = @{ - "Accept" = "application/json" - } - - # If authorization headers exist, add them - if (Test-Path variable:script:zvmHeaders) { - $headers["Authorization"] = "Bearer $($script:zvmHeaders.Authorization)" - } - - $params = @{ - Uri = $submittedURI - Method = $method - Headers = $headers - TimeoutSec = 100 - ContentType = "application/json" - } - - # Ensure GET requests do not include a body - if ($method -ne "GET") { - $params["Body"] = $body - } - - # Handle authentication requests - if ($uri -match "auth/realms/.*/protocol/openid-connect/token" -and $method -eq "POST") { - $data = @{ - 'client_id' = $script:zertoClientId - 'username' = $credential.GetNetworkCredential().UserName - 'password' = $credential.GetNetworkCredential().Password - 'grant_type' = 'password' - } - - $params.Uri = "https://{0}:{1}/auth/realms/zerto/protocol/openid-connect/token" -f $script:zvmServer, $script:zvmPort - $params.Body = $data - $params.ContentType = "application/x-www-form-urlencoded" - } - - # Handle SSL/TLS trust issue for PowerShell 5.1 - if ($PSVersionTable.PSVersion.Major -lt 6) { - Add-Type -TypeDefinition @" - using System; - using System.Net; - using System.Security.Cryptography.X509Certificates; - public class TrustAllCertsPolicy : ICertificatePolicy { - public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) { - return true; - } - } -"@ -PassThru - [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy - } else { - $params["SkipCertificateCheck"] = $true - } - - # Execute API request + # Set the zvmLastAction time and try to submit the REST Request + $script:zvmLastAction = (Get-Date).Ticks + # If running PwSh - Use this Invoke-RestMethod with passed Variables if ($PSVersionTable.PSVersion.Major -ge 6) { - $apiRequestResults = Invoke-RestMethod @params -ResponseHeadersVariable responseHeaders + # If we are authenticating to the ZVM, Use this block to use Invoke-WebRequest and format the Headers and Body as expected. + if ($uri -eq "auth/realms/zerto/protocol/openid-connect/token" -and $method -eq "POST") { + $data = @{ + 'client_id' = $script:zertoClientId + 'username' = $credential.GetNetworkCredential().UserName + 'password' = $credential.GetNetworkCredential().Password + 'grant_type' = 'password' + } + + $params = @{ + 'Uri' = 'https://' + $script:zvmServer + ':' + $script:zvmPort + '/auth/realms/zerto/protocol/openid-connect/token' + 'Method' = 'Post' + 'Body' = $data + 'ContentType' = 'application/x-www-form-urlencoded' + } + $apiRequestResults = Invoke-RestMethod @params -SkipCertificateCheck + + $ExpiresIn = $apiRequestResults.expires_in + $script:AuthExpiresAt = (Get-Date).AddSeconds($ExpiresIn) + $script:refreshToken = $apiRequestResults.refresh_token + $responseHeaders = @{ } + $responseHeaders['Authorization'] = "Bearer " + @($apiRequestResults.access_token) + + # If we are logging out from the ZVM, use this block to use Invoke-WebRequest and format the Headers and Body as expected. + } elseif ($uri -eq "auth/realms/zerto/protocol/openid-connect/logout" -and $method -eq "POST") { + $data = @{ + 'client_id' = $script:zertoClientId + 'logout' = 'true' + } + + $params = @{ + 'Uri' = 'https://' + $script:zvmServer + ':' + $script:zvmPort + '/auth/realms/zerto/protocol/openid-connect/logout' + 'Method' = 'Post' + 'Body' = $data + 'ContentType' = 'application/x-www-form-urlencoded' + } + + $apiRequestResults = Invoke-RestMethod @params -SkipCertificateCheck + + } else { + $apiRequestResults = Invoke-RestMethod -Uri $submittedURI -Headers $script:zvmHeaders -Method $method -Body $body -ContentType $contentType -Credential $credential -SkipCertificateCheck -ResponseHeadersVariable responseHeaders -TimeoutSec 100 + } } else { - # PowerShell 5.1 workaround: use Invoke-WebRequest instead - $webResponse = Invoke-WebRequest @params - $apiRequestResults = $webResponse.Content | ConvertFrom-Json -ErrorAction SilentlyContinue - $responseHeaders = $webResponse.Headers - } - - # Debugging - Inspect Response Type - Write-Verbose "Response Type: $($apiRequestResults.GetType().FullName)" - Write-Verbose "Response Content: $($apiRequestResults | ConvertTo-Json -Depth 10)" - - # Ensure response is parsed properly - if ($apiRequestResults -is [string]) { - try { - $apiRequestResults = $apiRequestResults | ConvertFrom-Json -ErrorAction Stop - } catch { - Write-Verbose "Response is not in JSON format, returning raw output." - } - } - - # Handle token expiration update - if ($uri -match "auth/realms/.*/protocol/openid-connect/token" -and $method -eq "POST") { - if ($apiRequestResults -is [System.Collections.IDictionary]) { - Write-Verbose "API response is a dictionary. Extracting values..." - } - $script:AuthExpiresAt = (Get-Date).AddSeconds($apiRequestResults.expires_in) - $script:refreshToken = $apiRequestResults.refresh_token - $headers["Authorization"] = "Bearer " + $apiRequestResults.access_token - } + # If running PowerShell 5.1 --> Do the Following + # Check to see if All Certs are Trusted. If not, Create the Policy to Trust All Certificates + if ([System.Net.ServicePointManager]::CertificatePolicy.GetType().Name -ne "TrustAllCertsPolicy") { + Try { + $type = @' +using System.Net; +using System.Security.Cryptography.X509Certificates; +public class TrustAllCertsPolicy : ICertificatePolicy { + public bool CheckValidationResult( ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) { + return true; } - catch { +} +'@ + Add-Type -TypeDefinition $type -ErrorAction SilentlyContinue + } Catch { + if ($error[0].Exception -ne "Cannot add type. The type name 'TrustAllCertsPolicy already exists.") { + Write-Debug $error[0] + } + } + [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy + + } + # If we are authenticating to the ZVM, Use this block to use Invoke-WebRequest and format the Headers as expected. + if ($uri -eq "auth/realms/zerto/protocol/openid-connect/token" -and $method -eq "POST") { + $data = @{ + 'client_id' = $script:zertoClientId + 'username' = $credential.GetNetworkCredential().UserName + 'password' = $credential.GetNetworkCredential().Password + 'grant_type' = 'password' + } + + $params = @{ + 'Uri' = 'https://' + $script:zvmServer + ':' + $script:zvmPort + '/auth/realms/zerto/protocol/openid-connect/token' + 'Method' = 'POST' + 'Body' = $data + 'ContentType' = 'application/x-www-form-urlencoded' + } + $apiRequestResults = Invoke-RestMethod @params + + $ExpiresIn = $apiRequestResults.expires_in + $script:AuthExpiresAt = (Get-Date).AddSeconds($ExpiresIn) + $script:refreshToken = $apiRequestResults.refresh_token + $responseHeaders = @{ } + $responseHeaders['Authorization'] = "Bearer " + @($apiRequestResults.access_token) + } elseif ($method -ne "GET") { + # If the Method is something other than 'GET' use this call with a body parameter + $apiRequestResults = Invoke-RestMethod -Uri $submittedURI -Headers $script:zvmHeaders -Method $method -Body $body -ContentType $contentType -Credential $credential -TimeoutSec 100 + } else { + # If the Method we are calling is 'GET' use this call without a body parameter + $apiRequestResults = Invoke-RestMethod -Uri $submittedURI -Headers $script:zvmHeaders -Method $method -ContentType $contentType -Credential $credential -TimeoutSec 100 + } + } + } catch { + # If an error is encountered, Catch Write-Error -ErrorRecord $_ -ErrorAction $callerErrorActionPreference - return } - # Return response based on $returnHeaders flag + # If the calling function does not need the headers (Default Action) return the results of the API Call if (-not $returnHeaders) { return $apiRequestResults } else { - return [PSCustomObject]@{ - apiRequestResults = $apiRequestResults - Headers = $responseHeaders - } + #If Headers are required, build a PS Custom Object with the Results and the Headers + $apiRequestAndHeaderResults = New-Object -TypeName psobject + $apiRequestAndHeaderResults | Add-Member -MemberType NoteProperty -Name "apiRequestResults" -Value $apiRequestResults + $apiRequestAndHeaderResults | Add-Member -MemberType NoteProperty -Name "Headers" -Value $responseHeaders + return $apiRequestAndHeaderResults + #} } }