env.dev

Windows Environment Variables: setx, set & PowerShell

set is session-only, setx persists but truncates at 1,024 chars, PowerShell has $env: and [Environment]. User vs system scope, PATH pitfalls, GUI editor.

By env.dev Updated

Windows gives you three different mechanisms for environment variables: set (cmd, current session only), setx (persistent, but silently truncates values at 1,024 characters), and PowerShell's $env: drive plus the [Environment] .NET API. Pick the wrong one and the standard outcomes are a variable that vanishes when the window closes — or a destroyed PATH: setx PATH merges the system and user paths, cuts the result at 1,024 characters, prints SUCCESS anyway, and writes the damage to the registry. Even GitHub Desktop's installer shipped that bug (desktop/desktop#18176).

TL;DR

  • set and $env: last until the window closes; setx and [Environment]::SetEnvironmentVariable persist in the registry.
  • Never touch PATH with setx — it truncates at 1,024 characters and reports SUCCESS. Use the GUI editor or PowerShell's [Environment] API.
  • Persistent changes only reach new processes. Open a new terminal — and fully restart Windows Terminal or VS Code, since their tabs inherit from the parent process.
  • User scope writes HKCU\Environment (no admin needed); Machine scope writes HKLM and requires an elevated prompt.
  • setx cannot delete a variable — remove it with [Environment]::SetEnvironmentVariable('NAME', $null, 'User').

How do you set an environment variable in cmd?

set affects the current cmd session and the processes it launches — nothing else, and nothing after the window closes:

text
:: Session-only — gone when this window closes
set API_KEY=abc123

:: Read it back
echo %API_KEY%

:: List everything (or filter by prefix)
set
set API

:: Inline-for-one-command needs two statements in cmd:
set DEBUG=1&& node app.js

Two traps. First, spaces are literal: set FOO = bar defines a variable named FOO  (trailing space) with value  bar — write set FOO=bar with no spaces around =; the same rule is why the chained command above has no space before &&set DEBUG=1 && would set DEBUG to with a trailing space. Second, the Unix prefix form FOO=bar command does not exist in cmd at all; it produces the infamous "is not recognized" error that the NODE_NO_WARNINGS troubleshooting guide dissects.

What does setx do — and when does it bite?

setx writes the value to the registry so future processes inherit it. User scope by default; /M targets the machine scope and needs an elevated prompt:

text
:: Persist for the current user (takes effect in NEW terminals only)
setx API_KEY "abc123"

:: Persist machine-wide — requires "Run as administrator"
setx /M JAVA_HOME "C:\Program Files\Eclipse Adoptium\jdk-21"

:: Note: setx does NOT update the current session.
:: %API_KEY% is still undefined in this window.

The 1,024-character truncation trap

setx hard-caps stored values at 1,024 characters. Worse, the classic append idiom setx PATH "%PATH%;C:\new\bin" expands %PATH% to the combined machine + user path, truncates that merge to 1,024 characters, and writes it all into the user value — duplicating system entries, deleting whatever fell past the limit, and printing WARNING: The data being saved is truncated to 1024 characters. SUCCESS: Specified value was saved. A modern dev machine's combined PATH easily exceeds 1,024 characters, so this destroys real entries. GitHub Desktop's installer did exactly this to users' PATHs before the fix in issue #18176. There is no undo beyond a registry backup or restore point. Never use setx for PATH.

How do you set environment variables in PowerShell?

PowerShell separates the two jobs cleanly: $env: for the session, the [Environment] .NET class for persistence:

powershell
# Session-only
$env:API_KEY = 'abc123'

# Inline for one command
$env:DEBUG = '1'; node app.js

# Persist for the current user — no length truncation, no admin needed
[Environment]::SetEnvironmentVariable('API_KEY', 'abc123', 'User')

# Persist machine-wide (elevated prompt required)
[Environment]::SetEnvironmentVariable('JAVA_HOME', 'C:\jdk-21', 'Machine')

# Read a specific scope (the $env: drive only shows the merged process view)
[Environment]::GetEnvironmentVariable('API_KEY', 'User')

# Delete from a scope
[Environment]::SetEnvironmentVariable('API_KEY', $null, 'User')

SetEnvironmentVariable with a scope writes the same registry keys the GUI does and broadcasts the WM_SETTINGCHANGE message — but without setx's truncation. It is the only scriptable method worth using for PATH. Note that persisting a variable does not set it in your current session; do both if you need it immediately.

What is the difference between user and system scope?

Persistent variables live in two registry locations:

ScopeRegistry keyAdmin?Applies to
UserHKCU\EnvironmentNoYour account only
System (Machine)HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\EnvironmentYesAll users + services

When the same name exists in both, the user value wins — except PATH, which is special-cased: Windows concatenates system PATH first, then user PATH. Default to user scope; reserve system scope for variables a service or another account genuinely needs, because every system change also forces an elevation prompt and affects users you may not be thinking about.

How do you edit PATH safely?

The GUI editor is the safest tool Windows ships for this — it shows one entry per row, has no length cliff, and separates user from system cleanly. Open it directly:

powershell
# Straight to "Environment Variables" (user + system)
rundll32.exe sysdm.cpl,EditEnvironmentVariables

# Or: Win key -> type "environment" ->
# "Edit environment variables for your account" (no admin)
# "Edit the system environment variables"      (admin)

For scripts, the read-modify-write pattern against a single scope:

powershell
# Append to USER PATH without touching the system half
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
if ($userPath -notlike '*C:\tools\bin*') {
  [Environment]::SetEnvironmentVariable('Path', "$userPath;C:\tools\bin", 'User')
}

The idempotency check matters: installers that blindly append on every run are why PATHs balloon toward the limit that makes setx destructive in the first place.

Why doesn't my new variable show up in the terminal?

Every Windows process receives a copy of its parent's environment block at creation — there is no live link to the registry. After a persistent change, Explorer is told via the WM_SETTINGCHANGE broadcast, so programs launched from the Start menu pick up the new value. Anything already running keeps its stale copy. The practical consequences:

  • Windows Terminal tabs are not enough. New tabs are children of the same WindowsTerminal.exe process and inherit its old block — quit the whole app and relaunch.
  • VS Code's integrated terminal inherits from VS Code. Restart VS Code itself (a full window reload), not just the terminal panel.
  • Services and Docker Desktop read machine scope at their own start — restart the service, not the console. The same start-time snapshot rule shapes how Docker on Windows containers see host variables.
  • No reboot is required for normal use — relaunching the process chain is sufficient. Reboot only when something at session level (e.g. the shell that launches everything) refuses to refresh.

Which method should you use?

One-off for a command or session

set X=1 (cmd) or $env:X = 1 (PowerShell)

Persist a normal variable, scripted

[Environment]::SetEnvironmentVariable('X', '1', 'User')

Edit PATH by hand

rundll32 sysdm.cpl,EditEnvironmentVariables

Quick persist of a short value, no script

setx X "1" — fine under 1,024 chars, never for PATH

For project configuration — API keys, database URLs, per-repo settings — persistent machine variables are the wrong layer entirely. A .env file loaded by your runtime keeps config scoped to the project and out of the registry; see the Node.js env variables guide and the .env file guide for that pattern.

When should you not use persistent environment variables?

  • Secrets — registry values are plaintext, readable by any process in scope, and captured by backup tools. Use Windows Credential Manager, a secrets manager, or per-project .env files that never leave the repo directory.
  • Per-project config — a global DATABASE_URL guarantees the wrong project reads it eventually. Scope config to the project with .env files or direnv-style tooling.
  • Anything a script sets repeatedly — unconditional appends and rewrites are how PATH rot happens. Persistent writes belong in installers and one-time setup, guarded by an existence check.

Frequently Asked Questions

What is the difference between set and setx on Windows?

set changes the variable in the current cmd session only and is lost when the window closes. setx writes the value to the registry so all future processes inherit it — but it does not update the current session, and it truncates values to 1,024 characters. They complement each other; neither does both jobs.

Why did setx destroy my PATH?

setx PATH "%PATH%;..." expands %PATH% to the combined system + user path, truncates the result to 1,024 characters, writes it into the user PATH, and still prints SUCCESS after the truncation WARNING. Entries past the limit are permanently lost unless you have a restore point or registry backup. Edit PATH via the GUI or [Environment]::SetEnvironmentVariable instead.

How do I set a permanent environment variable in PowerShell?

[Environment]::SetEnvironmentVariable('NAME', 'value', 'User') persists for your account; use 'Machine' as the scope (in an elevated prompt) for system-wide. Unlike setx there is no 1,024-character truncation. Also set $env:NAME for the current session if you need the value immediately.

Why does my new environment variable not work until I open a new terminal?

Processes copy their environment from the parent at creation; registry changes only reach processes started afterwards. New tabs in Windows Terminal and new panels in VS Code inherit from the already-running parent app, so fully restart the application — but a reboot is normally unnecessary.

How do I delete an environment variable permanently on Windows?

setx has no delete option — it can only write values. Use [Environment]::SetEnvironmentVariable('NAME', $null, 'User') (or 'Machine'), remove the value in the GUI editor, or delete it from HKCU\Environment with reg delete. Restart affected apps afterwards.

User or system scope — which should I pick?

User scope, unless a Windows service or another account must read the value. User variables need no admin rights, live in HKCU\Environment, and override same-named system variables — except PATH, where Windows concatenates system PATH first, then user PATH.

References

Hitting "is not recognized" when copying Unix commands? The inline env vars on Windows guide covers cmd vs PowerShell vs cross-env, and the env validator lints your .env files.

Was this helpful?

Frequently Asked Questions

What is the difference between set and setx on Windows?

set changes the variable in the current cmd session only and is lost when the window closes. setx writes the value to the registry so all future processes inherit it — but it does not update the current session, and it truncates values to 1,024 characters. They complement each other; neither does both jobs.

Why did setx destroy my PATH?

setx PATH "%PATH%;..." expands %PATH% to the combined system + user path, truncates the result to 1,024 characters, writes it into the user PATH, and still prints SUCCESS after the truncation WARNING. Entries past the limit are permanently lost unless you have a restore point or registry backup. Edit PATH via the GUI or [Environment]::SetEnvironmentVariable instead.

How do I set a permanent environment variable in PowerShell?

[Environment]::SetEnvironmentVariable('NAME', 'value', 'User') persists for your account; use 'Machine' as the scope (in an elevated prompt) for system-wide. Unlike setx there is no 1,024-character truncation. Also set $env:NAME for the current session if you need the value immediately.

Why does my new environment variable not work until I open a new terminal?

Processes copy their environment from the parent at creation; registry changes only reach processes started afterwards. New tabs in Windows Terminal and new panels in VS Code inherit from the already-running parent app, so fully restart the application — but a reboot is normally unnecessary.

How do I delete an environment variable permanently on Windows?

setx has no delete option — it can only write values. Use [Environment]::SetEnvironmentVariable('NAME', $null, 'User') (or 'Machine'), remove the value in the GUI editor, or delete it from HKCU\Environment with reg delete. Restart affected apps afterwards.

User or system scope — which should I pick?

User scope, unless a Windows service or another account must read the value. User variables need no admin rights, live in HKCU\Environment, and override same-named system variables — except PATH, where Windows concatenates system PATH first, then user PATH.

Stay up to date

Get notified about new guides, tools, and cheatsheets.