I have a simple script:
$ErrorPreference = 'Stop'
$user = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
if ($null -eq $user) {
throw 'User and password are required'
} else {
$password = (Get-Credential -credential $user).Password
if ($null -eq $password) {
throw 'Password is required'
} else {
$plainpassword = [System.Net.NetworkCredential]::new("", $password).Password
$service = Get-WMIObject -class Win32_Service -filter "name='myservice'"
if ($null -ne $service) {
[void]$service.change($null, $null, $null, $null, $null, $false, $user, $plainpassword)
} else {
throw 'Service was not installed properly'
}
}
}
and when I press "Cancel" in the dialog of credentials input, then the user and password must be $null
. So, I test them on $null
. But I get an error:
Get-Credential : Cannot bind argument to parameter 'Credential' because it is null.
At C:....ps1:6 char:45
$password = (Get-Credential -credential $user).Password
~~~~~
CategoryInfo : InvalidData: (:) [Get-Credential], ParameterBindingValidationException
FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.GetCredenti
alCommand
Password is required
At C:....ps1:8 char:9
throw 'Password is required'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CategoryInfo : OperationStopped: (Password is required:String) [], RuntimeException
FullyQualifiedErrorId : Password is required
and I see throw 'Password is required'
but at the same time I see:
Get-Credential : Cannot bind argument to parameter 'Credential' because it is null.
and I expect that if $user
is $null
then it should not happen and I would exit before this line even. Why this happen and how to handle this?
PS. It seems $ErrorPreference = 'Stop'
is redundant, just to be sure...
CodePudding user response:
tl;dr
# Get the current user's domain-qualified name, using whoami.exe for simplicity
# (same as [System.Security.Principal.WindowsIdentity]::GetCurrent().Name).
# (Alternatively, use "$env:USERDOMAIN\$env:USERNAME", or
# if the domain prefix isn't needed just $env:USERNAME)
$user = whoami
# Prompt for the current user's password.
# Note the use of -UserName rather than -Credential.
# -Message is then unfortunately *required* in *Windows PowerShell*,
# but no longer in *PowerShell (Core) 7 *
$cred = get-credential -UserName $user -Message 'Enter your credentials:'
# Abort, if the prompt was canceled (return value is $null)
# or an empty password was given (a [pscredential] instance was returned,
# but the length of its .Password property is 0).
if ($null -eq $cred -or $cred.Password.Length -eq 0) { throw "Password is required." }
# Get the plain-text password for the later WMI method call.
# !! THIS SHOULD GENERALLY BE AVOIDED.
$password = $cred.GetNetworkCredential().Password
# ... Make your WMI call
As for what you tried:
Get-Credential -credential $user
Get-Credential
's -Credential
parameter is a strange beast and best avoided, because it expects an argument that already is a [pscredential]
instance as its argument.
Passing just a user name to it, as you did, so as to pre-fill the username field, works in principle, but causes a (statement-terminating) error when you cancel the dialog, accompanied by a confusing error message:
Cannot bind argument to parameter 'Credential' because it is null
- this despite the fact that you clearly did pass an argument to-Credential
.The reason for the error is that passing a mere use name to
-Credential
itself triggers a credential prompt, due to the-Credential
parameter being decorated with theSystem.Management.Automation.CredentialAttribute
attribute, and this happens beforeGet-Credential
receives the return value from that prompt:If that prompt is canceled,
$null
is returned, and the attempt to then bind$null
to the-Credential
parameter triggers the error.Conversely, if you pass a preexisting
[pscredential]
instance, no prompt is displayed and the instance is simply passed through.
Use the -UserName
parameter instead (assuming you need to pre-fill a username in the prompt), which behaves more sensibly; unfortunately, for syntactic reasons, in Windows PowerShell you must then also use the -Message
parameter, as shown above; fortunately, this is no longer a requirement in PowerShell (Core) 7 :
If you cancel the dialog,
$null
is returned[1], and no error occurs.- NOTE: In PowerShell (Core) 7 ,
Get-Credential
no longer uses a GUI dialog to prompt, it prompts directly in the console window. You can no longer cancel such a prompt per se, but you can press Ctrl C, which automatically terminates the script as a whole, however.
- NOTE: In PowerShell (Core) 7 ,
Otherwise, a
[pscredential]
instance is returned, containing the username and the password as a[securestring]
.
[1] Technically, there is no output, which PowerShell signals with a special singleton sometimes called "AutomationNull" - see this answer. However, in expressions such as -eq
comparisons, it behave like $null
.
CodePudding user response:
Because that cmdlet has required params you have to wrap the call in a try catch block to catch that ParameterBindingException
$ErrorPreference = 'Stop'
$user = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
if ($null -eq $user) {
throw 'User and password are required'
} else {
try {
$cred = Get-Credential -Credential $user
$password = ($cred.getNetworkCredential()).Password
$service = Get-WMIObject -class Win32_Service -filter "name='myservice'"
if ($null -ne $service) {
[void]$service.change($null, $null, $null, $null, $null, $false, $user, $password)
} else {
throw 'Service was not installed properly'
}
}
catch [System.Management.Automation.ParameterBindingException]{
throw 'Password is required'
}
}