skip to content

set — Environment Variables

Set, display, and delete cmd.exe environment variables within the current session, perform integer arithmetic with /A, and read interactive user input with /P.

18 min read 115 snippets deep dive

set — Environment Variables#

What it is#

set is a built-in cmd.exe command that manages environment variables for the current command-prompt session. It can display all variables, create or update a variable, delete a variable, perform integer arithmetic (/A), and prompt the user for input (/P). Changes made with set last only for the lifetime of the current cmd.exe process — they do not persist after the window closes. To make a variable permanent (user- or system-wide), use setx instead.

Availability#

set is built into cmd.exe on every Windows version. PowerShell equivalents: $env:VAR, [System.Environment]::SetEnvironmentVariable().

set /?

Output:

Displays, sets, or removes cmd.exe environment variables.

SET [variable=[string]]
SET [variable]
SET /A expression
SET /P variable=[promptString]

Syntax#

set [variable[=value]]
set /A expression
set /P variable=[prompt]

Output: (varies by form — see sections below)

Essential options#

FormMeaning
setList all environment variables
set PREFIXList all variables whose name starts with PREFIX
set VAR=valueCreate or update VAR
set VAR=Delete VAR (empty right-hand side)
set /A VAR=exprEvaluate integer arithmetic expression and assign
set /P VAR=promptDisplay prompt and read one line from stdin into VAR

Listing variables#

Running set with no arguments prints every variable in the current environment in NAME=value form. Providing a prefix filters to matching names.

set

Output:

ALLUSERSPROFILE=C:\ProgramData
APPDATA=C:\Users\alicedev\AppData\Roaming
COMPUTERNAME=MYHOST
ComSpec=C:\Windows\system32\cmd.exe
...
set path

Output:

Path=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;...
PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC

Creating and updating variables#

set VAR=value assigns the string to the right of = verbatim — no quotes required (and including quotes makes them part of the value). Variable names are case-insensitive.

set GREETING=Hello, world!
echo %GREETING%

Output:

Hello, world!
set PROJECT=myapp
set BASE=C:\Projects
set OUTDIR=%BASE%\%PROJECT%\dist
echo %OUTDIR%

Output:

C:\Projects\myapp\dist

Deleting a variable#

Assigning an empty value removes the variable from the environment. There must be nothing after the =.

set GREETING=
set GREETING

Output:

Environment variable GREETING not defined

Arithmetic with /A#

/A evaluates a C-style integer expression and stores the result. Supports +, -, *, /, % (modulo), <<, >>, &, |, ^, ~, unary -. Variables inside the expression do not need % delimiters.

set /A RESULT=7 * 6
echo %RESULT%

Output:

42
set COUNT=10
set /A COUNT=COUNT+1
echo %COUNT%

Output:

11
set /A HEX=0xFF
echo %HEX%

Output:

255

Interactive input with /P#

/P prints a prompt string (without a newline) then pauses until the user presses Enter, storing everything typed into the variable.

set /P NAME=Enter your name: 
echo Hello, %NAME%!

Output:

Enter your name: Alice Dev
Hello, Alice Dev!

Substring expansion#

cmd.exe supports substring slicing via %VAR:~start,length%. Negative offsets count from the end. This is useful for extracting date parts from %DATE% or %TIME%.

set FULL=HelloWorld
echo %FULL:~0,5%

Output:

Hello
echo %FULL:~-5%

Output:

World
rem Extract year-month from %DATE% (depends on locale)
set YM=%DATE:~-4,4%-%DATE:~-10,2%
echo %YM%

Output:

2026-04

Search-and-replace expansion#

%VAR:find=replace% substitutes all occurrences of find with replace in the variable’s value.

set PATH_UNIX=/home/alice/projects/myapp
set PATH_WIN=%PATH_UNIX:/=\%
echo %PATH_WIN%

Output:

\home\alice\projects\myapp
rem Strip spaces from a variable
set PADDED=  hello  
set TRIMMED=%PADDED: =%
echo [%TRIMMED%]

Output:

[hello]

Variable scope and setx#

Changes made with set apply only to the current session; child processes inherit a copy at launch but cannot propagate changes back. setx writes to the registry for persistence across sessions.

rem Permanent user-level variable
setx MY_TOOL_HOME "C:\Tools\mytool"

