DEV Community

Hunnicuttt
Hunnicuttt

Posted on • Edited on

PowerShell Nihilism as Software Discipline

Every last mistake: practical, emotional, hubris..... it's me; a thousand times over... every time.

I'm only speaking to this sysadmin here as a self-admonition. Because I constantly forget these things.

Fell asleep at 4am finishing it up after a long debate about foreach loops with another industry vet who (like me on other powershell problems), spent too much time researching, testing variations, believing they came up with a good solution.... I had to break it to him; the same as the stark truth has hit me too many times.


IMPORTANT UPDATE:

While much of this critique remains; there was a particular 'discovery' I thought I made in regard to the powershell implementation of foreach that guided rules around its use in preference over pipeline operations. After much patience from the powershell dev team; they were able to clarify exactly how the pipeline works; where performance considerations end and stylistic boundaries begin; and frankly, what it is to be absolute pros in the exercise of community engagement.

Powershell Official Discord
Here's the whole thread

Having learned so much, I will be writing a stand-alone follow up to this 'style guide' for powershell development practices that integrate all these expert thoughts along with the tips and tricks I've learned to Invoke-CodeClarity

I warned you; hubris indeed.

With that being said; If you're here before I get back to working on something esoteric like this and now that it is quite clear that the foreach loop does not act as yielding generator (at least for the foreseeable future) The inspiration and well-known progenitor rule that guided this thought is this:

  • Prefer going vertical with your code instead of horizontal

Number 1 rule for PowerShell development:

- Never write idiomatic PowerShell.


If you show me PowerShell that looks like PowerShell, it's never getting near my prod.


  • Underneath its simplicity is a pile of over-engineered abstractions trying really hard to look simple.
  • That God-Like feeling of writing 'perfect powershell' when you dug into it's internals, flipping $myInvocation reflections and .GetSteppablePipeline() to eek out some more juice? yeah... that's stockholm syndrome.

  • If it looks too clean to be real, it's probably hiding an AST, a COM object, and a scriptblock logging penalty.

I want classes, type annotations, sealed behavior, and zero magic.


- Don’t use syntactic sugar.

  • It’s not sweet. It’s sticky.
  • That one-liner will rot your stack trace and give your future self trust issues.
  • Save the ? { $_.Enabled } for your lunch-break scripts.

  • Take an 'Anti-Aliasing stance' by:

    • Lean deeply into verbosity.
    • Never use aliases. Where-Object is a statement. ? is a cry for help.
    • Use -not, not !. Save ! for languages that care about you. at least enough to make it a first-class citizen.
    • Avoid short-form anything. If you type less, you know less.
  • Never use Where-Object unless you're at the terminal.

    • Don’t hide logic inside { $_ } sugar cubes.
    • Build meaningful booleans. Give your conditions names.
    • $isValid = $Value -is [ValueType] -or $Value -is [string]
    • if ($isValid) { ... }

Readable. Testable. Traceable.
Don’t write code you can’t explain without squinting.


  • Prefer $global:ActualVariableName over guessing.
  • But Really: Never use $global: unless you're building a panic button.
  • Globals are shared hallucinations.
  • Build modules with private state, or cache inside a getter function.
  • If you're reaching for a singleton in PowerShell — ask yourself who hurt you.

Be explicit. Be readable. Be annoying to lazy devs and let the too-clever ones think your dumb. It's okay, their code runs like snails.


Never use the pipeline unless you're at the terminal. Ever.

  • Scripts aren’t a concert — they don’t need to flow.
  • The pipeline hides performance costs, debug context, and your sins.
  • Always prefer -InputObject or passing explicitly via variables.
  • Chaining is for smokers and functional languages. If smalltalk only knew... it would come over and beat you with that pipe powershell.

