What I absolutely hate about Powershell

June 7, 2026 • 2 min read • powershell

Powershell in all of its wisdom has some really bad behaviors. Perhaps it's to make scripting easier for non-programmers. I would argue that these behaviors are worse for those less-experienced because they will cause bugs that are not obvious. Even experienced programmmers would say what the heck! Perhaps there is a legitimate reason for these behaviors. As a person who loves programming languages, I cannot make any sense of it. Here are a few examples.

  1. Reading files

    • Problem: when reading text files using the Get-Content command-let, the return type changes depending on the content. If the content has only one line, the return type is a string. However, if the content has more than one line, the return type is an array. You should always want to expect the same return type.

      • Here are two text files and their content:

        1. 1_line.txt

          hello world

        2. 2_lines.txt

          hello world
          Brian!

      • Here are the return types for each file using Get-Content:

        1. 1_line.txt - one line in file, returns String

          $data = (Get-Content .\1_line.txt)
          Write-Host $data.GetType()
          System.String
          

        2. 2_lines.txt - more than 1 line, returns Object Array

          $data = (Get-Content .\2_lines.txt)
          write-host $data.GetType()
          System.Object[]
          

    • Solution 1: always force the file to read as a string and then split on the newlines

      $data = (Get-Content .\1_line.txt -Raw) -split '`n'
      Write-Host $data.GetType()
      System.String[]
      

    • Solution 2: use .NET classes to read file lines (requires that $ExecutionContext.SessionState.LanguageMode -ne "ConstrainedLanguage")

      $data = [System.IO.File]::ReadAllLines(".\1_line.txt")
      Write-Host $data.GetType()
      System.String[]
      

  2. Block scope

    • Problem: Powershell does not have block scope. It has Global, Local, and Script scope. Powershell also has scope modifiers. Since there is no block scope, you can reference a variable after the block executed:

      if ($true) {
          $ghost = "ghost should be out of scope"
      }
      Write-Host $ghost.Trim()
      ghost should be out of scope
      

    • However, if the block does not execute, the variable reference can fail:

      if ($false) {
          $ghost2 = "ghost should be out of scope"
      }
      Write-Host $ghost2.Trim()
      You cannot call a method on a null-valued expression.
      At line:1 char:9
      +         Write-Host $ghost2.Trim()
      +         ~~~~~~~~~~~~~~~~~~~~~~~~~
          + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
          + FullyQualifiedErrorId : InvokeMethodOnNull
      

    • Again, the block does not run, so ghost does not exist outside the block. This behavior should always be expected outside of the block.

    • Unfortunately, other dynamically typed, interpreted laguages such as Python, Ruby, and Javascript behave in this way too. Javascript does support block scope when using let instead of var

      {
          var some_var = 5;
      }
      console.log(some_var);
      5
      

      {
          let some_other_var = 10;
      }
      console.log(some_other_var);
      Uncaught ReferenceError: some_other_var is not defined      VM190:1 
      at :1:1
      (anonymous) @   VM190:1
      


<- Back