First of all, some prerequisites: if you’re working on Windows, you must install the following things immediately:

  • Windows 11
  • PowerShell 7
  • Windows Terminal

This is the standard Windows power user environment. Under no circumstances should you still be using Windows 10, Windows PowerShell (which stops at 5.1 and is a distinct product from PowerShell 6+), or the Command Prompt.

Everything Old is New Again Link to heading

As Microsoft desperately tries to curry favour with a developer community that has hated it with the heat of a million burning suns for thirty years, they’re adopting more and more Unix/POSIXisms in the way C#/.NET code is configured and executed. This is probably inevitable given that .NET code now has to run on POSIX-compliant operating systems, but it means that .NET developers have on choice but to learn a bunch of concepts that until now Microsoft has been hiding behind Visual Studio.

Shells and Command Lines Link to heading

The official wikipedia definition is here but the short version is that the shell is the thing that interacts with you and tells the underlying OS what you want to do. The shell can spawn other programs; the shell can spawn another instance of a shell (called a sub-shell). The Windows Desktop is a shell; when you double-click a program icon the shell spawns that program.

The Command Prompt is also a shell - a command-line one. Unlike the Windows Desktop, the Command Prompt (cmd.exe) can spawn copies itself as a sub-shell: open Command Prompt and type cmd.exe and you’ll get a sub-shell.

The Command Prompt is obsolete, rudimentary, and only included for backwards compatibility. Install Windows Terminal and PowerShell 7 and use it to start processes from the command line instead.

In addition to sub-shells, you can have multiple shell programs running at the same time. If you start multiple PowerShell windows from the Windows Desktop, each of those PowerShell sessions will inherit the environment variables set in the Desktop, but will not share environment variables with each other - because none of them are sub-shells of another PowerShell session.

Of course, you can start a PowerShell session as a sub-shell from an existing PowerShell session - type pwsh from the PowerShell window.

Environment Variables Link to heading

Environment variables are user-definable values that are accessible by a process. They are called “environment” variables because they are specific to, and scoped to, the environment in which the process runs.

And because “environment” is so bloody overused in this field: “Environment” means the specific memory space allocated to that process by the operating system

When you start a process from a shell, any shell variables set in the shell are inherited by the process in the process' environment. In Windows just about every process, from user applications to Windows Services, is started by some kind of shell or shell-like parent.

What this means is that you can set the environment variables seen by a process by setting those variables in a shell, and then starting that process from that shell.

Things are more complicated for Windows Services and other non-user-interactive programs, but the principle is the same

PowerShell Variables Aren’t Environment Variables Link to heading

PowerShell is a shell with a powerful scripting language, and Microsoft decided to keep PowerShell variables in a different space than environment variables. In PowerShell, environment variables are kept in a virtual drive named env:.

For instance, if you do this in a PowerShell session:

$MyVariable = "variable text"

you’ve created a [string] typed variable that can be used by other PowerShell commands, but it’s not an environment variable - other processes you start from that PowerShell session won’t see the variable or its value.

To set a true environment variable, you have to use the following syntax:

$env:MyVariable = "variable text"

This tells the operating system that MyVariable is an environment variable and should be exposed to any child processes.

Why Should You Care Link to heading

Increasingly .NET programs need to be able to run in wildly different deployment modes: locally on a developer’s workstation using the built-in Kestrel HTTP server, OutOfProcess behind an IIS server, in an OCI container, or on a CI server as part of a test run. Using environment variables to configure an ASP.NET application is one of the most common patterns.

CI servers, Azure App Services, etc. all have their own syntax for passing envrionment variables to a hosted ASP.NET app - consult their documentation - but for the case where you have an ASP.NET app that you want to run locally without IIS, you can set the environment variables this way in PowerShell:

$env:ConnectionTimeout = '20' # all environment variables are strings
$env:ConnectionStrings:SqlDatabase = 'sql:tcp-...' # Note you can use : in the variable name and value

dotnet run WeatherApi.csproj