Author Posts

October 14, 2014 at 6:44 am

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

October 14, 2014 at 7:01 am

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.

October 14, 2014 at 8:16 am

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

Thanks,

John

October 14, 2014 at 8:22 am

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.)

October 14, 2014 at 8:23 am

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

October 14, 2014 at 10:54 am

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.