PSCredential Parameter Help

This topic contains 5 replies, has 3 voices, and was last updated by Profile photo of Matt McNabb Matt McNabb 2 years, 1 month ago.

  • Author
    Posts
  • #19670
    Profile photo of John Bruett
    John Bruett
    Participant

    So, i think that I am going crazy and hoping someone can point out my flaw. If you review the function below I am expecting to be able to pass a string value of domain\username to the Credential parameter function and have that convert the string into a PSCredential object similar to MS AD cmdlets. However that's not the case and I receive the conversion error listed. My expectations are set by this article on TechNet that states the following:

    (i)If a parameter accepts a PSCredential object, Windows PowerShell supports several types of input, such as the following:(/i)

    • (b)Empty (/b)If you supply no input to a mandatory –credential parameter, Windows PowerShell prompts you for the user name and password.
    • (b)String (/b)If you supply a string to the –credential parameter, Windows PowerShell treats it as a user name and prompts you for the password.
    • (b)Credential(/b) If you supply a credential object to the –credential parameter, Windows PowerShell accepts it as is.

    (b)Keep in mind I haven't actually finished the cmdlet but it should at least run as written with the correct input and switches.(/b)

    Function Set-DNSServers{
        [cmdletbinding(SupportsShouldProcess=$true)]
        PARAM(
            [Parameter(Mandatory=$true)]
            [ValidateNotNullOrEmpty()]
            [string[]]$Computers,
            [Parameter(Mandatory=$true)]
            [System.Management.Automation.PSCredential]$Credential
            )
    
        foreach($host in $computers){
            write-verbose "Gathering network adapters on $host"
            # get network adapaters from WMI
            $nics = Get-WmiObject -Class Win32_NetworkAdapterConfiguration -ComputerName $Computers -Credential $Credential | Select-Object -Property PSComputerName,DNSHostname,Description,IPAddress,IPEnabled,DNSServerSearchOrder
    
            # if verbose is enabled output your findings
            if($PSCmdlet.MyInvocation.BoundParameters['Verbose']){
                write-verbose "Found the following network adapters on $host"
                write-verbose ($nics | FT -AutoSize | Out-String)
            } # End IF
        } # end For $host/$computers
    } # End Function
    
    Set-DNSServers -Computers  -Credential  -Verbose -WhatIf
    
    Set-DNSServers : Cannot process argument transformation on parameter 'Credential'. Cannot convert the  value of type "System.String" to type "System.Management.Automation.PSCredential".
    At C:\jbtemp\PS\Scripts\Systems\Set-DNSServers.ps1:24 char:52
    + Set-DNSServers -Computers  -Credential  -Verbose -WhatIf
    +                                                    ~~~~~~~~~~~
        + CategoryInfo          : InvalidData: (:) [Set-DNSServers], ParameterBindingArgumentTransformationException
        + FullyQualifiedErrorId : ParameterArgumentTransformationError,Set-DNSServers
    
  • #19673
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    To get that behavior, there's a special attribute you have to apply to your parameter. Try this:

        [cmdletbinding(SupportsShouldProcess=$true)]
        PARAM(
            [Parameter(Mandatory=$true)]
            [ValidateNotNullOrEmpty()]
            [string[]]$Computers,
    
            [Parameter(Mandatory=$true)]
            [System.Management.Automation.PSCredential]
            [System.Management.Automation.CredentialAttribute()]
            $Credential
            )
    

    Note: The order seems to be important. In my testing, if I put the [CredentialAttribute()] before the [PSCredential] type literal, I would still get the same errors you reported. If the CredentialAttribute() was placed after everythign else, then it started to allow strings to be passed in. This may be a bug in PowerShell; I don't think the order of attributes should matter, but it's something to be aware of for now.

    Also, if you want, you can omit the word Attribute: [System.Management.Automation.Credential()] works fine as well.

  • #19674
    Profile photo of John Bruett
    John Bruett
    Participant

    Dave, you're a genius. How the heck did you ever find that. I've never even heard of the CredentialAttribute(), obviously 🙂

    Thanks,

    John

  • #19675
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    I ran this command to see how the cmdlets were doing it:

    Trace-Command ParameterBinding -PSHost { Get-Credential 'domain\user' }
    
    # Got this output (among other things):
    
    # DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args [Get-Credential]
    # DEBUG: ParameterBinding Information: 0 : BIND POSITIONAL cmd line args [Get-Credential]
    # DEBUG: ParameterBinding Information: 0 :     BIND arg [domain\user] to parameter [Credential]
    # DEBUG: ParameterBinding Information: 0 :         Executing DATA GENERATION metadata:
    # [System.Management.Automation.CredentialAttribute]
    

    That clued me into the CredentialAttribute class, which I think I may have heard about before, but had forgotten. Then I just wrote a couple of test functions using that attribute in my param block to see how it worked (and came across that odd "order matters" situation, which is apparently fixed in the PowerShell v5 preview.)

  • #19676
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    Trace-Command is your friend. You can learn all sorts of tricky things from it. 🙂

  • #19684
    Profile photo of Matt McNabb
    Matt McNabb
    Participant

    Awesome work Dave! I had a similar issue with credentials in functions and stumbled on the same answer in a blog post by Jeff Hicks. I use it like:

    [System.Management.Automation.Credential()]$Credential
    

    I used to exclude the type constraint and then do some logic like:

    if ($Credential -s [string])
    {
        $Credential = Get-Credential -Username $Credential -Message "Enter password for $Credential"
    }

    It's nice to find these little tricks. I'll have to check out Trace-Command in more detail to see what else I am missing.

You must be logged in to reply to this topic.