SecretManagement.Hashicorp.Vault.KV.Extension/SecretManagement.Hashicorp.Vault.KV.Extension.psm1

# For ConvertTo-ReadOnlyDictonary
using namespace System.Collections.ObjectModel
using namespace System.Collections.Generic
# enum and Variables setup for use
$script:HashicorpVaultConfigValues = @('VaultServer', 'VaultAuthType', 'VaultToken', 'VaultAPIVersion', 'KVVersion', 'OutputType', 'Verbose')
$script:AllVariables = @('VaultServer', 'VaultAuthType', 'VaultToken', 'VaultAPIVersion', 'KVVersion', 'OutputType', 'TokenRenewable', 'TokenLifespan', 'TokenType', 'TokenExpireTime', 'Verbose')

enum HashicorpVaultAuthTypes {
    None
    AppRole
    LDAP
    userpass
    Token
    RenewToken
}

$script:HashicorpAuthTypes = @('None', 'AppRole', 'LDAP', 'userpass', 'Token')
[string]$script:VaultServer
[HashicorpVaultAuthTypes]$script:VaultAuthType = 'None'
[Securestring]$script:VaultToken
[string]$script:VaultAPIVersion = 'v1'
[string]$script:KVVersion = 'v2'
[string]$script:OutputType = 'Hashtable'
# Internally used
[bool]$script:TokenRenewable
[double]$script:TokenLifespan
[string]$script:TokenType
[datetime]$script:TokenExpireTime = '01/01/1600'
[bool]$script:Verbose

