PowerShell: Sorting Version Strings

Recently we had a large array of version strings we needed to sort. Like this, but way too long to sort by hand:

$Versions = @(
    '2.1.3',
    '1.2.3',
    '1.2.12'
)

Piping this array to Sort-Object changes the order, but not correctly.

$Versions | Sort-Object
1.2.12
1.2.3
2.1.3

It thinks 1.2.12 comes before 1.2.3. Comparing character-by-character, that’s true. 1 is less than 3. We need it to interpret everything after the period as one number. Then it’ll see that 3 is less than 12.

We can do this by casting the elements of the array to version before sorting.

[version[]]$Versions | Sort-Object

Major  Minor  Build  Revision
-----  -----  -----  --------
1      2      3      -1
1      2      12     -1
2      1      3      -1

The components are parsed out and stored individually as Major, Minor, and Build. Now that we’re sending versions instead of strings to Sort-Object, it compares the 3 build to the 12 build and gets the order right.

Of course, now we have version objects instead of the strings we started with. We can convert back with the ToString() method.

[version[]]$Versions | Sort-Object | foreach {$_.ToString()}
1.2.3
1.2.12
2.1.3

That one-liner is usually all that’s needed. The main limitation is the version class. It works with up to four integer components delimited by dots. That doesn’t handle some common conventions.

Versions are often prefixed with v, like v1.2.3. Fortunately, that doesn’t change the sorting. Just trim it out.

'v1.2.3'.TrimStart('v')
1.2.3

TrimStart() removes the v from the start of the string if it’s present, otherwise it’s a no-op. It’s safe to call on a mix of prefixed and non-prefixed strings. Run it on everything and sort like before.

Some of the patterns defined in the ubiquitous semver allow more characters and delimiters.

  • 1.0.0-alpha.1
  • 1.0.0+21AF26D3—-117B344092BD

One of these adds build metadata and semver doesn’t consider build metadata in precedence, so depending on your situation you might be able to just trim off the problem characters. If not, you’ll need a different parser.

Hope this helped!

Operating Ops

Need more than just this article? We’re available to consult.

You might also want to check out these related articles:

PowerShell: The Programmer’s Shell

A couple years ago, I switched all my workstations to PowerShell. Folks often ask me why I did that. What made it worth the trouble of learning a new syntax? Here are the things about Posh that made me switch:

Mac, Linux, and Windows Support

I usually run PowerShell on Mac OS, but it also supports Linux and Windows. It gives me a standardized interface to all three operating systems.

Object Oriented Model

This is the big reason. It’s the thing that makes PowerShell a programming language like Python or Ruby instead of just a scripting language like Bash.

In Bash everything is a string. Say we’ve found something on the filesystem:

bash-3.2$ ls -l | grep tmp
drwxr-xr-x   4 adam  staff   128 Oct 15 18:10 tmp

If we need that Oct 15 date, we’d parse it out with something like awk:

bash-3.2$ ls -l | grep tmp | awk '{print $6, $7}'
Oct 15

That splits the line on whitepace and prints out the 6th and 7th fields. If the whitespacing of that output string changes (like if you run this on someone else’s workstation and they’ve tweaked their terminal), this will silently break. It won’t error, it just won’t parse out the right data. You’ll get downstream failures in code that expected a date but got something different.

PowerShell is object oriented, so it doesn’t rely on parsing strings. If we find the same directory on the filesystem:

PS /Users/adam> Get-ChildItem | Where-Object Name -Match tmp

    Directory: /Users/adam

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----          10/15/2020  6:10 PM                tmp

It displays similarly, but that’s just formatting goodness. Underneath, it found an object that represents the directory. That object has properties (Mode, LastWriteTime, Length, Name). We can get them by reference:

PS /Users/adam> Get-ChildItem | Where-Object Name -Match tmp | Select-Object LastWriteTime

LastWriteTime
-------------
10/15/2020 6:10:55 PM

We tell the shell we want the LastWriteTime property and it gets the value. It’ll get the same value no matter how it was displayed. We’re referencing a property not parsing output strings.

This makes Posh less fragile, but also gives us access to the standard toolbox of programming techniques. Its functions and variable scopes and arrays and dictionaries and conditions and loops and comparisons and everything else work similarly to languages like Python and Ruby. There’s less Weird Stuff. Ever have to set and unset $IFS in Bash? You don’t have to do that in PowerShell.

Streams

