Search code examples
powershellinvoke-command

When using invoke-command with credentials, credential parameter not properly initialized


So I tried calling the following code with and without credentials.

$username = "user"
$password = "password"
$cred = new-object -argumentlist $username, $password

#Invoke-Command -ComputerName computer -FilePath "C:\Some\Path.ps1" -Credential $cred
Invoke-Command -ComputerName localhost -FilePath "C:\Some\Path.ps1"

after calling in cmd appeared the following lines:

cmdlet New-Object at command pipeline position 1
Supply values for the following parameters:
TypeName:

Since I do not know what this parameters are referring to I type a letter and then the following error occurs:

new-object : Cannot find type [;]: verify that the assembly containing this type is loaded.
At C:\Some\Path.ps1:8 char:9
+ $cred = new-object -argumentlist $username, $password
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidType: (:) [New-Object], PSArgumentException
    + FullyQualifiedErrorId : TypeNotFound,Microsoft.PowerShell.Commands.NewObjectCommand

[localhost] Connecting to remote server localhost failed with the following error message : Access is denied. For more
information, see the about_Remote_Troubleshooting Help topic.
    + CategoryInfo          : OpenError: (localhost:String) [], PSRemotingTransportException
    + FullyQualifiedErrorId : AccessDenied,PSSessionStateBroken

I have abs no idea what it means. Can somebody help me please?


Solution

  • Don't fret - PowerShell is a strangely complex language runtime, and the initial learning curve can be quite steep :)

    In .NET - the runtime framework on top of which PowerShell is built - all objects have a type - and so you need to specify the type of object you want when creating one.

    To find the appropriate type name, let's first take a look at the -Credential parameter to which you will be passing the object as an argument. We can use Get-Help <commandName> -Parameter <parameterName> to inspect the parameter definition:

    PS ~> Get-Help Invoke-Command -Parameter Credential
    
    -Credential <pscredential>
    
        Required?                    false
        Position?                    Named
        Accept pipeline input?       true (ByPropertyName)
        Parameter set name           ComputerName, Uri, FilePathComputerName, FilePathUri, VMId, VMName, FilePathVMId, FilePathVMName
        Aliases                      None
        Dynamic?                     false
    
    

    On the first line we see that it expects a value of type pscredential.

    For interactive scripts, the easiest way to construct a pscredential object is to use the Get-Credential command - it'll prompt you for a password and return a pscredential object you can then pass to Invoke-Command (and re-use later if desirable):

    PS ~> $credential = Get-Credential -Username $username # this will prompt you for a password
    Enter your credentials.
    Password for user asd: ********
    
    PS ~> Invoke-Command {...} -Credential $credential # now we can pass the credential object to the command parameter
    

    Assuming the target computers are all part of the same domain and the credentials you possess have access to connect to the WinRM endpoint on them, you can reuse the same credential object against them all:

    foreach ($computerName in $listOfComputerNames) {
      Invoke-Command -ComputerName $computerName -ScriptBlock { ... } -Credential $credential
    }
    

    ... or you can pass all the computer names to Invoke-Command in one go:

    Invoke-Command -ComputerName $listOfComputerNames -ScriptBlock { ... } -Credential $credential
    

    For unattended use however, you might still need to find a way to instantiate a credential object without Get-Credential.

    Let's try passing the correct type name to New-Object:

    PS ~> $cred = New-Object -TypeName pscredential -ArgumentList $username, $password
    
    New-Object : Cannot find an overload for "PSCredential" and the argument count: "2".
    At line:1 char:9
    + $cred = New-Object -TypeName pscredential -ArgumentList $username, $p ...
    +         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : InvalidOperation: (:) [New-Object], MethodException
        + FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand
    

    Oh no! The arguments passed to New-Object doesn't match the constructor parameters defined by the pscredential type.

    The easiest way to inspect all overload signatures for a given type's constructor is to invoke the static new method on the type, without passing an argument list:

    PS ~> [pscredential]::new
    
    OverloadDefinitions
    -------------------
    pscredential new(string userName, securestring password)
    pscredential new(psobject pso)
    

    The constructor with the signature pscredential new(string userName, securestring password) is probably exactly what we want - but it says it's expecting a securestring-type object rather than a regular string.

    To instantiate a [securestring] from plain text, we can use the ConvertTo-SecureString cmdlet:

    PS ~> $securePassword = $password |ConvertTo-SecureString -AsPlainText -Force
    PS ~> $credential = New-Object pscredential -ArgumentList $username, $securePassword
    PS ~> # yay, no more errors!
    Ps ~> Invoke-Command {...} -Credential $credential
    

    As an alternative to the New-Object command, you can also use the static ::new method we used to discover the constructor signatures earlier:

    PS ~> $credential = [pscredential]::new($username, $securePassword)