A Sufficiently Viable Implementation (SVI) for Running Code Under The System Account on Nano Server

A Sufficiently Viable Implementation (SVI) for Running Code Under The System Account on Nano Server

In the world of computing it is the small changes that can cost you the most in the long run. One bright morning I decided I wanted to make the Chocolatey openssh package run under Nano Server (without Chocolatey). This led to a lot of code refactoring to account for the many little limiting differences of Nano. One of these differences ended up being the implementation of how to run some configuration code in the SYSTEM account context.

A Simplicity Manifesto

The installation instructions from the openssh project called for using psexec.exe to gain access to the system account context to run some code - no problem right?

Wrong - the initial psexec.exe stub only comes as a 32-bit EXE - mainly so it can run on the vast majority of 32-bit and 64-bit Windows editions available. However, Nano is native 64-bit and does not support the 32-bit subsystem (the WOW64 Subsystem). I don’t mean it’s not installed by default, I mean it simply does not support it at all.

To me, it is amazing how many times the answer to refactoring for problems like this has to do with not worrying about the esoteric “ideal” way to accomplish a task and go for what works.

I always try to have a secondary simplicity objective that, if possible, I don’t want special code branches for a specific platform (Nano in this case) in my deployment automation. If something works for Nano and can be used for other platform targets as well - that type of solution gets my attention quickly! For the Openssh package I have an aggressively broad OS target to support Windows 7 SP1 with PowerShell 2 through Nano Server with the least complex code. The reason for the aggressive low end specification (Windows 7 SP1 Out of the Box) is that much of my automation is leveraged in building OS stacks - so running into very hard minimum requirements tends to be the norm, not the exception.

A desire for simplicity also causes me to tack toward solutions that work without upgrading anything on a out of the box machine (no “runtime” requirements) - I find not following this objective can cause a cascade of challenges. For example - “The PowerShell CMDLets for scheduled tasks require PowerShell version 4. PowerShell Version 4 requires .NET version 4.5. Hmmm - does our company critical “Ancient Crusty Application” run on .NET 4.0?

When you play with baseline system runtimes just to get your minimum automation working, you frequently end up in the seat of pushing a huge load of changes that have to be tested all the way up the business application stack to be validated. I’d rather just use a shovel for a quick fix that be driving the bulldozer behind that mess!

Finally I tack away from solutions that are complex to a silly degree - you’ll see what I mean soon.

I also want to steer toward something that is an easy implementation for an automator - so that I can reuse the technique in the broadest number of future scenarios and so can others - for instance, perhaps I could call .NET classes in PowerShell to create a solution but then I am beholden to both of those technologies and i have to make sure that the .NET classes I use can be leveraged on all RTM enabled versions of .NET across my OS platform targets.

Finally, along the lines of re-usability I favor solutions that and supports the most automation languages and environments possible - again I favor this over idealogical convictions to a specific language.

Requirements and Desirements

Lets summarize these design requirements and desirements (with a few more of the general ones I always try to hit):

I use the word “desirements” to express implementation objectives or orientations that are held to whenever possible not no matter what.

  1. Works on Windows 7 RTM (PowerShell 2.0, .NET 2.0) through Nano Server (no 32-bit, .NET Core). [broadtargeting]
  2. Works on systems that do not have 32-bit support (Nano - but also server core without the optional WOW64 subsystem installed). [broad targeting]
  3. Works without requiring me to know or store passwords. [security][simplicity]
  4. Is a simple solution to understand, implement and reuse. [simplicity][reuse][code sharing]
  5. Does not invoke execution contexts that are hard to debug. [simplicity]
  6. Works in the most automation environments / languages. [broad targeting]
  7. Does not require upgrading anything in the automation stack when using out of the box OS builds. [simplicity][broad targeting]
  8. Does not require any special preparation to be used - this can enable direct download and use of automation. [simplicity]
  9. Does not require automation to bring along or install ancillary exes or software. [simplicity]
  10. Does not have to deal with writing temporary scripts to the hard drive. [security][simplicity]

Sufficiently Viable Implementation (SVI)

It’s interesting that Microsoft’s tag line for Nano Server is “Just Enough OS” - this reflects the current trend toward lean systems. In a similar way, the above list of requirements and desirements guides toward a “Sufficiently Viable Implementation” and away from hanging up on esoteric implementation ideals. Another way I like to think of this is to ensure that any “no matter what” convictions you operate against are truly lean toward adding practical value to your ultimate targeted purposes.

Minimum Viable Product (MVP) is a term that is thrown around a lot in the Lean / Agile startup world. It is used to indicate a release strategy that iterates through from a bare minimum level of functionality through to a delightful to use product - somewhere in between the product hits its Minimum Viable milestone. These iterations are intended as ever improving “proofs of concept” to gain the attention of stakeholders (e.g. customers) for feedback on whether the product is shaping up to be truly useful for their needs. Although it contains the term “viable”, it is still surrounded by the understanding that it will need revisions (potentially many) to suit stakeholders. In the context of MVP, it seems that “Viable” means “a customer could play with it to understand the major concepts involved”. The MVP is one-time milestone a product achieves - after which is becomes more than viable, it becomes downright usable.

As with all terminology “MVP” also gets broadened by common usage to encompass other ideas, like lean coding in general.

In automation development I have noticed that the code I work on is:

  1. Under continuous improvement…
  2. but also benefits dramatically from a continuous focus on minimal implementation changes

This creates the need to make each functionality milestone similar to the concept of MVP - however, unlike an MVP the scope can be very small - just a piece of code or a piece of a solution.

