Wow! Four months since my last post. New city, new home, new job, new tiny human in less than a month, and thankfully, same Wegmans.
I lucked out on the job front. Found a fun team full of talented folks using modern tools, with a role focusing on Microsoft and related tech, in a predominantly *nix / OSS oriented environment. Plenty of room to learn and explore!
Of course, with everyone being well-versed in languages like bash and Python, and with most infrastructure residing on *nix systems using tools like Puppet for configuration, learning PowerShell hasn’t made it to their to-do list. Yet. Hopefully that changes - from my perspective, nothing comes close to PowerShell as an approachable, task-oriented glue language - imagine if it were open sourced and ported to other systems?
Until then, rather than miss out on the goodness PowerShell enables, we need to figure out an approach to invoke PowerShell from cross platform systems!
There are a number of approaches, each with benefits and drawbacks. At a high level, we could use…
- Remoting solutions like SSH or WinRM
- API oriented solutions like flancy, or something heavier like ASP.NET Web API
- Web based solutions, from tools like rundeck, to shoe-horning Jenkins, to WebCommander, to building your own ASP.NET / C# page
The other options are absolutely worth exploring, but a remoting solution would be very flexible with minimal overhead. So, how can we use WinRM or SSH to call PowerShell from non-Windows systems?
Wait, an SSH server on Windows? Do you really want to rely on some third party or outdated open source option?
Given that it’s so early, you should definitely consider testing this out and contributing feedback. You might help shape design decisions and improve the solution for you, and everyone else using it.
Getting Started with SSH on Windows
This is the easy part! Just follow the instructions here. At some point, this will hopefully receive CI/CD treatment to deliver stable and dev releases, perhaps to a package manager we can hit from OneGet.
If you’re reading this in early 2016, be sure to grab the right architecture; the link in the instructions will point you to the 32-bit release.
Running PowerShell from SSH
I ran into no issues getting up and running with non-interactive commands. Let’s look at a few examples:
# Run ipconfig ssh -l firstname.lastname@example.org ts1 ipconfig
# PowerShell isn't the default shell. Let's try it out. ssh -l email@example.com ts1 powershell.exe -command "get-service sshd"
# Double hop issue? Nope! ssh -l firstname.lastname@example.org ts1 powershell.exe -command "get-aduser wframe"
Nice! So, SSHD is working, we can run PowerShell.exe, and we don’t have to worry about the double-hop issue that you might be familiar with on the PowerShell remoting / Kerberos side!
How about something a little longer, with variables?
ssh -l email@example.com ts1 powershell.exe -noprofile -command " $Service = 'SSHD' Get-Service $Service "
Here’s where we run into our first issue. When calling PowerShell from bash or another interpreter, you need to be mindful of all the interpreters. In our case, bash helpfully replaced $service for us. After bash, presumably cmd.exe needs to be accounted for (i.e. we’re not dropped right into PowerShell), and then PowerShell itself. Maybe you have a snippet that needs double quotes: have fun!
Ideally, you manage Windows systems from Windows systems that are already running PowerShell, and avoid the mess, but you can’t always make that call.
In this example, we can simply escape the $:
ssh -l firstname.lastname@example.org ts1 powershell.exe -noprofile -command " \$Service = 'SSHD' Get-Service \$Service "
Now, you could always abstract this into a shell script, perhaps using expect to handle the password, but ideally we could wrap this in Python or Ruby. I had no luck getting this to work in Paramiko or it’s abstraction, spur, but feel free to give it a try, and be sure to let others know if you have any luck!
That’s about it - let’s move on and look at using WinRM.
WinRb / WinRM
There are a number of ways to invoke PowerShell over WinRM from other languages. For many, the Python and Ruby modules will likely be your first stop.
We’re going to skip the Python pywinrm module, given that it requires either plaintext auth, Kerberos, or fun with SSL. If you already use Kerberos or have a PKI infrastructure in place, you could safely use this. Otherwise, you’ll find a number of guides for pywinrm, WinRb, and more, instructing you to configure WinRM to enable basic auth and AllowUnencrypted. Don’t.
We’ll use the Ruby module for WinRM. Install Ruby on your system,
gem install -r winrm, and away we go!
Using WinRM From Ruby
We’ll assume you already have have PowerShell remoting enabled and accessible from another Windows system. Thanks to Dan Wanek and Matt Wrock, we can now use NTLM authentication, which doesn’t come with the dependencies or complexities of Kerberos or a PKI.
Security note: Do consider using SSL and/or a more modern authentication mechanism like Kerberos, but for quick and dirty testing, NTLM is a far safer bet than basic auth with AllowUnencrypted. Would love to hear some real security folks weigh in on the current method…
Let’s get started!
Here’s the gist of what I might run. More examples abound, just keep in mind we’re using create_executor’s run_powershell_script method, not the deprecated alternatives.
#!/usr/local/bin/ruby require 'winrm' winrm = WinRM::WinRMWebService.new('http://ts1:5985/wsman', :negotiate, :user => 'wframe', :pass => 'my password') winrm.create_executor do |executor| executor.run_powershell_script("get-service 'sshd'") do |stdout, stderr| STDOUT.print stdout STDERR.print stderr end end
This is my first time touching Ruby outside of some sparing use with the GitHub pages setup behind this blog, but I figured it would be worth trying to abstract this into a simple, re-usable function. Cobbled this together from various stackoverflow queries; feedback or tips welcome.
Let’s kick the tires a bit:
# Double hop issue? Yep! ps-winrm.rb -u wframe -c "get-aduser wframe"
The error message is a bit garbled, but you can get the gist of it. While the underlying cause differs, we run into the double hop issue PowerShell remoting typically gives us.
Given that NTLM support was just implemented, and that a cursory search doesn’t turn up anything on CredSSP, it looks like the usual CredSSP workaround won’t help.
Security note: CredSSP will make a red team happy. Do consider the security implications, as well as the fact that if you’re using RDP, you’re already using CredSSP.
Another workaround that works here would be to live dangerously and hit a domain controller directly:
That’s about it! I have seen some odd issues with commands that use runspaces, but most every-day use cases seem to work.
Which Should I Use?
Both options have their strengths and weaknesses, this is really up to you. I personally prefer to use languages like Ruby or Python over bash, and SSHD is quite new, so I’ll likely lean towards WinRM in the interim. Once I can get Paramiko or spur working against Microsoft’s SSHD, I’ll likely switch over to that.
Looking ahead, SSH on Windows seems very promising, and should enable simpler integration with cross platform solutions.
The WinRM implementations are worth exploring and will continue improving, but will come with some limitations, and don’t seem to be receiving contributions from Microsoft (yet).
What’s on the Horizon?
A nice side-effect of Microsoft using GitHub is that we get a glimpse of what’s coming down the road.
Check out the Win32-OpenSSH issues - you’ll see some notes on current limitations and bugs, along with some interesting ideas and plans. Curious to see how they’ll implement key based authentication for domain accounts (issue 39).
Be sure to kick the wheels of Microsoft’s SSHD implementation. You can actually contribute, whether you submit a pull request, a thoughtful bug report, or an idea for a feature. It’s still quite early, so if you get in your ideas now, who knows, you might help improve this for everyone!