CredentialRetriever.psm1

Function Invoke-AIMClient {

    <#
    .SYNOPSIS
    Defines specified CLIPasswordSDK command and arguments
 
    .DESCRIPTION
    Defines a CLIPasswordSDK process object with arguments required for specific command.
 
    .PARAMETER ClientPath
    The Path to CLIPasswordSDK.exe.
    Defaults to value of $Script:AIM.ClientPath, which is set during module import or via Set-AIMConfiguration.
 
    .PARAMETER Command
    The CLIPasswordSDK command to execute. Defaults to GetPassword.
 
    .PARAMETER CommandParameters
    The CLIPasswordSDK command parameters
 
    .PARAMETER Options
    Additional command options.
 
    .EXAMPLE
    Invoke-AIMClient -CommandParameters "/p AppDescs.AppID=TestApp /p RequiredProps=UserName,Address /p Query="Safe=TestSafe;Folder=Root;UserName=TestUser1" /o PassProps.UserName,PassProps.Address,Password,PasswordChangeInProcess""
 
    Invokes the GetPassword action using the provided arguments.
 
    .NOTES
        AUTHOR: Pete Maan
 
    #>


    [CmdLetBinding(SupportsShouldProcess)]
    param(

        [Parameter(
            Mandatory = $False,
            ValueFromPipelineByPropertyName = $True
        )]
        [string]$ClientPath = $Script:AIM.ClientPath,

        [Parameter(
            Mandatory = $False,
            ValueFromPipelineByPropertyName = $True
        )]
        [string]$Command = 'GetPassword',

        [Parameter(
            Mandatory = $True,
            ValueFromPipelineByPropertyName = $True
        )]
        [string]$CommandParameters,

        [Parameter(Mandatory = $False,
            ValueFromPipelineByPropertyName = $True
        )]
        [string]$Options
    )

    Begin {

        #Create process
        $Process = New-Object System.Diagnostics.Process

    }

    Process {

        #Check we have the path to the required client executable
        if (-not $ClientPath) {

            throw "CLIPasswordSDK path not set `nRun Set-AIMConfiguration to set path to CLIPasswordSDK"

        } elseif (-not (Test-Path -LiteralPath $ClientPath -PathType Leaf)) {

            throw "CLIPasswordSDK not found at '$ClientPath' `nRun Set-AIMConfiguration to set path to CLIPasswordSDK"

        }

        if ($PSCmdlet.ShouldProcess($ClientPath, "$CommandParameters")) {

            Write-Debug "Command Arguments: $Command $Options $CommandParameters"

            #Assign process parameters

            $Process.StartInfo.WorkingDirectory = "$(Split-Path $ClientPath -Parent)"
            $Process.StartInfo.Filename = $ClientPath
            $Process.StartInfo.Arguments = "$Command $Options $CommandParameters"
            $Process.StartInfo.RedirectStandardOutput = $True
            $Process.StartInfo.RedirectStandardError = $True
            $Process.StartInfo.UseShellExecute = $False
            $Process.StartInfo.CreateNoWindow = $True
            $Process.StartInfo.WindowStyle = 'hidden'

            #Start Process
            $Result = Start-AIMClientProcess -Process $Process -ErrorAction Stop

            #Return Error or Result
            if ($Result.StdErr -match '((?:^[A-Z]{5}[0-9]{3}[A-Z])|(?:ERROR \(\d+\)))(?::)? (.+)$') {

                #APPAP008E Problem occurred while trying to use user in the Vault
                Write-Debug "ErrorId: $($Matches[1])"
                Write-Debug "Message: $($Matches[2])"
                Write-Error -Message $Matches[2] -ErrorId $Matches[1]

            } ElseIf ($Result.ExitCode) {

                #Process failed without a recognised error message (e.g. crash or missing dependency)
                $Message = 'CLIPasswordSDK exited with code 0x{0:X8}' -f $Result.ExitCode
                if ($Result.StdErr) { $Message = "$Message`: $(([string]$Result.StdErr).Trim())" }
                Write-Error -Message $Message -ErrorId 'AIMClientExitCode'

            } Else { $Result }
        }

    }

    End {

        $Process.Dispose()

    }

}