Output:

SUCCESS: Specified value was saved.

Common pitfalls#

  1. Trailing spaces in set VAR=value — the space before the newline is included in the value; avoid them or quote the assignment.
  2. Quotes become part of the valueset VAR="hello" stores "hello" with the quotes; drop the outer quotes for a bare string.
  3. set changes are session-scoped — closing the window discards them; use setx for persistence.
  4. /A uses integer arithmetic only — floating-point is not supported; use PowerShell or a helper script for decimals.
  5. %VAR% expansion at parse time — inside an if or for block, the value is captured when the block is parsed, not when the line runs. Use setlocal enabledelayedexpansion and !VAR! for runtime expansion.
  6. set PREFIX lists, not readsset PATH prints all variables beginning with PATH, not just %PATH%; use echo %PATH% for a single variable.

Real-world recipes#

Build a timestamped output path#

set /A YEAR=%DATE:~-4,4%
rem Or use substring approach:
set STAMP=%DATE:~-4,4%%DATE:~-10,2%%DATE:~-7,2%
set OUTFILE=C:\Reports\report_%STAMP%.csv
echo Output: %OUTFILE%

Output:

Output: C:\Reports\report_20260428.csv

Interactive confirmation prompt in a batch script#

@echo off
set /P CONFIRM=Delete all temp files? [y/N]: 
if /i "%CONFIRM%"=="y" (
    del /Q /F C:\Temp\*
    echo Deleted.
) else (
    echo Aborted.
)

Output:

Delete all temp files? [y/N]: n
Aborted.

Accumulate a counter across a loop#

@echo off
setlocal enabledelayedexpansion
set COUNT=0
for %%f in (C:\Logs\*.log) do (
    set /A COUNT+=1
)
echo Found %COUNT% log files.

Output:

Found 7 log files.

Temporarily override PATH for a single build#

@echo off
set OLD_PATH=%PATH%
set PATH=C:\Tools\mingw64\bin;%PATH%
gcc -o build\hello src\hello.c
set PATH=%OLD_PATH%
echo PATH restored.

Output:

PATH restored.

set vs setx vs registry edits#

Three tools touch environment variables, each with a different scope and persistence model. Confusing them is the most common source of “I set it but it disappeared” frustration.

ToolScopePersistenceTakes effect
set VAR=valueCurrent cmd.exe onlyLifetime of the shellImmediately, this shell
setx VAR valueCurrent user (default)Permanent (registry)New shells only
setx VAR value /MSystem-widePermanent (registry)New shells; requires admin
reg add HKCU\Environment ...Current userPermanent (registry)New shells; needs broadcast
[Environment]::SetEnvironmentVariable(name, value, "User")Current user (PowerShell)PermanentNew shells only
[Environment]::SetEnvironmentVariable(name, value, "Machine")System-widePermanentNew shells; needs admin
[Environment]::SetEnvironmentVariable(name, value, "Process")Current processThis process onlyImmediately

The most important nuance: setx never affects the current shell. After setx FOO=bar, the original cmd.exe window still has the old value (or no value). Spawn a new shell to see the change.

setx GREETING "Hello permanent"
echo %GREETING%

Output:

SUCCESS: Specified value was saved.

(%GREETING% is still empty in this shell — open a new cmd to see it)

rem In a new cmd window
echo %GREETING%

Output:

Hello permanent

setx value-length limit#

setx truncates values longer than 1024 characters with no warning, which is a notorious problem when extending PATH blindly. Always check %PATH% length before piping it through setx.

echo %PATH% | find /c /v ""

Output:

1

(Hint: the character count is in %PATH% itself; use PowerShell for precision.)

($env:PATH).Length

Output:

1872

For PATH editing, prefer PowerShell [Environment]::SetEnvironmentVariable("Path", ..., "User") which has no length cap, or edit via “System Properties → Environment Variables” GUI.

# Safe permanent PATH append without truncation
$old = [Environment]::GetEnvironmentVariable("Path","User")
$new = $old + ";C:\Tools\mingw64\bin"
[Environment]::SetEnvironmentVariable("Path", $new, "User")

Output: (no console output; PATH updated for current user)

PowerShell equivalents#

