PSVault.psm1

# Override Write-Verbose in this module so calling function is added to the message
function script:Write-Verbose
{
    [CmdletBinding()]
    param
    (
       [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)] [String] $Message
    )

    begin
    {}

    process
    {
        try
        {
            $PSBoundParameters['Message'] = $((Get-PSCallStack)[1].Command) + ': ' + $PSBoundParameters['Message']
        }
        catch
        {}

        Microsoft.PowerShell.Utility\Write-Verbose @PSBoundParameters
    }

    end
    {}
}

function Get-VaultCredential
{
    <#
        .SYNOPSIS
            Get vault credential

        .DESCRIPTION
            Get vault credential

        .PARAMETER Name
            Name of credential entry

        .EXAMPLE
            Get-VaultCredential -Name cred1

        .EXAMPLE
            (Get-VaultCredential -Name cred2).GetNetworkCredential().Password
    #>


    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory=$true,  ValueFromPipelineByPropertyName=$true, Position = 0)]
        [string]
        $Name
    )

    begin
    {
        Write-Verbose -Message "Begin (ErrorActionPreference: $ErrorActionPreference)"
        $origErrorActionPreference = $ErrorActionPreference
        $verbose = $PSBoundParameters.ContainsKey('Verbose') -or ($VerbosePreference -ne 'SilentlyContinue')
    }

    process
    {
        Write-Verbose -Message "Process begin (ErrorActionPreference: $ErrorActionPreference)"

        try
        {
            # Make sure that we don't continue on error, and that we catches the error
            $ErrorActionPreference = 'Stop'

            $vault = New-Object -TypeName 'Windows.Security.Credentials.PasswordVault'

            $vaultcredential = $vault.FindAllByResource($Name)
            if ($vaultcredential.AdditionalTypeData)
            {
                # PS Core
                $vaultcredential = $vaultcredential.AdditionalTypeData.GetEnumerator() | Select-Object -First 1 -ExpandProperty Value | Select-Object -First 1
            }
            else
            {
                $vaultcredential = $vaultcredential | Select-Object -First 1
            }

            $null = $vaultcredential.RetrievePassword()
            $username = $vaultcredential.UserName
            $password = $vaultcredential.Password
            $credential = [pscredential]::new($username, ($password | ConvertTo-SecureString -AsPlainText -Force))

            # Return
            $credential
        }
        catch
        {
            Write-Verbose -Message "Encountered an error: $_"
            Write-Error -ErrorAction $origErrorActionPreference -Exception $_.Exception
        }
        finally
        {
            $ErrorActionPreference = $origErrorActionPreference
        }

        Write-Verbose -Message 'Process end'
    }

    end
    {
        Write-Verbose -Message 'End'
    }
}

function Set-VaultCredential
{
    <#
        .SYNOPSIS
            Set vault credential

        .DESCRIPTION
            Set vault credential

        .PARAMETER Name
            Name of credential entry

        .PARAMETER Credential
            Credential object with username/password to store in vault

        .PARAMETER Username
            Username to store in vault

        .PARAMETER Password
            Password to store in vault
            Either [string] or [securestring]

        .EXAMPLE
            Set-VaultCredential -Name cred1

        .EXAMPLE
            Set-VaultCredential -Name cred2 -Username userx -Password sEcReT
    #>


    [CmdletBinding(DefaultParameterSetName='PSCredential')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword','')]
    param
    (
        [Parameter(Mandatory=$true,  ValueFromPipelineByPropertyName=$true, Position = 0)]
        [string]
        $Name,

        [Parameter(Mandatory=$true,  ValueFromPipelineByPropertyName=$true, Position = 1, ParameterSetName='PSCredential')]
        [pscredential]
        $Credential,

        [Parameter(Mandatory=$true,  ValueFromPipelineByPropertyName=$true, ParameterSetName='UserPass')]
        [string]
        $Username,

        [Parameter(Mandatory=$true,  ValueFromPipelineByPropertyName=$true, ParameterSetName='UserPass')]
        [object]
        $Password
    )

    begin
    {
        Write-Verbose -Message "Begin (ErrorActionPreference: $ErrorActionPreference)"
        $origErrorActionPreference = $ErrorActionPreference
        $verbose = $PSBoundParameters.ContainsKey('Verbose') -or ($VerbosePreference -ne 'SilentlyContinue')
    }

    process
    {
        Write-Verbose -Message "Process begin (ErrorActionPreference: $ErrorActionPreference)"

        try
        {
            # Make sure that we don't continue on error, and that we catches the error
            $ErrorActionPreference = 'Stop'

            $vault = New-Object -TypeName 'Windows.Security.Credentials.PasswordVault'

            if ($Password -is [securestring])
            {
                $Credential = [pscredential]::new($Username, $Password)
            }

            if ($Credential)
            {
                $Username = $Credential.UserName
                $Password = $Credential.GetNetworkCredential().Password
            }
            else
            {
                $Password = [string] $Password
            }

            $vaultcredential = [Windows.Security.Credentials.PasswordCredential]::new($Name, $Username, $Password)
            $vault.Add($vaultcredential)
        }
        catch
        {
            Write-Verbose -Message "Encountered an error: $_"
            Write-Error -ErrorAction $origErrorActionPreference -Exception $_.Exception
        }
        finally
        {
            $ErrorActionPreference = $origErrorActionPreference
        }

        Write-Verbose -Message 'Process end'
    }

    end
    {
        Write-Verbose -Message 'End'
    }
}

Write-Verbose -Message "Loading assembly"
$origErrorActionPreference = $ErrorActionPreference
$ErrorActionPreference = 'Stop'
try
{
    $null = [Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials,ContentType=WindowsRuntime]
}
catch
{
    # PS Core
    $null = Add-Type -AssemblyName (Join-Path -Path $PSScriptRoot -ChildPath 'WinRT.Runtime.dll')
    $null = Add-Type -AssemblyName (Join-Path -Path $PSScriptRoot -ChildPath 'Microsoft.Windows.SDK.NET.dll')
}
finally
{
    $ErrorActionPreference = $origErrorActionPreference
}

Export-ModuleMember -Function Get-VaultCredential
Export-ModuleMember -Function Set-VaultCredential