PS.CredentialManager.psm1

<#
.SYNOPSIS
    Read a credential object from the credential store. If no valid credential is available it will
    prompt for the credential and store it in the credential store.
.DESCRIPTION
    This cmdlet can be used in scripts to avoid prompting for a credential everytime the script runs. This method
    is much safer then storing the credential in the script itself or using your own method. The Windows Data
    Protection API (DPAPI) is used to encrypt the password. The password can only be decrypted by the same
    user who encrypted it. Resetting your password will void the decryption key and make the credential unusable.
    As the encryption key is tied to the user, the credential store must be personal, which means a seperate
    store for each user. The default store is the directory '$Env:USERPROFILE\Credentials'.
.NOTES
    Author: Graeme Meyer

    Version History:
    1.2 - 2022-02-27 - Fixed name regex. Added "-" char at least.
    1.1 - 2020-09-02 - @GraemeMeyer forks - Graeme Meyer
        - Minor changes including relocating the credential store to the UserProfile to avoid problems with
        corporate OneDrives.
        - Creation of the .psd1 manifest in preparation for upload to the PowerShell Gallery.
        - Code formatting to align with my preferences.

    1.0 - 06-07-2016 - Initial release - Theo Hardendood, Metis IT B.V.
        - NOTE: This module was forked, modified and distributed with the kind permission of it's original author,
        Theo Hardendood.
.PARAMETER Name
    The name of the credential. Used for naming the files in the credential store.
    The name is not case sensitive. Two files will be used for each credential: '<Name>.username'
    and '<Name>.password'. Whitespace or special characters are not allowed.
.PARAMETER StorePath
    The path to the credential store. Default is '$Env:USERPROFILE\Credentials'. This must be a writeable
    directory that will be created if it does not exist.
.PARAMETER Credential
    Save the supplied credential in the credential store, overwriting an existing credential.
.PARAMETER UserName
    The user name used in the credential when prompting. This will only be used when asking for a new credential,
    and can be changed by the user.
.PARAMETER Message
    The message that appears in the credential prompt.
.PARAMETER DoNotPrompt
    Do not prompt for the credential if it cannot be found or read and throw an exception.
.PARAMETER Reset
    Reset credential by prompting for a new one.
.PARAMETER Delete
    Delete credential and do not prompt for a new one.
.EXAMPLE
    $cred = Get-StoredCredential -Name vCenter

    Read credential for vCenter and return as PSCredential object. The cmdlet will prompt for username and
    password if the credential cannot be read.
.EXAMPLE
    $cred = Get-StoredCredential -Name JustAName -UserName 'Administrator'

    If it must ask for a new credential, the user name field will be filled in as a suggestion.
.EXAMPLE
    $cred = Get-StoredCredential -Name JustAName -StorePath 'E:\Credentials\myname'

    Uses the file 'E:\Credentials\myname\JustAName.username' to store the user name and the file
    'E:\Credentials\myname\JustAName.password' to store the password.
