Do developers really dislike PowerShell?

My friend Aleksandar Nikolić asked a question on twitter

Why do developers dislike #PowerShell?
Why is it hard to find a developer to work on PowerShell cmdlets? … Is it really that hard to support basic PowerShell concepts?

Which can’t be answered in a tweet. If you create non-PowerShell command lines programs in C# (or any .Net language), then I really encourage you to read to the end and not go off in a huff after you have read the following opinion about what you do.

The act of creating a non-trivial .NET command-line program, instead of one or more
PowerShell cmdlets demonstrates a lack of understanding of what the program is for.

Read it again.

And again.

I’ve also said that Microsoft (or any other large software company) should
“Treat shipping such .NET programs (or trying to) as inadequate job performance”.

This only applies to .NET – programs written in anything else can’t assume PowerShell, but these are provocative things to say to and about .NET programmers, so I had better explain and justify them

Snobbery and Gatekeeping.

In plenty of places I’ve met the attitude “What I do is the true form of the art”: in photography, “that is taking snaps” or in Scuba diving “that’s just swimming”, and it applies to “it’s only proper programming if…

When I learned to program, BASIC, C, FORTRAN, LISP, Pascal etc. were called “High level” languages (BASIC was still an acronym, Java hadn’t been thought of); “low-level” details of what CPUs do were left to those who wrote in assembly language and to writers of compilers / interpreters, nobody else cares. To at least some of those who write in those languages, running the resulting programs makes someone a “user”; and gluing programs together in batches doesn’t elevate users to programmer status. “User” is sneery - I was told once “Only drug pedlars and computer programmers call the people who pay them ‘users’”. Having written Assembly language and Batch files and in more high-level languages than I can remember, I have no time for anyone who says their gatekeeping criteria for programming excludes writing some sequences of instructions for a computer on grounds of the tool or language used to do it. And with PowerShell calling .NET APIs in a more streamlined way than C#, the boundaries such people might draw to exclude a .bat file get fuzzy.

PowerShell, particularly, attracts complaints about verbosity. Recently I’ve been working with Octopus Deploy, which runs operations as projects. A project has a deployment process which is a sequence of steps, and each step contains at least one action like “run a script”, “deploy a website”, “import a database”. I’ve been developing PowerShell tools for Octopus that let me write something like this

gop ran* -dp | % steps | % actions | % properties    

For “Get the Octopus project(s) matching ran* (there’s one named random quotes), and give its deployment process, get the process’s steps, their actions and each action’s properties (Properties hold the parameters for the action).
Turning verbosity up to 11 would re-write it like this:

Get-OctopusProject -Project 'Random Quotes' -DeploymentProcess |  
  ForEach-Object -Process {$_.Steps} |  
   ForEach-Object -Process {$_.Actions} |  
     ForEach-Object -Process {$_.Properties}

The first line alone is longer than the original! Staying with Octopus for another example, a release of a project creates deployments to carry out its actions in one or more environments, and the task performing the actions, creates a log file. It takes half a dozen API calls to get key points from the log of the last release, I can do that like this:

(gop ran* -A)[0].Deployments().task().raw() -split '\r?\n' | sls 'complete|fail'

For “get all releases of the project, select the first (most recent), get its deployments and the task for each one. Then get tasks’ raw logs – they are single strings that needs splitting at new-lines, and pick out the lines which contain “complete” or “fail”
That’s a terse way to get do half a dozen different calls to Octopus’ Rest API. and it came from my command history: writing for others to read it might look more like this:

Get-OctopusProject -Project 'Random Quotes' -AllReleases |  
  Select-Object -First 1 |  
    ForEach-Object {$_.Deployments()} |  
      ForEach-Object {$_.task()} |  
        ForEach-Object {$_.raw() -split '\r?\n'} |  
         Select-String -Pattern 'complete|fail'

PowerShell verbosity is optional; in C# it’s mandatory. After saying PowerShell was more streamlined, it might seem I’m trying to be provocative, but I’m not: scripts in “glue languages”, depend on sophisticated objects – like a Deployment object with a .Task() method - and they try to smooth out type differences between types. C# code ported to PowerShell has tell-tales like
if ( ! [string]::IsNullOrEmpty($s) )
or
if ($a.count -gt 0)
where PowerShell writes
if ($s)
or
if ($a)
because empty strings and empty arrays are treated as Boolean false.

If you’ve come to think that strict type-enforcement is always good there’s something “improper” about allowing anything to act as a Boolean, or implicit .ToString() operations or treating a type-name before a parameter as a request to cast to that type, not a requirement to be of that type. But if you expect to be able to perform the same filter, sort or export operations on Octopus Projects that you do on virtual hard disks, or on firewall rules, then strict type-enforcement seems like needless pedantry. Neither position is wholly right or wrong.

Mental mutilation

