├── BetterCredentials.psd1 ├── BetterCredentials.psm1 ├── Connect-RemoteDesktop.ps1 ├── CredentialManagement.cs ├── LICENSE └── ReadMe.md /BetterCredentials.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | 3 | # Script module or binary module file associated with this manifest. 4 | RootModule = 'BetterCredentials.psm1' 5 | 6 | # Version number of this module. 7 | ModuleVersion = '4.5' 8 | 9 | # ID used to uniquely identify this module 10 | GUID = 'd63b6487-26db-49ca-b282-e69a256c23cc' 11 | 12 | # Author of this module 13 | Author = 'Joel Bennett' 14 | 15 | # Company or vendor of this module 16 | CompanyName = 'HuddledMasses.org' 17 | 18 | # Copyright statement for this module 19 | Copyright = '(c) 2014 Joel Bennett. All rights reserved.' 20 | 21 | # Description of the functionality provided by this module 22 | Description = 'A (compatible) major upgrade for Get-Credential, including support for storing credentials in Windows Credential Manager, and for specifying the full prompts when asking for credentials, etc.' 23 | 24 | # Minimum version of the Windows PowerShell engine required by this module 25 | # PowerShellVersion = '' 26 | 27 | # Name of the Windows PowerShell host required by this module 28 | # PowerShellHostName = '' 29 | 30 | # Minimum version of the Windows PowerShell host required by this module 31 | # PowerShellHostVersion = '' 32 | 33 | # Minimum version of Microsoft .NET Framework required by this module 34 | # DotNetFrameworkVersion = '' 35 | 36 | # Minimum version of the common language runtime (CLR) required by this module 37 | # CLRVersion = '' 38 | 39 | # Processor architecture (None, X86, Amd64) required by this module 40 | # ProcessorArchitecture = '' 41 | 42 | # Modules that must be imported into the global environment prior to importing this module 43 | # RequiredModules = @() 44 | 45 | # Assemblies that must be loaded prior to importing this module 46 | # RequiredAssemblies = @() 47 | 48 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 49 | # ScriptsToProcess = @() 50 | 51 | # Type files (.ps1xml) to be loaded when importing this module 52 | # TypesToProcess = @() 53 | 54 | # Format files (.ps1xml) to be loaded when importing this module 55 | # FormatsToProcess = @() 56 | 57 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 58 | # NestedModules = @() 59 | 60 | # Functions to export from this module 61 | FunctionsToExport = 'Get-Credential','Find-Credential','Set-Credential', 'Remove-Credential','Test-Credential' 62 | 63 | # Cmdlets to export from this module 64 | CmdletsToExport = @() 65 | 66 | # Variables to export from this module 67 | VariablesToExport = @() 68 | 69 | # Aliases to export from this module 70 | AliasesToExport = @('gcred', 'scred', 'rcred', 'tcred', 'fdcred') 71 | 72 | # DSC resources to export from this module 73 | # DscResourcesToExport = @() 74 | 75 | # List of all modules packaged with this module 76 | # ModuleList = @() 77 | 78 | # List of all files packaged with this module 79 | FileList = @('CredentialManagement.cs','BetterCredentials.psm1','BetterCredentials.psd1','about_bettercredentials.help.txt', 'LICENSE') 80 | 81 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 82 | PrivateData = @{ 83 | 84 | PSData = @{ 85 | # Tags applied to this module. These help with module discovery in online galleries. 86 | Tags = @('Credential','Get-Credential','Vault','Storage') 87 | 88 | # A URL to the license for this module. 89 | LicenseUri = 'http://opensource.org/licenses/MIT' 90 | 91 | # A URL to the main website for this project. 92 | ProjectUri = 'https://github.com/Jaykul/BetterCredentials' 93 | 94 | # A URL to an icon representing this module. 95 | # IconUri = '' 96 | 97 | # ReleaseNotes of this module 98 | ReleaseNotes = ' 99 | This release adds a lot of functionality to the module, allowing enumeration and deletion, etc. 100 | 101 | - Add Test-Credential for explicitly checking whether a credential is already stored 102 | - Add Set-Credential for explicitly storing or updating stored credentials 103 | - Add Remove-Credential for clearing stored credentials 104 | - Add Find-Credential to search stored credentials 105 | ' 106 | 107 | } # End of PSData hashtable 108 | 109 | } # End of PrivateData hashtable 110 | 111 | # HelpInfo URI of this module 112 | # HelpInfoURI = '' 113 | 114 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 115 | # DefaultCommandPrefix = '' 116 | 117 | } 118 | 119 | -------------------------------------------------------------------------------- /BetterCredentials.psm1: -------------------------------------------------------------------------------- 1 | ## Copyright (c) 2014, Joel Bennett 2 | ## Licensed under MIT license 3 | 4 | $ScriptRoot = Get-Variable PSScriptRoot -ErrorAction SilentlyContinue | ForEach-Object { $_.Value } 5 | if (!$ScriptRoot) { 6 | $ScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent 7 | } 8 | 9 | Add-Type -Path $ScriptRoot\CredentialManagement.cs 10 | 11 | ## Private Functions 12 | function DecodeSecureString { 13 | #.Synopsis 14 | # Decodes a SecureString to a String 15 | [CmdletBinding()] 16 | [OutputType("System.String")] 17 | param( 18 | # The SecureString to decode 19 | [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] 20 | [Alias("Password")] 21 | [SecureString]$secure 22 | ) 23 | end { 24 | if ($secure -eq $null) { 25 | return "" 26 | } 27 | $BSTR = [System.Runtime.InteropServices.marshal]::SecureStringToBSTR($secure) 28 | Write-Output [System.Runtime.InteropServices.marshal]::PtrToStringAuto($BSTR) 29 | [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR) 30 | } 31 | } 32 | 33 | function EncodeSecureString { 34 | #.Synopsis 35 | # Encodes a string as a SecureString (for this computer/user) 36 | [CmdletBinding()] 37 | [OutputType("System.Security.SecureString")] 38 | param( 39 | # The string to encode into a secure string 40 | [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] 41 | [String]$String 42 | ) 43 | end { 44 | [char[]]$Chars = $String.ToString().ToCharArray() 45 | $SecureString = New-Object System.Security.SecureString 46 | foreach ($c in $chars) { 47 | $SecureString.AppendChar($c) 48 | } 49 | $SecureString.MakeReadOnly(); 50 | Write-Output $SecureString 51 | } 52 | } 53 | 54 | ## Public Functions 55 | 56 | function Find-Credential { 57 | <# 58 | .SYNOPSIS 59 | Searches stored credentials 60 | 61 | .DESCRIPTION 62 | Find-Credential is a wrapper around CredEnumerate 63 | It allows you to retrieve some or all credentials from the Windows Credential Manager (Vault) 64 | 65 | .EXAMPLE 66 | Find-Credential 67 | 68 | Returns all the stored credentials for the user 69 | .EXAMPLE 70 | Find-Credential TERMSRV/* 71 | 72 | Returns all the credentials stored for windows' Remote Desktop client 73 | .EXAMPLE 74 | Find-Credential * 75 | 76 | Returns all the stored credentials for the user that were placed in the credential vault by BetterCredentials 77 | .EXAMPLE 78 | Find-Credential | Where UserName -match User@Example.org 79 | 80 | Filters credentials to find everything for a specific username 81 | #> 82 | [Alias('fdcred')] 83 | [CmdletBinding()] 84 | param( 85 | # A filter for the Target name. May contain an asterisk wildcard at the start OR at the end. 86 | [String]$Filter 87 | ) 88 | [CredentialManagement.Store]::Find($Filter) 89 | } 90 | 91 | function Test-Credential { 92 | <# 93 | .Synopsis 94 | Tests whether or not a credential with the given username exists in the credential vault. 95 | .Description 96 | The Test-Credential function returns a true value if a credential with the given username exists in your credential vault. If it does exist, you can use the Get-Credential function with the assurance that it will not prompt for credentials. 97 | 98 | Calling Test-Credential prior to Get-Credential prevents prompting the user for the credentials when the credential does not already exist in the credential store. This is useful for trapping errors in scripts that need to run unattended, and prevents Get-Credential from causing the execution of such scripts to hang. 99 | .Example 100 | Test-Credential UserName 101 | 102 | If you haven't stored the password for "UserName", Test-Credential returns a false value but does not prompt for the password. Otherwise it returns a true value. 103 | .Example 104 | Test-Credential UserName* 105 | 106 | A trailing asterisk is a wildcard character that matches zero or more characters at the end of the given user name. 107 | .Example 108 | Test-Credential * 109 | 110 | An asterisk alone as the value of the UserName parameter returns true if there are any credentials in the credential vault that were placed there by BetterCredentials. 111 | .Example 112 | Test-Credential '' 113 | 114 | An empty string value of the UserName parameter returns true if there are any credentials in the credential vault at all, whether or not placed there by BetterCredentials. 115 | .Notes 116 | History: 117 | v 4.4 Test-Credential added to BetterCredentials 118 | v 4.5 Changed to be based on Find 119 | #> 120 | [Alias('tcred')] 121 | [CmdletBinding()] 122 | [OutputType("System.Boolean")] 123 | param( 124 | [Parameter(Position = 1, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] 125 | [Alias("Credential")] 126 | [PSObject]$UserName 127 | ) 128 | process { 129 | if ( $UserName -is [System.Management.Automation.PSCredential]) { 130 | $target = $UserName.UserName 131 | } else { 132 | $target = $UserName.ToString() 133 | } 134 | return [CredentialManagement.Store]::Find($target).Count -gt 0 135 | } 136 | } 137 | 138 | function Set-Credential { 139 | <# 140 | .Synopsis 141 | Creates or updates a stored credential 142 | .Description 143 | Set-Credential is a wrapper around the Windows Authentication API's CredWrite 144 | It allows you to store a credential in Windows Credential Manager (Vault) with metadata attached 145 | .Example 146 | Set-Credential Jaykul@HuddledMasses.org 147 | 148 | .Link 149 | Find-Credential 150 | .Link 151 | Get-Credential 152 | .Link 153 | https://msdn.microsoft.com/en-us/library/windows/desktop/aa375187 154 | .Link 155 | https://msdn.microsoft.com/en-us/library/windows/desktop/aa374788 156 | #> 157 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification = "Let other people worry about that, this is a useful option")] 158 | [Alias('scred')] 159 | [CmdletBinding()] 160 | param( 161 | # A credential to store 162 | [PSCredential]$Credential, 163 | 164 | # The unique target string to identify the credential 165 | [string]$Target, 166 | 167 | # How to store the credential ("Generic" or "DomainPassword") 168 | [CredentialManagement.CredentialType]$Type = "Generic", 169 | 170 | # Where to store the credential ("Session", "LocalComputer", "Enterprise") 171 | [CredentialManagement.PersistanceType]$Persistence = "LocalComputer", 172 | 173 | # Some text to describe or further identify the credentials 174 | [Alias("Message")] 175 | [string]$Description 176 | ) 177 | 178 | # Weird validation rules: 179 | if ($Type -eq "DomainPassword") { 180 | if ($Target.Length -gt 337) { 181 | throw "Target name must be less than 337 characters long for domain credentials" 182 | } 183 | } 184 | 185 | if ($Credential.Password.Length -gt 256) { 186 | # Because it's stored as UTF-16 bytes with a max of 512 187 | throw "Credential Password cannot be more than 256 characters" 188 | } 189 | 190 | if ($Target) { 191 | $Credential | Add-Member NoteProperty Target $Target -Force 192 | } 193 | if ($Description) { 194 | $Credential | Add-Member NoteProperty Description $Description -Force 195 | } 196 | 197 | # If the user passed the value, or if it's not set 198 | if ($PSBoundParameters.ContainsKey("Type") -or !$Credential.Type) { 199 | $Credential | Add-Member NoteProperty Type $Type -Force 200 | } 201 | 202 | if ($PSBoundParameters.ContainsKey("Persistence") -or !$Credential.Persistence) { 203 | $Credential | Add-Member NoteProperty Persistence $Persistence 204 | } 205 | 206 | [CredentialManagement.Store]::Save($Credential) 207 | } 208 | 209 | function Remove-Credential { 210 | <# 211 | .SYNOPSIS 212 | Remove a credential from the Windows Credential Manager (Vault) 213 | 214 | .DESCRIPTION 215 | Removes the credential for the specified target 216 | #> 217 | [Alias('rcred')] 218 | [CmdletBinding()] 219 | param( 220 | [Parameter(ValueFromPipelineByPropertyName, ValueFromPipeline, Mandatory)] 221 | [string]$Target, 222 | 223 | # How to store the credential ("Generic" or "DomainPassword") 224 | [Parameter(ValueFromPipelineByPropertyName)] 225 | [CredentialManagement.CredentialType]$Type = "Generic" 226 | ) 227 | 228 | process { 229 | [CredentialManagement.Store]::Delete($Target, $Type, $false) 230 | } 231 | 232 | } 233 | 234 | function Get-Credential { 235 | <# 236 | .Synopsis 237 | Gets a credential object based on a user name and password. 238 | .Description 239 | The Get-Credential function creates a credential object for a specified username and password, with an optional domain. You can use the credential object in security operations. 240 | 241 | This function is an improvement over the default Get-Credential cmdlet in several ways: 242 | Obviously it accepts more parameters to customize the security prompt (including forcing the call through the console) 243 | It also supports storing and retrieving credentials in your Windows Credential Manager, but otherwise functions identically to the built-in command 244 | 245 | Whenever you pass a UserName as a parameter to Get-Credential, it will attempt to read the credential from your Vault. 246 | .Example 247 | Get-Credential UserName -store 248 | 249 | If you haven't stored the password for "UserName", you'll be prompted with the regular PowerShell credential prompt, otherwise it will read the stored password. 250 | In either case, it will store (update) the credentials in the Vault 251 | .Example 252 | $Cred = Get-Credential -user key -pass secret | Get-Credential -Store 253 | Get-Credential -user key | % { $_.GetNetworkCredential() } | fl * 254 | 255 | This example demonstrates the ability to pass passwords as a parameter. 256 | It also shows how to pass credentials in via the pipeline, and then to store and retrieve them 257 | NOTE: These passwords are stored in the Windows Credential Vault. You can review them in the Windows "Credential Manager" (they will show up prefixed with "WindowsPowerShell") 258 | .Example 259 | Get-Credential -inline 260 | 261 | Will prompt for credentials inline in the host instead of in a popup dialog 262 | .Notes 263 | History: 264 | v 4.5 Add Find-Credential, Set-Credential, Remove-Credential 265 | v 4.4 Add a Test-Credential 266 | v 4.3 Update module metadata and copyrights, etc. 267 | v 4.2 Provide -Force switch to force prompting instead of loading 268 | v 4.1 Modularize and Release 269 | v 4.0 Change -Store to save credentials in the Windows Credential Manager (Vault) 270 | v 3.0 Modularize so I can "Requires" it 271 | v 2.9 Reformat to my new coding style... 272 | v 2.8 Refactor EncodeSecureString (and add unused DecodeSecureString for completeness) 273 | NOTE these are not at all like the built-in ConvertFrom/ConvertTo-SecureString 274 | v 2.7 Fix double prompting issue when using -Inline 275 | Use full typename for PSCredential to maintain V2 support - Thanks Joe Hayes 276 | v 2.6 Put back support for passing in the domain when getting credentials without prompting 277 | v 2.5 Added examples for the help 278 | v 2.4 Fix a bug in -Store when the UserName isn't passed in as a parameter 279 | v 2.3 Add -Store switch and support putting credentials into the file system 280 | v 2.1 Fix the comment help and parameter names to agree with each other (whoops) 281 | v 2.0 Rewrite for v2 to replace the default Get-Credential 282 | v 1.2 Refactor ShellIds key out to a variable, and wrap lines a bit 283 | v 1.1 Add -Console switch and set registry values accordingly (ouch) 284 | v 1.0 Add Title, Description, Domain, and UserName options to the Get-Credential cmdlet 285 | #> 286 | [Alias('gcred')] 287 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password", Justification = "Let other people worry about that, this is a useful option")] 288 | [OutputType("System.Management.Automation.PSCredential")] 289 | [CmdletBinding(DefaultParameterSetName = "Prompted")] 290 | param( 291 | # A default user name for the credential prompt, or a pre-existing credential (would skip all prompting) 292 | [Parameter(ParameterSetName = "Prompted", Position = 1, Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] 293 | [Parameter(ParameterSetName = "Delete", Position = 1, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] 294 | [Parameter(ParameterSetName = "Promptless", Position = 1, Mandatory = $true)] 295 | [Alias("Credential")] 296 | [PSObject]$UserName = $null, 297 | 298 | # Allows you to override the default window title of the credential dialog/prompt 299 | # 300 | # You should use this to allow users to differentiate one credential prompt from another. In particular, if you're prompting for, say, Twitter credentials, you should put "Twitter" in the title somewhere. If you're prompting for domain credentials. Being specific not only helps users differentiate and know what credentials to provide, but also allows tools like KeePass to automatically determine it. 301 | [Parameter(ParameterSetName = "Prompted", Position = 2, Mandatory = $false)] 302 | [string]$Title = $null, 303 | 304 | # Allows you to override the text displayed inside the credential dialog/prompt. 305 | # Note: this is stored with the credentials as the "Description" 306 | # 307 | # You should use this to describe what the credentials are for. 308 | [Parameter(ParameterSetName = "Prompted", Position = 3, Mandatory = $false)] 309 | [Alias("Message")] 310 | [string]$Description = $null, 311 | 312 | # Specifies the default domain to use if the user doesn't provide one (by default, this is null) 313 | [Parameter(ParameterSetName = "Prompted", Mandatory = $false)] 314 | [Parameter(ParameterSetName = "Promptless", Mandatory = $false)] 315 | [string]$Domain = $null, 316 | 317 | # The Get-Credential cmdlet forces you to always return DOMAIN credentials (so even if the user provides just a plain user name, it prepends "\" to the user name). This switch allows you to override that behavior and allow generic credentials without any domain name or the leading "\". 318 | [Parameter(ParameterSetName = "Prompted", Mandatory = $false)] 319 | [Parameter(ParameterSetName = "Promptless", Mandatory = $false)] 320 | [switch]$GenericCredentials, 321 | 322 | # Forces the credential prompt to occur inline in the console/host using Read-Host -AsSecureString (not implemented properly in PowerShell ISE) 323 | [Parameter(ParameterSetName = "Prompted", Mandatory = $false)] 324 | [switch]$Inline, 325 | 326 | # Store the credential in the file system (overwriting existing credentials) 327 | # NOTE: These passwords are STORED ON DISK encrypted using Windows DPAPI 328 | # They are encrypted, but anyone with ACCESS TO YOUR LOGIN ACCOUNT can decrypt them 329 | [Parameter(ParameterSetName = "Prompted", Mandatory = $false)] 330 | [Parameter(ParameterSetName = "Promptless", Mandatory = $false)] 331 | [switch]$Store, 332 | 333 | # Remove stored credentials from the file system 334 | [Parameter(ParameterSetName = "Delete", Mandatory = $true)] 335 | [switch]$Delete, 336 | 337 | # Ignore stored credentials and re-prompt 338 | # Note: when combined with -Store this overwrites stored credentials 339 | [Alias("New")] 340 | [switch]$Force, 341 | 342 | # The password 343 | [Parameter(ParameterSetName = "Promptless", Mandatory = $true)] 344 | $Password 345 | ) 346 | process { 347 | Write-Verbose ($PSBoundParameters | Out-String) 348 | [Management.Automation.PSCredential]$Credential = $null 349 | if ( $UserName -is [System.Management.Automation.PSCredential]) { 350 | $Credential = $UserName 351 | } elseif (!$Force -and $UserName -ne $null) { 352 | $UserName = $UserName.ToString() 353 | if ($Domain) { 354 | if ($Delete) { 355 | [CredentialManagement.Store]::Delete("${Domain}\${UserName}") 356 | } else { 357 | $Credential = [CredentialManagement.Store]::Load("${Domain}\${UserName}") 358 | } 359 | } else { 360 | if ($Delete) { 361 | [CredentialManagement.Store]::Delete($UserName) 362 | } else { 363 | $Credential = [CredentialManagement.Store]::Load($UserName) 364 | } 365 | } 366 | } 367 | 368 | Write-Verbose "UserName: $(if($Credential){$Credential.UserName}else{$UserName})" 369 | if ($Password) { 370 | if ($Password -isnot [System.Security.SecureString]) { 371 | $Password = EncodeSecureString $Password 372 | } 373 | Write-Verbose "Creating credential from inline Password" 374 | 375 | if ($Domain) { 376 | $Cred = New-Object System.Management.Automation.PSCredential ${Domain}\${UserName}, ${Password} 377 | } else { 378 | $Cred = New-Object System.Management.Automation.PSCredential ${UserName}, ${Password} 379 | } 380 | if ($Credential) { 381 | $Credential | Get-Member -type NoteProperty | % { 382 | Add-Member -InputObject $Cred -MemberType NoteProperty -Name $_.Name -Value $Credential.($_.Name) 383 | } 384 | } 385 | $Credential = $Cred 386 | } 387 | 388 | Write-Verbose "Password: $(if($Credential){$Credential.Password}else{$Password})" 389 | if (!$Credential) { 390 | Write-Verbose "Prompting for credential" 391 | if ($Inline) { 392 | if ($Title) { 393 | Write-Host $Title 394 | } 395 | if ($Description) { 396 | Write-Host $Description 397 | } 398 | if ($Domain) { 399 | if ($UserName -and $UserName -notmatch "[@\\]") { 400 | $UserName = "${Domain}\${UserName}" 401 | } 402 | } 403 | if (!$UserName) { 404 | $UserName = Read-Host "User" 405 | if (($Domain -OR !$GenericCredentials) -and $UserName -notmatch "[@\\]") { 406 | $UserName = "${Domain}\${UserName}" 407 | } 408 | } 409 | Write-Verbose "Generating Credential with Read-Host -AsSecureString" 410 | $Credential = New-Object System.Management.Automation.PSCredential $UserName, $(Read-Host "Password for user $UserName" -AsSecureString) 411 | } else { 412 | if ($GenericCredentials) { 413 | $Type = "Generic" 414 | } else { 415 | $Type = "Domain" 416 | } 417 | 418 | ## Now call the Host.UI method ... if they don't have one, we'll die, yay. 419 | ## BugBug? PowerShell.exe (v2) disregards the last parameter 420 | Write-Debug "Generating Credential with Host.UI.PromptForCredential($Title, $Description, $UserName, $Domain, $Type, $Options)" 421 | $Options = if ($UserName) { 422 | "ReadOnlyUserName" 423 | } else { 424 | "Default" 425 | } 426 | $Credential = $Host.UI.PromptForCredential($Title, $Description, $UserName, $Domain, $Type, $Options) 427 | } 428 | } 429 | 430 | if ($Store) { 431 | if ($Description) { 432 | Add-Member -InputObject $Credential -MemberType NoteProperty -Name Description -Value $Description 433 | } 434 | [CredentialManagement.Store]::Save($Credential) 435 | } 436 | 437 | # Make sure it's Generic 438 | if ($GenericCredentials -and $Credential.UserName.Contains("\")) { 439 | ${UserName} = @($Credential.UserName -Split "\\")[-1] 440 | $Cred = New-Object System.Management.Automation.PSCredential ${UserName}, $Credential.Password 441 | if ($Credential) { 442 | $Credential | Get-Member -type NoteProperty | % { 443 | Add-Member -InputObject $Cred -MemberType NoteProperty -Name $_.Name -Value $Credential.($_.Name) 444 | } 445 | } 446 | $Credential = $Cred 447 | } 448 | 449 | return $Credential 450 | } 451 | } 452 | 453 | Export-ModuleMember -Function *-Credential -Alias gcred, scred, rcred, tcred, fdcred 454 | -------------------------------------------------------------------------------- /Connect-RemoteDesktop.ps1: -------------------------------------------------------------------------------- 1 | #function Connect-RemoteDesktop { 2 | <# 3 | .SYNOPSIS 4 | Connect an RDP session with PSCredentials 5 | 6 | .DESCRIPTION 7 | Calls BetterCredentials\Set-Credential to store the credential 8 | in a way that RemoteDesktop will recognize 9 | 10 | .NOTES 11 | Inspired by Connect-Mstsc from Jaap Brasser http://www.jaapbrasser.com 12 | 13 | .EXAMPLE 14 | Connect-RemoteDesktop -ComputerName server01 15 | 16 | Creates a remote desktop session to server01 17 | 18 | .EXAMPLE 19 | $Cred = BetterCredentials\Get-Credential Jaykul@HuddledMasses.org -Store 20 | New-RemoteDesktop server01, server02 $Cred 21 | 22 | Creates an RDP session to each of server01 and server02, using the specified credentials 23 | 24 | .EXAMPLE 25 | Get-ADComputer -Filter { Name -like *SQL* } | New-RemoteDesktop -Credential Jayku@HuddledMasses.org -Width 1024 26 | 27 | Creates an RDP session to each server with a name that has "SQL" in it, using the stored credentials for Jaykul 28 | 29 | .EXAMPLE 30 | $Cred = BetterCredentials\Find-Credential -Target ContosoAzureRDP 31 | C:\PS> Get-AzureVM | Get-AzureEndPoint -Name 'Remote Desktop' | New-RemoteDesktop -ComputerName {$_.Vip,$_.Port -join ':'} -Credential $Cred 32 | 33 | First retrieves credentials for ContosoAzureRDP. You might have stored these previously by running a command like: 34 | 35 | C:\PS> BetterCredentials\Set-Credential -Target ContosoAzureRDP -Credential contoso\joel 36 | 37 | Then, starts an RDP session for each Azure Virtual Machine with those credentials 38 | #> 39 | [cmdletbinding(SupportsShouldProcess, DefaultParametersetName = 'UserPassword')] 40 | param ( 41 | # This can be a single computername or an array of computers to which RDP session will be opened 42 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)] 43 | [Alias('CN','IPAddress')] 44 | [string[]]$ComputerName, 45 | 46 | # The credential for the remote system 47 | [Parameter(ValueFromPipelineByPropertyName = $true, Position = 1)] 48 | [PSCredential]$Credential, 49 | 50 | # Sets the /admin switch on the mstsc command: Connects you to the session for administering a server 51 | [switch]$Admin, 52 | 53 | # Sets the /multimon switch on the mstsc command: Configures the Remote Desktop Services session monitor layout to be identical to the current client-side configuration 54 | [Parameter(ParameterSetName="MultiMonitorFullScreen")] 55 | [switch]$MultiMon, 56 | 57 | # Sets the /f switch on the mstsc command: Starts Remote Desktop in full-screen mode 58 | [Parameter(ParameterSetName="FullScreen")] 59 | [switch]$FullScreen, 60 | 61 | # Sets the /public switch on the mstsc command: Runs Remote Desktop in public mode 62 | [switch]$Public, 63 | 64 | # Sets the /w: parameter on the mstsc command: Specifies the width of the Remote Desktop window 65 | [Parameter(ParameterSetName="Size")] 66 | [Alias('W','X')] 67 | [int]$Width, 68 | 69 | # Sets the /h: parameter on the mstsc command: Specifies the height of the Remote Desktop window 70 | [Parameter(ParameterSetName="Size")] 71 | [Alias('H','Y')] 72 | [int]$Height, 73 | 74 | [switch]$Wait 75 | ) 76 | 77 | begin { 78 | 79 | [string]$MstscArguments = -join $( 80 | switch ($true) { 81 | {$Admin} { '/admin ' } 82 | {$MultiMon} { '/multimon ' } 83 | {$FullScreen} { '/f ' } 84 | {$Public} { '/public ' } 85 | {$Width} { "/w:$Width " } 86 | {$Height} { "/h:$Height " } 87 | } 88 | ) 89 | } 90 | process { 91 | foreach ($Computer in $ComputerName) { 92 | $ProcessInfo = New-Object System.Diagnostics.ProcessStartInfo 93 | $Process = New-Object System.Diagnostics.Process 94 | 95 | # Remove the port number for CmdKey otherwise credentials are not entered correctly 96 | if ($Computer.Contains(':')) { 97 | $ComputerCmdkey = ($Computer -split ':')[0] 98 | } else { 99 | $ComputerCmdkey = $Computer 100 | } 101 | 102 | Set-Credential -Target TERMSRV/$ComputerCmdkey -Credential $Credential -Type DomainPassword 103 | 104 | $ProcessInfo.FileName = "$($env:SystemRoot)\system32\mstsc.exe" 105 | $ProcessInfo.Arguments = "$MstscArguments /v $Computer" 106 | $ProcessInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Normal 107 | $Process.StartInfo = $ProcessInfo 108 | if ($PSCmdlet.ShouldProcess($Computer, 'Connecting mstsc')) { 109 | [void]$Process.Start() 110 | if ($Wait) { 111 | $null = $Process.WaitForExit() 112 | } 113 | } 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /CredentialManagement.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, Joel Bennett 2 | // Licensed under MIT license 3 | using System; 4 | using System.ComponentModel; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | using Microsoft.Win32.SafeHandles; 8 | using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME; 9 | 10 | namespace CredentialManagement 11 | { 12 | using System.Management.Automation; 13 | using System.Security; 14 | 15 | public enum CredentialType : uint 16 | { 17 | None = 0, 18 | Generic = 1, 19 | DomainPassword = 2, 20 | DomainCertificate = 3, 21 | DomainVisiblePassword = 4, 22 | GenericCertificate = 5, 23 | DomainExtended = 6, 24 | Maximum = 7, 25 | MaximumEx = 1007 26 | } 27 | 28 | public enum PersistanceType : uint 29 | { 30 | Session = 1, 31 | LocalComputer = 2, 32 | Enterprise = 3 33 | } 34 | 35 | public static class SecureStringHelper 36 | { 37 | // Methods 38 | public static SecureString CreateSecureString(string plainString) 39 | { 40 | var result = new SecureString(); 41 | if (!string.IsNullOrEmpty(plainString)) 42 | { 43 | foreach (var c in plainString.ToCharArray()) 44 | { 45 | result.AppendChar(c); 46 | } 47 | } 48 | result.MakeReadOnly(); 49 | return result; 50 | } 51 | 52 | public static SecureString CreateSecureString(IntPtr ptrToString, int length = 0) 53 | { 54 | string password = length > 0 55 | ? Marshal.PtrToStringUni(ptrToString, length) 56 | : Marshal.PtrToStringUni(ptrToString); 57 | return CreateSecureString(password); 58 | } 59 | 60 | public static string CreateString(SecureString secureString) 61 | { 62 | string str; 63 | IntPtr zero = IntPtr.Zero; 64 | if ((secureString == null) || (secureString.Length == 0)) 65 | { 66 | return string.Empty; 67 | } 68 | try 69 | { 70 | zero = Marshal.SecureStringToBSTR(secureString); 71 | str = Marshal.PtrToStringBSTR(zero); 72 | } 73 | finally 74 | { 75 | if (zero != IntPtr.Zero) 76 | { 77 | Marshal.ZeroFreeBSTR(zero); 78 | } 79 | } 80 | return str; 81 | } 82 | } 83 | 84 | public static class IntPtrHelpers 85 | { 86 | public static IntPtr Increment(this IntPtr ptr, int cbSize) 87 | { 88 | return new IntPtr(ptr.ToInt64() + cbSize); 89 | } 90 | 91 | public static IntPtr Increment(this IntPtr ptr) 92 | { 93 | return ptr.Increment(Marshal.SizeOf(typeof(T))); 94 | } 95 | 96 | public static T ElementAt(this IntPtr ptr, int index) 97 | { 98 | var offset = Marshal.SizeOf(typeof(T))*index; 99 | var offsetPtr = ptr.Increment(offset); 100 | return (T)Marshal.PtrToStructure(offsetPtr, typeof(T)); 101 | } 102 | } 103 | 104 | public static class Store 105 | { 106 | private static string FixTarget(string target) 107 | { 108 | if (!target.Contains(":")) 109 | { 110 | if (target.Contains("=")) 111 | { 112 | target = "MicrosoftPowerShell:" + target; 113 | } 114 | else 115 | { 116 | target = "MicrosoftPowerShell:user=" + target; 117 | } 118 | } 119 | return target; 120 | } 121 | 122 | public static PSObject[] Find(string filter = "", bool fix = true) 123 | { 124 | uint count = 0; 125 | int Flag = 0; 126 | IntPtr credentialArray = IntPtr.Zero; 127 | PSObject[] output = null; 128 | 129 | if(string.IsNullOrEmpty(filter)) { 130 | filter = null; 131 | Flag = 1; 132 | } else if(fix) { 133 | filter = FixTarget(filter); 134 | } 135 | 136 | NativeMethods.PSCredentialMarshaler helper = new NativeMethods.PSCredentialMarshaler(); 137 | 138 | if(NativeMethods.CredEnumerate(filter, Flag, out count, out credentialArray)) { 139 | IntPtr cred = IntPtr.Zero; 140 | output = new PSObject[count]; 141 | for (int n = 0; n < count; n++) 142 | { 143 | cred = credentialArray.ElementAt(n); 144 | output[n] = (PSObject)helper.MarshalNativeToManaged(cred); 145 | } 146 | helper.CleanUpNativeData(credentialArray); 147 | } else { 148 | int error = Marshal.GetLastWin32Error(); 149 | if( error != (int) NativeMethods.CREDErrorCodes.ERROR_NOT_FOUND ) { 150 | throw new Win32Exception(error); 151 | } 152 | } 153 | return output; 154 | } 155 | 156 | public static void Save(PSObject credential) 157 | { 158 | var cred = credential.BaseObject as PSCredential; 159 | if (cred == null) 160 | { 161 | throw new ArgumentException("Credential object does not contain a PSCredential", "credential"); 162 | } 163 | 164 | if (!NativeMethods.CredWrite(credential, 0)) 165 | { 166 | throw new Win32Exception(Marshal.GetLastWin32Error()); 167 | } 168 | } 169 | 170 | public static void Delete(string target, CredentialType type = CredentialType.Generic, bool fix = true) 171 | { 172 | if(fix) { 173 | target = FixTarget(target); 174 | } 175 | if (!NativeMethods.CredDelete(target, type, 0)) 176 | { 177 | throw new Win32Exception(Marshal.GetLastWin32Error()); 178 | } 179 | } 180 | 181 | public static void Get(string target, bool fix = true) 182 | { 183 | if(fix) { 184 | target = FixTarget(target); 185 | } 186 | PSObject cred; 187 | 188 | if (!NativeMethods.CredFindBestCredential(target, CredentialType.Generic, 0, out cred)) 189 | { 190 | throw new Win32Exception(Marshal.GetLastWin32Error()); 191 | } 192 | } 193 | 194 | 195 | public static PSObject Load(string target, CredentialType type = CredentialType.Generic, bool fix = true) 196 | { 197 | PSObject cred = null; 198 | if(fix) { 199 | target = FixTarget(target); 200 | } 201 | 202 | if(!NativeMethods.CredRead(target, type, 0, out cred)) { 203 | int error = Marshal.GetLastWin32Error(); 204 | if( error != (int) NativeMethods.CREDErrorCodes.ERROR_NOT_FOUND ) { 205 | throw new Win32Exception(error); 206 | } 207 | } 208 | 209 | return cred; 210 | } 211 | 212 | } 213 | 214 | 215 | public class NativeMethods 216 | { 217 | public enum CREDErrorCodes 218 | { 219 | NO_ERROR = 0, 220 | ERROR_NOT_FOUND = 1168, 221 | ERROR_NO_SUCH_LOGON_SESSION = 1312, 222 | ERROR_INVALID_PARAMETER = 87, 223 | ERROR_INVALID_FLAGS = 1004, 224 | ERROR_BAD_USERNAME = 2202, 225 | SCARD_E_NO_READERS_AVAILABLE = (int)(0x8010002E - 0x100000000), 226 | SCARD_E_NO_SMARTCARD = (int)(0x8010000C - 0x100000000), 227 | SCARD_W_REMOVED_CARD = (int)(0x80100069 - 0x100000000), 228 | SCARD_W_WRONG_CHV = (int)(0x8010006B - 0x100000000) 229 | } 230 | 231 | [DllImport("Advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)] 232 | public static extern bool CredRead(string target, CredentialType type, int reservedFlag, 233 | [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(PSCredentialMarshaler))] 234 | out PSObject credentialout); 235 | 236 | [DllImport("Advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true)] 237 | public static extern bool CredWrite([In] 238 | [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(PSCredentialMarshaler))] 239 | PSObject userCredential, [In] UInt32 flags); 240 | 241 | [DllImport("advapi32.dll", EntryPoint = "CredDeleteW", CharSet = CharSet.Unicode, SetLastError = true)] 242 | public static extern bool CredDelete(string target, CredentialType type, int flags); 243 | 244 | [DllImport("Advapi32.dll", EntryPoint = "CredFree", SetLastError = true)] 245 | public static extern bool CredFree([In] IntPtr cred); 246 | 247 | [DllImport("advapi32.dll", EntryPoint = "CredEnumerateW", CharSet = CharSet.Unicode, SetLastError = true)] 248 | public static extern bool CredEnumerate(string filter, int flag, out uint count, out IntPtr pCredentials); 249 | 250 | [DllImport("advapi32.dll", EntryPoint = "CredFindBestCredentialW", CharSet = CharSet.Unicode, SetLastError = true)] 251 | public static extern bool CredFindBestCredential(string target, CredentialType type, int reservedFlag, 252 | [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(PSCredentialMarshaler))] 253 | out PSObject credentialout); 254 | 255 | [DllImport("ole32.dll")] 256 | public static extern void CoTaskMemFree(IntPtr ptr); 257 | 258 | 259 | public class PSCredentialMarshaler : ICustomMarshaler 260 | { 261 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 262 | private class NATIVECREDENTIAL 263 | { 264 | public UInt32 Flags; 265 | public CredentialType Type = CredentialType.Generic; 266 | public string TargetName; 267 | public string Comment; 268 | public FILETIME LastWritten; 269 | public UInt32 CredentialBlobSize; 270 | public IntPtr CredentialBlob; 271 | public PersistanceType Persist = PersistanceType.Enterprise; 272 | public UInt32 AttributeCount; 273 | public IntPtr Attributes; 274 | public string TargetAlias; 275 | public string UserName; 276 | } 277 | 278 | public void CleanUpManagedData(object ManagedObj) 279 | { 280 | // Nothing to do since all data can be garbage collected. 281 | } 282 | 283 | public void CleanUpNativeData(IntPtr pNativeData) 284 | { 285 | if (pNativeData == IntPtr.Zero) 286 | { 287 | return; 288 | } 289 | CredFree(pNativeData); 290 | } 291 | 292 | public int GetNativeDataSize() 293 | { 294 | return Marshal.SizeOf(typeof(NATIVECREDENTIAL)); 295 | } 296 | 297 | public IntPtr MarshalManagedToNative(object obj) 298 | { 299 | PSCredential credential; 300 | PSObject credo = obj as PSObject; 301 | if (credo != null) 302 | { 303 | credential = credo.BaseObject as PSCredential; 304 | } 305 | else 306 | { 307 | credential = obj as PSCredential; 308 | } 309 | 310 | if (credential == null) 311 | { 312 | Console.WriteLine("Error: Can't convert!"); 313 | return IntPtr.Zero; 314 | } 315 | var nCred = new NATIVECREDENTIAL() 316 | { 317 | UserName = credential.UserName, 318 | CredentialBlob = Marshal.SecureStringToCoTaskMemUnicode(credential.Password), 319 | CredentialBlobSize = (uint)credential.Password.Length * 2, 320 | TargetName = "MicrosoftPowerShell:user=" + credential.UserName, 321 | Type = CredentialType.Generic, 322 | Persist = PersistanceType.Enterprise 323 | }; 324 | 325 | if (credo != null) 326 | { 327 | foreach (var m in credo.Members) 328 | { 329 | switch (m.Name) 330 | { 331 | case "Target": 332 | if (m.Value != null) 333 | nCred.TargetName = m.Value.ToString(); 334 | break; 335 | case "TargetAlias": 336 | if (m.Value != null) 337 | nCred.TargetAlias = m.Value.ToString(); 338 | break; 339 | case "Type": 340 | if (m.Value != null) 341 | nCred.Type = (CredentialType)m.Value; 342 | break; 343 | case "Persistence": 344 | if (m.Value != null) 345 | nCred.Persist = (PersistanceType)m.Value; 346 | break; 347 | case "Description": 348 | if (m.Value != null) 349 | nCred.Comment = m.Value.ToString(); 350 | break; 351 | case "LastWriteTime": 352 | // ignored 353 | break; 354 | } 355 | } 356 | } 357 | IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(nCred)); 358 | Marshal.StructureToPtr(nCred, ptr, false); 359 | return ptr; 360 | } 361 | 362 | public object MarshalNativeToManaged(IntPtr pNativeData) 363 | { 364 | if (pNativeData == IntPtr.Zero) 365 | { 366 | return null; 367 | } 368 | 369 | var ncred = (NATIVECREDENTIAL)Marshal.PtrToStructure(pNativeData, typeof(NATIVECREDENTIAL)); 370 | 371 | var securePass = (ncred.CredentialBlob == IntPtr.Zero) ? new SecureString() 372 | : SecureStringHelper.CreateSecureString(ncred.CredentialBlob, (int)(ncred.CredentialBlobSize)/2); 373 | 374 | var credEx = new PSObject(new PSCredential(ncred.UserName ?? "-", securePass)); 375 | 376 | credEx.Members.Add(new PSNoteProperty("Target", ncred.TargetName)); 377 | credEx.Members.Add(new PSNoteProperty("TargetAlias", ncred.TargetAlias)); 378 | credEx.Members.Add(new PSNoteProperty("Type", (CredentialType)ncred.Type)); 379 | credEx.Members.Add(new PSNoteProperty("Persistence", (PersistanceType)ncred.Persist)); 380 | credEx.Members.Add(new PSNoteProperty("Description", ncred.Comment)); 381 | credEx.Members.Add(new PSNoteProperty("LastWriteTime", DateTime.FromFileTime((((long)ncred.LastWritten.dwHighDateTime) << 32) + ncred.LastWritten.dwLowDateTime))); 382 | 383 | return credEx; 384 | } 385 | 386 | public static ICustomMarshaler GetInstance(string cookie) 387 | { 388 | return new PSCredentialMarshaler(); 389 | } 390 | } 391 | 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Joel Bennett 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | The Better Credentials Module 2 | ============================= 3 | 4 | The goal of BetterCredentials is to provide a completely backwards-compatible Get-Credential command that enhances the in-the-box Get-Credential by adding additional features which are missing from the built-in command. Specifically, storing credentials for automation, and providing more complete prompts with an explanation of what the credentials are for. 5 | 6 | TO INSTALL: 7 | =========== 8 | 9 | Use the PowerShellGet module included in WMF (PowerShell) 5, or the [PackageManagement Preview](http://www.microsoft.com/en-us/download/details.aspx?id=49186) for PowerShell 3 and 4. 10 | 11 | Just run: 12 | 13 | ```posh 14 | Install-Module BetterCredentials -AllowClobber 15 | ``` 16 | 17 | The `-AllowClobber` switch is to allow the BetterCredentials module to do what it's designed to do: provide you a better, backwards compatible `Get-Credential` command, clobbering the built-in version. 18 | 19 | Features 20 | ======== 21 | 22 | Prompting 23 | --------- 24 | 25 | The original motivation for writing BetterCredentials was to take advantage of some of the features of PowerShell's underlying credential API which are inexplicably ignored in the built-in `Get-Credential`, particularly to allow one-off prompting for passwords inline (that is, in the console, instead of via the credentials dialog), without having to resort to a configuration change. 26 | 27 | You can use the `-Inline` switch to force prompting in the host instead of with a popup dialog, or even pass in a `-Password` value (secure string or not, I won't judge) which allows you to easily create credential objects without a prompt at all. 28 | 29 | Additionally, you can set the `-Title` parameter to control the text that's show at the top of the prompt window, and even set the `-Description` parameter to add text in the prompt. 30 | 31 | 32 | Storage 33 | ------- 34 | 35 | Despite the fact that this feature arrived late in the life of BetterCredentials, clearly the best feature is the fact that it can store your passwords in the Windows Credential Manager (sometimes called the Vault), and retrive them on demand so you don't have to enter them over and over. The Windows Credential Manager is what's used by Internet Explorer and Remote Desktop to store passwords, and it keeps them safely encrypted to _your_ account and machine, and provides a user interface where they can be securely reviewed, deleted or even modified. 36 | 37 | On our BetterCredentials\Get-Credential command, the `-Store` switch causes the returned credentials to be stored in the vault, and the `-Delete` switch makes sure they are not. As of version 4.5, you can also use the `Set-Credential` and `Remove-Credental` commands to explicitly store or remove credentials. 38 | 39 | Once you've stored credentials in the vault, future requests for the same credential -- where you pass in a username (and optionally, a domain) will simply return the credentials without prompting. Because of this, there is also a `-Force` switch (alias: `-New`) which prevents loading and forces the prompt to be displayed. When you need to change a stored password, use both together: 40 | 41 | BetterCredentials\Get-Credential -Force -Store 42 | 43 | Additionally, in 4.5 there are two commands for searching and/or testing for credentials in the vault: `Find-Credential` and `Test-Credential`... 44 | 45 | 46 | Unattended Usage 47 | ---------------- 48 | 49 | When Get-Credential is called from a script running unattended, e.g. in a scheduled task, script execution will hang prompting for credentials if there is no credential in the vault corresponding to the given username. Normally one might execute `Get-Credential username -Store` to populate the credential vault prior to putting the scheduled task into production, but might also forget to do so. In version 4.5 the new `Test-Credential` command solves the script hanging problem by returning a true or false value depending on whether a credential corresponding to a user name is currently stored in the vault. 50 | 51 | ##### NOTES 52 | 53 | In my scripts and sample code, I nearly always use `BetterCredentials\Get-Credential` as a way to make sure that I'm invoking this overload of Get-Credential, but the idea is that you can simply import the BetterCredentials module in your profile and automatically get this overload whenever you're calling Get-Credential. Of course, I haven't (yet) overloaded the [Credential] transform attribute, so the automatic prompting when you pass a user name to a `-Credential` attribute doesn't use my module -- you have to explicitly call `Get-Credential`. 54 | 55 | Licensed under MIT license, see [License](LICENSE). 56 | --------------------------------------------------------------------------------