Home > Enterprise >  Is it correct error (it looks that throw does not work)?
Is it correct error (it looks that throw does not work)?

Time:10-29

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 the System.Management.Automation.CredentialAttribute attribute, and this happens before Get-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.
  • 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'
    }
}
  • Related