Public/Invoke-IBWAPI.ps1

function Invoke-IBWAPI
{
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory=$true,Position=0)]
        [Uri]$Uri,
        [Microsoft.PowerShell.Commands.WebRequestMethod]$Method=([Microsoft.PowerShell.Commands.WebRequestMethod]::Get),
        [PSCredential]$Credential,
        [Object]$Body,
        [string]$ContentType='application/json; charset=utf-8',
        [string]$OutFile,
        [string]$SessionVariable,
        [Microsoft.PowerShell.Commands.WebRequestSession]$WebSession,
        [switch]$SkipCertificateCheck
    )

    ###########################################################################
    # This function is largely just a wrapper around Invoke-RestMethod that is able
    # to trap errors and present them to the caller in a more useful fashion.
    # For instance, HTTP 400 errors are normally returned as an Exception without
    # any context. But Infoblox returns details about *why* the request was bad in
    # the response body. So we swallow the original exception and throw a new one
    # with the specific error details.
    #
    # We also allow for disabling certificate validation on a per-call basis.
    # However, due to how the underlying .NET framework caches cert validation
    # results, hosts that were ignored may continue to be ignored for
    # a period of time after the initial call even if validation is turned
    # back on. This issue only affects the Desktop edition.
    ###########################################################################

    # Build a hashtable out of our optional parameters that we will later
    # send to Invoke-RestMethod via splatting
    # https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.core/about/about_splatting
    $opts = @{}
    $paramNames = 'Method','Credential','Body','ContentType','OutFile','WebSession'
    $PSBoundParameters.Keys | Where-Object { $_ -in $paramNames } | ForEach-Object { $opts.$_ = $PSBoundParameters.$_ }

    # parameters with default values don't appear in $PSBoundParameters, so we need to add manually
    if (-not $opts.Method) { $opts.Method = $Method }
    if (-not $opts.ContentType) { $opts.ContentType = $ContentType }

    # add Core edition parameters if necessary
    if ($SkipCertificateCheck -and $script:SkipCertSupported) {
        $opts.SkipCertificateCheck = $true
    }

    if ('SkipHeaderValidation' -in (Get-Command Invoke-RestMethod).Parameters.Keys) {
        # PS Core doesn't like the way our multipart Content-Type header looks for some
        # reason. So we need to disable its built-in validation.
        $opts.SkipHeaderValidation = $true
    }

    # deal with session stuff
    if ($opts.WebSession) {
        Write-Debug "using explicit session"
    } elseif ($savedSession = Get-IBSession $Uri $opts.Credential) {
        Write-Debug "using saved session"
        $opts.WebSession = $savedSession
    } else {
        Write-Debug "no existing session"
        # prepare to save the session for later
        $opts.SessionVariable = 'innerSession'
    }

    try
    {
        if ($SkipCertificateCheck -and !$script:SkipCertSupported) {
            [CertValidation]::Ignore();
            Write-Verbose "Disabled cert validation"
        }

        try {
            if ($PSCmdlet.ShouldProcess($Uri, $opts.Method)) {

                # send the request
                $response = Invoke-RestMethod -Uri $Uri @opts

                # attempt to detect a master candidate's meta refresh tag
                if ($response -is [Xml.XmlDocument] -and $response.OuterXml -match 'CONTENT="0; URL=https://(?<gm>[\w.]+)"') {
                    $gridmaster = $matches.gm
                    Write-Warning "WAPIHost $($Uri.Authority) is requesting a redirect to $gridmaster. Retrying request against that host."

                    # retry the request using the parsed grid master
                    $Uri = [uri]$Uri.ToString().Replace($Uri.Authority, $gridmaster)
                    Invoke-RestMethod -Uri $Uri @opts
                } else {
                    Write-Output $response
                }

                # make sure to send our session variable up to the caller scope if defined
                if ($savedSession) {
                    if ($SessionVariable) {
                        Set-Variable -Name $SessionVariable -Value $savedSession -Scope 2
                    }
                } else {
                    if ((-not $opts.WebSession) -and $SessionVariable) {
                        Set-Variable -Name $SessionVariable -Value $innerSession -Scope 2
                    }

                    # save the session variable internally to re-use later
                    Set-IBSession $Uri $opts.Credential $innerSession
                }

            }
        }
        finally {
            if ($SkipCertificateCheck -and !$script:SkipCertSupported) {
                [CertValidation]::Restore();
                Write-Verbose "Enabled cert validation"
            }
        }
    }
    catch
    {
        $response = $_.Exception.Response

        if ($response.StatusCode -eq [System.Net.HttpStatusCode]::BadRequest) {

            # Since we can't catch explicit exception types between PowerShell editions
            # without errors for non-existent types, we need to string match the type names
            # and re-throw anything we don't care about.
            $exType = $_.Exception.GetType().FullName
            if ('System.Net.WebException' -eq $exType) {

                # This is the exception that gets thrown in PowerShell Desktop edition

                # get the response object: System.Net.HttpWebResponse
                $response = $_.Exception.Response

                # grab the raw response body
                $sr = New-Object IO.StreamReader($response.GetResponseStream())
                $sr.BaseStream.Position = 0
                $sr.DiscardBufferedData()
                $body = $sr.ReadToEnd()
                Write-Debug "Error Body: $body"

            } elseif ('Microsoft.PowerShell.Commands.HttpResponseException' -eq $exType) {

                # This is the exception that gets thrown in PowerShell Core edition

                # get the response object
                # Linux type: System.Net.Http.CurlHandler+CurlResponseMessage
                # Mac type: ???
                # Win type: System.Net.Http.HttpResponseMessage
                $response = $_.Exception.Response

                # Currently in PowerShell 6, there's no way to get the raw response body from an
                # HttpResponseException because they dispose the response stream.
                # https://github.com/PowerShell/PowerShell/issues/5555
                # https://get-powershellblog.blogspot.com/2017/11/powershell-core-web-cmdlets-in-depth.html
                # However, a "processed" version of the body is available via ErrorDetails.Message
                # which *should* work for us. The processing they're doing should only be removing HTML
                # tags. And since our body should be JSON, there shouldn't be any tags to remove.
                # So we'll just go with it for now until someone reports a problem.
                $body = $_.ErrorDetails.Message
                Write-Debug "Error Body: $body"

            } else { throw }

            Write-Verbose $body
            $wapiErr = ConvertFrom-Json $body -EA SilentlyContinue
            if ($wapiErr) {
                throw [Exception] "$($wapiErr.Error)"
            } else {
                throw [Exception] $body
            }

        } else {
            # just re-throw everything else
            throw
        }
    }




    <#
    .SYNOPSIS
        Send a request to the Infoblox WAPI (REST API).
 
    .DESCRIPTION
        This function is largely just a wrapper around Invoke-RestMethod that supports trapping and exposing syntax errors with the WAPI and the ability to ignore certificate validation. It is what all of the *-IBObject functions use under the hood and shouldn't be necessary to call directly most of the time.
 
    .PARAMETER Uri
        The full Uri of the WAPI endpoint. (e.g. https://gridmaster.example.com/wapi/v2.2/network)
 
    .PARAMETER Method
        The HTTP method to use in the request. Default is GET.
 
    .PARAMETER Credential
        Username and password for the Infoblox appliance. This parameter is required unless -WebSession is specified.
 
    .PARAMETER Body
        The body of the request. This is usually a JSON string if needed. NOTE: If you have non-ASCII characters, you may need to explicitly encode your JSON body as UTF-8. For example, [Text.Encoding]::UTF8.GetBytes($body).
 
    .PARAMETER ContentType
        The Content-Type header for the request. Default is 'application/json; charset=utf-8'.
 
    .PARAMETER OutFile
        Specifies the output file that this cmdlet saves the response body. Enter a path and file name. If you omit the path, the default is the current location.
 
    .PARAMETER SessionVariable
        Specifies a variable for which this cmdlet creates a web request session and saves it in the value. Enter a variable name without the dollar sign ($) symbol.
 
    .PARAMETER WebSession
        Specifies an existing WebSession object to use with the request. If specified, the SessionVariable parameter will be ignored.
 
    .PARAMETER SkipCertificateCheck
        If set, SSL/TLS certificate validation will be disabled.
 
    .EXAMPLE
        Invoke-IBWAPI -Uri 'https://gridmaster.example.com/wapi/v2.2/network' -Credential (Get-Credential)
 
        Retrieve the list of network objects from the grid master using interactive credentials.
 
    .LINK
        Project: https://github.com/rmbolger/Posh-IBWAPI
 
    .LINK
        New-IBObject
 
    .LINK
        Get-IBObject
 
    .LINK
        Invoke-RestMethod
 
    #>

}