Function Skip-CertificateCheck {
    <#
    .SYNOPSIS
    Bypass SSL Validation
 
    .DESCRIPTION
    Sets a certificate policy which skips ssl certificate validation for Windows PowerShell web requests.
    The certificate policy type is compiled once per session.
    Outputs the previously configured certificate policy, which should be restored once requests complete.
 
    .EXAMPLE
    $CertificatePolicy = Skip-CertificateCheck
 
    Skips certificate validation, saving the previous certificate policy to $CertificatePolicy.
 
    #>


    if ($PSEdition -ne 'Core') {

        if (-not ('CredentialRetriever.TrustAllCertificatePolicy' -as [type])) {

            Add-Type -TypeDefinition @'
namespace CredentialRetriever
{
    public class TrustAllCertificatePolicy : System.Net.ICertificatePolicy
    {
        public bool CheckValidationResult(System.Net.ServicePoint sp, System.Security.Cryptography.X509Certificates.X509Certificate cert, System.Net.WebRequest req, int problem)
        {
            return true;
        }
    }
}
'@


        }

        [System.Net.ServicePointManager]::CertificatePolicy
        [System.Net.ServicePointManager]::CertificatePolicy = New-Object -TypeName CredentialRetriever.TrustAllCertificatePolicy

    }

}

Function Start-AIMClientProcess {

    <#
    .SYNOPSIS
    Starts AIM CLI process
 
    .DESCRIPTION
    Designed to receive AIMClient process object from Invoke-AIMClient.
 
    Returns Object containing ExitCode, StdOut & StdErr
 
    .PARAMETER Process
    System.Diagnostics.Process object containing CLIPasswordSDK parameters
 
    .EXAMPLE
    Start-AIMClientProcess -Process $Process
 
    Invokes the Start method on the $Process object
 
    .NOTES
        AUTHOR: Pete Maan
 
    #>


    [CmdLetBinding(SupportsShouldProcess)]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '', Justification = 'ShouldProcess handling is in Invoke-AIMClient')]
    param(

        [Parameter(
            Mandatory = $True,
            ValueFromPipelineByPropertyName = $True
        )]
        [System.Diagnostics.Process]$Process
    )

    Begin {

    }

    Process {

        #Start Process
        $Process.start() | Out-Null

        #Read Output Stream First
        $StdOut = $Process.StandardOutput.ReadToEnd()
        $StdErr = $Process.StandardError.ReadToEnd()

        #If you wait for the process to exit before reading StandardOutput
        #the process can block trying to write to it, so the process never ends.
        $Process.WaitForExit()

        Write-Debug "Exit Code: $($Process.ExitCode)"

        [PSCustomObject] @{

            'ExitCode' = $Process.ExitCode
            'StdOut'   = $StdOut
            'StdErr'   = $StdErr

        }

    }

    End {

        $Process.Dispose()

    }

}

# .ExternalHelp CredentialRetriever-help.xml
Function Get-AIMConfiguration {
    [CmdletBinding()]
    Param()

    Get-Variable -Name AIM -Scope Script -ValueOnly -ErrorAction SilentlyContinue

}