Never write cmdlets unless you're newing a class you wrote.

  • Cmdlets are overkill unless you’re exposing behavior from your own system.
  • If there’s no [MyModule.MyType] behind it, it’s just a function in a trench coat trying to sell you a stolen watch.
    Treat functions like APIs. Cmdlets are just UI for your types.

  • Always write functions with explicit returns.

  • PowerShell returns anything it touches — even when you didn’t ask.

  • → Use return every time. Be intentional.

  • → Use $null = or [void]() to shut up side effects you don’t want.

  • Never let PowerShell decide what your function says. It’ll be wrong.

  • Method calls return values — even when you don’t care.

  • → If you're not using it, silence it. Don't let it leak into your output stream.

  • PowerShell accepts fake enum values. Don’t trust the binding — enforce it yourself.

    • enum State { Start; Stop }
    • function Run { param([State]$Mode) $Mode }
    • Run 'Destroy' # → returns $null, no error
    • → PowerShell fails silently on bad enum input.
    • → If you don't check, your logic runs on $null like Leroy Jenkins.
    • Fix it with [ValidateSet()]:
    • function Run {
    • param([ValidateSet('Start','Stop][string]$Mode)")
    • $Mode
    • }
    • Run 'Destroy' # → throws, as it should**

Never use a PowerShell construct to solve a PowerShell problem inside of PowerShell.

  • Reach for PowerShell only when you’re outside of it.
  • Inside PowerShell, defer to .NET.
  • New the object. Call the method. Don’t rely on the engine’s interpretation of your intent.
  • PowerShell will try to help. Don’t let it.
  • PowerShell is not a language. It's not a runtime, it's not a platform. It is only an execution substrate.

  • Never use a PowerShell primitive type if there's any other alternative that can be new'd from .NET.

    • [int] is cute until it’s secretly [System.Int32] with extra baggage.
    • [string] is a trap. Coerce it to [System.String] if you care about methods behaving.
    • Avoid [hashtable], [array], [psobject], [scriptblock], and anything else that lives in PowerShell’s imagination.
    • If you must touch a PowerShell type, immediately .ToString(), .ToArray(), or .Cast<>() it into something real.
  • Avoid [type](value) casting unless you're okay with PowerShell lying to you.

    • It looks like a .NET cast.
    • It feels like a .NET cast.
    • It is not a .NET cast.
    • Under the hood, it's LanguagePrimitives::ConvertTo().
    • That means PowerShell will 'help' you. You don't want help.
    • You want control, predictability, and exceptions when things go wrong.
    • Use [int]::Parse(), [datetime]::TryParse(), or .Cast<T>() if you actually care.
    • Stop writing code that 'works'. Write code that fails when it should.
  • PowerShell casting is a vibe, not a contract.

    • Even then, Strong typing doesn’t mean safe input — validate explicitly.

There is no faster or safer way to iterate: use foreach. Stop Trying. ...in both(!!!) versions of Powershell

  • foreach ($file in Get-ChildItem -Recurse) {} is the correct form. This is a lazy-loaded generator expression.
  • $result = foreach ($file in Get-ChildItem -Recurse) {} for collecting output — stable and explicit.
  • foreach ($item in $collection.GetEnumerator()) {} when you want full control.
  • Don’t preload. Don’t pipe. Don’t decorate.

  • These forms are the truth. Everything else is the engine trying to be clever.


On Throwing, Catching, and Losing Your Mind

~ or ~

"If you didn’t rethrow it — it didn’t happen."


PowerShell’s exception model is a handshake agreement with chaos.

  • throw looks like an exception.
  • throw feels like an exception.
  • throw is actually a terminating error inside a non-terminating environment.
  • And no, it’s not okay.
  • throw in a function? Good luck — sometimes it bubbles, sometimes it swallows, sometimes it yeets sideways and gets eaten by $ErrorActionPreference.

So if you catch, always rethrow — and rethrow properly.

  • throw by itself? — rethrows the current error object.
  • throw $_? — creates a new error and wipes the stack trace.
  • throw $PSItem.Exception? — you just pulled out the .NET exception and yeeted it without context.
  • throw $_.Exception? Still bad. Just slower because you used an alias. You're discarding PowerShell’s error record and losing the script context.
  • throw inside a catch block is your only safe bet — just use throw raw. No extras.

PowerShell exceptions aren’t real exceptions. They're error records, wrapped in runtime delusion.

  • Most of your bugs come from thinking PowerShell will behave like other languages. It won’t.
  • $ErrorActionPreference = 'Stop' should be the first line in every script you write. Not negotiable.
  • If you don’t force errors to terminate, PowerShell will “handle” them by pretending everything is fine while quietly torching your logic.

PowerShell's throw is the programming equivalent of flipping a table in a room full of polite Minnesotans — dramatic, but everyone just quietly resets the chairs and moves on unless you really mean it.

For both Powershell and Minnesota. I'm born and raised; I'm permitted.


Enums and classes — there isn't time in the world.

  • If you want them to be more than Snover’s shower-thought, write them somewhere else.
  • PowerShell lets you define types. It doesn’t know what they’re for.
  • You're not writing models — you're building containers for structured suffering.
  • Use a duplicate object in your getters. No, PowerShell, you can’t hold my baby.
  • Classes and Enums... They look like types. They act like variables. They debug like demons.
  • Then they streak across prod with their privates exposed, and you get blamed for public indecency.

  • If you still need a singleton in PowerShell: here’s the cliff notes:

    • Don’t use $global: — that’s not a singleton, that’s a liability.
    • Use a module-scoped variable ($script:) inside a .psm1 file.
    • Expose a Get-Instance function that initializes once and returns always.
    • Encapsulate access — never let the outside world touch it directly.
    • If you must reset it, make that a deliberate exported function.
    • You’re not writing Go. You’re duct-taping state to a fantasy. Be careful.

Classes and enums... sure, they’re busted.

  • You don’t use classes in PowerShell because they’re good.
  • You're going to need them if you want to escape the hell of cmdlet and function return diarrhea of anything that could be writ to the terminal munged together into an object[]
  • You're not using them for OOP style or for code organization, you're using them because you're tired of getting crapped on by your own debug statements.

Dot-sourcing is an act of war — either against PowerShell’s module system, or against yourself. Choose wisely.

  • It leaks scope.**
  • It overrides variables you didn’t mean to share.**
  • It pretends your script is part of the caller’s logic.**

Encapsulate everything. PowerShell doesn’t know how to contain your code — you have to.


As my first language, and after over a decade in it:

  • Stop trying to adhere to the language spec.
  • The spec doesn’t protect you.
  • The spec won’t save you.
  • The spec was designed for fast answers — not for codebases with consequences.

It’s easier to learn to code than it is to learn PowerShell.

  • When you learn PowerShell, you still have to learn to code.
  • So you’ve just spent twice as much time and struggle... for no reason.
  • And all you got was conformity to a spec written for scripting around .NET, not building with it.
  • Learn to code. Then use PowerShell as a tool, not a teacher.

  • Write code — not PowerShell ...when writing PowerShell.
  • Writing code in PowerShell takes less time than writing PowerShell code.
  • The language wants you to write scripts.
  • You should be writing software. Yes. Your ten lines of code to set up a scheduled task is "Software". Make it fast and make it last.
  • Lean on .NET. Use real logic. Skip the sugar. Don't learn the sugar when you get stuck; You don't have all the time in the world just to learn how to do it the wrong way. Respect Yourself.
  • PowerShell can be elegant, but clean code is faster, more readable, and more maintainable than clever hacks.

  • Researching first-principles and applying them is faster than researching:

    • ... your objective,
    • ... the PowerShell abstraction that almost solves it,
    • ... why that abstraction quietly fails,
    • ... how to bypass PowerShell's resistance to correction,
    • ...to just to end up applying first-principles anyway.

Follow this and your code will be between 2x–500x faster (no joke), dramatically more maintainable, and infinitely less embarrassing to debug in front of other adults. You'll get more done, with less empty calories, with more energy to go deeper.

  • Just think about it for a second. None of this stuff is easy; you're grinding your nose into the dust for nothing when you wrestle with the desire to write idiomatic powershell code even when you know better.

  • It's not more readable... if someone is reading your code; they know how to read code.

  • If someone is learning your code; the basic constructs... it really and truly is all you need to teach them. Don't let the elitism of massive unpeneratible ecosystems keep you or anyone else feeling like you can't write California-Grade code.

  • This isn’t about writing better PowerShell — it’s about writing real code using PowerShell as a tool, not as a crutch.

  • You don’t need the language’s permission to write something that lasts.

  • You don’t need the language’s permission to learn to code.

Do I love it? Yeah.
Did it teach me more bad tricks than a magician with a drinking problem?
Absolutely.
Did it teach me everything I know? Yeah.
Can I write in any language now because of it? Also yeah.
It made me a polyglot. And for that... I owe PowerShell everything.

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

If you found this article helpful, please give a ❤️ or share a friendly comment!

Got it