Streams are a huge feature of PowerShell, and there are already great articles that cover the details. I’m only going to highlight one thing that makes me love them: they let me add informative output similar to a DEBUG log line in Python and other programming languages. Let’s convert our search for tmp into a super-simple script:

[CmdletBinding()]
param()

function Get-Thing {
    [CmdletBinding()]
    param()
    $AllItems = Get-ChildItem
    Write-Verbose "Filtering for 'tmp'."
    return $AllItems | Where-Object Name -Match 'tmp'
}

Get-Thing

If we run this normally we just get the tmp directory:

PS /Users/adam> ./streams.ps1

    Directory: /Users/adam

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----          10/15/2020  6:10 PM                tmp

If we run it with -Verbose, we also see our message:

PS /Users/adam> ./streams.ps1 -Verbose
VERBOSE: Filtering for 'tmp'.

    Directory: /Users/adam

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----          10/15/2020  6:10 PM                tmp

We can still pipe to the same command to get the LastWriteTime:

PS /Users/adam> ./streams.ps1 -Verbose | Select-Object LastWriteTime
VERBOSE: Filtering for 'tmp'.

LastWriteTime
-------------
10/15/2020 6:10:55 PM

The pipeline reads objects from a different stream, so we can send whatever we want to the verbose stream without impacting what the user may pipe to later. More on this in a future article. For today, I’m just showing that scripts can present information to the user without making it harder for them to use the rest of the output.

The closest you can get to this in Bash is stderr, but that stream is used for more than just information, and realistically you can’t guess the impact of sending messages to it. Having a dedicated stream for verbose messages makes it trivial to provide information without disrupting behavior.

PowerShell is a big language and there’s a lot more to it than what I’ve covered here. These are just the things that I get daily value from. To me, they more than compensate for the (minimal) overhead of learning a new syntax.

Happy scripting!

Adam
Operating Ops

Need more than just this article? We’re available to consult.

You might also want to check out these related articles:

PowerShell Scripts with Arguments

Hello!

I write a lot of utility scripts. Little helpers to automate repetetive work. Like going through all those YAML files and updating that one config item, or reading through all those database entries and finding the two that are messed up because of that weird bug I just found.

These scripts are usually small. I often don’t keep them very long. I also usually have to run them against multiple environments, and sometimes I have to hand them to other engineers. They need to behave predictably everywhere, and they need to be easy to read and run. They can’t be hacks that only I can use.

In my work, that means a script that takes arguments and passes them to internal functions that implement whatever I’m trying to do. Let’s say I need to find a thing with a known index, then reset it. Here’s the pattern I use in PowerShell:

[CmdletBinding()]
param(
    [int]$Index
)

function Get-Thing {
    [CmdletBinding()]
    param(
        [int]$Index
    )
    return "Thing$Index"
}

function Reset-Thing {
    [CmdletBinding()]
    param(
        [string]$Thing
    )
    # We'd do the reset here if this were a real script.
    Write-Verbose "Reset $Thing"
}

$Thing = Get-Thing -Index $Index
Reset-Thing -Thing $Thing

We can run that from a prompt with the Index argument:

./Reset-Thing.ps1 -Index 12 -Verbose
VERBOSE: Reset Thing12

Some details:

  • The param() call for the script has to be at the top. Posh throws errors if you put it down where the functions are invoked.
  • CmdletBinding() makes the script and its functions handle standard arguments like -Verbose. More details here.
  • This uses Write-Verbose to send informative output to the verbose “stream”. This is similar to setting the log level of a Python script to INFO. It allows the operator to select how much output they want to see. More details here.
  • As always, use verbs from Get-Verb when you’re naming things.
  • I could have written this with just straight commands instead of splitting them into Get and Reset functions, especially for an example this small, but it’s almost always better to separate out distinct pieces of logic. It’ll be easier to read if I have to hand it to someone else who’s not familiar with the operation. Same if I have to put it aside for a while and come back to it after I’ve forgotten how it works.

This is my starting point when I’m writing a helper script. It’s usually enough to let me sanely automate a one-off without getting derailed into full-scale application development.

Happy scripting,

Adam

Need more than just this article? We’re available to consult.

You might also want to check out these related articles:

PowerShell on OS X: Git Hooks

Hello!

PowerShell works great on Mac OS X. It’s my default shell. I usually only do things the Posh way, but sometimes the underlying system bubbles back up. Like when I’m writing git hooks.

In Posh, git hooks live in the same place and still have to be executable on your platform. That doesn’t change. But, the scripts themselves can be different. You have two options.