# .ExternalHelp CredentialRetriever-help.xml
Function Get-AIMCredential {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'Suppress alert from ToSecureString ScriptMethod')]
    [CmdletBinding(DefaultParameterSetName = 'Default')]
    Param(
        # Unique ID of the application
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]
        $AppID,

        # Safe name
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Default'
        )]
        [ValidatePattern('^[^;"]*$')]
        [string]
        $Safe,

        # Folder name
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Default'
        )]
        [ValidatePattern('^[^;"]*$')]
        [string]
        $Folder,

        # Object name
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Default'
        )]
        [ValidatePattern('^[^;"]*$')]
        [string]
        $Object,

        # Search username
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Default'
        )]
        [ValidatePattern('^[^;"]*$')]
        [string]
        $UserName,

        # Search address
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Default'
        )]
        [ValidatePattern('^[^;"]*$')]
        [string]
        $Address,

        # Search database
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Default'
        )]
        [ValidatePattern('^[^;"]*$')]
        [string]
        $Database,

        # Set PolicyID
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Default'
        )]
        [ValidatePattern('^[^;"]*$')]
        [string]
        $PolicyID,

        # Free query of account properties
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Query'
        )]
        [ValidatePattern('^[^"]*$')]
        [string]
        $Query,

        # Set QueryFormat
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateSet('exact', 'regexp')]
        [string]
        $QueryFormat,

        # Required Properties
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true
        )]
        [string[]]
        $RequiredProps,

        # Reason to record in audit log
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidatePattern('^[^"]*$')]
        [string]
        $Reason,

        # Port for communication with the provider
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true
        )]
        [int]
        $Port,

        # Number of seconds to try
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true
        )]
        [int]
        $Timeout,

        # Return an error if a password change is in progress
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true
        )]
        [switch]
        $FailRequestOnPasswordChange,

        # Output PSCredential object
        [Parameter(Mandatory = $false)]
        [switch]
        $AsCredential,

        # Output password as SecureString
        [Parameter(Mandatory = $false)]
        [switch]
        $AsSecureString
    )

    Begin {
        #Function Parameters which will form any query string
        $QueryParameters = @(
            'Safe',
            'Folder',
            'Object',
            'UserName',
            'Address',
            'Database'
            'PolicyID'
        )

        $ConnectionParms = @(
            'Port',
            'Timeout'
        )

        #Delimiter for separating the output fields
        $Separator = '#_-_#'

        #CLIPasswordSDK argument prefix: / on Windows, - on Linux
        $Prefix = if ($IsWindows -eq $false) { '-' } else { '/' }

        if ($AsCredential -and $AsSecureString) {
            throw 'AsCredential and AsSecureString cannot be used together.'
        }

    }

    Process {

        #Array to hold the Properties to return
        [array]$ReturnProps = @()
        #Hashtable to hold the Results to Output
        [hashtable]$Output = @{ }

        #Initial Command String
        $Command = "${Prefix}p AppDescs.AppID=`"$AppID`""

        If ($PSCmdlet.ParameterSetName -eq 'Query') {

            $QueryString = $Query

        } Else {

            #Build query string from search parameters
            #"Property=Value;Property=Value;Property=Value"
            $QueryString = ($QueryParameters | Where-Object { $PSBoundParameters.ContainsKey($_) } | ForEach-Object {
                    "$_=$($PSBoundParameters[$_])"
                }) -join ';'

        }

        If ($QueryString) {

            #Add Query to Command String
            $Command = "$Command ${Prefix}p Query=""$QueryString"""

        }

        #Build Command String
        switch ( $PSBoundParameters.Keys ) {

            'QueryFormat' {

                #Add QueryFormat Command String
                $Command = "$Command ${Prefix}p QueryFormat=`"$QueryFormat`""

            }

            'Reason' {

                #Add Reason to Command String
                $Command = "$Command ${Prefix}p Reason=`"$Reason`""

            }

            'FailRequestOnPasswordChange' {

                #Add FailRequestOnPasswordChange to Command String
                $Command = "$Command ${Prefix}p FailRequestOnPasswordChange=$("$($FailRequestOnPasswordChange.IsPresent)".ToLower())"

            }

            { $ConnectionParms -contains $PSItem } {

                #Add ConnectionParms to Command String
                $Command = "$Command ${Prefix}p ConnectionParms.$_=$($PSBoundParameters[$_])"

            }

        }

        #UserName is required for PSCredential output
        $Props = @($RequiredProps | Where-Object { $_ })
        If ($AsCredential -and ($Props -notcontains 'UserName')) { $Props += 'UserName' }

        If ($Props.Count -gt 0) {

            #Add RequiredProps to Command String
            $Props | ForEach-Object {

                $ReturnProps += "PassProps.$_"
            }

            $Command = "$Command ${Prefix}p RequiredProps=$($Props -join ',')"

        }

        #Add Password & PasswordChangeInProcess to output fields
        $ReturnProps += 'Password'
        $ReturnProps += 'PasswordChangeInProcess'
        #Create Output fields string PropX,PropY,PropZ, Password, PasswordChangeInProcess
        $ReturnProps = $ReturnProps -join ','

        #Build Command String
        $Command = "$Command ${Prefix}o $ReturnProps ${Prefix}d $Separator"

        #Invoke Credential Provider
        $Result = Invoke-AIMClient -CommandParameters $Command

        #Output on StdOut
        If ($null -ne $Result.StdOut) {

            #split returned results at Separator
            $Results = ($Result.StdOut) -Split $Separator

            #use $returnProps to determine propertynames
            $ReturnProps = $ReturnProps.Split(',')

            For ($i = 0 ; $i -lt $ReturnProps.length ; $i++) {

                #PropertyName=PropertyValue
                $Value = ($Results[$i]).trim()

                #<na> (property does not exist) & <null> (property has no value) are output as $null
                If (($ReturnProps[$i] -like 'PassProps.*') -and ($Value -in '<na>', '<null>')) { $Value = $null }

                $Output[$(($ReturnProps[$i]) -replace 'PassProps.', '')] = $Value

            }

            #Create Output Object with Property Values
            $OutputObject = New-Object -TypeName PSObject -Property $Output

            #Add ScriptMethod to output object to convert password to Secure String
            $OutputObject | Add-Member -MemberType ScriptMethod -Name ToSecureString -Value {

                $this.Password | ConvertTo-SecureString -AsPlainText -Force

            } -Force

            #Add ScriptMethod to output object to convert username & password to Credential Object
            $OutputObject | Add-Member -MemberType ScriptMethod -Name ToCredential -Value {

                New-Object System.Management.Automation.PSCredential($this.UserName, $this.ToSecureString())

            } -Force

            #Return the result from AIM CP
            if ($AsCredential) {
                $OutputObject.ToCredential()
            } elseif ($AsSecureString) {
                $OutputObject.ToSecureString()
            } else {
                $OutputObject
            }

        }

    }

}

# .ExternalHelp CredentialRetriever-help.xml
function Get-CCPCredential {

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'Suppress alert from ToSecureString ScriptMethod')]
    [CmdletBinding(DefaultParameterSetName = 'Default')]
    Param(
        # Unique ID of the application
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Default'
        )]
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Query'
        )]
        [string]
        $AppID,

        # Safe name
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Default'
        )]
        [string]
        $Safe,

        # Folder name
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Default'
        )]
        [string]
        $Folder,

        # Object name
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Default'
        )]
        [string]
        $Object,

        # Search username
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Default'
        )]
        [string]
        $UserName,

        # Search address
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Default'
        )]
        [string]
        $Address,

        # Search database
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Default'
        )]
        [string]
        $Database,

        # Search PolicyID
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Default'
        )]
        [string]
        $PolicyID,

        # Reason to record in audit log
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Default'
        )]
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Query'
        )]
        [string]
        $Reason,

        # Free query of account properties
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'Query'
        )]
        [string]
        $Query,

        # Format of free query
        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'Query'
        )]
        [ValidateSet('Exact', 'Regexp')]
        [string]
        $QueryFormat,

        # Number of seconds to try
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Default'
        )]
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Query'
        )]
        [int]
        $ConnectionTimeout,

        # Return an error if a password change is in progress
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Default'
        )]
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Query'
        )]
        [switch]
        $FailRequestOnPasswordChange,

        # Credentials to send in request to CCP
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Default'
        )]
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Query'
        )]
        [ValidateNotNullOrEmpty()]
        [PSCredential]
        $Credential,

        # Use current system credentials for request to CCP
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Default'
        )]
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Query'
        )]
        [Switch]
        $UseDefaultCredentials,

        # Use certificate to authenticate to CCP
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Default'
        )]
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Query'
        )]
        [X509Certificate]
        $Certificate,

        # Use certificate to authenticate to CCP
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Default'
        )]
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Query'
        )]
        [string]
        $CertificateThumbPrint,

        # Unique ID of the CCP webservice in IIS
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Default'
        )]
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Query'
        )]
        [string]
        $WebServiceName = 'AIMWebService',

        # CCP URL
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Default'
        )]
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'Query'
        )]
        [string]
        $URL,

        [parameter(
            Mandatory = $false,
            ValueFromPipeline = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'Default'
        )]
        [parameter(
            Mandatory = $false,
            ValueFromPipeline = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'Query'
        )]
        [switch]
        $SkipCertificateCheck,

        # HTTP method for request to CCP
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $false,
            ParameterSetName = 'Default'
        )]
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $false,
            ParameterSetName = 'Query'
        )]
        [ValidateSet('GET', 'POST')]
        [string]
        $Method = 'GET',

        # Output PSCredential object
        [Parameter(Mandatory = $false)]
        [switch]
        $AsCredential,

        # Output password as SecureString
        [Parameter(Mandatory = $false)]
        [switch]
        $AsSecureString
    )

    Begin {

        #Collection of parameters which are to be excluded from the request
        [array]$ExcludedParameters += [System.Management.Automation.PSCmdlet]::CommonParameters
        [array]$ExcludedParameters += [System.Management.Automation.PSCmdlet]::OptionalCommonParameters
        [array]$ExcludedParameters += 'URL', 'WebServiceName', 'Credential', 'UseDefaultCredentials', 'CertificateThumbPrint', 'Certificate', 'SkipCertificateCheck', 'Method', 'AsCredential', 'AsSecureString'

        if ($AsCredential -and $AsSecureString) {
            throw 'AsCredential and AsSecureString cannot be used together.'
        }

        if ($PSEdition -ne 'Core') {

            #A SecurityProtocol of SystemDefault (0) lets Schannel negotiate the strongest protocol
            #both ends support, and is left untouched. Only a process pinned to explicit legacy
            #protocols needs TLS 1.2 adding, and it is combined with the protocols already permitted.
            $SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol

            if (([int]$SecurityProtocol -ne 0) -and
                ([Net.SecurityProtocolType].GetEnumNames() -contains 'Tls12') -and
                (-not ($SecurityProtocol.HasFlag([Net.SecurityProtocolType]::Tls12)))) {

                Write-Verbose 'Adding TLS12 to Security Protocol'
                [Net.ServicePointManager]::SecurityProtocol = $SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12

            }

        }

    }

    Process {

        #Collect bound request parameters, converting switches to boolean values
        $RequestParams = [ordered]@{ }
        $PSBoundParameters.keys | Where-Object { $ExcludedParameters -notcontains $_ } | ForEach-Object {

            $RequestParams[$_] = if ($PSBoundParameters[$_] -is [switch]) { $PSBoundParameters[$_].IsPresent } else { $PSBoundParameters[$_] }

        }

        $Request = @{
            'URI'             = "$($URL.TrimEnd('/'))/$WebServiceName/api/Accounts"
            'Method'          = $Method
            'ContentType'     = 'application/json'
            'ErrorAction'     = 'Stop'
            'UseBasicParsing' = $true
        }

        Switch ($Method) {

            'GET' {

                #Request parameters sent in URL query string
                $QueryString = ($RequestParams.Keys | ForEach-Object {
                        "$_=$([System.Uri]::EscapeDataString($RequestParams[$_]))"
                    }) -join '&'

                $Request['URI'] += "?$QueryString"

            }

            'POST' {

                #Request parameters sent in JSON body
                $Request['Body'] = $RequestParams | ConvertTo-Json

            }

        }

        # Add authentication parameters to request
        Switch ($($PSBoundParameters.keys)) {
            { $PSItem -contains 'Credential' } { $Request['Credential'] = $Credential }
            { $PSItem -contains 'UseDefaultCredentials' } { $Request['UseDefaultCredentials'] = $true }
            { $PSItem -contains 'CertificateThumbPrint' } { $Request['CertificateThumbPrint'] = $CertificateThumbPrint }
            { $PSItem -contains 'Certificate' } { $Request['Certificate'] = $Certificate }
        }

        $RestoreCertificatePolicy = $false

        #in PSCore use SkipCertificateCheck parameter
        if ($PSEdition -eq 'Core') {

            $Request.Add('SkipCertificateCheck', $SkipCertificateCheck.IsPresent)

        } elseif ($SkipCertificateCheck) {

            #Skip SSL Validation, saving previous certificate policy
            $CertificatePolicy = Skip-CertificateCheck
            $RestoreCertificatePolicy = $true

        }

        $result = $null

        Try {

            #send request
            $result = Invoke-RestMethod @Request

        } Catch {

            $ErrorRecord = $PSItem
            $ErrorMessage = $ErrorRecord.Exception.Message
            $ErrorID = $ErrorRecord.FullyQualifiedErrorId

            $err = $null

            #Only parse responses that look like JSON
            if ("$ErrorRecord".TrimStart().StartsWith('{')) {

                try {

                    $err = $ErrorRecord | ConvertFrom-Json -ErrorAction Stop

                } catch {

                    #Response is not valid JSON, keep original exception details
                    $err = $null

                }

            }

            #CCP errors use ErrorMsg/ErrorCode, IIS/ASP.NET errors use Message
            if ($err.ErrorMsg) {
                $ErrorMessage = $err.ErrorMsg
                $ErrorID = $err.ErrorCode
            } elseif ($err.Message) {
                $ErrorMessage = $err.Message
            }

            #report the error and continue with any further pipeline input
            $PSCmdlet.WriteError(

                [System.Management.Automation.ErrorRecord]::new(

                    $ErrorMessage,
                    $ErrorID,
                    [System.Management.Automation.ErrorCategory]::NotSpecified,
                    $ErrorRecord

                )

            )

        } Finally {

            #Restore previous certificate policy
            if ($RestoreCertificatePolicy) {

                [System.Net.ServicePointManager]::CertificatePolicy = $CertificatePolicy

            }

        }

        if ($null -ne $result) {

            #Add ScriptMethod to output object to convert password to Secure String
            $result | Add-Member -MemberType ScriptMethod -Name ToSecureString -Value {

                $this.Content | ConvertTo-SecureString -AsPlainText -Force

            } -Force

            #Add ScriptMethod to output object to convert username & password to Credential Object
            $result | Add-Member -MemberType ScriptMethod -Name ToCredential -Value {

                New-Object System.Management.Automation.PSCredential($this.UserName, $this.ToSecureString())

            } -Force

            #Return the result from CCP
            if ($AsCredential) {
                $result.ToCredential()
            } elseif ($AsSecureString) {
                $result.ToSecureString()
            } else {
                $result
            }

        }

    }

    End { }

}

# .ExternalHelp CredentialRetriever-help.xml
Function Set-AIMConfiguration {
    [CmdletBinding(SupportsShouldProcess)]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateScript( { Test-Path $_ -PathType Leaf })]
        [ValidateNotNullOrEmpty()]
        [string]$ClientPath
    )

    Process {

        $ConfigFile = Join-Path -Path $HOME -ChildPath 'AIMConfiguration.xml'

        if ($PSCmdlet.ShouldProcess($ConfigFile, "Set ClientPath to $ClientPath")) {

            Set-Variable -Name AIM -Value ([pscustomobject]@{ ClientPath = $ClientPath }) -Scope Script

            $Script:AIM | Export-Clixml -Path $ConfigFile -Force

        }

    }

}

#Read config and make available in script scope
$ConfigFile = Join-Path -Path $HOME -ChildPath 'AIMConfiguration.xml'
If (Test-Path $ConfigFile) {
    Write-Verbose "Importing Settings: $ConfigFile"
    $config = Import-Clixml -Path $ConfigFile
    Set-Variable -Name AIM -Value $config -Scope Script
} Else {
    #Use CLIPasswordSDK default install location, if present
    $ClientPath = @(
        "$env:ProgramFiles\CyberArk\ApplicationPasswordSdk\CLIPasswordSDK.exe",
        '/opt/CARKaim/sdk/clipasswordsdk'
    ) | Where-Object { Test-Path -LiteralPath $_ -PathType Leaf } | Select-Object -First 1
    If ($ClientPath) {
        Write-Verbose "Using CLIPasswordSDK: $ClientPath"
        Set-Variable -Name AIM -Value ([pscustomobject]@{ ClientPath = $ClientPath }) -Scope Script
    }
}