.EXAMPLE
    Get-StoredCredential -Name JustAName -Credential $cred

    Store the credential $cred in the credential store. Use this method to store a credential if it is used
    in a script running under a service account and you cannot log in under that account. To make this work, create
    a script with the below contents (don't forget to use the correct UserName and Password) and run it under the
    service account. Make sure the path to the credential store is valid.

    $securePassword = ConvertTo-SecureString -String 'ThePassword' -AsPlainText -Force
    $cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'TheUserName', $securePassword
    Get-StoredCredential -Name JustAName -StorePath 'E:\Credentials\ServiceAccount' -Credential $cred

    Do not forget to overwrite or delete this script afterward, or your password is still exposed.
.EXAMPLE
    Get-StoredCredential -Name JustAName -Delete

    Delete the credential JustAName. If the -Delete parameter is used then no PSCredential object will be returned.
#>

function Get-StoredCredential {
    [Alias("Set-StoredCredential")]
    [Alias("New-StoredCredential")]
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $true, Position = 0, HelpMessage = "The name of the credential.")]
        [ValidateNotNullOrEmpty()]
        [string]$Name,
        [Parameter(Mandatory = $false, Position = 1, HelpMessage = "The path to the credential store.")]
        [string]$StorePath,
        [Parameter(Mandatory = $false, Position = 2, HelpMessage = "Save the supplied credential in the credential store, overwriting the existing credential.")]
        [PSCredential]$Credential,
        [Parameter(Mandatory = $false, Position = 3, HelpMessage = "The user name used in the credential when prompting.")]
        [string]$UserName,
        [Parameter(Mandatory = $false, Position = 4, HelpMessage = "The message that appears in the credential prompt.")]
        [string]$Message,
        [Parameter(Mandatory = $false, Position = 5, HelpMessage = "Do not prompt for the credential if it cannot be read and throw an exception.")]
        [Switch]$DoNotPrompt,
        [Parameter(Mandatory = $false, Position = 6, HelpMessage = "Reset credential by prompting for a new one.")]
        [Switch]$Reset,
        [Parameter(Mandatory = $false, Position = 7, HelpMessage = "Delete credential and do not prompt for a new one.")]
        [Switch]$Delete
    )

    begin {
    }

    process {
        $ErrorActionPreference = "Stop"
        try {
            if ($Name -notmatch "^^[\w-]+$") {
                throw "Name cannot contain whitespace or special characters."
            }
            if ([String]::IsNullOrEmpty($StorePath)) {
                $p_StorePath = $Env:USERPROFILE + "\Credentials"
            }
            else {
                $p_StorePath = $StorePath
            }
            if (-Not (Test-Path -Path $p_StorePath -PathType Container)) {
                New-Item -Path $p_StorePath -ItemType Directory | Out-Null
            }
            $p_UserNamePath = [String]::Format("{0}\{1}.username", $p_StorePath, $Name)
            $p_PasswordPath = [String]::Format("{0}\{1}.password", $p_StorePath, $Name)
            if ($Delete.IsPresent) {
                if (Test-Path -Path $p_UserNamePath -PathType Leaf) {
                    Remove-Item -Path $p_UserNamePath -Force
                }
                if (Test-Path -Path $p_PasswordPath -PathType Leaf) {
                    Remove-Item -Path $p_PasswordPath -Force
                }
                return
            }
            if ($Credential -ne $null) {
                $Credential.UserName | Out-File $p_UserNamePath -Force
                $Credential.Password | ConvertFrom-SecureString | Out-File $p_PasswordPath -Force
                return $Credential
            }
            try {
                if ($Reset.IsPresent) {
                    throw "Request new credential"
                }
                $p_UserName = Get-Content -Path $p_UserNamePath
                $p_Password = Get-Content -Path $p_PasswordPath | ConvertTo-SecureString
                $p_Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $p_UserName, $p_Password
                return $p_Credential
            }
            catch {
                if ($DoNotPrompt.IsPresent) {
                    throw "Cannot read credential, and prompting for a new credential is not allowed."
                }
                if ([String]::IsNullOrEmpty($Message)) {
                    $p_Message = "Please enter credential for $Name"
                }
                else {
                    $p_Message = $Message
                }
                $p_Args = @{}
                if ([String]::IsNullOrEmpty($UserName) -eq $false) {
                    $p_Args = @{ "UserName" = "$UserName" }
                }
                else {
                    if ([String]::IsNullOrEmpty($p_UserName) -eq $false) {
                        $p_Args = @{ "UserName" = "$p_UserName" }
                    }
                }
                $p_Credential = Get-Credential -Message $p_Message @p_Args
                if ($null -ne $p_Credential) {
                    $p_Credential.UserName | Out-File $p_UserNamePath -Force
                    $p_Credential.Password | ConvertFrom-SecureString | Out-File $p_PasswordPath -Force
                }
                return $p_Credential
            }
        }
        catch {
            Write-Error "Get-StoredCredential: $($_.Exception.Message)"
        }
    }
}