function Invoke-TssRestApi {
    <#
    .SYNOPSIS
    Invokes the Secret Server Rest API

    .DESCRIPTION
    Invokes the Thycotic Secret Server REST API

    .EXAMPLE
    $session = New-TssSession -SecretServer https://alpha -Credential $ssCred
    Invoke-TssRestApi -Uri 'https://vault.company.com/SecretServer/api/v1/secrets -PersonalAccessToken $session.AccessToken

    Performs request to the URI specified, returning all secrets the current credential has access to view (minimum).

    .EXAMPLE
    $session = nts https://alpha $ssCred
    ira "$($session.ApiUrl)/secrets" $session.AccessToken

    Performs request to the URI specified, returning all secrets the current credential has access to view (minimum) using alias names for each function.

    .LINK
    https://github.com/thycotic-ps/thycotic.secretserver/blob/main/src/functions/Invoke-TssRestApi.ps1

    .NOTES
    More detailed examples: https://thycotic-ps.github.io/thycotic.secretserver/docs/invoke-tssrestapi/
    #>
    [Cmdletbinding()]
    param(
        # Secret Server REST API URL
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0)]
        [Alias('Url')]
        [uri]
        $Uri,

        # Valid Access Token issued by Secret Server
        [Parameter(ValueFromPipelineByPropertyName, Position = 1)]
        [Alias('PAT')]
        [string]
        $PersonalAccessToken,

        # Method used for the web request, supported by Secret Server
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateSet('GET', 'DELETE', 'PATCH', 'POST', 'PUT')]
        [string]
        $Method,

        # Specifies the body of the request.
        [Parameter(ValueFromPipelineByPropertyName)]
        [Object]
        $Body,

        # Specifies the file path to write the content.
        [Parameter(ValueFromPipelineByPropertyName)]
        [string]
        $OutFile,

        # Specifies the content type of the web request.
        # If this parameter is omitted and the request method is POST, Invoke-RestMethod sets the content type to application/x-www-form-urlencoded. Otherwise, the content type is not specified in the call.
        [string]
        $ContentType = 'application/json',

        # Header of the web request. Enter a hash table or dictionary.
        [System.Collections.IDictionary]
        [Alias('Header')]
        $Headers,

        # Indicates using the credentials of the current user to send the web request (winauth).
        [Alias('UseDefaultCredential')]
        [switch]
        $UseDefaultCredentials,

        # Specifies that the cmdlet uses a proxy server for the request, rather than connecting directly to the Internet resource. Enter the URI of a network proxy server.
        [uri]
        $Proxy,

        # Specifies a user account that has permission to use the proxy server that is specified by the Proxy parameter. The default is the current user.
        # Type a user name, such as "User01" or "Domain01\User01", or enter a PSCredential object, such as one generated by the Get-Credential cmdlet.
        # This parameter is valid only when the Proxy parameter is also used in the command. You cannot use the ProxyCredential and ProxyUseDefaultCredentials parameters in the same command.
        [PSCredential]
        [Management.Automation.CredentialAttribute()]
        $ProxyCredential,

        # Indicates that the cmdlet uses the credentials of the current user to access the proxy server that is specified by the Proxy parameter.
        # This parameter is valid only when the Proxy parameter is also used in the command. You cannot use the ProxyCredential and ProxyUseDefaultCredentials parameters in the same command.
        [switch]
        $ProxyUseDefaultCredentials,

        # Output a custom type name for the results.
        [Parameter(ValueFromPipelineByPropertyName)]
        [string[]]
        $PSTypeName,

        # A set of additional properties to add to an object
        [Parameter(ValueFromPipelineByPropertyName)]
        [Collections.IDictionary]
        $Property,

        # A list of property names to remove from an object
        [string[]]
        $RemoveProperty,

        # Expand a given property from an object
        [string]
        $ExpandProperty
    )

    process {
        #region Prepare Parameters
        $irmSplat = @{ } + $PSBoundParameters    # First, copy PSBoundParameters and remove the parameters that aren't Invoke-RestMethod's
        $irmSplat.Remove('PersonalAccessToken') # * -PersonalAccessToken
        $irmSplat.Remove('PSTypeName') # * -PSTypeName
        $irmSplat.Remove('Property') # *-Property
        $irmSplat.Remove('RemoveProperty') # *-RemoveProperty
        $irmSplat.Remove('ExpandProperty') # *-ExpandProperty
        if ($PersonalAccessToken) {
            # If there was a personal access token, set the authorization header
            if ($Headers) {
                # (make sure not to step on other headers).
                $irmSplat.Headers.Authorization = "Bearer $PersonalAccessToken"
            } else {
                $irmSplat.Headers = @{
                    Authorization = "Bearer $PersonalAccessToken"
                }
            }
        } else {
        }
        if ($Body -and $Body -isnot [string]) {
            # If a body was passed, and it wasn't a string
            # $irmSplat.Body = $Body | ConvertTo-Json -Depth 100 # make it JSON.
        }
        if (-not $irmSplat.ContentType) {
            # If no content type was passed
            $irmSplat.ContentType = $ContentType # set it to the default.
        }
        #endregion Prepare Parameters

        #region Call Invoke-RestMethod

        # We call Invoke-RestMethod with the parameters we've passed in.
        # It will take care of converting the results from JSON.
        Invoke-RestMethod @irmSplat |
            & { process {
                    $in = $_
                    # What it will not do is "unroll" them.
                    if ($in -eq 'null') {
                        return
                    }
                    if ($ExpandProperty) {
                        if ($in.$ExpandProperty) {
                            $in.$ExpandProperty
                        }
                    } elseif ($in.code -like '*API_*') {
                        $PSCmdlet.WriteError(
                            [Management.Automation.ErrorRecord]::new(
                                [Exception]::new("$($in.message)"), "$($in.code)", 'InvalidOperation', $in))
                        $PSCmdlet.WriteVerbose("$in")
                        return
                    } elseif ($in -notlike '*<html*') {
                        # Otherwise, As long as the value doesn't look like HTML,
                        $_ # pass it down the pipe.
                    } else {
                        # If it happened to look like HTML, write an error
                        $PSCmdlet.WriteError(
                            [Management.Automation.ErrorRecord]::new(
                                [Exception]::new('Response was HTML, Request Failed.'),
                                'ResultWasHTML', 'NotSpecified', $in))
                        $PSCmdlet.WriteVerbose("$in") # and write the full content to verbose.
                        return
                    }
                    # Redirect standard error (2) to same place as standard output (1)
                } } 2>&1 |
            & { process {
                    # One more step of the pipeline will unroll each of the values.
                    if ($_ -is [string]) { return $_ }
                    if ($null -ne $_.Count -and $_.Count -eq 0) { return }
                    if ($PSTypeName -and # If we have a PSTypeName (to apply formatting)
                        $_ -isnot [Management.Automation.ErrorRecord] # and it is not an error (which we do not want to format)
                    ) {
                        $_.PSTypeNames.Clear() # then clear the existing typenames and decorate the object.
                        foreach ($t in $PSTypeName) {
                            $_.PSTypeNames.add($T)
                        }
                    }

                    if ($Property) {
                        foreach ($propKeyValue in $Property.GetEnumerator()) {
                            if ($_.PSObject.Properties[$propKeyValue.Key]) {
                                $_.PSObject.Properties.Remove($propKeyValue.Key)
                            }
                            $_.PSObject.Properties.Add($(
                                    if ($propKeyValue.Value -as [ScriptBlock[]]) {
                                        [PSScriptProperty]::new.Invoke(@($propKeyValue.Key) + $propKeyValue.Value)
                                    } else {
                                        [PSNoteProperty]::new($propKeyValue.Key, $propKeyValue.Value)
                                    }))
                        }
                    }
                    if ($RemoveProperty) {
                        foreach ($propToRemove in $RemoveProperty) {
                            $_.PSObject.Properties.Remove($propToRemove)
                        }
                    }
                    return $_ # output the object and we're done.
                } }
        #endregion Call Invoke-RestMethod
    }
}