# Private Functions
function ConvertTo-ReadOnlyDictionary {
    <#
        .SYNOPSIS
        Converts a hashtable to a ReadOnlyDictionary[String,Object]. Needed for SecretInformation
        .NOTES
        From Justin Grote at https://github.com/JustinGrote/SecretManagement.KeePass/blob/main/SecretManagement.KeePass.Extension/Private/ConvertTo-ReadOnlyDictionary.ps1
    #>

    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline)][hashtable]$hashtable
    )
    process {
        $dictionary = [SortedDictionary[string, object]]::new([StringComparer]::OrdinalIgnoreCase)
        $hashtable.GetEnumerator().foreach{
            $dictionary[$_.Name] = $_.Value
        }
        [ReadOnlyDictionary[string, object]]::new($dictionary)
    }
}
function Invoke-CustomWebRequest {
    <#
    .SYNOPSIS
        Custom Web Request function to support non standard methods
    #>

    [Cmdletbinding()]
    param (
        [Parameter(Mandatory)]
        [string]$Uri,
        [Parameter(Mandatory)]
        [object]$Headers,
        [Parameter(Mandatory)]
        [string]$Method
    )
    Add-Type -AssemblyName System.Net.Http
    $Client = New-Object -TypeName System.Net.Http.HttpClient
    $Client.DefaultRequestHeaders.Accept.Add($headers['Accept'])
    $Request = New-Object -TypeName System.Net.Http.HttpRequestMessage
    $Request.Method = $method
    $Request.Headers.Add('X-Vault-Token', $headers['X-Vault-Token'])
    $Request.Headers.Add('ContentType', $headers['Content-type'])
    $Request.RequestUri = $Uri

    $Result = $Client.SendAsync($Request)
    $StatusCode = $Result.Result.StatusCode
    if ($StatusCode -eq "OK") {
        $Result.Result.Content.ReadAsStringAsync().Result | ConvertFrom-Json
    } else {
        Throw "$statuscode for $method on $uri"
    }
    $Client.Dispose()
    $Request.Dispose()
}
function Invoke-VaultAPIQuery {
    <#
    .SYNOPSIS
        Abstracts logic for which methods, and API calls should be done.
    #>

    [CmdletBinding()]
    param (
        [Parameter()]
        [string]$VaultName,
        [Parameter()]
        [string]$SecretName,
        [Parameter()]
        [object]$SecretValue,
        [Parameter()]
        [hashtable]$Metadata
    )
    try {
        $serverURI = $($script:VaultServer), $($script:VaultAPIVersion) -join '/'
        $baseURI = "$serverURI/$VaultName"
        $CallStack = (Get-PSCallStack)[1]
        $CallingCommand = $CallStack.Command
        $CallingVerb, $CallingNoun = ($CallingCommand -split '-')

        if ($script:KVVersion -eq 'v1') {
            $uri = "$baseURI/$SecretName"
            $listuri = "$baseURI/$SecretName"
        } elseif ($script:KVVersion -eq 'v2') {
            $uri = "$baseURI/data/$SecretName"
            $listuri = "$baseURI/metadata/$SecretName"
        }

        switch ($CallingVerb) {
            Get {
                $Method = 'GET'
                continue
            }
            Set {
                $Method = 'POST'
                if ($SecretName -match '/') {
                    $Name = $($SecretName -split '/')[-1]
                } else {
                    $Name = $SecretName
                }
                if ($SecretValue.GetType().Name -eq 'Hashtable') {
                    $Body = New-VaultAPIBody -data $SecretValue, $Metadata
                } else {
                    $Body = New-VaultAPIBody -data @{$Name = $SecretValue }, $Metadata
                }
                continue
            }
            Test {
                $method = 'GET'
                $uri = "$serverURI/sys/health", "$serverURI/sys/mounts"
                continue
            }
            Remove {
                $method = 'DELETE'
                # Deletes the latest secret version, but does not destory it.
                # KV version2 supports versions, which can't be implemented yet.
                # TODO provide a argument for type of action to take on KV v2
                $uri
                continue
            }
            Resolve {
                $method = 'LIST'
                $uri = $listuri
                continue
            }
        }

        $VaultSplat = @{
            URI     = $uri
            Method  = $Method
            Headers = New-VaultAPIHeader
        }
        if ($null -ne $body) { $VaultSplat['Body'] = $body }

        if ($method -eq 'List') {
            if($iscoreCRL) {
                $VaultSplat.Remove('Method')
                $VaultSplat['CustomMethod'] = 'LIST'
                Invoke-RestMethod @VaultSplat -ErrorVariable RestError
            } else {
                Invoke-CustomWebRequest @VaultSplat -ErrorVariable RestError
            }
        } elseif ($CallingVerb -eq 'Test') {
            foreach ($u in $uri) {
                $VaultSplat['URI'] = $u
                Invoke-RestMethod @VaultSplat -ErrorVariable RestError
            }
        } else {
            Invoke-RestMethod @VaultSplat -ErrorVariable RestError
        }
    } catch {
        Write-Error -Message "Received an error: $($RestError.message)"
    } finally {
        #Probably unecessary, but precautionary.
        $VaultSplat, $listuri, $uri, $Method, $Metadata, $Body = $null
    }
}
function Invoke-VaultToken {
    <#
    .SYNOPSIS
        Retrieves Token based on Supported Credential
    #>

    [CmdletBinding()]
    param (
        [Parameter()]
        [SecureString] $Password,
        [Parameter()]
        [string] $VaultName,
        [Parameter()]
        [hashtable] $AdditionalParameters
    )
    Test-VaultVariable -Arguments $AdditionalParameters
    Write-Verbose "Grabbing token for $VaultName"
    Write-Debug "Current TokenExpireTime: $($script:TokenExpireTime) and is a $(($script:TokenExpireTime).Gettype()) "
    Write-Debug "Is Token renewable? $($script:TokenRenewable)"
    Write-Debug "Token has a lifespan of $($script:TokenLifespan) seconds."
    Write-Debug "Token type is $($script:TokenType)"
    # Retrieve a token
    if ($Null -eq $script:VaultToken) {
        Write-Verbose "Retrieving a Token for authenticating to Vault"
        $RenewToken = $false
        #continue
    } elseif ($Null -ne $script:VaultToken -and $script:TokenExpireTime -lt (Get-date)) {
        # Retrieve a new token if expired
        Write-Verbose "Token Expired at $($script:TokenExpireTime). Retieving a new token"
        $script:VaultToken = $null
        $RenewToken = $false
        #continue
    } elseif ($Null -ne $script:VaultToken -and (New-TimeSpan -Start (Get-date) -End ($script:TokenExpireTime)).Minutes -le 1 -and $script:TokenRenewable) {
        # Renew a new token if about to expire
        Write-Verbose "Token about to Expire at $($script:TokenExpireTime). Renewing the token for $($script:TokenLifespan) seconds."
        $RenewToken = $true
        $script:VaultAuthType = 'RenewToken'
        #continue
    } elseif ($Null -ne $Password -and $Password -eq $script:VaultToken ) {
        Write-Verbose "Force renewing token."
        $RenewToken = $true
        $script:VaultAuthType = 'RenewToken'
    } else {
        Write-Verbose "Token is set to expire at: $($script:TokenExpireTime) and is of $($script:TokenType)"
        return
    }

    $AuthType = $script:VaultAuthType

    if ($Password -and $AuthType -ne 'Token' -and -not $RenewToken) {
        $Login = Read-Host -Prompt "What is the $(if($AuthType -eq 'Approle'){'Role-Id'} else {'Username'})?"
        $Credential = [System.Management.Automation.PSCredential]::new($Login, $Password)
    }
    switch ($script:VaultAuthType) {
        "AppRole" {
            if ( -not $Credential) {
                $Credential = Get-Credential -Message "Please Enter Role-Id and Secret-Id"
            }
            $UserName = $Credential.UserName
            #Following TryParse from https://stackoverflow.com/a/62416925
            $AppRoleResult = [System.Guid]::empty
            if (-not [System.Guid]::TryParse($UserName, [System.Management.Automation.PSReference]$AppRoleResult)) {
                throw "Approle Role-id must be a valid guid"
            }
            $UserLogin = "$($script:VaultServer)/$($script:VaultAPIVersion)/auth/approle/login"
            $UserPassword = "{`"role_id`":`"$UserName`",`"secret_id`":`"$($Credential.GetNetworkCredential().Password)`"}"
            continue
        }
        "LDAP" {
            if (-not $Credential) {
                $Credential = Get-Credential -Message "Please Enter LDAP credentials"
            }
            $UserName = $Credential.UserName
            $UserLogin = "$($script:VaultServer)/$($script:VaultAPIVersion)/auth/ldap/login/$UserName"
            $UserPassword = "{`"password`":`"$($Credential.GetNetworkCredential().Password)`"}"
            continue
        }
        "RenewToken" {
            $UserLogin = "$($script:VaultServer)/$($script:VaultAPIVersion)/auth/token/renew-self"
            $Headers = New-VaultAPIHeader
            continue
        }
        "Token" {
            if ($Password) {
                $script:VaultToken = $Password
            } else {
                $script:VaultToken = (Get-Credential -UserName Token -Message "Please Enter the token").Password
            }
            break
        }
        "userpass" {
            if (-not $Credential) {
                $Credential = Get-Credential -Message "Please Enter UserName and Password credentials"
            }
            $UserName = $Credential.UserName
            $UserLogin = "$($script:VaultServer)/$($script:VaultAPIVersion)/auth/userpass/login/$UserName"
            $UserPassword = "{`"password`":`"$($Credential.GetNetworkCredential().Password)`"}"
            continue
        }
        default {
            throw "This shouldn't be possible please create an issue on https://github.com/joshcorr/SecretManagement.Hashicorp.Vault.KV"
        }
    }
    try {
        if ($script:VaultAuthType -notin @('Token', 'RenewToken')) {
            $auth = (Invoke-RestMethod -Method POST -Uri $UserLogin -Body $UserPassword -ErrorVariable RestError)
            $auth_info = $auth.auth
            $script:VaultToken = $auth_info.client_token | ConvertTo-SecureString -AsPlainText -Force
        } elseif ($script:VaultAuthType -eq 'RenewToken') {
            $auth = (Invoke-RestMethod -Method POST -Uri $UserLogin -Headers $headers -ErrorVariable RestError)
            $auth_info = $auth.auth
            $script:VaultToken = $auth_info.client_token | ConvertTo-SecureString -AsPlainText -Force
        }

        #Lookup/test token
        $token_uri = "$($script:VaultServer)/$($script:VaultAPIVersion)/auth/token/lookup"
        $token_body = @{'token' = $([PSCredential]::new("token", $($script:VaultToken)).GetNetworkCredential().Password) } | ConvertTo-Json
        $Headers = New-VaultAPIHeader
        $token_info = (Invoke-RestMethod -Method POST -Uri $token_uri -Body $token_body -Headers $headers -ErrorVariable RestError)

        # Storing the information for checking before future calls.
        $script:TokenRenewable = $token_info.data.renewable
        $script:TokenType = $token_info.data.type
        $script:TokenLifespan = $token_info.data.ttl
        $script:TokenExpireTime = $token_info.data.expire_time
    } catch {
        if ($null -ne $RestError.message) {
            throw "Received an error: $($RestError.message)"
        } else {
            throw $PSItem
        }
    } finally {
        if ($RenewToken) {
            $script:VaultAuthType = $AuthType
        }
        $auth, $auth_info, $UserName, $UserPassword, $UserLogin, $Credential, $AppRoleResult, $Password, $Login, $token_body, $token_uri, $token_info, $headers = $null
    }
}
function New-Vault {
    <#
    .SYNOPSIS
        Creates a new vault
    #>

    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory)]
        [string] $VaultName,
        [Parameter(ValueFromPipelineByPropertyName)]
        [hashtable] $AdditionalParameters
    )
    process {
        try {
            $serverURI = $($script:VaultServer), $($script:VaultAPIVersion), 'sys/mounts', $VaultName -join '/'

            if ($script:KVVersion -eq 'v1') {
                $version = '1'
            } else {
                $version = '2'
            }
            $VaultSplat = @{
                URI     = $serverURI
                Method  = 'POST'
                Headers = New-VaultAPIHeader
            }
            $VaultOptions = @{
                type        = 'kv'
                description = $AdditionalParameters['Description']
                options     = @{
                    version = $version
                }
            }
            $body = $VaultOptions | ConvertTo-Json

            if ($null -ne $body) { $VaultSplat['Body'] = $body }
            Invoke-RestMethod @VaultSplat
        } catch {
            throw
        } finally {
            #Probably unecessary, but precautionary.
            $VaultSplat, $VaultOption, $listuri, $uri, $Method, $Body = $null
        }
    }
}
function New-VaultAPIBody {
    <#
    .SYNOPSIS
        Creates the Body of an API call for Set-Secret
    #>

    [CmdletBinding()]
    param (
        [Parameter()]
        [hashtable[]]$Data
    )
    try {
        $CombinedData = New-Object -TypeName System.Collections.Hashtable
        foreach ($ht in $Data.GetEnumerator()) {
            #Because of multiple Hashtables
            foreach ($d in $ht.GetEnumerator()) {
                if ($d.key -in $CombinedData.Keys ) {
                    Write-Verbose -Message "Key: '$($d.key)' already provided"
                }
                if ($d.key -in @('cas', 'checkandset')) {
                    $options = @{"cas" = [int]$d.value }
                }
                $CombinedData["$($d.key)"] = $d.value
            }
        }
        if ($script:KVVersion -eq 'v1') {
            $Tempbody = $CombinedData
        } elseif ($script:KVVersion -eq 'v2') {
            $Tempbody = @{
                data = $CombinedData
            }
            if ($null -ne $options) {
                $Tempbody['options'] = $options
            }
        }
        $OutputBody = $Tempbody | ConvertTo-Json -Depth 10
        return $OutputBody
    } catch {
        throw
    } finally {
        $CombinedData, $OutputBody, $Tempbody, $options, $ht, $data = $Null
    }
}
function New-VaultAPIHeader {
    <#
    .SYNOPSIS
        Creates a header for an API call
    .NOTES
        Token conversion From https://stackoverflow.com/a/57431985
    #>

    @{
        'Content-Type'  = 'application/json'
        'Accept'        = 'application/json'
        'X-Vault-Token' = $([PSCredential]::new("token", $($script:VaultToken)).GetNetworkCredential().Password)
    }
}
function Remove-Vault {
    <#
    .SYNOPSIS
        Removes a vault
    #>

    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory)]
        [string] $VaultName,
        [Parameter(ValueFromPipelineByPropertyName)]
        [hashtable] $AdditionalParameters
    )
    process {
        try {
            $serverURI = $($script:VaultServer), $($script:VaultAPIVersion), 'sys/mounts', $VaultName -join '/'
            Write-Verbose "Removing $VaultName. $AdditionalParameters['Description']"
            $VaultSplat = @{
                URI     = $serverURI
                Method  = 'DELETE'
                Headers = New-VaultAPIHeader
            }

            Invoke-RestMethod @VaultSplat
        } catch {
            throw
        } finally {
            #Probably unecessary, but precautionary.
            $VaultSplat, $serverURI = $null
        }
    }
}
function Resolve-VaultSecretPath {
    <#
    .SYNOPSIS
        Walks the Hashicorp KV strucutre to list secrets
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$VaultName,
        [Parameter()]
        [string]$Path
    )
    $Data = (Invoke-VaultAPIQuery -VaultName $VaultName -SecretName $Path).data
    foreach ($k in $data.Keys) {
        $KeyPath = $Path, $k -join '/'

        if ($KeyPath.endswith('/')) {
            $ResolveSplat = @{
                VaultName = $VaultName
                Path      = $keyPath.Trim('/')
            }
            Resolve-VaultSecretPath @ResolveSplat
        } else {
            $KeyPath.TrimStart('/')
        }
    }
}
function Test-VaultVariable {
    <#
    .SYNOPSIS
        Ensures that all Static Variables are configured
    #>

    [Cmdletbinding()]
    param (
        [Parameter()]
        [hashtable]$Arguments
    )
    foreach ($k in $Arguments.GetEnumerator()) {
        $key = $($k.Key)
        if ($key -notin $script:HashicorpVaultConfigValues) {
            Write-Warning -Message "$key not in accepted config values, skipping"
            continue
        }
        if ($key -eq 'VaultToken') {
            New-Variable -Name $key -Value $($k.Value | ConvertTo-SecureString) -Scope Script
            continue
        }
        if ($null -eq (Get-Variable -Name $key -ErrorAction SilentlyContinue) -or (Get-Variable -Name $key -ErrorAction SilentlyContinue) -ne $key ) {
            New-Variable -Name $key -Value $k.Value -Scope Script -Force
        }
    }
}
# Public functions
function Get-Secret {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [string] $Name,
        [Parameter(ValueFromPipelineByPropertyName)]
        [string] $VaultName,
        [Parameter(ValueFromPipelineByPropertyName)]
        [hashtable] $AdditionalParameters
    )
    process {
        $VerboseSplat = @{Verbose = $AdditionalParameters['Verbose']}
        $null = Test-SecretVault -VaultName $VaultName -AdditionalParameters $AdditionalParameters
        if ($Name -match '/') {
            $SecretName = $($Name -split '/')[-1]
        } else {
            $SecretName = $Name
        }
        $SecretData = Invoke-VaultAPIQuery -VaultName $VaultName -SecretName $Name @VerboseSplat
        #jscpd:ignore-start
        switch ($script:KVVersion) {
            'v1' {
                switch ($script:OutputType) {
                    'PSCredential' {
                        if ($SecretData.data.psobject.properties.Name -notcontains $SecretName) {
                            $Secret = $SecretData.data
                            $SecretObject = [PSCredential]::new($Name, ($Secret | ConvertTo-SecureString -AsPlainText -Force))
                        } else {
                            $Secret = $SecretData.data
                            $SecretObject = [PSCredential]::new($Name, ($Secret.$SecretName | ConvertTo-SecureString -AsPlainText -Force))
                        }
                        continue
                    }
                    'Hashtable' {
                        $Secret = $SecretData.data
                        $Hashtable = New-Object -TypeName System.Collections.Hashtable
                        $Secret.psobject.properties | ForEach-Object { $Hashtable[$PSItem.name] = $PSItem.value }
                        $SecretObject = $Hashtable
                        continue
                    }
                    default { throw "$($script:OutputType) OutputType not supported" }
                }
                continue
            }
            'v2' {
                switch ($script:OutputType) {
                    'PSCredential' {
                        if ($SecretData.data.data.psobject.properties.Name -notcontains $SecretName) {
                            $Secret = $SecretData.data.data
                            $SecretObject = [PSCredential]::new($Name, ($Secret | ConvertTo-SecureString -AsPlainText -Force))
                        } else {
                            $Secret = $SecretData.data.data
                            $SecretObject = [PSCredential]::new($Name, ($Secret.$SecretName | ConvertTo-SecureString -AsPlainText -Force))
                        }
                        continue
                    }
                    'Hashtable' {
                        $Secret = $SecretData.data.data
                        $Hashtable = New-Object -TypeName System.Collections.Hashtable
                        $Secret.psobject.properties | ForEach-Object { $Hashtable[$PSItem.name] = $PSItem.value }
                        $SecretObject = $Hashtable
                        continue
                    }
                    default { throw "$($script:OutputType) OutputType not supported" }
                }
                continue
            }
            default { throw "Unknown KeyVaule version" }
        }
        #jscpd:ignore-end
        return $SecretObject
    }
}
function Get-SecretInfo {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [string] $Filter,
        [Parameter(ValueFromPipelineByPropertyName)]
        [string] $VaultName,
        [Parameter(ValueFromPipelineByPropertyName)]
        [hashtable] $AdditionalParameters
    )
    process {
        $VerboseSplat = @{Verbose = $AdditionalParameters['Verbose']}
        $null = Test-SecretVault -VaultName $VaultName -AdditionalParameters $AdditionalParameters
        $Filter = "*$Filter"
        $VaultSecrets = Resolve-VaultSecretPath -VaultName $VaultName @VerboseSplat
        $VaultSecrets |
            Where-Object { $PSItem -like $Filter } |
            ForEach-Object {
            if ($script:KVVersion -eq 'v1') {
                $Metadata = $null
            } else {
                $vault_metadata = (Invoke-VaultAPIQuery -VaultName $VaultName -SecretName $PSItem @VerboseSplat).data.metadata
                $Metadata = New-Object -TypeName System.Collections.Hashtable
                $vault_metadata.psobject.properties | ForEach-Object { $Metadata[$PSItem.Name] = $PSItem.Value }
                $Dictonary = ConvertTo-ReadOnlyDictionary -hashtable $Metadata
            }
            [Microsoft.PowerShell.SecretManagement.SecretInformation]::new(
                "$PSItem",
                "String",
                $VaultName,
                $Dictonary)
        }
    }
}
function Remove-Secret {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [string] $Name,
        [Parameter(ValueFromPipelineByPropertyName)]
        [string] $VaultName,
        [Parameter(ValueFromPipelineByPropertyName)]
        [hashtable] $AdditionalParameters
    )
    process {
        $VerboseSplat = @{Verbose = $AdditionalParameters['Verbose']}
        $null = Test-SecretVault -VaultName $VaultName -AdditionalParameters $AdditionalParameters
        $SecretData = Invoke-VaultAPIQuery -VaultName $VaultName -SecretName $Name @VerboseSplat

        #$? represents the success/fail of the last execution
        if (-not $?) {
            throw $SecretData
        }
        return $?
    }
}
function Set-Secret {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [string] $Name,
        [Parameter(ValueFromPipelineByPropertyName)]
        [object] $Secret,
        [Parameter(ValueFromPipelineByPropertyName)]
        [string] $VaultName,
        [Parameter(ValueFromPipelineByPropertyName)]
        [hashtable] $AdditionalParameters,
        [Parameter(ValueFromPipelineByPropertyName)]
        [hashtable] $Metadata
    )
    process {
        $VerboseSplat = @{Verbose = $AdditionalParameters['Verbose']}
        $null = Test-SecretVault -VaultName $VaultName -AdditionalParameters $AdditionalParameters
        $type = $Secret.GetType()
        switch ($Secret.GetType()) {
            'byte' {
                $SecretValue = $Secret
                continue
            }
            'String' {
                $SecretValue = $Secret
                continue
            }
            'SecureString' {
                $SecretValue = $Secret | ConvertFrom-SecureString -AsPlainText
                continue
            }
            'PSCredential' {
                $SecretValue = $Secret.Password | ConvertFrom-SecureString -AsPlainText
                continue
            }
            'Hashtable' {
                $SecretValue = $Secret
                continue
            }
            default {
                throw "Unsupported secret type: $($Secret.GetType().Name)"
            }
        }
        Write-Verbose "Setting a secret with type: $type"
        $SecretData = Invoke-VaultAPIQuery -VaultName $VaultName -SecretName $Name -SecretValue $SecretValue -Metadata $Metadata @VerboseSplat

        #$? represents the success/fail of the last execution
        if (-not $?) {
            throw $SecretData
        }
        return $?
    }
}
function Test-SecretVault {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory)]
        [string] $VaultName,
        [Parameter(ValueFromPipelineByPropertyName)]
        [hashtable] $AdditionalParameters
    )
    process {
        $ErrorActionPreference = 'STOP'
        Test-VaultVariable -Arguments $AdditionalParameters

        # Ensure there is a VaultServer
        if ($null -eq $script:VaultServer) {
            $script:VaultServer = Read-Host -Prompt "Please provide the URL for the HashiCorp Vault (Example: https://myvault.domain.local)"
        }

        # Ensure an authtype is defined
        if ('None' -eq $script:VaultAuthType) {
            $script:VaultAuthType = Read-Host -Prompt "Please provide the AuthType for your HashiCorp Vault. Supported Types: $($script:HashicorpAuthTypes)"
        }

        # Unlock-SecretVault can safely handle ignoring existing tokens.
        Invoke-VaultToken -vaultName $VaultName -AdditionalParameters $AdditionalParameters

        if ($Null -eq $script:OutputType) {
            $script:OutputType = 'Hashtable'
            Write-Verbose "Setting Default Output Type to Hashtable"
        }

        #The rest runs provided the top 4 items are correct
        try {
            Write-Verbose -Message "Checking the health of $VaultName"
            $VaultHealth = (Invoke-VaultAPIQuery -VaultName $VaultName)
        } catch {
            $CheckError = $PSItem.Exception.Response.StatusCode.value__
            $URL = $PSItem.TargetObject.RequestURI.AbsoluteUri
            #Right from https://www.vaultproject.io/api-docs#http-status-codes
            Switch ($CheckError) {
                '400' { Write-Warning -Message "$URL; Invalid request, missing or invalid data" ; continue }
                '403' { Write-Warning -Message "$URL; Forbidden, your authentication details are either incorrect, you don't have access to this feature, or - if CORS is enabled - you made a cross-origin request from an origin that is not allowed to make such requests."; continue }
                '404' { Write-Warning -Message "$URL; Invalid path. This can both mean that the path truly doesn't exist or that you don't have permission to view a specific path."; continue }
                '429' { Write-Warning -Message "$URL; Default return code for health status of standby nodes"; continue }
                '473' { Write-Warning -Message "$URL; Default return code for health status of performance standby nodes"; continue }
                '500' { Write-Warning -Message "$URL; Internal server error. An internal error has occurred, try again later."; continue }
                '502' { Write-Warning -Message "$URL; A request to Vault required Vault making a request to a third party; the third party responded with an error of some kind."; continue }
                '503' { Write-Warning -Message "$URL; Vault is down for maintenance or is currently sealed. Try again later."; continue }
                default { throw "$URL; Something occured while communicating with $($script:VaultServer)" }
            }
        }
        if ($CheckError -notin @('403', '404')) {
            if ($VaultHealth[0].sealed -eq 'True') {
                Throw "The Hashicorp Vault at $($script:VaultServer) is sealed"
            }

            #This should return $null if the vault doesn't exist
            if ($VaultHealth[1].Gettype().Name -eq 'PSCustomObject' ) {
                #Some older version may not support this method
                $SelectedVault = $VaultHealth[1].$("$VaultName/")
            } else {
                $SelectedVault = $VaultHealth[1] -Match "$VaultName/"
            }
            if ($null -eq $SelectedVault) {
                #Create Vault if one specified doesn't exist
                $Response = Read-Host -Prompt "$VaultName does not exist on $($script:VaultServer). Attempt to create it? (Yes/No)"
                if ($Response -imatch '^Y$|^yes$') {
                    New-Vault -VaultName $VaultName -AdditionalParameters $AdditionalParameters
                }
            }
        }
        return $?
    }
}
function Unlock-SecretVault {
    <#
    .SYNOPSIS
        Retrieves Token based on Supported Credential
    #>

    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [SecureString] $Password,
        [Parameter(ValueFromPipelineByPropertyName)]
        [Alias('Name')]
        [Alias('Vault')]
        [string] $VaultName,
        [Parameter(ValueFromPipelineByPropertyName)]
        [hashtable] $AdditionalParameters
    )
    process {
        Invoke-VaultToken -Password $Password -VaultName $VaultName -AdditionalParameters $AdditionalParameters
    }
}
function Unregister-SecretVault {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [string] $VaultName,
        [Parameter(ValueFromPipelineByPropertyName)]
        [hashtable] $AdditionalParameters
    )
    process {
        $null = Test-SecretVault -VaultName $VaultName -AdditionalParameters $AdditionalParameters
        $Response = Read-Host -Prompt "Do you want to disable $VaultName on $($script:VaultServer) as well? (Yes/No) NOTE: This will remove all Secrets"
        if ($Response -imatch '^Y$|^yes$') {
            Write-Verbose "Disabling $VaultName on $($script:VaultServer)"
            Remove-Vault -VaultName $VaultName -AdditionalParameters $AdditionalParameters
        }
    }
}