I have coined the term “Sufficiently Viable Implementation” to better handle the concept of maintaing a “lean code engineering” focus throughout the ongoing lifecycle of the code being managed and at every “scope of change”. Essentially the code goes through a cycle of SVIs, like this:

SVI #1 => SVI #2 => SVI #3

Sufficiently - indicates it is up to the designed-for task, but has maintained a focus on minimalizing around the target value add (lean engineering). At the same time “sufficiency” does not persue ‘simplicity’ for its own sake - which can become an ideological exercise in coding/design stoicism. “Sufficiency” seems to embody “Make it as simple as possible, but not simpler.”

Viable - means that you can actually use it to do real work (in this context).

Implementation - this means we are not talking about a product - but a potentially much, much smaller subset of design or implementation. Really it can apply to any scope or design or coding.

“Sufficiently Viable” attempts to nail Pareto’s law (aka “The 80/20 Rule”) in regard to the balance to get 80% of functionality with 20% of the complexity required for a full force solution.

This concept of Sufficiently Viable Implementation fascinates me because I have always been rewarded in spades when I adhere to it - solutions are more maintainable and many times can handle unforeseen use cases with little or no modification.

Another thing I like about the SVI concept is that it can be applied to all aspects of development - not just coding. I you build toolchains for others, it encourages you to keep the number of tools and interfaces to a minimum, which naturally facilitates adoption of the solution. When you design technology at the conceptual level - it guides away from too many ideological abstractions - which can keep things “sufficient to their intended purpose” Going to add a new parameter to a function - “what’s the SVI?”. Going to Going to build out an automation framework - “what’s the SVI?”

The Agile Automation Stack

Most importantly to me, I feel an SVI focus helps me keep automation stacks lean and flexible (agile). If my automation coding choices DO NOT drive higher versions of PowerShell, .NET, python and other runtimes that are highly shared with the business applications, it gives my automation that “runs anywhere” agility that is considered a hallmark of good implementations.

The SVI I Landed On for This Challenge

The implementation that I landed on that seems to meet most of the Requirements and Desirements outlined above is, (drum roll)…

Scheduled tasks configured using schtasks.exe.

Discarded Alternatives

Here are some of the alternatives I discarded and references to the above requirements/desirements that drove the discarding:

  • psexec.exe - causes friction with or violates: #2, #5, #9
  • PowerShell Scheduled Jobs (available in PSH 3) - causes friction with or violates: #1, #5, #6, #7
  • PowerShell Task Scheduler CMDLets (available in PSH 4) - causes friction with or violates: #1, #3, #4, #5, #6, #7
  • .NET Code For Launching in System Account Context - causes friction with or violates: #1, #4, #5, #6

By contrast, a scheduled task implemented using schtasks.exe meets the requirements and the vast majority of desirements.

The Code

When I say “Task Scheduler” your head may become dizzy with questions like: [a] How I am going to process and parse time values, [b] how do I know how long it will take for the job to run and [c] how will I know the job is done and [d] how to I clean up that task?

By maintaining the focus on sufficiency, it turns out there is a way to implement this where you do not even have to consider some of these problems, let’s look at the code:

FYI - my code is implemented in PowerShell but it can be very easily refactored into any automation language.

Write-Output "Installing Server Keys into SSH-Agent"

schtasks.exe /create /RU "NT AUTHORITY\SYSTEM" /RL HIGHEST /SC ONSTART /TN "ssh-add" /TR "'$TargetFolder\ssh-add.exe'  $fullpathkeylist" /F

schtasks.exe /Run /I /TN "ssh-add"

If (!([bool](schtasks /query /tn CreateExplorerShellUnelevatedTask /V /FO LIST | findstr "Result:" | findstr "0"))) {Throw "Opps, task errored with this exit code: $(schtasks /query /tn CreateExplorerShellUnelevatedTask /V /FO LIST | findstr "Result:")"}

schtasks.exe /Delete /TN "ssh-add" /F

The first call to schtasks.exe shows several novel techniques:

  1. How to schedule a task that uses admin privileges without needing an account and password (as long as the account running this command has admin). Footnote: Even if the PowerShell Scheduled Task CMDLets were available, I find their implementation cumbersomely verbose, for example [see here] (/post/continue-your-automation-to-run-once-after-restarting-a-headless-windows-system/#example-4-powershell-code-for-comparison-to-schtasks-exe)

  2. How to schedule a task for system context using functionality built into every version of Windows.

  3. How to schedule a task where you don’t have to muck about with parsing and calculating time values. By using “ONSTART” and then manually kicking off the task, we avoid time parsing and calculation.

The second call to schtasks.exe shows how to immediately trigger the task - so we don’t have to wait nor guess when it might complete.

The third line that calls schtasks.exe shows how to pull the exit code of the task. It is a little PowerShell intense - and not in my production code below. However, I did use “findstr” so that it is easier to port to other languages or you can get creative in your language of choice with its native string search functions or regular expressions.

The final call to schtasks.exe cleans up the task to leave things neat and tidy.

Works Everywhere / Reusable Technique

Due to it’s Sufficiently Viable Implementation - this code happens to work all the way back to Windows 7 with PowerShell 2.0 and .NET 2.0 - and most likely works farther back than that.

This Code In Production

When I have implemented code in production, I like to reference it because it gives you some confidence that the code is truly applicable in the contexts I claim to have designed it for: https://github.com/DarwinJS/ChocoPackages/blob/master/openssh/tools/chocolateyinstall.ps1#L642-L648