Cowboy in the desert.

Improving PowerShell compatibility

Octopus allows PowerShell scripts to be executed during a deployment. PowerShell is incredibly powerful and makes a great tool for deployment automation, so it's a feature we rely on heavily.

To invoke PowerShell scripts, we currently use the Runspace classes to host PowerShell inside the Octopus Tentacle process. We read the PowerShell script from a file into a string and execute it, as if you were typing it into a PowerShell console.

The bugs, oh the bugs...

By hosting PowerShell in our own process, there are some differences compared to when running scripts directly using PowerShell.exe. Since Octopus was first released we've been continually improving the level of compatibility, and we've got a heap of integration tests that we run across different configurations of Windows (2003, 2008 incl. R2 and 2012, x86 and x64). Most of the time, it just works. Except when it doesn't!

Almost every week we still get a bug report along the lines of:

I have a PowerShell script that runs fine when I run it from PowerShell.exe, but when I run it under Tentacle, I get...


While there are plenty of examples on "how to host PowerShell is a .NET application", I've never seen a definitive guide on "how to host a 100% PowerShell.exe-compatible 'everything just works!' host in a .NET application", and there's a big difference between those two. There are plenty of open source applications out there using pretty similar hosting code to us, but they all seem to suffer from these problems.

Process.Start to the rescue!

In an attempt to avoid seeing any more of these bug reports, from Octopus 1.4 onwards we'll be switching to a new PowerShell invocation model. We're just going to call PowerShell.exe and pipe the results.

If you're interested in knowing roughly how we'll call PowerShell, view this gist. It also has the benefit of being a hundred times simpler than our current model.

One of the problems that this raises is backwards compatibility and supporting existing scripts that could break under the new model.

Multiple PowerShell modes?

Originally, I was planning to support multiple PowerShell invocation modes, making it possible to choose whether to use the in-memory (current) vs. powershell.exe (new) mode. Users would get a choice when setting up a package or script step.

But that's all too complicated. Who wants to be choosing between four ways of invoking a PowerShell script? Do we want to be dealing with bug reports for four different PowerShell invocation mdoels? Besides, it turns out there's no advantage to hosting PowerShell in process anyway as far as Octopus is concerned. It's actually slower because we have to create and tear down an AppDomain. I simply can't find any reason to keep the existing model except legacy support.

Taking a step back, is it a good idea to encourage people to write scripts that only work when run under Tentacle, and don't work when run under PowerShell.exe? If "lock in" was our strategy then perhaps. But we built Octopus on standard platforms like NuGet and PowerShell precisely to avoid that.

What this means

The current plan is to:

  1. Make the new model the default model for calling PowerShell scripts (so it should "just work" for everyone) from 1.4
  2. Make the old model available by setting a special variable to request the "legacy" PowerShell mode, and then remove the option in a future release (probably 1.5 or 1.6)

I don't think there will be many cases where people rely on a behavior in our "legacy" (hosted) mode but if there is we'll support them for one or two more releases. Otherwise, people should prepare to move over to the new PowerShell model. In 99% of cases it will just work. In the 1% where it doesn't, it should be fixed anyway.