It is practically impossible to teach good programming to students that have had a prior exposure to BASIC: as potential programmers they are mentally mutilated beyond hope of regeneration - Dijkstra

Early in my Computer Science degree, a lecturer put that quote up on the board – to the shock of the majority who’d started with BASIC. The code below may be the best known in the history of programming

main( ) {
    printf("hello, world\n");
}

Why do I, Dijkstra-like, blame the seemingly innocuous hello world for harm akin to BASIC’s GOTO statement?
Because it teaches that programs are about printing output: programmers start out with a model “take user input”, “do things with numbers and text strings”, “print output”. This model simply doesn’t apply when we’re telling Octopus to make a release or setting a firewall rule or asking for a VM to be shut down - the purpose of many programs isn’t to print anything on the screen.

In fact printing is often actively unhelpful on two fronts. How so?
Here’s a screen shot from the git command-line tool. Git repositories have been a boon for developers, but the user-mendacity of the command line tool costs time and causes frustration.

The log sub-command in git showing colouring and layout issues

The screen shot above shows what I’ve dubbed developers going Berserk with crayons. Origin/main and Origin/HEAD are not dangerous, wrong or urgent so why are they red? Is main especially good/ok/working? And if not why is it in green? And why is HEAD in cyan? Tags would be in bright yellow; I think there’s only a desire to distinguish types of things linked to a commit, so there’s no message in the colours but I always get an uneasy feeling that there IS but that I don’t understand what. Yellow, used for the 40-digit hash, renders inconsistently - here it’s the next most prominent thing.

Git log is flexible, but to answer “who committed things for work item 1234” it must be told print in a way that can be parsed by something else. With a PowerShell Get-GitLog command (which automatically has a short form Gitlog - no more verbose!) you could add
| ? Comment -match '#1234' | % author

Printed output is, too often, what the person “doing stuff with numbers and strings”, thought was important, take git push for example

The push sub command in git showing programmer centric output

I contend that most users want that to be taken care of inside a black box.

A lot of effort goes into what gets printed, but it is delivered badly if it is wanted at all.

But PowerShell would change how my program is called…

Input should be able to come from parameters. When C# authors opt to deliver command-line functionality through PowerShell each of their cmdlets can say “support these parameters, help users complete them like this, validate like that” and so on. An author who opts to create a C# console app must write code to process its arguments. Remember: users care most about how stuff goes in and what comes out, but programmers can be pre-occupied with what happens between, meaning that (like outputs) inputs can be shaped too much by internals and not enough by user requirements.

Git is far from the only case of a single executable which runs multiple commands. I often cite IPConfig as an example: its sub-commands DisplayDNS, FlushDNS etc could be separate programs, and arguably that would be better done that way. net.exe goes back to the 1980s and has added functions over time. I doubt if many users have a strong preference for syncing time with the existing Time command, or with net Time or some other executable and doubt even more that there is a consensus.

But lashing more onto single commands doesn’t make for a good user experience, commands in net.exe aren’t consistent with each other, net computer and net user can’t agree whether to use /del or /delete ; these two use
net <type of thing> <name> /action
so you’d expect net service <name> /start (or /stop) instead of the actual net start <servicename>.

Another developer complaint is PowerShell makes me pick form a list of verbs and specify a noun, but net.exe (like git) shows developers can choose badly. Why “use” for “connect a local name to a share”, why not “mount” or “connect” or something else?
The objection to having Add-Computer.exe and Delete-computer.exe with a lot of duplication (like seeing if the computer exists) melts away when they are cmdlets in the same C# class.
PowerShell approves “Remove” but not “Delete” and I’ve heard developers object that they would need to use new instead of create, they’re synonyms! It really doesn’t matter which one is picked; if the original authors of git had chosen Copy instead of Clone would anyone care today? Git uses both pull and fetch for “bring something here”, but you need to know what each synonym brings: Here’s the help for those two.

Git's misleading help for fetch which downloads and pull - which fetches

The help which begins “fetch” isn’t for the fetch command. If fetch downloads and pull fetches why are the names that way (Why pull and push, why not send and receive or get and put? I don’t think there’s any strong reason).
One of the following descriptions is for git’s show command but 4 of the 5 begin “show”

Graphical user interface, text Description automatically generated

Is getting the history showing how a branch got to its current state, part of the git branch command, or is it one of the commands above, and if so, which? How does the program score on taking the inputs we want in the way we want? 2 or 3 out of 10?

In the end developers argue that as much as possible should be left to them, but some of that is burning time without adding value for users. With a choice between “C# to work as PowerShell cmdlets” and “C# to work as a console app” how should we react to those who say “We’ll make something which works less well by expending spend extra effort writing print routines and input handling” . I’m for telling them bluntly “that’s doing a lousy job.


<<Previous post       Next Post>>