Another solution to multi-hop PowerShell remoting

I've discovered another solutions for the problem of two hops in the PowerShell remoting, that I haven't seen anywhere in my searches on the Internet. I'm sure that some people know about it but it doesn't seem to be widely known.

First, what's the problem: If you create a PowerShell remote session to another computer, and then in that session try to open the second remote session ("the second hop") to the third computer, it will fail. That's because the credentials received by the first session are somehow marked as stale and can't be used to create another session. It's supposed to increase the security, even though it looks like more pain that gain. My guess is that it's a limitation that follows from some internals of the Windows remoting, otherwise there would be some more straightforward way to get around it. But in reality the way around it is anything but straightforward, including such things as sending the password in text form.

The typical workaround that pops up everywhere is with the command Enable-WSManCredSSP, for example here. However that solution doesn't seem to work in the small private networks that have no domain and no DNS. Such as a private network you might create for the local VMs inside your Hyper-V. There is simply no way to enter the Fully Qualified Domain Name if there is no name to start with, if getting to the VM by address! And without FQDN it seems to have issues.

But I've stumbled upon another solution. I think this solution would work everywhere, and with any transport. It certainly works in the small private networks. It has its own security drawbacks, so it's up to you to judge, which drawbacks are worse in which situation.

Here is how it goes:

PowerShell has a concept of session configuration, like a profile. When you set up a session to a remote machine, you can tell it that you want to use such and such named configuration. The configuration has to be defined in advance on the remote machine. Here "in advance" is flexible: you can connect a session without a configuration first, create the configuration from it, disconnect, and then connect another session with the configuration you've just created.

The configuration sets all kinds of interesting properties for the session. You can get the list of existing configurations as:

Get-PSSessionConfiguration

It shows only a small subset of properties, to get all the properties use

Get-PSSessionConfiguration | % { gm -InputObject $_ }

And that's still not all, there are more properties that you can set with the Register-PSSessionConfiguration or Set-PSSessionConfiguration  but which for whatever reason don't show in Get.  If you plan to send any sizable amount of data to the remote machine (such as copy files through the remoting channel), MaximumReceivedDataSizePerCommandMB and MaximumReceivedObjectSizeMB are your friends, the defaults are just way too low (and seem to apply to the whole session, not just a single command).

I've noticed there the properties RunAsUser and RunAsPassword which looked interesting. As it happens, in Register-PSSessionConfiguration the switch that sets both of them is -RunAsCredential. They looked very much like the Set-User-Id (SetUID) idea on Unix, and that's exactly what they turned out to be: when you connect to the PowerShell remoting using a configuration with them, you connect as one user but then the commands run as another user. When you do that, you'd normally want to both limit what remote users are allowed to use this configuration (using the SDDL parameter) and what commands they are allowed to run (somehow with the PowerShell runspace configuration, haven't tried it myself yet). But what we'll be doing here is setting up a user to run as itself, so we don't have to care about security. When a configuration gets created, the default SDDL is limited to the user who created it (same one in our case), and obviously there is no need to limit the allowed commands for the same user.

What we're interested in here is the side effect: the RunAsUser re-authenticates the user locally from scratch, without carrying the stigma of the forbidden second hop. It's nice and clean and perfectly suitable for hopping away into another remote session.

So, here it goes, the How-To. The examples I'll show use the simple user name for the credential, and will prompt for the password in a dialog window. But you can also use a credential object with the password embedded in it. First open the trampoline session that will be used to create the configuration:

PS C:\tmp> Enter-PSSession -ComputerName 169.254.129.152 -Credential Administrator
[169.254.129.152]: PS C:\Users\Administrator\Documents>

Then define the configuration in it:

[169.254.129.152]: PS C:\Users\Administrator\Documents> Register-PSSessionConfiguration -Name zzz -RunAsCredential Administrator -MaximumReceivedDataSizePerCommandMB 1000 -MaximumReceivedObjectSizeMB 1000
WARNING: When RunAs is enabled in a Windows PowerShell session configuration, the Windows security model cannot enforce
 a security boundary between different user sessions that are created by using this endpoint. Verify that the Windows
PowerShell runspace configuration is restricted to only the necessary set of cmdlets and capabilities.
   WSManConfig: Microsoft.WSMan.Management\WSMan::localhost\Plugin
Type            Keys                                Name
----            ----                                ----
Container       {Name=zzz}                          zzz

An important warning: you might want to be careful with the parameter -Force. If the configuration with this name already exists, it will unregister it and then register a new one. Good by itself but in reality unregistering a session might mess it up, and the session will stop working until the service WinRM is restarted. Depending on the Windows version you have, Register-PSSessionConfiguration might be restarting that service automatically or might not.  If you need to change an existing configuration, use Set-PSSessionConfiguration -Force instead.

Another warning: on some builds of Windows 10 Preview I see the connection being dropped in the middle of the registration, so that in result the session gets registered but the RunAsCredential part not set. If you encounter this, connect again, and use Set-PSSessionConfiguration to add the RunAsCredential.

Now exit that trampoline session and create the new, real one:

 PS C:\tmp> Enter-PSSession -ComputerName 169.254.129.152 -Credential Administrator -ConfigurationName zzz
[169.254.129.152]: PS C:\Users\Administrator\Documents>

Unfortunately, the Enter-PSSession command has a check that forbids the recursive calls, so we can't just enter another session for the second hop. However we can create a second hop session and run the commands in it without entering:

[169.254.129.152]: PS C:\Users\Administrator\Documents> Invoke-Command -ComputerName 169.254.203.67 -Credential Administrator -ScriptBlock { "yeah!" }
yeah!
[169.254.129.152]: PS C:\Users\Administrator\Documents> $s = New-PSSession -ComputerName 169.254.203.67 -Credential Administrator
[169.254.129.152]: PS C:\Users\Administrator\Documents> Invoke-Command -Session $s -ScriptBlock { "yeah again!" }
yeah again!
[169.254.129.152]: PS C:\Users\Administrator\Documents>

We can do two hops directly as well:

[169.254.129.152]: PS C:\Users\Administrator\Documents> exit
PS C:\tmp> Invoke-Command -ComputerName 169.254.129.152 -Credential Administrator -ConfigurationName zzz -ScriptBlock { Invoke-Command -ComputerName 169.254.203.67 -Credential Administrator -ScriptBlock { "yeah!" } }
yeah!
PS C:\tmp>

In this last case the dialog window to enter the password will appear twice, for each connection hop.

Works awesomely! The only downside of this arrangement is that the account password gets stored in an encrypted secure string somewhere on the second machine. This might or might not be a problem for you. And of course if the password changes, you'd have to change it in the configuration with Set-PSSessionConfiguration.

Update: Nowadays CredSSP is easy to set up too.