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 I have two text files:

        1_line.txt

        hello world
        

        2_lines.txt

        hello world
        Brian!
        
      • Here are the return types for each file using Get-Content:

        1 line in file, return String

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

        output

         System.String
        

        More than 1 line, return Object Array

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

        output

         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()
      

      output

         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()
      

      output

         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()
      

      output

        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()
      

      output

        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 <anonymous>:1:1
     (anonymous)	@	VM190:1