Private/Invoke-IdoIt.ps1

Function Invoke-IdoIt {
    <#
    .SYNOPSIS
    Invoke-IdoIt

    .DESCRIPTION
    The Invoke-IdoIt Cmdlet will call the i-doit RPC API endpoint with the provieded query parameters and will return a WebRequest result.
    The result of the web request is validated and if everything looks ok the cmdlet returns the property <result>.

    The result is already converted from an JSON string into an PSObject. The NoteProperties that are returned depend on the provided method
    for the idoit request.

    .PARAMETER Method
    This parameter the method yout want to call at the RPC endpoint. The different methods are descriped in the idoit api documentation.

    If you define the "idoit.login" method you must provide the following headers:

    X-RPC-Auth-Username = <Username>
    X-RPC-Auth-Password = <Password>
    Content-Type = "application/json"

    .PARAMETER Params
    This is a hashtable objects with all method specific parameters to pass to the RPC endpoint. The different value/key pairs are described in the
    idoit api reference.

    Beside the values you pass to this parameter Invoke-IdoIt will always add some static ones like
    - ApiKey
    - Request id
    - Version

    ApiKey is read from a global variable.

    .PARAMETER Headers
    This is an optional parameter to pass specific header fields in the POST request. This parameter is optional and only needed if you call the
    idoit.login.

    .PARAMETER Uri
    The Uri parameter can be used to set the connection URI. If this optional parameter is not provided Invoke-IdoIt
    is looking in the $global:CmdbUri varibale.

    .PARAMETER RawOutput
    You can provide a [Ref] parameter to the function to get back the raw response from the invoke to the I-doIt API.

    You have to put the parameter in parantheses like this:
    -RawOutput ([Ref]$Output)

    The return value is a Microsoft.PowerShell.Commands.HtmlWebResponseObject

    .EXAMPLE
    PS> Invoke-IdoIt -Method "cmdb.location_tree.read" -Params @{"id"=1234}

    This will invoke the metho cmdb.location_tree.read for the object 1234

    .NOTES
    Version
    0.1.0 29.12.2017 CB initial release
    0.2.0 31.12.2017 CB redesign of the function to be more generic
    0.2.1 03.01.2018 CB Added CmdletBinding and Verbose/Debug output; Added Ref Parameter RawOutput
    #>


    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidGlobalVars", "")]
    [CmdletBinding()]
    Param (
        [Parameter( Mandatory = $True )]
        [ValidateNotNullOrEmpty()]
        [String]$Method,

        [Parameter( Mandatory = $True )]
        [ValidateNotNull()]
        [Hashtable]$Params,

        [Parameter( Mandatory = $False )]
        [Hashtable]$Headers,

        [Parameter( Mandatory = $False )]
        [String]$Uri,

        [Parameter( Mandatory = $False )]
        [Ref]$RawOutput
    )

    # When –Debug is used, we will not get a prompt each time it is used :-) Thanks to Boe Prox
    # https://learn-powershell.net/2014/06/01/prevent-write-debug-from-bugging-you/
    If ($PSBoundParameters['Debug']) {
        $DebugPreference = 'Continue'
    }

    $RequestId = New-IdoItRequestId

    $RequestBody = @{
        "method" = $Method
        "version" = "2.0"
        "id" = $RequestId
        "params" = $Params
    }

    #Add the API Key to the params if it is not already defined
    If (!$RequestBody.Params.ContainsKey("ApiKey")) {

        $RequestBody.Params.Add("apikey", $Global:cmdbApiKey)

    }

    If (!$RequestBody.params.ContainsKey("Uri")) {

        If ($Global:CmdbUri.Length -gt 0) {

            $Uri = $Global:cmdbUri

        }

    }

    $RequestBody = ConvertTo-Json -InputObject $RequestBody -Depth 4

    Write-Debug "Request body: $RequestBody"

    If (!$PSBoundParameters.ContainsKey("Headers")) {

        $Headers = @{"Content-Type" = "application/json"; "X-RPC-Auth-Session" = $global:cmdbSession}

    }

    Write-Debug "Request headers: $($Headers | Out-String)"

    #define higher tls version - otherwise tls1.0 will fail on more secure web sockets
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

    Try {

        Write-Verbose "Trying to innvoke WebRequest to I-doIt"
        $InvokeResult = Invoke-WebRequest -Uri $Uri -Method Post -Body $RequestBody -Headers $Headers

    }
    Catch {

        Throw $_

    }

    If ($PSBoundParameters.ContainsKey("RawOutput")) {

        Write-Verbose "RawOuput parameter provided. Saving raw data to ref variable"
        $RawOutput.Value = $InvokeResult

    }

    If ($InvokeResult.StatusCode -eq 200) {

        Write-Verbose "Request status code 200 returned"
        #i-doit puts numbers in the JSON response in quotes :-( - This is breaking type conversion into integer when calling ConvertFrom-Json
        #Before converting the JSON to an PSObject we remove quotes from numbers with this little magic regex!

        $ContentJson = $InvokeResult.Content | ConvertFrom-IdoItJsonResponse | ConvertFrom-Json

        Write-Verbose "Response: $ContentJson"
        Write-Verbose "Response: $($ContentJson.Result)"
        #$TempJson = $InvokeResult.content -replace $Regex, '$1'

        #$ContentJson = ConvertFrom-Json $TempJson

        #Check for error object
        Write-Verbose "Checking if response contains error object"

        If ($ContentJson.PSObject.Properties.Name -Contains 'Error') {

            Throw "Error code $($ContentJson.Error.Code) - $($ContentJson.error.data.error)"

        }

        Else {

            Write-Verbose "Checking if the response id matches the request id"

            #We check that we get back our requestId. This ensures that the send JSON request could be read by the service
            If ( -Not (Compare-IdoItRequestId -RequestId $RequestId -ResponseId $ContentJson.id)) {
            #If ($ContentJson.id -ne $RequestID) {

                Throw "Request id mismatch. Expected value was $RequestID but it is $($ContentJson.id)"

            }

            #Return only the result part of the response
            $ContentJson.result

        }



    }
    Else {
        Return "Error"
    }

}