How to use Invoke-Command to pass logged in credentials to the remote execution

This topic contains 8 replies, has 5 voices, and was last updated by  Bill Friedmann 5 months, 2 weeks ago.

Viewing 8 posts - 1 through 8 (of 8 total)
  • Author
    Posts
  • #37357
    Profile photo of Bill Friedmann
    Bill Friedmann
    Participant

    I want to use Invoke-Command to execute a script block on a remote computer, where the script block needs to access a network file share while executing on the server side. I have read articles that I need to include -Authentication Credssp to allow client side credentials to be passed to the server side, but I do not want to be prompted to enter client side credentials when I include the -Credential arg on the Invoke-Command. I would just like to make the Invoke-Command call passing the same credentials that the client side script is executing with. Web searches mention that I could save the user credentials in a file on the client side, and then read this file to create a PSCredentials object, but I do not want to have to remember to update this file when a password change occurs.

    Is there a way for Invoke-Command to just implicitly pass the client side credentials to the remote script block?

    Thanks.

    #37358
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    That's what it does if you don't use the -Credential parameter at all; you authenticate as yourself.

    #37371
    Profile photo of Bill Friedmann
    Bill Friedmann
    Participant

    When I leave off the -Credential arg, the Invoke-Command aborts with the error

    "Missing an argument for Parameter 'Credential'. Specify a parameter of type System.Management.Automation.PSCredential' and try again."

    The command line I used was:

    Invoke-Command -ComputerName $RemoteHost -ScriptBlock $RemoteScriptBlock -Authentication CredSSP

    I need the -Authentication CredSSP arg since the remote script block needs to use the client side credentials on the remote computer to access a network file share. If I add the -Credential $Username arg, the command is successful, but the -Credential arg prompts me for a password. I just want Invoke-Command to use the credentials of the user that is executing the Invoke-Command, implicitly.

    #37372
    Profile photo of Richard Diphoorn
    Richard Diphoorn
    Participant
    #37374
    Profile photo of Bill Friedmann
    Bill Friedmann
    Participant

    Yes, this is a double hop. I have followed all the steps needed for passing credentials to the remote computer, where the Invoke-Command is successful.

    The important point of this post is that I do not want to be prompted for credentials when the client side logic executes the Invoke-Command and also do not want to read credentials from a saved file. I just want the Invoke-Command to implicitly use the credentials of the user executing the command, and be successful with using the -Authentication CredSSP arg.

    Is there is a way to capture the credentials of the user executing a script and create a PSCredentials object containing that info, where the PSCredentials object could them be passed as the -Credentials $MyCred arg on Invoke-Command? That would seem to be a good workaround.

    #37383
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    "Is there is a way to capture the credentials of the user executing a script and create a PSCredentials object containing that info"

    Absolutely not. That would be quite a gaping security hole 🙂 If that capability existed, literally any malware that happened to execute on your system would be able to just take your password.

    I didn't realize that you are required to use the -Credential parameter when using -Authentication CredSSP, but if that's the way it is, then I guess that's what you'll have to work around. You might find the $PSDefaultParameterValues variable to be helpful in this regard, or a proxy function. I do something similar with my Nuget API key for the Publish-Module command; I have this in my PowerShell profile:

        function global:Publish-Module
        {
            [CmdletBinding(DefaultParameterSetName='ModuleNameParameterSet', SupportsShouldProcess = $true, ConfirmImpact='Medium', PositionalBinding=$false)]
            param(
                [Parameter(ParameterSetName='ModuleNameParameterSet', Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
                [ValidateNotNullOrEmpty()]
                [string]
                ${Name},
    
                [Parameter(ParameterSetName='ModulePathParameterSet', Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
                [ValidateNotNullOrEmpty()]
                [string]
                ${Path},
    
                [Parameter()]
                [ValidateNotNullOrEmpty()]
                [string]
                ${NuGetApiKey},
    
                [ValidateNotNullOrEmpty()]
                [string]
                ${Repository},
    
                [ValidateNotNullOrEmpty()]
                [string]
                ${ReleaseNotes},
    
                [ValidateNotNullOrEmpty()]
                [string[]]
                ${Tags},
    
                [ValidateNotNullOrEmpty()]
                [uri]
                ${LicenseUri},
    
                [ValidateNotNullOrEmpty()]
                [uri]
                ${IconUri},
    
                [ValidateNotNullOrEmpty()]
                [uri]
                ${ProjectUri}
            )
    
            begin
            {
                if (-not $PSBoundParameters.ContainsKey('NuGetApiKey'))
                {
                    $apiKeyFile = "$PSScriptRoot\PowerShellGetApiKey.encrypted.xml"
    
                    if ((Test-Path -Path $apiKeyFile) -and
                        (Get-Module ProtectedData -ListAvailable))
                    {
                        $apiKey = Import-Clixml -Path $apiKeyFile | Unprotect-Data
                        if ($null -ne $apiKey) { $PSBoundParameters['NuGetApiKey'] = $apiKey }
                    }
                }
    
                try {
                    $outBuffer = $null
                    if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
                    {
                        $PSBoundParameters['OutBuffer'] = 1
                    }
    
                    $wrappedCmd = Get-Command -Name PowerShellGet\Publish-Module -CommandType Function -ErrorAction Stop
                    $scriptCmd = { & $wrappedCmd @PSBoundParameters }
                    $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
                    $steppablePipeline.Begin($PSCmdlet)
                } catch {
                    throw
                }
            }
    
            process
            {
                try {
                    $steppablePipeline.Process($_)
                } catch {
                    throw
                }
            }
    
            end
            {
                try {
                    $steppablePipeline.End()
                } catch {
                    throw
                }
            }
        }
    

    Most of that is just the auto-generated proxy function for Publish-Module, except for this bit in the Begin block:

                if (-not $PSBoundParameters.ContainsKey('NuGetApiKey'))
                {
                    $apiKeyFile = "$PSScriptRoot\PowerShellGetApiKey.encrypted.xml"
    
                    if ((Test-Path -Path $apiKeyFile) -and
                        (Get-Module ProtectedData -ListAvailable))
                    {
                        $apiKey = Import-Clixml -Path $apiKeyFile | Unprotect-Data
                        if ($null -ne $apiKey) { $PSBoundParameters['NuGetApiKey'] = $apiKey }
                    }
                }
    

    I had previously saved my API key using the ProtectedData module (which is where the Unprotect-Data command comes from), and exported it to disk with Export-Clixml. This function just reversed that process: Import-Clixml and pipe to Unprotect-Data, assuming that I haven't already passed in an API key when calling the command.

    #37391
    Profile photo of Bill Friedmann
    Bill Friedmann
    Participant

    I see your example used the approach to save some protected data in an XML file.

    This approach is similar to other posts I have seen where one could save a user account password as a secure string in an XML file and then read/convert the secure string back into a PSCredentials object just before calling the Invoke-Command.

    Since my credentials are part of a domain where there is a password change required every 90 days, I did not want the maintenance chore to have to remember to re-create the XML credentials file on the password change day.

    The very fact that one can even use a file system file to save a secure string password and then read the file when the need for the credentials is required is no less secure than allowing credentials to be implicitly passed to the remote script block.

    If there is no other way for a script to determine the credentials it is executing under, and then use those credentials to pass to a remote script block, then I guess I will have to resort to saving the account password as a secure string in an XML file, and then remember to re-create the file when the password change occurs. I just thought I would bring this up in a technical forum to see if there is a better option.

    #37403
    Profile photo of Chris Salzgeber
    Chris Salzgeber
    Participant

    You need CredSSP.

Viewing 8 posts - 1 through 8 (of 8 total)

You must be logged in to reply to this topic.