Sequentially Parallel

Why I Built EnvUtils

TL;DR You can find EnvUtils module on Github.

Before experimenting with using Windows as a daily driver for work, I used to use Ubuntu.1

One thing I like about the Unix shell is that you can prefix a command with an environment variable, allowing the environment value to be overridden solely for that specific command.

# assuming FAVOURITE_FRUIT was not set before
FAVOURITE_FRUIT=kiwi python -c 'import os; print(os.getenv("FAVOURITE_FRUIT))'
kiwi

This situation might be familiar to those who have used the express web framework, where setting NODE_ENV to production, for instance, allows validation of production settings.

NODE_ENV=production node index.js

The cool thing about this feature is that it will restore the variable to whatever value it had before you ran the command.

# let's set this to a value
export FAVOURITE_FRUIT=apple

FAVOURITE_FRUIT=kiwi python -c 'import os; print(os.getenv("FAVOURITE_FRUIT))'
kiwi

# the value should be restored
echo $FAVOURITE_FRUIT
apple

How about Windows?

While you cannot do the same thing on Windows, here is an attempt using cmd:

set FAVOURITE_FRUIT=kiwi & python -c "import os; print(os.getenv('FAVOURITE_FRUIT'))" & set FAVOURITE_FRUIT=
kiwi

And another using PowerShell:

$env:FAVOURITE_FRUIT='kiwi'; python -c 'import os; print(os.getenv("FAVOURITE_FRUIT"))'; $env:FAVOURITE_FRUIT=''

In both cases, we can get close to the Unix example, but it is important to note that we lose the ability to restore the variable to its previous value.

While you can modify the examples to achieve this functionality, they will become less convenient to type as one-liners and will also become less readable.

Exploring a solution using PowerShell

I was already experimenting with using PowerShell as my main shell, while occasionally using WSL as an escape hatch, for when I couldn’t be bothered to learn the proper way to do something.2

Looking at the PowerShell docs, there was a built in command that looked similar to what I wanted to achieve: Invoke-Command.

This cmdlet allowed you to run commands on a local or remote computer. What made me particularly interested in it, was the the -ScriptBlock parameter:

Invoke-Command -ScriptBlock { <# write your command here #> }

What if I could write something similar, that took the environment overrides as a parameter, much like our original example.

Building my own cmdlet

After learning enough PowerShell to become dangerous, I came up with the following:

# let's give it a value, to prove it restores the environment
$env:FAVOURITE_FRUIT = "apple"

# run the one-liner
Invoke-Environment -Environment @{ FAVOURITE_FRUIT = "kiwi" } { python -c "import os; print(os.getenv('FAVOURITE_FRUIT'))" }
kiwi

# check that the value was restored
$env:FAVOURITE_FRUIT
apple

Invoke-Environment covers all my use cases:

Making it work with dotenv files

After using Invoke-Environment a couple of times, I wanted to keep a record of all the environment overrides in a file, so that I could also use it on docker. For that, I had to support dotenv files.

Here is an illustration of a typical dotenv file usage with docker:

# filename: .env
LOG_LEVEL=debug
TARGET_URL=https://example.com

# you can inject the above dotenv file into a container
docker run --env-file .env <image>

And with Invoke-Environment:

Invoke-Environment -EnvironmentFile .env { <# write your command here #> }

Enabling support for the dotenv format involved writing a basic parser to convert the file into a PowerShell Hash Table. This was such a useful feature that I exposed it as a cmdlet:

$Environment = ConvertFrom-Environment .env
$Environment["LOG_LEVEL"]
debug

Make the environment last between commands

One last common pattern that I wanted to be able to do was the ability to source a dotenv file for a given shell session. This would enable the use of overrides across command invocations. I still aimed to keep the dotenv file format, and avoid needing to preface each line with a set solely for running call .env.

That being said, I added the following commands to support this workflow: New-Environment, Get-Environment, and Remove-Environment.

Here is an example workflow using these cmdlets:

# load the env into the shell session
New-Environment -EnvironmentFile .env

# run your commands
# command-1 arg1 agr2
# command-2 arg1

# you can view your session overrides at any point
Get-Environment

# you can restore the environment to the previous state
# by closing your shell or by running
Remove-Environment

Packaging it as a PowerShell module

You can find all the above cmdlets packaged as a module on Github.

Clone the project into a folder in your env:$PSModulePath to start using it, and let me know if you find any bugs.

I have kept the destructive commands well behaved e.g by supporting -WhatIf and -Confirm switches.

A PowerShell gallery will soon follow (possibly with a 1.0.0 release).


  1. Started as an experiment that never stopped. I will write more about this soon. ↩︎

  2. I rarely reach for WSL these days, unless I want to test something on an unix shell and I don’t want to start a container, or there’s something in coreutils that I really need. ↩︎