I'm surprised that I didn't get the answer for this common scenario after searching the internet for a while.
How can I set an environment variable in PowerShell if it does not already exist?
The following code defines environment variable FOO for the current process, if it doesn't exist yet.
if ($null -eq $env:FOO) { $env:FOO = 'bar' }
# If you want to treat a *nonexistent* variable the same as
# an existent one whose value is the *empty string*, you can simplify to:
if (-not $env:FOO) { $env:FOO = 'bar' }
# Alternatively, via the Env: *drive*:
if (-not (Test-Path env:FOO)) { $env:FOO = 'bar' }
# Or even (quietly fails if the variable already exists):
New-Item -ErrorAction Ignore env:FOO -Value bar
In PowerShell (Core) 7.1+, which has null-coalescing operators, you can simplify to:
$env:FOO ??= 'bar'
Note:
Environment variables are strings by definition. If a given environment variable is defined, but has no value, its value is the empty string ('') rather than $null. Thus, comparing to $null can be used to distinguish between an undefined environment variable and one that is defined, but has no value.
However, note that assigning to environment variables in PowerShell / .NET prior to v7.5 / .NET 9 made no distinction between $null and '', and either value results in undefining (removing) the target environment variable (in v7.5+ / .NET 9+, assigning '' now creates an empty env. var.); similarly, in cmd.exe set FOO= results in removal/non-definition of variable FOO, and the GUI dialog (accessible via sysdm.cpl) doesn't allow you to define a variable with an empty string either. However, the Windows API (SetEnvironmentVariable) does permit creating environment variables that contain the empty string, albeit in-process only.
On Unix-like platforms, empty-string values are allowed too, and the native, POSIX-compatible shells (e.g, bash and /bin/sh) - unlike PowerShell - also allow you to create them (e.g, export FOO=). Note that environment variable definitions and lookups are case-sensitive on Unix, unlike on Windows.
Note: If the environment variable is created on demand by the assignment above ($env:FOO = ...), it will exist for the current process and any child processes it creates only Thanks, PetSerAl.
The following was mostly contributed by Ansgar Wiechers, with a supplement by Mathias R. Jessen:
On Windows[*], if you want to define an environment variable persistently, via the registry, you need to use the static SetEnvironmentVariable() method of the [System.Environment] class:
# user environment
[Environment]::SetEnvironmentVariable('FOO', 'bar', 'User')
# system environment (requires elevation, i.e. admin privileges)
[Environment]::SetEnvironmentVariable('FOO', 'bar', 'Machine')
Note that these definitions take effect in future sessions (processes), so in order to define the variable for the current process as well, run $env:FOO = 'bar' in addition, which is effectively the same as [Environment]::SetEnvironmentVariable('FOO', 'bar', 'Process').
When using [Environment]::SetEnvironmentVariable() with User or Machine, a WM_SETTINGCHANGE message is sent to other applications to notify them of the change (though few applications react to such notifications).
This doesn't apply when targeting Process (or when assigning to $env:FOO), because no other applications (processes) can see the variable anyway.
See also: Creating and Modifying Environment Variables (TechNet article).
[*] On Unix-like platforms, attempts to target the persistent scopes - User or Machine- are quietly ignored, as of .NET (Core) 7, and this non-support for defining persistent environment variables is unlikely to change, given the lack of a unified mechanism across Unix platforms.
Code
function Set-LocalEnvironmentVariable {
param (
[Parameter()]
[System.String]
$Name,
[Parameter()]
[System.String]
$Value,
[Parameter()]
[Switch]
$Append
)
function Write-MyMessage {
param(
[System.String]
$VarName
)
WWrite-Host "Local Environment variable " -ForegroundColor DarkYellow -NoNewline
Write-Host "`"$VarName`"" -NoNewline -ForegroundColor Yellow
Write-Host " ➡ " -ForegroundColor DarkYellow -NoNewline
try {
Write-Host "`"$((Get-Item env:$VarName).Value)`"" -ForegroundColor Yellow
}
catch {
Write-Host """""" -ForegroundColor Yellow
}
}
if ($Append.IsPresent) {
if (Test-Path "env:$Name") {
$Value = (Get-Item "env:$Name").Value + $Value
}
}
New-Item env:$Name -Value "$value" -Force | Out-Null
if ($PSBoundParameters.Verbose.IsPresent) {
Write-MyMessage -VarName $Name
}
}
function Set-PersistentEnvironmentVariable {
param (
[Parameter()]
[System.String]
$Name,
[Parameter()]
[System.String]
$Value,
[Parameter()]
[Switch]
$Append
)
function Write-MyMessage {
param(
[System.String]
$VarName
)
Write-Host "Persistent Environment variable " -NoNewline -ForegroundColor DarkYellow
Write-Host "`"$VarName`"" -NoNewline -ForegroundColor Yellow
Write-Host " ➡ " -NoNewline -ForegroundColor DarkYellow
try {
Write-Host "`"$((Get-Item env:$VarName).Value)`"" -ForegroundColor Yellow
}
catch {
Write-Host """""" -ForegroundColor Yellow
}
}
Set-LocalEnvironmentVariable -Name $Name -Value $Value -Append:$Append
if ($Append.IsPresent) {
$value = (Get-Item "env:$Name").Value
}
if ($IsWindows) {
setx "$Name" "$Value" | Out-Null
if ($PSBoundParameters.Verbose.IsPresent) {
Write-MyMessage -VarName $Name
}
return
}
if ($IsLinux -or $IsMacOS) {
$pattern = "\s*export\s+$name=[\w\W]*\w*\s+>\s*\/dev\/null\s+;\s*#\s*$Name\s*"
$files = @("~/.bashrc", "~/.zshrc", "~/.bash_profile", "~/.zprofile")
$files | ForEach-Object {
if (Test-Path -Path $_ -PathType Leaf) {
$content = [System.IO.File]::ReadAllText("$(Resolve-Path $_)")
$content = [System.Text.RegularExpressions.Regex]::Replace($content, $pattern, [System.Environment]::NewLine);
$content += [System.Environment]::NewLine + "export $Name=$Value > /dev/null ; # $Name" + [System.Environment]::NewLine
[System.IO.File]::WriteAllText("$(Resolve-Path $_)", $content)
}
}
if ($PSBoundParameters.Verbose.IsPresent) {
Write-MyMessage -VarName $Name
}
return
}
throw "Invalid platform."
}
On Windows you can use:
On Linux we can add export VARIABLE_NAME=Variable value to file ~/.bash_profile. For a new bash terminal the process execute these instructions located in ~/.bash_profile.
On MacOS similiar to Linux but if you have zsh terminal the file is .zprofile, if the default terminal is bash, the file is .bash_profile. In my function code we need to add detection of default terminal if you wish. I assume that default terminal is zsh.
Examples
#Set "Jo" value to variable "NameX", this value is accesible in current process and subprocesses, this value is accessible in new opened terminal.
Set-PersistentEnvironmentVariable -Name "NameX" -Value "Jo"; Write-Host $env:NameX
#Append value "ma" to current value of variable "NameX", this value is accesible in current process and subprocesses, this value is accessible in new opened terminal.
Set-PersistentEnvironmentVariable -Name "NameX" -Value "ma" -Append; Write-Host $env:NameX
#Set ".JomaProfile" value to variable "ProfileX", this value is accesible in current process/subprocess.
Set-LocalEnvironmentVariable "ProfileX" ".JomaProfile"; Write-Host $env:ProfileX
Output
References
Check About Environment Variables
Shell initialization files
ZSH: .zprofile, .zshrc, .zlogin - What goes where?