WindowsCredentialManager.psm1
using namespace AdysTech.CredentialManager using namespace System.ComponentModel $debugBinPath = Join-Path $PSScriptRoot 'bin/Debug/netstandard2.0/publish' if (Test-Path $debugBinPath) { Write-Warning "Debug build detected. Using assemblies at $debugBinPath" Add-Type -Path $debugBinPath/*.dll } else { Add-Type -Path $PSScriptRoot/*.dll } $ErrorActionPreference = 'Stop' $SCRIPT:DefaultNamespace = 'powershell' function Get-WinCredential { <# .SYNOPSIS Fetches a credential from the Windows Credential Manager. If you do not specify a name or target, it will fetch all credentials by default. #> [CmdletBinding(DefaultParameterSetName = 'Name')] [OutputType([PSCredential])] [OutputType([AdysTech.CredentialManager.ICredential])] param( #The name of the secret that you wish to fetch [Parameter(Position = 0, ParameterSetName = 'Name')][ValidateNotNullOrEmpty()][string]$Name, #The namespace for the name of the credential you wish to use. Defaults to "powershell" [Parameter(ParameterSetName = 'Name')][ValidateNotNullOrEmpty()][string]$Namespace = $DefaultNamespace, #The target you wish to fetch. Supports "Like" wildcard syntax [Parameter(ParameterSetName = 'Target', ValueFromPipeline)][ValidateNotNullOrEmpty()][string]$Target, #Retrieve the raw credential object. NOTE: May expose secrets as plaintext! [Switch]$Raw, #Retrieve all Windows Credentials, not just the ones in the powershell namespace [Switch]$All ) process { if ($Name) { $Target = Resolve-Target $Namespace $Name } #NOTE: Null will return nothing vs. the absence of a parameter which returns everything [ICredential[]]$credentials = if ($Target) { [CredentialManager]::EnumerateICredentials($Target) } else { $allCredentials = [CredentialManager]::EnumerateICredentials() if ($all) { $allCredentials } else { $allCredentials | Where-Object { $PSItem.TargetName.StartsWith($DefaultNamespace) } } } foreach ($cred in $credentials) { if ($Raw) { $cred } else { $cred | ConvertFrom-WinICredential } } } } function Save-WinCredential { <# .SYNOPSIS Sets a credential in the Windows Credential Manager. #> [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Credential')] param( #The credential that you want to store. [Parameter(Position = 0, Mandatory, ValueFromPipeline)][PSCredential]$Credential, #The name of the secret that you wish to set [Parameter(Position = 1, ParameterSetName = 'Credential')][ValidateNotNullOrEmpty()][string]$Name, #The namespace for the name of the credential you wish to use. Defaults to "powershell" [Parameter(ParameterSetName = 'Credential')][ValidateNotNullOrEmpty()][string]$Namespace = $DefaultNamespace, #The target you wish to set. If not specified, uses powershell:username as the target [Parameter(ParameterSetName = 'Target')][ValidateNotNullOrEmpty()][string]$Target, #Allow overwrite of existing credentials [Switch]$AllowClobber ) process { if ($Name) { $Target = Resolve-Target $Namespace $Name } if (-not $Target) { $Target = Resolve-Target $Namespace $Credential.UserName } if (-not $PSCmdlet.ShouldProcess($Target, "Save Credential [Username $($Credential.UserName)]")) { return } if ((Get-WinCredential -Target $Target) -and -not $AllowClobber) { Write-Error "Credential for target '$Target' already exists. Use -AllowClobber to overwrite." return } $result = [CredentialManager]::SaveCredentials($Target, $Credential.GetNetworkCredential(), [CredentialType]::Generic, $true) if (-not $result) { Write-Error "Failed to save credential for target '$Target'" return } Write-Verbose "Created Windows Credential with Target Name: $($result.TargetName)" } } function ConvertFrom-WinICredential { <# .SYNOPSIS Converts an ICredential object to a PSCredential object. #> [CmdletBinding()] [OutputType([PSCredential])] [OutputType([AdysTech.CredentialManager.ICredential])] param( #The ICredential object to convert [Parameter(Mandatory, ValueFromPipeline)][ValidateNotNullOrEmpty()][AdysTech.CredentialManager.ICredential]$ICredential ) process { $netCred = $ICredential.ToNetworkCredential() $Username = if ($netCred.UserName) { $netCred.UserName } else { '**UNSPECIFIED**' } [PSCredential]::new($Username, $netCred.SecurePassword) } } function Remove-WinCredential { <# .SYNOPSIS Removes a credential from the Windows Credential Manager. For safety, you must explicitly specify the name of the credential, you cannot pass a credential because the username may accidentally match something you didn't intend. #> [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Name', ConfirmImpact = 'High')] [OutputType([PSCredential])] [OutputType([AdysTech.CredentialManager.ICredential])] param( #The name of the secret that you wish to remove [Parameter(Mandatory, Position = 0, ParameterSetName = 'Name')][ValidateNotNullOrEmpty()][string]$Name, #The namespace for the name of the credential you wish to use. Defaults to "powershell" [Parameter(ParameterSetName = 'Name')][ValidateNotNullOrEmpty()][string]$Namespace = $DefaultNamespace, #The target you wish to remove. Supports "Like" wildcard syntax [Parameter(Mandatory, ParameterSetName = 'Target')][ValidateNotNullOrEmpty()][string]$Target ) process { if ($Name) { $Target = Resolve-Target $Namespace $Name } if (-not $PSCmdlet.ShouldProcess($Target, "Remove Credential [Username $($Credential.UserName)]")) { return } try { [bool]$result = [CredentialManager]::RemoveCredentials($Target, [CredentialType]::Generic) if (-not $result) { Write-Error "Failed to remove credential for target '$Target'" return } } catch { $innerException = $PSItem.Exception.InnerException $APIErrorCode = $innerException.ErrorCode $APIErrorMessage = ([Win32Exception]$apiErrorCode).Message $PSItem.ErrorDetails = switch ($APIErrorMessage) { 'Element not found.' { "The credential '$target' does not exist or could not be deleted." } default { "Failed to remove credential for target '$Target': $($InnerException.Message): $APIErrorMessage" } } $PSCmdlet.ThrowTerminatingError($PSItem) } Write-Verbose "Removed Windows Credential with Target: $target" } } function Resolve-Target ([ValidateNotNullOrEmpty()][string]$Namespace, [ValidateNotNullOrEmpty()][string]$Name) { $Namespace, $Name -join '/' } |