Powershell environment variable - powershell

In a Powershell script (.ps1)
Launched from the command line of a console (cmd.exe)
How can set and environment variable in the console,
so that when the Powershell script ends processing,
and exits to the console where was invoked...
the environment variable exists,
and can be read by a batch file,
or viewed with the SET command ?
do not want to set a 'Machine' or a 'User' variable...
only a console process variable...
the same variable you get if you use SET in the console

To run a PowerShell script from cmd.exe invariably requires a (powershell.exe / pwsh.exe) child process, and child processes fundamentally cannot set environment variables for their parent process[1].
Your best bet is to have your *.ps1 file output the name and value of the desired environment variable and then have the calling cmd.exe process create it, based on that output.
Security note: Blindly defining environment variables based on the name-value pairs output by another command (a *.ps1 script, in your case) should only be done if you trust that command not to output malicious definitions.
Here's a simple example (run directly from an interactive cmd.exe session):
for /f "delims== tokens=1,*" %v in ('powershell.exe -c "'FOO=bar'"') do #set "%v=%w"
The above defines environment variable %FOO% with value bar, based on the PowerShell command outputting the literal name-value pair FOO=bar.
Verify with echo %FOO%.
To extend this approach to defining multiple environment variables, make the command output each definition on its own line (which in PowerShell you can achieve by outputting an array of strings):
for /f "delims== tokens=1,*" %v in ('powershell.exe -c "'FOO=bar', 'BAZ=bam'"') do #set "%v=%w"
The above additionally defines %BAZ% with value bam.
To make this more convenient, I suggest creating a wrapper batch file (*.cmd) that performs the above:
Note that you'll have to use %%v and %%w instead of %v and %w there.
Instead of -c (for -Command) with the demo command, use -File with the path to your *.ps1 file to invoke it.
Also consider use of -NoProfile as well, to bypass loading of your PowerShell environment's $PROFILE file, which not only slows things down, but may pollute your command's output.
[1] As LotPings points out, child processes inherit copies of the parent process' environment variables. Modifications of these copies are never seen by the parent. A child process is fundamentally unable to modify its parent's environment, which is a restriction at the OS level - for good reasons: Modifying a running process' environment by an arbitrary (child) process would be a serious security concern.

Related

How to execute Powershell's "start-process -Verb RunAs" from inside a Batch where the elevated command inherits the Batch's environment?

1. Problem
I have a complicated batch file where some parts need to run with elevated/admin rights (e.g. interacting with Windows services) and I found a Powershell way to do that:
powershell.exe -command "try {$proc = start-process -wait -Verb runas -filepath '%~nx0' -ArgumentList '<arguments>'; exit $proc.ExitCode} catch {write-host $Error; exit -10}"
But there's a huge caveat! The elevated instance of my script (%~nx0) starts with a fresh copy of environment variables and everything I set "var=content" before is unavailable.
2. What I've tried so far
This Powershell script doesn't help either because Verb = "RunAs" requires UseShellExecute = $true which in turn is mutually exclusive to/with StartInfo.EnvironmentVariables.Add()
$p = New-Object System.Diagnostics.Process
$p.StartInfo.FileName = "cmd.exe";
$p.StartInfo.Arguments = '/k set blasfg'
$p.StartInfo.UseShellExecute = $true;
$p.StartInfo.Verb = "RunAs";
$p.StartInfo.EnvironmentVariables.Add("blasfg", "C:\\Temp")
$p.Start() | Out-Null
$p.WaitForExit()
exit $p.ExitCode
And even if that would work I'd still need to transfer dozens of variables...
3. unappealing semi-solutions
because circumventing the problem is no proper solution.
helper tools like hstart - because I can't relay on external tools. Only CMD, Powershell and maybe VBscript (but it looks like runas plus wait and errorlevel/ExitCode processing isn't possible with/in vbs).
passing (only required) variables as arguments - because I need dozens and escaping them is an ugly chore (both the result and doing it).
restarting the whole script - because it's inefficient with all the parsing, checking processing and other tasks happening again (and again and ...). I'd like to keep the elevated parts to a minimum and some actions can later be run as a normal user (e.g service start/stop).
Writing the environment to a file and rereading it in the elevated instance - because it's an ugly hack and I'd hope there's a cleaner option out there. And writing possibly sensitive information to a file is even worse than storing it temporarily in an environment variable.
Here's a proof of concept that uses the following approach:
Make the powershell call invoke another, aux. powershell instance as the elevated target process.
This allows the outer powershell instance to "bake" Set-Item statements that re-create the caller's environment variables (which the outer instance inherited, and which can therefore be enumerated with Get-ChilItem Env:) into the -command string passed to the aux. instance, followed by a re-invocation of the original batch file.
Caveat: This solution blindly recreates all environment variables defined in the caller's process in the elevated process - consider pre-filtering, possibly by name patterns, such as by a shared prefix; e.g., to limit variable re-creation to those whose names start with foo, replace Get-ChildItem Env: with Get-ChildItem Env:foo* in the command below.
#echo off & setlocal
:: Test if elevated.
net session 1>NUL 2>NUL && goto :ELEVATED
:: Set sample env. vars. to pass to the elevated re-invocation.
set foo1=bar
set "foo2=none done"
set foo3=3" of snow
:: " dummy comment to fix syntax highlighting
:: Helper variable to facilitate re-invocation.
set "thisBatchFilePath=%~f0"
:: Re-invoke with elevation, synchronously, reporting the exit
:: code of the elevated run.
:: Two sample arguments, ... and "quoted argument" are passed on re-invocation.
powershell -noprofile -command ^
trap { [Console]::Error.WriteLine($_); exit -667 } ^
exit ( ^
Start-Process -Wait -PassThru -Verb RunAs powershell ^
"\" -noprofile -command `\" $(Get-ChildItem Env: | ForEach-Object { 'Set-Item \\\"env:' + $_.Name + '\\\" \\\"' + $($_.Value -replace '\""', '`\\\""') + '\\\"; ' }) cmd /c '\`\"%thisBatchFilePath:'=''%\`\" ... \`\"quoted argument\`\" & exit'; exit `$LASTEXITCODE`\" \"" ^
).ExitCode
echo -- Elevated re-invocation exited with %ERRORLEVEL%.
:: End of non-elevated part.
exit /b
:ELEVATED
echo Now running elevated...
echo -- Arguments received:
echo [%*]
echo -- Env. vars. whose names start with "foo":
set foo
:: Determine the exit code to report.
set ec=5
echo -- Exiting with exit code %ec%...
:: Pause, so you can inspect the output before exiting.
pause
exit /b %ec%
Note:
trap { [Console]::Error.WriteLine($_); exit -667 } handles the case where the user declines the elevation prompt, which causes a statement-terminating error that the trap statement catches (using a try / catch statement around the Start-Process call is also an option, and usually the better choice, but in this case trap is syntactically easier).
Specifying pass-through arguments (arguments to pass directly to the re-invocation of the (elevated) batch file, after the cmd /c '\`\"%thisBatchFilePath:'=''%\`\" part above):
If arguments contain ', you must double them ('')
If arguments need double-quoting, you must enclose them in '\`\"...\`\" (sic), as shown with \`\"quoted argument\`\" above.
The cmd /c '<batch-file> & exit' re-invocation technique is required to ensure robust exit-code reporting, unfortunately - see this answer for details.
The explicit exit $LASTEXITCODE statement after the batch-file re-invocation is required to make the PowerShell CLI report the specific exit code reported by the batch file - without that, any nonzero exit code would be mapped to 1. See this answer for a comprehensive discussion of exit codes in PowerShell.
A piping hot solution
Derived from mklement0's working proof of concept and Jeroen Mostert's Base64 suggestion I've built a solution with this approach:
Pipe in data from inside the batch to the outer Powershell.
Let it convert the piped data into a Base64 string.
which is passed on into the command line of the elevated Powershell.
which in turn converts it back and pipes it into the new batch's instance.
It is more flexible because you're not limited to environment variables (you can essentially pass on anything (text based)) and the Powershell command doesn't need to be edited to choose what gets piped through. But it has a few limitations mklement0's implementation doesn't suffer from:
Environment variables containing newlines will not be passed on correctly and can cause chaos (depending on what comes after the LF, see barz).
Currently every line piped through (except for the first one) gets one whitespace prepended to it (so far I couldn't figure out how to fix that). It's usually not a problem and can be worked around (fooDoubleQouting is a negative example).
the elevated instance doesn't react to console input as usual any more (see notes).
Example / test batch:
#echo off & setlocal EnableDelayedExpansion
::# Test if elevated.
net session 1>NUL 2>NUL && goto :ELEVATED
(set LF=^
%=this line is empty=%
)
::# Set sample env. vars. to pass to the elevated instance.
set foo1=bar
set "foo2=none done"
set foo3=3" of snow
set "barz= Line1!LF! foo1=Surprise^! foo1 isn't '%foo1%' anymore. It was unintentionally overwritten."
set barts=1 " 3_consecutive_" "_before_EOL
set "barl=' sdfs' ´´`` =43::523; \/{[]} 457wb457;; %%%^!2^!11^!^!"
::# ' dummy comment#1 to fix syntax highlighting.
::# Helper variable to facilitate re-invocation (in case %~f0 contains any single quotes).
set "_selfBat=%~f0"
::# DDE - so "!" don't get expanded anymore. Was only needed for "set barz=..."
setlocal DisableDelayedExpansion
::# print variables etc. to console before self invocation & elevation.
call :testPrint
::# Generate pipe input. Be aware of CMD's handicaps of whats allowed in a command block.
::# eg. "REM" is not allowed and neither is echoing an unescaped closing parenthesis: ")" -> "^^^)"
(
echo[foo_Setting_one=extra-varval.
set ^^"
echo[bar_stuff=in between.^^^)^^^"
set bar
echo["fooDoubleQouting=testertest"
) | powershell.exe -nologo -noprofile -command ^
trap { [Console]::Error.WriteLine($_); exit -667 } ^
exit ( ^
Start-Process -PassThru -Wait -WindowStyle Maximized -Verb RunAs 'powershell.exe' ^
"\"-nol -nop -comm `\" $('Write-Output $([Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String(\\\"' + $([Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($(foreach ($i in $input) {\"$i`n\"})))) + '\\\")))') | cmd.exe '/D','/U','/T:4F','/S','/C',' \`\"%_selfBat:'=''%\`\" \`\"quoted argument\`\" nonQtdArg & exit'; exit `$LastExitCode `\" \"" ^
).exitCode
echo[
echo[ ---- Returned errorlevel is: %ERRORLEVEL%
pause
endlocal & endlocal & exit /b %ERRORLEVEL%
:testPrint
echo[
echo[ ---- WhiteSpaceTest: "%barts%"
set foo
set bar
echo[
set ^"
exit /B
::# " dummy comment#2 to fix syntax highlighting again.
:ELEVATED
setlocal DisableDelayedExpansion
::# Read and parse piped in data.
::# (with "delims" & "eol" truly defined as empty so every line is read as-is, even empty lines)
for /F delims^=^ eol^= %%A in ('findstr.exe "^"') do (
echo[ Parsing %%~A
for /F "tokens=1,* delims=="eol^= %%B in ("%%~A") do (
echo[ into "%%~B"
echo[ equals "%%~C"
::# Convert the piped in data back into environment variables+values.
set "%%~B=%%~C" 2>NUL
)
echo[
)
echo[-------- END PIPEREADING --------
echo[-- Arguments received:
echo[ [%*]
call :testPrint
set "ERR=42"
echo[
::# to actually pause and/or wait for / react to user input(!) one needs to pipe in CON (console).
<con set /P ERR=Enter arbitrary exitcode / errorlevel:
endlocal & exit /B %ERR%
Notes:
see mklement0's notes.
The CMD /C '<batch-file_withEscaped'> & exit' re-invocation technique isn't required if you consistently exit /b X in your batch file. Then &\`\"%_selfBat%\`\" instead of CMD /C ... & exit is enough (with separately separated arguments: 'arg1','arg2').
'/D','/T:4F', - Ignore CMD's registry AutoRun commands and set fore-/background colors to white on dark red.
echo[ instead of echo is safer and quicker (cmd doesn't need to search for actual executables named echo.*).
<con is required in the elevated instance for anything needing user interaction (eg. pause or set /P ...). Without <con the now empty(?) piped in standard input (pipe#0) still delivers nul(?) to anything asking for it (my assumption). I'm sure there is a way too rescue stdin from the pipe and reattach it to con (maybe some kinde of breakthrou from in here).
barl's backticks get mangled.
Escaping hell
Here are the dynamic middle and inner command lines to show whats going on and shave away some escaping magic:
powershell.exe -nol -nop -comm "Write-Output $([Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String(\"<<BASE64_BLOB>>\"))) | cmd.exe '/D','/U','/T:4F','/S','/C',' \"<<path\this.cmd_withEscaped'>>\" \"quoted argument\" nonQtdArg & exit'; exit $LastExitCode"
Even with just my default environment that command line is somewhere around 5kB big(!), thanks to the <<BASE64_BLOB>>.
cmd.exe /D /U /T:4F /S /C " "<<path\this.cmd_withNormal'>>" "quoted argument" nonQtdArg & exit"
So yeah, like has been said, the environment is not meant to be passed from a user to another, by design, because of the security implications. It doesn't mean that it can't be done, even if it's not something you're "supposed" to do. While I do think you should look into what do you actually want to achieve, I absolutely hate the type of answers where people tell you what you achsually "should" do and not answer the actual question at all. So I'm giving you both options here.
"Passing" the environment
You have several options here
From the elevated child process, read the environment variables from the unelevated caller parent process' memory using the NtQueryInformationProcess and ReadProcessMemory APIs.
Then either overwrite the variables on the target process (in your case, the current process) with WriteProcessMemory or just set them as you normally would. You can achieve this with only Powershell, albeit you need to add some C# code to call the required API functions.
Here in my Enable-Privilege.ps1 example you can see how to implement NtQueryInformationProcess in PowerShell and manipulate a parent process (I wrote it for the purpose of modifying privilege tokens inside self/parent/any process). You would actually want to use a part of it because you need to enable SeDebugPrivilege to be able to manipulate memory of other processes.
This option would probably be most "clean" and robust solution without any instantly obvious caveats. See this codeproject article for more information: Read Environment Strings of Remote Process
Inside the unelevated parent process, iterate through all the environment variables and write them as a string to a single environment variable. Then pass that single environment value as an argument when spawning the elevated child process, where you can parse that string and write those environment values back. You would likely run to the same caveats as option 3 here though.
Pipe the variables from the parent to the child, like has been proposed here already. The problem here is that the batch processor is really finicky and the rules of parsing and escaping are super janky, so it's very likely you would run to issues with special characters and other similar caveats with this option.
Using a kernel-mode driver, overwrite the security token of the unelevated process with a elevated one, writing back the original token after you are done. On the surface this would seem like the perfect solution, since you could actually stay inside the previously-unelevated process and retain it's environment without changing context, only the security context would be replaced. As in kernel-mode you can modify everything, the security tokens are simple memory structs inside kernel memory which you can change. The problem with this approach is that it completely bypasses the windows security model, as it's supposed to be impossible to change the security token of an existing process. Because it's supposed to be "impossible", it goes deep into undocumented territory and inside the kernel you can easily break stuff if you don't know what you're doing, so this is definitely the "advanced" option (even though this particular thing is not too complicated, it's basically just writing some memory). As it's something you're not supposed to be doing, there is a possibility it breaks something since Windows does not expect a process to suddenly have a different security context. That being said, I've used this approach with no problems in the past. It could be broken in the future though by any change in the security design. You would also need to either enable testsigning (aka Disable Driver Signature Enforcement), digitally sign your driver or use some other method to bypass this requirement (f.ex. through a hypervisor or an exploit), but that is out of the scope of this answer.
The achsually version
"because circumventing the problem is no proper solution."
In this case, I would do exactly that. Since your problem is of such nature that a easy solution for it doesn't exist, because it's not supported by design. It's hard to propose a specific solution since the lack of information of what it is you're actually trying to achieve here.
I'm gonna try to cover this in a general sense. First is to think about the what it is you're actually trying to achieve here part. What are the operations you need to do which require elevation? There would be multiple ways to achieve whatever it is in a supported fashion.
Examples:
For whatever you need to read/write/modify, you could change the security security settings of the target (instead of the source). Meaning that let's say you need to access a specific registry key, service, file, folder, whatever, you could simply modify the ACL of the target to allow the source (i.e. the user) to do whatever operation you need. If you need to modify a single service for example, you could add the start/stop/modify right for only that single process.
If the thing you need is specific to the types of operations rather than specific targets, you could add the required privileges to the "Users" group. Or make a new group with the required privileges, and then add the user to that group.
If you want more granular control on what can/can't be done and/or the operations are specific, you could write a simple program and run it as a elevated service. Then you could just tell that service to do the required operations from the unelevated batch script, so no requesting elevation and spawning new process would be needed. You could simply do my-service.exe do-the-thing from batch, and that my-service would do the operation you need.
You could also always ask for the elevation in beginning of the script, but as it's clear you don't want to do this with full administrator rights, you could create a new user for just this purpose which you add to a new group for it which has the required privileges you need. Note that without resorting to the aforementioned kernel-mode ""hacks"", you cannot add new privileges for a user on-the-fly, only enable/disable/remove existing ones. What you can do though is add them beforehand based on what you need, but that will need to happen before the process is started.

Export/SET environment variables in windows through a shell script

There is a script.sh file
set FABRIC_CFG_PATH=<some path>
set CORE_PEER_LOCALMSPID=<some id>
If I'm running this script in windows, the env variables are not getting set.
Whereas if setting the env using the cmd approach,
E.g., on windows cmd
set FABRIC_CFG_PATH=<some path>
It works fine.
So how can I set the env in windows through a shell script file?
Since your intent is to define current-process-only environment variables (rather than persistently defined ones, which on Windows are stored in the registry) you need to use a script file / batch file that runs in-process in order for environment variables defined therein to be seen by the script's caller.
Therefore:
If the caller is a cmd.exe session, you must use a batch file: a plain-text file with filename extension .cmd (or, less preferably, .bat[1]) that uses cmd.exe syntax.
If the caller is a PowerShell session, you must use a PowerShell script: a plain-text file with filename extension .ps1 that uses PowerShell syntax.
Note: While you can call a .cmd file (batch file) from PowerShell too (but not directly vice versa), this will not work as intended, because of necessity it runs in a (cmd.exe) child process, whose environment variables aren't seen by the PowerShell caller.
As for .sh files: they have no predefined meaning on Windows, but may be defined by third-party applications, such as Git Bash. In the case of the latter, invoking a .sh file passes it to the POSIX-compatible Bash shell, which has its own syntax. More importantly, invoking such a file won't work as intended when called from either cmd.exe or PowerShell, because Bash must run in a child process, and child processes cannot set environment variables for their parents.
cmd.exe / batch-file example:
Create a file named envVars.cmd, for instance, and place the following lines in it:
#echo off
:: Note: Do NOT use `setlocal` here
set "FABRIC_CFG_PATH=C:\path\to\some directory\config"
set "CORE_PEER_LOCALMSPID=42"
Then, from your cmd.exe session / another batch file, call the file as follows to make the environment variable-definitions take effect for the current process (assuming the file is in the current directory):
.\envVars.cmd
You will then able to refer to the newly defined variables as %FABRIC_CFG_PATH% and %CORE_PEER_LOCALMSPID%.
PowerShell example:
Create a file named envVars.ps1, for instance, and place the following lines in it:
$env:FABRIC_CFG_PATH='C:\path\to\some directory\config'
$env:CORE_PEER_LOCALMSPID=42
Then, from a PowerShell session / another PowerShell script, call the file as follows to make the environment variable-definitions take effect for the current process (assuming the file is in the current directory):
./envVars.ps1
You will then able to refer to the newly defined variables as $env:FABRIC_CFG_PATH and $env:CORE_PEER_LOCALMSPID.
[1] See this answer.
After some study on the executables/batch files in windows, I have come to the conclusion that I need to write a batch .bat file to use the set command to set the env variables as I desire.

How to set %ERRORLEVEL% in cmd /c?

I want to be able to set the %ERRORLEVEL% environment variable (also referred to as a "system variable") to an arbitrary value in the beginning of a command prompt script (i.e. cmd /c) running in PowerShell. Whenever people want to set %ERRORLEVEL% (1), they do the following:
cmd /c "exit /b 3"
cmd /c "echo %ERRORLEVEL%"
However, although %ERRORLEVEL% is set to 3 when the above is run in the normal Command Prompt, if these exact lines are executed in PowerShell, the exit code environment variable is not set to 3 and remains 0. In addition, you cannot do the following:
cmd /v:on /c "exit /b 3 & echo !ERRORLEVEL!"
The exit command breaks completely out of the command execution (i.e. cmd /c) and no other command after the & is run.
Therefore, I tried executing the following command in PowerShell:
cmd /v:on /c "SET %ERRORLEVEL% = 4 & echo !ERRORLEVEL!"
The expected output is 4, but this always outputs 0. I cannot figure out why I am unable to SET the %ERRORLEVEL% environment variable. I have used delayed command execution (example here), but no amount of fiddling seems to work here.
Does anyone have any idea of why the command SET %ERRORLEVEL% = 4 does not work?
If you are not able SET environment variables, then how can set %ERRORLEVEL% to an arbitrary value in a string of commands separated by an ampersand (&) like above?
If you want to store the eventual exit code in a cmd.exe variable for later use with exit, do not try to set ERRORLEVEL; instead, use a custom variable name; e.g., ec (for exit code):
# Execute in PowerShell
PS> cmd /v /c 'set "ec=3" & echo ... & exit /b !ec!'; $LASTEXITCODE
...
3
On the cmd.exe side:
set "ec=3" sets variable ec to value 3
Enclosing the name-value pair in "..." isn't strictly necessary, but clearly delineates the end of the value, and allows use of values with embedded special characters such as &.
The variable's value can later be referenced as %ec% (up-front expansion) or !ec! (delayed expansion, if enabled via setlocal enabledelayedexpansion or via command-line switch /v - see help setlocal)
echo ... is a sample command representing further commands
exit /b !ec! exits with the value of variable ec as the exit code; note how the variable is of necessity referenced as !ec! rather than %ec%, via delayed expansion, because the variable was set as part of the same statement.
Whether you use /b (exit the current batch file only) or not doesn't make a difference here; either way, the exit statement in this case determines the process exit code of the cmd instance.
The commands are sequenced (unconditionally executed one after the other), with the & operator, as part of a single statement.
On the PowerShell side:
PowerShell's analog to cmd.exe's & is ;, the statement separator - it allows you to place multiple statements on a single line.
$LASTEXITCODE is the automatic PowerShell variable that contains the process exit code of the most recently executed external program, which in this case is cmd.exe's, with value 3.
Does anyone have any idea why the command SET %ERRORLEVEL% = 4 does not work?
To summarize the helpful information from the comments on the question:
Fundamentally, do no try to set the dynamic (pseudo) environment variable %ERRORLEVEL% - it is automatically maintained by cmd.exe to reflect the most recent command's exit code - see the bottom section.
Do not put spaces around = in a cmd.exe variable assignment:
The space(s) before the = become part of the variable name.
The space(s) after become part of the value.
Do not enclose the target variable name in %...% (unless you want to indirectly set a variable, via another variable whose value contains the name of the variable to assign to).
set %ERRORLEVEL% = 4
With %ERRORLEVEL% reflecting 0 at the start of your command sequence, the above assignment creates a variable literally named 0  (that is, the value of %ERRORLEVEL% followed by a single space), whose value is  4 (that is, a single space followed by 4).
Variables in cmd.exe:
Fundamentally, with the exceptions discussed below, variables in cmd.exe are all environment variables.
Unlike in shells such as Bash and PowerShell, there is no separate namespace for shell-local variables that are not seen by child processes.
This has the following implications:
Predefined persistent environment variables such as %Path% share the same namespace as custom variables you define with the SET command, so you have to be mindful of name collisions.
Similarly, child processes you run from your cmd.exe session / batch file inherit custom variables you've created in the session.
Note that the use of setlocal does not change that; setlocal is a cmd-internal scoping mechanism that allows you to control the lifetime of custom environment variables, by localizing them to the scope (batch file) in which setlocal was called, and removing them via a subsequent endlocal call or, implicitly, at the end of the enclosing batch file.
Custom variables are process-only environment variables that go out of scope with the cmd.exe process; persistent environment variable definitions must be created and modified via the registry, such as with the setx.exe utility.
In addition to the predefined (persistent) environment variables and custom (session-only) environment variables, cmd.exe maintains dynamic pseudo environment variables such as %ERRORLEVEL% and %RANDOM% (see list below):
These pseudo environment variables have dynamic values maintained by cmd.exe itself...
... and they are not visible to child processes.
Note: Strictly speaking, these dynamic variables are only enabled with the so-called command extensions turned on, but that is true by default (you can disable them with /E:OFF, but that is ill-advised).
Because these dynamic variables are not strictly part of the process environment that child processes inherit a copy of, they are not environment variables, even though help SET somewhat confusingly calls them dynamic environment variables.
You shouldn't (and cannot) modify these variables.
If you try, what really happens is that you shadow (override) these pseudo variables with real, custom environment variables, which by definition have static values.
Later code that relies on the variables by that name to have their usual, dynamic behavior can therefore malfunction.
The list of dynamic variables, as retrieved on Windows 10 via help SET (emphasis added):
If Command Extensions are enabled, then there are several dynamic
environment variables that can be expanded but which don't show up in
the list of variables displayed by SET. These variable values are
computed dynamically each time the value of the variable is expanded.
If the user explicitly defines a variable with one of these names, then
that definition will override the dynamic one described below:
%CD% - expands to the current directory string.
%DATE% - expands to current date using same format as DATE command.
%TIME% - expands to current time using same format as TIME command.
%RANDOM% - expands to a random decimal number between 0 and 32767.
%ERRORLEVEL% - expands to the current ERRORLEVEL value
%CMDEXTVERSION% - expands to the current Command Processor Extensions
version number.
%CMDCMDLINE% - expands to the original command line that invoked the
Command Processor.
%HIGHESTNUMANODENUMBER% - expands to the highest NUMA node number
on this machine.

Powershell setting EnvironmentVariable works in ISE but not Console

I have an odd Powershell behavior i wish to understand.
If i set a permanent Environment Variable and start a process like the following
[Environment]::SetEnvironmentVariable('FOO','BAR','User')
Start-Process notepad
This works as expected in ISE Editor and if i type it after one other in the Console. However if i run it as a .\script.ps1 Script from the Console the Start-Process will ignore the new or the changed Environment Variable. Even the Environment Variable is properly set before Start-Process is executed. I tested this by adding Sleep and checking the Environment Variable Dialogue manually. If the script is run a second time the Process will read the Environment Variable as expected since it has been changed before already.
Why is the Console behavior not the same as in ISE in this case?
I already tried if this has to do with specific Assemblies that are loaded in ISE and not the Console but it does not seam so.
I also tried to run as STA but it did not work either.
Note: This answer is specific to Windows, because System.Environment.SetEnvironmentVariable only supports modifying persistent environment-variable definitions (via target scopes User and Machine) there. The fundamentals of how PowerShell determines a child process' environment apply on Unix-like platforms too, however.
[Environment]::SetEnvironmentVariable() with a target-scope System.EnvironmentVariableTarget argument of User or Machine only updates the persistent environment-variable definitions in the registry - it doesn't also update the current process's in-memory variables.
By contrast, target Process updates only the current process' variables non-persistently.
As such, [Environment]::SetEnvironmentVariable('FOO','BAR','Process') is the equivalent of $env:FOO = 'BAR'
Start-Process by default uses the current process's environment variables[1] and therefore doesn't see variables (yet) that were created or updated by targeting the User or Machine scopes in the same process.[2]
Start-Process's -UseNewEnvironment parameter is in principle designed to do what you want: it is meant to start the new process with environment-variable values read from the registry, ignoring the calling process' values - however, this feature is broken as of PowerShell [Core] v7.0 - see this GitHub issue.
The workaround is to also define the new variable in the current process:
# Update both the registry and the current process.
foreach ($targetScope in 'User', 'Process') {
[Environment]::SetEnvironmentVariable('FOO', 'BAR', $targetScope)
}
# Start a new process with the new value in effect.
Start-Process -NoNewWindow -Wait powershell '-c \"`$env:FOO is: $env:FOO\"'
Note that - unlike what -UseNewEnvironment should do - this makes the new process inherit all process-only (in-memory) environment variables / values too.
[1] A process is given a block of environment variables on startup, often a copy of the parent process's block (as PowerShell itself does by default when creating child processes). That startup block may or may not reflect the then-current registry definitions. In-process modifications of the environment block are lost when the process terminates, unless they are explicitly persisted, such as with [Environment]::SetEnvironmentVariable() and target scopes User or Machine. As all programs that modify the persistently defined environment variables should do, [Environment]::SetEnvironmentVariable() broadcasts Windows message WM_SETTINGCHANGE as a notification of the change, but few programs are designed to listen to it and therefore few update their in-process environment variables in response (which isn't appropriate for all programs).
[2] However, if you start the new process as an administrator with -Verb RunAs (Windows-only) using the current user's credentials, the new process will see the new/updated definitions, because it then does not use the current process' environment variables and instead reads the then-current definitions from the registry.

In vbscript, how do I run a batch file or command, with the environment of the current cmd prompt window?

In vbscript, how do I run a batch file or command, in the current cmd prompt window,
without starting a new process.
For example. According to script56.chm (the vbscript help apparently)
Windows Script Host
Run Method (Windows Script Host)
"Runs a program in a new process"
So if I have code that uses that e.g. a VBS file, and a BAT file.
An environment variable g has the value abc g=abc
from that command window,
The VBS file calls the BAT file with windows scripting host Run.
The bat process sets g=z. and finishes.. and the vbs process finishes.
The environment variable is left untouched as g=abc.
I know
CreateObject("Wscript.Shell").Run "c:\test.bat", 0
starts a new window as is clear when using 1 instead of 0. (since 0 hides the window)
How do I
-run the bat file from the vbs, in the same cmd environment that the vbs was called in, so changes affect the cmd environment it was called in?
-In the two windows case which this one is at the moment, how do I access the environment of the parent cmd window, from the batch file?
how do I run a batch file or command, in the current cmd prompt window, without starting a new process?
I don't think you can; your vbscript runs under a script host engine (such as cscript.exe or wscript.exe), and batch files are interpreted by the command interpreter (typically cmd.exe). Both are separate executables and neither is, to my knowledge, available as an in-process library, so you cannot interpret .vbs and .cmd files within the same process. I also highly doubt that the script host engine that is running your VBScript also could run the batch file in its parent cmd.exe - I don't think you can 'inject' a new batch file into a running cmd.exe.
how do I access the environment of the parent cmd window, from the batch file?
Not just access, but change - MSDN's "Changing Environment Variables" is quite explicit on this: "Altering the environment variables of a child process during process creation is the only way one process can directly change the environment variables of another process. A process can never directly change the environment variables of another process that is not a child of that process." You are trying to change the environment of the parent, not child, process. (I do wonder what 'directly' means in the context of this quote, though).
I would guess that the reason for this is security; imagine the havoc that could be wreaked if arbitrary processes could (maliciously or accidentally) change the PATH (or COMSPEC) environment variable of a running process, such as your vbscript host engine process - it could fail to launch your bat file entirely, breaking your program.
It would seem that you're out of luck - however, there are lots of other mechanisms for passing information between processes. Here are a couple of suggestions that are fairly simple to implement when talking between a batch file & vbscript, although it's by no means exhaustive:
Exit codes
Writing to & Parsing the consoleoutput (stdout) or a temp file
If you absolutely need to set environment variables in the parent cmd.exe (and also absolutely need the intermediate step of a vbscript), then you may have to write a wrapper batch file which runs the vbscript, consumes information produced by it and then sets environment variables; because the wrapper cmd is executing in the top-level cmd process, it will be able to change the env vars there.
Footnote: Note that you can change the permanent system/user environment variables (as opposed to process environment variables) from within a VBScript, but I wouldn't recommend this if you are trying to create a transient state; besides this won't affect already-running processes (like the parent cmd.exe) anyway.