PowerShell does not use set at all (which is reserved for Set-Variable, a different concept). Reading and writing environment variables uses the $env: drive or the static [Environment] class. Knowing both forms matters because they have different scopes.

cmd / batchPowerShell
set VAR=value$env:VAR = "value"
set VAR=Remove-Item Env:\VAR
echo %VAR%$env:VAR or Write-Output $env:VAR
set (list all)Get-ChildItem Env: or dir env:
set PREFIX (filter)Get-ChildItem Env:PREFIX*
setx VAR value[Environment]::SetEnvironmentVariable("VAR","value","User")
setx VAR value /M[Environment]::SetEnvironmentVariable("VAR","value","Machine")
set /A EXPR$result = expr (full PowerShell arithmetic)
set /P VAR=Prompt: $VAR = Read-Host "Prompt"
set "VAR=value with spaces"$env:VAR = "value with spaces"
%VAR:~0,5%$env:VAR.Substring(0,5)
%VAR:find=replace%$env:VAR -replace 'find','replace'
# Set a session variable
$env:GREETING = "Hello, world!"
$env:GREETING

Output:

Hello, world!
# Permanent user-level (writes registry; needs new shell to see)
[Environment]::SetEnvironmentVariable("GREETING", "Hello permanent", "User")

Output: (no output)

# Permanent machine-level (requires Administrator)
[Environment]::SetEnvironmentVariable("GREETING", "Hello system", "Machine")

Output: (no output; needs admin)

# Read all environment variables
Get-ChildItem Env: | Sort-Object Name

Output:

Name                Value
----                -----
ALLUSERSPROFILE     C:\ProgramData
APPDATA             C:\Users\alicedev\AppData\Roaming
...
# Remove a variable
Remove-Item Env:\GREETING

Output: (no output)

The three scopes of [Environment]::SetEnvironmentVariable#

The Target argument controls where the value is stored. Picking the wrong one is the cause of most “I set it but it didn’t stick” or “I set it and now PATH is duplicated” issues.

TargetStorageAffectsAdmin required
"Process"Current process memory onlyThis PowerShell session onlyNo
"User"HKCU\Environment registry keyNew shells of this userNo
"Machine"HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\EnvironmentNew shells of any userYes
# Three calls, three different effects:
[Environment]::SetEnvironmentVariable("API_KEY", "secret", "Process")
[Environment]::SetEnvironmentVariable("API_KEY", "secret", "User")
[Environment]::SetEnvironmentVariable("API_KEY", "secret", "Machine")

Output: (the third throws if not run as admin)

After a User or Machine update, currently running processes do not see the new value — they keep their inherited copy. To refresh without logging off, broadcast a WM_SETTINGCHANGE message:

# Force Explorer and new shells to pick up the change immediately
$signature = '[DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)] public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam, uint fuFlags, uint uTimeout, out UIntPtr lpdwResult);'
$type = Add-Type -MemberDefinition $signature -Name 'Win32SendMessage' -Namespace 'Win32Functions' -PassThru
$HWND_BROADCAST = [IntPtr]0xffff
$WM_SETTINGCHANGE = 0x1a
$result = [UIntPtr]::Zero
$type::SendMessageTimeout($HWND_BROADCAST, $WM_SETTINGCHANGE, [UIntPtr]::Zero, "Environment", 2, 5000, [ref]$result)

Output: (no visible output; future shells and Explorer see the new value)

Inspecting the registry directly#

Environment variables stored permanently live under two registry keys. Reading them with reg is sometimes faster than firing up PowerShell.

rem User-level variables
reg query HKCU\Environment

rem System-level variables
reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment"

Output:

HKEY_CURRENT_USER\Environment
    Path        REG_EXPAND_SZ    C:\Users\alicedev\AppData\Local\Programs\Python\Python313;...
    GREETING    REG_SZ           Hello permanent
    ...
rem Read a single user var
reg query HKCU\Environment /v GREETING

Output:

HKEY_CURRENT_USER\Environment
    GREETING    REG_SZ    Hello permanent
rem Write directly to the registry (does not broadcast WM_SETTINGCHANGE)
reg add HKCU\Environment /v FOO /t REG_SZ /d "bar" /f

Output:

The operation completed successfully.

Note: reg add skips the WM_SETTINGCHANGE broadcast, so other running apps will not see the change until restart. Always prefer setx or [Environment]::SetEnvironmentVariable for the broadcast.

Automatic / dynamic environment variables#

cmd.exe exposes several “magic” variables that are recomputed each time they are read. They are not stored anywhere — they are evaluated by the command processor itself.

VariableValue
%CD%Current directory (full path)
%DATE%Current date (locale-formatted)
%TIME%Current time
%RANDOM%Random integer 0-32767
%ERRORLEVEL%Exit code of last command
%CMDEXTVERSION%Cmd extensions version
%CMDCMDLINE%The original command line that started cmd
%HIGHESTNUMANODENUMBER%Highest NUMA node number
echo Current dir: %CD%
echo Time now:    %TIME%
echo Random:      %RANDOM%

Output:

Current dir: C:\Users\alicedev\Projects\myapp
Time now:    14:22:05.31
Random:      27842

Each call to %RANDOM% returns a different value, which is useful for unique temp filenames:

set TMP_FILE=C:\Temp\work_%RANDOM%_%RANDOM%.tmp
echo Using %TMP_FILE%

Output:

Using C:\Temp\work_27842_15193.tmp

Variable expansion edge cases#

%VAR% expansion happens at parse time. Inside for, if, and parenthesised blocks, the value is frozen when the block is parsed, not when each line executes. This is the single biggest source of batch-script bugs.

@echo off
set X=0
for /L %%i in (1,1,3) do (
    set /A X+=1
    echo X is now %X%
)
echo Final X is %X%

Output:

X is now 0
X is now 0
X is now 0
Final X is 3

The fix is setlocal enabledelayedexpansion plus !X! instead of %X%:

@echo off
setlocal enabledelayedexpansion
set X=0
for /L %%i in (1,1,3) do (
    set /A X+=1
    echo X is now !X!
)
echo Final X is !X!

Output:

X is now 1
X is now 2
X is now 3
Final X is 3

Quoting tricks for whitespace and special characters#

To assign a value containing trailing whitespace, equals signs, or special characters, quote both the name and value together:

rem Wrong — quotes are part of the value
set NAME="Alice"
echo "[%NAME%]"

Output:

["Alice"]
rem Right — the entire assignment is quoted
set "NAME=Alice"
echo [%NAME%]

Output:

[Alice]
rem Trailing spaces preserved
set "PROMPT_TEXT=Enter: "
set /P RESPONSE=%PROMPT_TEXT%

Output:

Enter:

The "VAR=value" form is the safest for assignments containing spaces, equals signs, or trailing whitespace — cmd.exe parses the quotes as delimiters and does not include them in the value.

setlocal and endlocal#

setlocal introduces a new variable scope; endlocal pops it. Any set calls between them are discarded when endlocal executes (or the script ends). This is essential for batch helpers that should not leak variables to the caller.

@echo off
set OUTSIDE=before

setlocal
set OUTSIDE=changed
set INSIDE=inner
echo Inside: OUTSIDE=%OUTSIDE%, INSIDE=%INSIDE%
endlocal

echo Outside: OUTSIDE=%OUTSIDE%, INSIDE=[%INSIDE%]

Output:

Inside: OUTSIDE=changed, INSIDE=inner
Outside: OUTSIDE=before, INSIDE=[]

To leak a single value out of a setlocal block, use the for /F trick:

@echo off
setlocal
set COMPUTED=hello-%RANDOM%
(endlocal & set RESULT=%COMPUTED%)
echo Result outside: %RESULT%

Output:

Result outside: hello-27842

setlocal enableextensions#

Command extensions (enabled by default since Windows 2000) provide set /A, set /P, the %~ expansion modifiers, and many other features. To explicitly require them:

@echo off
setlocal enableextensions
if errorlevel 1 (
    echo Extensions required — exiting.
    exit /b 1
)
set /A SUM=2+2
echo %SUM%

Output:

4

To enable both extensions and delayed expansion in one line:

setlocal enableextensions enabledelayedexpansion

Output: (none — exits 0 on success)

set /A operator reference#

set /A evaluates a C-style integer expression. All operators are 32-bit signed; overflow wraps. Floating-point is not supported.

OperatorMeaningExample
+ - * /Arithmeticset /A R=10/3 → 3
%Moduloset /A R=10%%3 → 1 (in cmd, escape % as %%)
( )Groupingset /A R=(2+3)*4 → 20
<< >>Bit shiftsset /A R=1<<4 → 16
&Bitwise ANDset /A R=12&10 → 8
``Bitwise OR
^Bitwise XORset /A R=12^^10 → 6 (escape ^)
~Bitwise NOTset /A R=~0 → -1
!Logical NOTset /A R=!0 → 1
,Statement separatorset /A A=1, B=2, R=A+B
= += -= *= /= %= &= `= ^= <<= >>=`Compound assignment
0x prefixHexadecimal literalset /A R=0xFF → 255
0 prefixOctal literalset /A R=010 → 8
set /A R=(100 + 50) / 5
echo %R%

Output:

30
set /A FLAGS=0x01 ^| 0x04
echo %FLAGS%

Output:

5

Substring and replacement modifier reference#

FormEffect
%VAR%Full value
%VAR:~N%Substring from offset N to end
%VAR:~N,L%Substring from offset N, length L
%VAR:~-N%Substring of last N characters
%VAR:~N,-M%From offset N to M characters before end
%VAR:find=replace%Replace all occurrences
%VAR:*find=replace%Replace from start up to (and including) find
set S=HelloWorld

echo %S:~0,5%
echo %S:~5%
echo %S:~-5%
echo %S:~2,3%
echo %S:World=Friend%

Output:

Hello
World
World
llo
HelloFriend

For dynamically computed start/length, use a helper variable:

set S=abcdefghij
set /A POS=3
set /A LEN=4
call set CHUNK=%%S:~%POS%,%LEN%%%
echo %CHUNK%

Output:

defg

Argument expansion modifiers (%~)#

In batch scripts and inside for /F blocks, you can apply path-component modifiers to %0, %1, …, %9 and for loop variables (%%i, %%j).

ModifierMeaning
%~1Remove surrounding quotes from %1
%~f1Full path
%~d1Drive letter
%~p1Path (without drive)
%~n1File name
%~x1Extension (with leading dot)
%~s1Short (8.3) path
%~a1File attributes
%~t1Last-modified timestamp
%~z1File size in bytes
%~dp1Drive + path (most useful for “directory of the script”)
%~nx1Name + extension
%~ftza1Combine modifiers
@echo off
echo Script: %~nx0
echo Dir:    %~dp0
echo First arg full path: %~f1

Output (if invoked as myscript.bat C:\file.txt):

Script: myscript.bat
Dir:    C:\Scripts\
First arg full path: C:\file.txt

A script that needs to reliably reference files relative to itself uses %~dp0:

@echo off
set SCRIPT_DIR=%~dp0
echo Config at: %SCRIPT_DIR%config.ini

Output:

Config at: C:\Scripts\config.ini

Secrets in environment variables — the security caveat#

Environment variables are visible to every child process and to anyone who can read your registry. They are not a secure secret store. For real secrets:

  • Use Windows Credential Manager (cmdkey, Get-Credential).
  • Use DPAPI-protected files (ConvertTo-SecureString).
  • Use a dedicated secret manager like Azure Key Vault, Hashicorp Vault, 1Password CLI.
# Safer than environment variable: write encrypted to disk
"my-api-key" | ConvertTo-SecureString -AsPlainText -Force |
    ConvertFrom-SecureString | Out-File "$env:USERPROFILE\.api_key.enc"

# Read back later
$key = Get-Content "$env:USERPROFILE\.api_key.enc" |
    ConvertTo-SecureString |
    ForEach-Object { [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($_)) }

Output: (no console output; secret encrypted at rest per user)

For dev workflows that must use environment variables (Node.js, Python, Docker), at least never commit a .env file and prefer dotenv-vault or similar.

Common pitfalls (continued)#

  1. setx does not affect the current shell — open a new cmd to see the change. The most-asked Stack Overflow question about Windows env vars is this one.
  2. setx truncates at 1024 characters — silent truncation when extending PATH; check with echo %PATH% | find /c /v "" or use PowerShell.
  3. set VAR =value with space before = — the variable is named VAR (with a trailing space), which is unreadable from %VAR%. Use the set "VAR=value" form.
  4. PowerShell Set-Variable is unrelatedSet-Variable manipulates PowerShell variables; environment variables use $env: or [Environment].
  5. Environment vars are not secrets — child processes inherit them; never put real secrets in set or setx.
  6. set writes to current process; setx writes to registry — they do not communicate. Run both if you want both immediate effect and persistence.
  7. %PATH% corruption on bad setxsetx PATH "%PATH%;C:\New" /M can pollute PATH because %PATH% is expanded with the current process’s already-merged user+machine PATH; the resulting machine PATH ends up containing user entries. Always edit the registry value separately for user and machine.

Real-world recipes (continued)#

Safe permanent PATH append (PowerShell)#

The single most common environment-variable task done wrong. This recipe preserves the existing user PATH, deduplicates, and broadcasts the change.

function Add-UserPath {
    param([Parameter(Mandatory)][string]$Path)
    $current = [Environment]::GetEnvironmentVariable("Path","User")
    $entries = $current -split ';' | Where-Object { $_ -and $_ -ne $Path }
    $new = ($entries + $Path) -join ';'
    [Environment]::SetEnvironmentVariable("Path", $new, "User")
    Write-Host "Added to user PATH: $Path"
}

Add-UserPath -Path "C:\Tools\mingw64\bin"

Output:

Added to user PATH: C:\Tools\mingw64\bin

(Open a new shell to use it; or update $env:Path in the current session too.)

Load a .env file into the current cmd session#

A small loader that parses KEY=value lines into the environment.

@echo off
for /F "usebackq tokens=1,* delims==" %%a in (".env") do (
    if not "%%a"=="" if not "%%a:~0,1%"=="#" set "%%a=%%b"
)
echo Loaded vars from .env

Output:

Loaded vars from .env

PowerShell version#

Get-Content .env | ForEach-Object {
    if ($_ -match '^\s*([^#=][^=]*)=(.*)$') {
        Set-Item "Env:$($Matches[1].Trim())" $Matches[2].Trim()
    }
}

Output: (none; environment variables set from .env)

Generate a timestamped filename#

%DATE% is locale-dependent — extract digits with substring slicing. The WMIC approach below is locale-proof.

@echo off
for /F "tokens=2 delims==" %%I in ('wmic os get localdatetime /value') do set DT=%%I
set STAMP=%DT:~0,8%_%DT:~8,6%
set OUTFILE=report_%STAMP%.csv
echo Output: %OUTFILE%

Output:

Output: report_20260525_142205.csv

Push and pop PATH for an isolated build#

A reusable batch wrapper that prepends a tool directory, runs the build, then restores PATH cleanly.

@echo off
setlocal
set "PATH=C:\Tools\mingw64\bin;%PATH%"
set "PATH=C:\Tools\cmake\bin;%PATH%"
cmake -S . -B build
cmake --build build
endlocal
echo PATH restored automatically by endlocal.

Output:

PATH restored automatically by endlocal.

Pure-PowerShell environment snapshot#

Dump current environment to JSON for archival or diffing.

Get-ChildItem Env: | Select-Object Name, Value |
    ConvertTo-Json | Out-File "env_$(Get-Date -Format yyyyMMdd_HHmmss).json"

Output: (no console output; JSON file written)

# Diff two snapshots
$a = Get-Content env_20260524_120000.json | ConvertFrom-Json
$b = Get-Content env_20260525_120000.json | ConvertFrom-Json
Compare-Object $a $b -Property Name, Value

Output:

Name      Value           SideIndicator
----      -----           -------------
NEW_VAR   added today     =>

Cross-shell variable transfer (cmd → PowerShell)#

Setting a variable in cmd and then reading it in a PowerShell session: spawn PowerShell from inside the same cmd so it inherits the environment.

@echo off
set BUILD_ID=2026-05-25_001
powershell -NoProfile -Command "Write-Host 'PowerShell saw BUILD_ID =' $env:BUILD_ID"

Output:

PowerShell saw BUILD_ID = 2026-05-25_001

The reverse (PowerShell setting a variable for a downstream cmd) works the same way — invoke cmd /c from inside PowerShell with $env:VAR already set.

$env:BUILD_ID = "2026-05-25_001"
cmd /c "echo cmd saw BUILD_ID=%BUILD_ID%"

Output:

cmd saw BUILD_ID=2026-05-25_001

See also#

Sources#