Option 1: Don’t Use PowerShell

Your existing hooks written in bash or zsh or whatever Linux-ey shell you were using will still work. That’s great if you already have a bunch and you don’t want to port them all.

If you’re writing anything new, though, use PowerShell. When I get into a mess on my Posh Apple, it’s usually because I mixed PowerShell with the legacy shell. You’re better off using just one.

Option 2: Update the Shebang

The shebang (#!) is the first line of executable scripts on Unix-like systems. It sets the program that’s used to run the script. We just need to write one in our hook script that points at pwsh (the PowerShell executable):

#!/usr/local/microsoft/powershell/7/pwsh
 
Write-Verbose -Verbose "We're about to commit!"

If you don’t have the path to your pwsh, you can find it with Get-Command pwsh.

After that, our hook works like normal:

git commit --allow-empty -m "Example commit."
VERBOSE: We're about to commit!
[master a905079] Example commit.

If you don’t set the shebang at all (leaving nothing but the Write-Verbose command in our example), your hook will run but OS X won’t treat it like PowerShell. You get “not found” errors:

git commit --allow-empty -m "Example commit."
.git/hooks/pre-commit: line 2: Write-Verbose: command not found
[master 1b2ebac] Example commit.

That’s actually good. If you have old hook scripts without shebang lines, they won’t break. Just make sure any new Posh scripts do have a shebang and everything should work.

Enjoy the Posh life!

Adam

Need more than just this article? We’re available to consult.

You might also want to check out these related articles:

How to Grep in PowerShell

Hello!

In oldschool Linux shells, you search files for a string with grep. You’re probably used to commands like this (example results from an OSS repo):

grep -r things .
./terraform.tfstate.backup:              "./count_things.py"
./count_things.py:def count_things(query):
./count_things.py:    count_things()
./terraform.tf:  program = ["python", "${path.module}/count_things.py"]

It outputs strings that concatenate the filename and the matching line. You can pipe those into awk or whatever other command to process them. Standard stuff.

You can achieve the same results in PowerShell, but it’s pretty different. Here’s the basic command:

Get-ChildItem -Recurse | Select-String 'things'
 
count_things.py:7:def count_things(query):
count_things.py:17:    count_things()
terraform.tf:6:  program = ["python", "${path.module}/count_things.py"]
terraform.tfstate.backup:25:              "./count_things.py"

This part is similar. Get-ChildItem recurses through the filesystem and passes the results to Select-String, which searches those files for the string things. The output looks the same. File on the left, matching line on the right. That’s just friendly formatting, though. Really what you’re getting is an array of objects that each represent one match. Posh summarizes that array with formatting that’s familiar, but actually processing these results is completely different.

We could parse out details the Linux way by piping into Out-String to convert the results into strings, splitting on :, and so on, but that’s not idiomatic PowerShell. Posh is object-oriented, so instead of manipulating strings we can just process whichever properties contain the information we’re searching for.

First, we need to know what properties are available:

Get-ChildItem -Recurse | Select-String 'things' | Get-Member
 
   TypeName: Microsoft.PowerShell.Commands.MatchInfo
 
Name               MemberType Definition
----               ---------- ----------
Equals             Method     bool Equals(System.Object obj)
GetHashCode        Method     int GetHashCode()
GetType            Method     type GetType()
RelativePath       Method     string RelativePath(string directory)
ToEmphasizedString Method     string ToEmphasizedString(string directory)
ToString           Method     string ToString(), string ToString(string directory)
Context            Property   Microsoft.PowerShell.Commands.MatchInfoContext Context {get;set;}
Filename           Property   string Filename {get;}
IgnoreCase         Property   bool IgnoreCase {get;set;}
Line               Property   string Line {get;set;}
LineNumber         Property   int LineNumber {get;set;}
Matches            Property   System.Text.RegularExpressions.Match[] Matches {get;set;}
Path               Property   string Path {get;set;}
Pattern            Property   string Pattern {get;set;}

Get-Member tells us the properties of the MatchInfo objects we piped into it. Now we can process them however we need.

Select One Property

If we only want the matched lines, not all the other info, and we can filter out the Line property with Select-Object.

Get-ChildItem -Recurse | Select-String 'things' | Select-Object 'Line'
 
Line
----
def count_things(query):
    count_things()
  program = ["python", "${path.module}/count_things.py"]
              "./count_things.py"

Sort Results

We can sort results by the content of a property with Sort-Object.

Get-ChildItem -Recurse | Select-String 'things' | Sort-Object -Property 'Line'
 
terraform.tfstate.backup:25:              "./count_things.py"
count_things.py:17:    count_things()
terraform.tf:6:  program = ["python", "${path.module}/count_things.py"]
count_things.py:7:def count_things(query):

Add More Filters

Often, I search for a basic pattern like ‘things’ and then chain in Where-Object to filter down to more specific results. It can be easier to chain matches as I go than to write a complex match pattern at the start.

Get-ChildItem -Recurse | Select-String 'things' | Where-Object 'Line' -Match 'def'
 
count_things.py:7:def count_things(query):

We’re not limited to filters on the matched text, either:

Get-ChildItem -Recurse | Select-String 'things' | Where-Object 'Filename' -Match 'terraform'
 
terraform.tf:6:  program = ["python", "${path.module}/count_things.py"]
terraform.tfstate.backup:25:              "./count_things.py"

There are tons of things you can do. The main detail to remember is that you need Get-Member to tell you what properties are available, then you can use any Posh command to process those properties.

Enjoy freedom from strings!

Adam

Need more than just this article? We’re available to consult.

You might also want to check out these related articles:

PowerShell on OS X: Setting Your Path Variable

Hello!

There are two tricky little problems when setting your path variable in PowerShell. Here’s how to get past them.

First, lots of guides show things like this:

$Env:Path += "PATH_STRING"

Which works on Windows but won’t work on OS X. The variable name has to be all-caps:

$Env:PATH += "PATH_STRING"

Next, the separator between path elements on Windows is ;, but on OS X it’s :. Swap them and you should be good to go:

# Windows-only, won't work:
# $Env:PATH += ";/Users/adam/opt/bin"
 
# Works on OS X:
$Env:PATH += ":/Users/adam/opt/bin"

Small details, but they were remarkably fiddly to figure out the first time I ran in to them. Lots of people use Posh on Windows, so lots of guides and docs won’t work on Mac. You may find similar compatibility problems in scripts, too. Hopefully this saves you from some frustration.

Happy scripting,

Adam

Need more than just this article? We’re available to consult.

You might also want to check out these related articles:

PowerShell: Python venv Missing Activate.ps1

Hello!

Ran in to a weird problem this week: I created a Python 3.7.9 venv, but I couldn’t activate it in PoweShell (my shell of choice). The Activate.ps1 script was missing.

The core docs for 3.7 list VENV/Scripts/Activate.ps1 as the command to activate venvs in PowerShell (which seemed odd because I’m used to VENV/bin/activate from Bash, but whatever). The Scripts directory didn’t even exist:

gci ./test_venv_379/
 
    Directory: /Users/adam/Local/fiddle/test_venv_379
 
Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----          10/22/2020  9:28 AM                bin
d----          10/22/2020  9:28 AM                include
d----          10/22/2020  9:28 AM                lib
-----          10/22/2020  9:28 AM             98 pyvenv.cfg

I recreated the venv and got the same results. I made new venvs with 3.7.8 and 3.6.11, and again the same results. When I made a 3.8.5 venv, though, it had a VENV/bin/Activate.ps1 (which works great).

gci ./test_venv_385/bin
 
    Directory: /Users/adam/Local/fiddle/test_venv_385/bin
 
Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-----          10/22/2020  9:13 AM           2236 activate
-----          10/22/2020  9:13 AM           1288 activate.csh
-----          10/22/2020  9:13 AM           2440 activate.fish
-----          10/22/2020  9:13 AM           8834 Activate.ps1
-----          10/22/2020  9:13 AM            263 easy_install
...

Then I read the docs for 3.8: VENV/Scripts/Activate.ps1 is the PowerShell activation script but VENV/bin/Activate.ps1 is the PowerShell Core activation script. The 3.7 and 3.6 docs don’t make this distinction, which I’d bet is because PowerShell Core wasn’t supported until 3.8. I’m running Posh on Mac, so of course I’m running Posh Core (only Core supports Mac and Linux).

I suspect the VENV/Scripts/Activate.ps1 file was missing from both venvs because Python detected my shell was Posh Core, which it didn’t support. That would also explain why my 3.8 venv only had a VENV/bin/Activate.ps1 file, the file needed by Posh Core.

Anyway, if you upgrade to 3.8 (I used 3.8.5) you should be good to go.

If you can’t upgrade. Upgrade! But if you really really can’t, you can still use a 3.7 venv in Posh Core. Just call the executables by path instead of activating:

./test_venv_385/bin/python --version
Python 3.8.5

Hope that gets you past the problem!

Adam

Need more than just this article? We’re available to consult.

You might also want to check out these related articles:

PowerShell: Sort Hash Table Into Ordered Dictionary

Hello!

PowerShell’s Hash Tables are unordered. The keys don’t always come back in the same order you entered them:

PS /Users/adam/Local/fiddle> $HashTable = @{                   
>>     'a' = 1
>>     'b' = 2
>>     'c' = 3
>>     'd' = 4
>> }
PS /Users/adam/Local/fiddle> $HashTable
 
Name                           Value
----                           -----
c                              3
b                              2
d                              4
a                              1

I created the hash in the order a, b, c, d but I got back c, b, d, a. That’s normal.

PowerShell also has Ordered Dictionaries that work like Hash Tables but preserve order:

PS /Users/adam/Local/fiddle> $OrderedDictionary = [ordered]@{
>>     'a' = 1
>>     'b' = 2
>>     'c' = 3
>>     'd' = 4
>> }
PS /Users/adam/Local/fiddle> $OrderedDictionary
 
Name                           Value
----                           -----
a                              1
b                              2
c                              3
d                              4

Today I had large hash and I needed to convert it to a dictionary that was sorted by key name. The hash was returned by a library I didn’t control so I couldn’t just re-define it as a dictionary. I had to convert it. There are a couple ways, but I found this was the cleanest:

$HashTable = @{
    'd' = 4
    'a' = 1
    'b' = 2
    'c' = 3
}
$OrderedDictionary = [ordered]@{}
foreach ($Item in ($HashTable.GetEnumerator() | Sort-Object -Property Key)) {
    $OrderedDictionary[$Item.Key] = $Item.Value
}
$OrderedDictionary

This outputs a dictionary that has been sorted by its keys:

PS /Users/adam/Local/fiddle> ./Ordering.ps1
 
Name                           Value
----                           -----
a                              1
b                              2
c                              3
d                              4

Because it’s a dictionary and not a regular hash, it’ll keep that ordering.

Happy scripting!

Adam

Need more than just this article? We’re available to consult.

You might also want to check out these related articles:

PowerShell On Apple Mac OS X

Hello!

I love PowerShell. Its object-oriented nature is welcome relief from the endless string parsing of bash.

Good news! PowerShell Core installs on Windows, OS X, and Linux. I made it my default in Terminal on OS X. Here’s how.

I tested this on OS X Catalina (10.15) with PowerShell Core 7.

First, install from Homebrew like the docs say:

brew install powershell

Then open Terminal, select Terminal > Preferences in the menu, and set shells to open with /usr/local/bin/pwsh:

TerminalDefaultShell

Now quit and re-open Terminal. Boom! 💥 You’re standardized on PowerShell.

If you’re like me, though, you also need to add a bunch of stuff to your path. It’s similar to Linux-land, just update an environment variable in your profile, but there were gotchas.

PowerShell exposes an Environment Provider that works like a filesystem drive. That’s where your path is set:

PS /Users/adam> Get-Item Env:PATH
 
Name                           Value
----                           -----
PATH                           /usr/local/microsoft/powershell/7:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

When I wrote this, the docs gave examples that used both Env:path and Env:Path, but neither worked on OS X. I had to use Env:PATH. It’s tricky because creating the wrong one doesn’t cause errors it just doesn’t do what you want.

The second gotcha was easier. In Windows the separator is ; so that’s what most examples use, but in OS X it’s :. I was copy/pasting from Windows code samples before I noticed the problem.

Just like Linux, modifications to Env:PATH are specific to your session. They’re lost on exit. We can make them permanent. First, find your PowerShell profile:

PS /Users/adam> $PROFILE
/Users/adam/.config/powershell/Microsoft.PowerShell_profile.ps1

Create that path and file if it doesn’t exist. Put this command in the Microsoft.PowerShell_profile.ps1 script:

$Env:PATH += ":/Users/adam/opt/path"

Now quit and re-open Terminal and your path should be up to date:

PS /Users/adam> Get-Item Env:PATH
 
Name                           Value
----                           -----
PATH                           /usr/local/microsoft/powershell/7:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/adam/opt/path

That’s it!

Happy automating,

Adam

Need more than just this article? We’re available to consult.

You might also want to check out these related articles:

PowerShell DSC: Stop On Errors

Hello!

I like my code to stop on errors, not continue past them. For example, if a script fails to enable logs for my app I want to know right away. I don’t want to find out tomorrow when I need logs that aren’t there. The earlier I know about errors, the better. I learned this as the Fail Early Fail Often pattern.

PowerShell has a dedicated type of error that’s non-terminating. Its default is to continue past them. That applies in DSC configurations, too.

We can see this with a simple DSC configuration and the Write-Error command in a Script resource:

Configuration Headless {
    Import-DscResource -ModuleName PSDesiredStateConfiguration
    Node 'localhost' {
        Log Before {
            Message = 'Before error.'
        }
        Script NonTerminatingError {
            GetScript = {@{Result = ''}}
            SetScript = {Write-Error 'Non-terminating error.'}
            TestScript = {Return $false}
        }
        Log After {
            Message = 'After error.'
        }
    }
}
 
Headless -ConfigurationData $ConfigurationData
Start-DscConfiguration -Wait -Force -Verbose -Path '.\Headless\'

The middle resource generates an error but the last resource still runs:

...
VERBOSE: [VAGRANT]:                            [[Log]Before] Before error.
VERBOSE: [VAGRANT]: LCM:  [ End    Set      ]  [[Log]Before]  in 0.0000 seconds.
VERBOSE: [VAGRANT]: LCM:  [ End    Resource ]  [[Log]Before]
VERBOSE: [VAGRANT]: LCM:  [ Start  Resource ]  [[Script]NonTerminatingError]
VERBOSE: [VAGRANT]: LCM:  [ Start  Test     ]  [[Script]NonTerminatingError]
VERBOSE: [VAGRANT]: LCM:  [ End    Test     ]  [[Script]NonTerminatingError]  in 0.0780 seconds.
VERBOSE: [VAGRANT]: LCM:  [ Start  Set      ]  [[Script]NonTerminatingError]
VERBOSE: [VAGRANT]:                            [[Script]NonTerminatingError] Performing the 
operation "Set-TargetResource" on target "Executing the SetScript with the user supplied 
credential".
VERBOSE: [VAGRANT]: LCM:  [ End    Set      ]  [[Script]NonTerminatingError]  in 0.0470 seconds.
VERBOSE: [VAGRANT]: LCM:  [ Start  Resource ]  [[Log]After]
VERBOSE: [VAGRANT]: LCM:  [ Start  Test     ]  [[Log]After]
VERBOSE: [VAGRANT]: LCM:  [ End    Test     ]  [[Log]After]  in 0.0000 seconds.
VERBOSE: [VAGRANT]: LCM:  [ Start  Set      ]  [[Log]After]
VERBOSE: [VAGRANT]:                            [[Log]After] After error.
...

We can tell DSC to stop on non-terminating errors by passing the ErrorAction flag when we start our configuration:

Start-DscConfiguration -Wait -Force -Verbose -ErrorAction 'Stop' -Path '.\Headless\'

Now it stops on the Write-Error:

VERBOSE: [VAGRANT]:                            [[Log]Before] Before error.
VERBOSE: [VAGRANT]: LCM:  [ End    Set      ]  [[Log]Before]  in 0.0000 seconds.
VERBOSE: [VAGRANT]: LCM:  [ End    Resource ]  [[Log]Before]
VERBOSE: [VAGRANT]: LCM:  [ Start  Resource ]  [[Script]NonTerminatingError]
VERBOSE: [VAGRANT]: LCM:  [ Start  Test     ]  [[Script]NonTerminatingError]
VERBOSE: [VAGRANT]: LCM:  [ End    Test     ]  [[Script]NonTerminatingError]  in 0.0780 seconds.
VERBOSE: [VAGRANT]: LCM:  [ Start  Set      ]  [[Script]NonTerminatingError]
VERBOSE: [VAGRANT]:                            [[Script]NonTerminatingError] Performing the 
operation "Set-TargetResource" on target "Executing the SetScript with the user supplied 
credential".
 
Stderr from the command:
 
powershell.exe : Non-terminating error.

There’s also an $ErrorActionPreference variable, but that didn’t work no matter where I set it. The ErrorAction flag seems to be the way.

This isn’t necessarily a best practice, the PowerShell default is to continue past errors, but I usually have a better life if I switch it to stop.

Happy automating!

Adam

Need more than just this article? We’re available to consult.

You might also want to check out these related articles: