Powershell GetEnvironmentVariable vs $Env - powershell

I have run into a couple cases where I am trying to use a command via command line, but the command is not recognized. I have narrowed it down to an issue with environment variables. In each case, the variable is present when I retrieve the variable with the underlying C# method, but not with the shorthand, $env:myVariable
For example, if I retrieve the variable like this, I will get a value.
[Environment]::GetEnvironmentVariable('ChocolateyInstall', 'Machine')
But, if I retrieve the variable like this, nothing is returned
$env:ChocolateyInstall
I then have to do something like this to to get my command to work.
$env:ChocolateyInstall = [Environment]::GetEnvironmentVariable('ChocolateyInstall', 'Machine')
I have not been able to find a good explanation as to why I have to do this. I've looked at this documentation, but nothing stands out to me. Ideally, I would like to install a CLI and then not have to deal with checking for and assigning environment variables for the command to work.

When opening a PowerShell session, all permanently stored environment variables1 will be loaded into the Environment drive (Env:) of this current session (source):
The Environment drive is a flat namespace containing the environment
variables specific to the current user's session.
The documentation you linked states:
When you change environment variables in PowerShell, the change
affects only the current session. This behavior resembles the behavior
of the Set command in the Windows Command Shell and the Setenv command
in UNIX-based environments. To change values in the Machine or User
scopes, you must use the methods of the System.Environment class.
So defining/changing an environment variable like this:
$env:ChocolateyInstall = [Environment]::GetEnvironmentVariable('ChocolateyInstall', 'Machine')
Will change it for the current session, thus being immediately effective, but will also only be valid for the current session.
The methods of [System.Environment] are more fine grained. There you can choose which environment variable scope to address. There are three scopes available:
Machine
User
Process
The Process scope is equivalent to the Environment drive and covers the environment variables available in your current session. The Machine and the User scope address the permanently stored environment variables1. You can get variables from a particular scope like this:
[Environment]::GetEnvironmentVariable('ChocolateyInstall', 'Machine')
And set them with:
[Environment]::SetEnvironmentVariable('ChocolateyInstall', 'any/path/to/somewhere', 'Machine')
If you want to have new variables from the Machine or User scope available in your current PowerShell session, you have to create a new one. But don't open a new PowerShell session from your current PowerShell session, as it will then inherit all environment variables from your current PowerShell session (source):
Environment variables, unlike other types of variables in PowerShell,
are inherited by child processes, such as local background jobs and
the sessions in which module members run. This makes environment
variables well suited to storing values that are needed in both parent
and child processes.
So, to address the problem you described, you most probably changed your permanently stored environment variables1, while already having an open PowerShell session. If so, you just need to open a new (really new, see above) session and you will be able to access your environment variables via the Environment drive. Just to be clear, opening a new session will even reload environment variables of the Machine scope. There is no reboot required.
1 That are the environment variables you see in the GUI when going to the System Control Panel, selecting Advanced System Settings and on the Advanced tab, clicking on Environment Variable. Those variables cover the User and the Machine scope.
Alternatively, you can open this GUI directly by executing:
rundll32 sysdm.cpl,EditEnvironmentVariables

Related

Possible to use commands provided by a recently installed program without restarting cmd/powershell?

I'm working on a script that automatically installs software. One of the programs to be installed includes its own command line commands and sub-commands when installed.
The goal is to use the program's provided commands to perform an action after its installation.
But running the command right after the program's installation I'm greeted by:
" is not recognized as an internal or external command , operable program or batch file"
If I open a new Powershell or cmd window the command is available in that instance.
What is the easiest way to to grant the script access to the commands?
Bender the Greatest's helpful answer explains the problem and shows you how to modify the $env:PATH variable in-session by manually appending a new directory path.
While that is a pragmatic solution, it requires that you know the specific directory path of the recently installed program.
If you don't - or you just want a generic solution that doesn't require you to hard-code paths - you can refresh the value of $env:PATH (the PATH environment variable) from the registry, via the [Environment]::GetEnvironmentVariable() .NET API method:
$env:PATH = [Environment]::GetEnvironmentVariable('Path', 'Machine'),
[Environment]::GetEnvironmentVariable('Path', 'User') -join ';'
This updates $env:PATH in-session to the same value that future sessions will see.
Note how the machine-level value (list of directories) takes precedence over the user-level one, due to coming first in the composite value.
Note:
If you happen to have made in-session-only $env:PATH modifications before calling the above, these modifications are lost.
If applicable, this includes modifications made by your $PROFILE file.
Hypothetically, other processes could have made additional modifications to the persistent Path variable definitions as well since your session started, which the call above will pick up too (as will future sessions).
This is because the PATH environment variable gets updated, but existing processes don't see that update unless they specifically query the registry for the live value of the update the PATH environment variable, then update PATH within its own process. If you need to continue in the same process, the workaround is to add the installation location to the PATH variable yourself after the program has been installed:
Note: I don't recommend updating the live value from the registry instead of the below in most cases. Other processes can modify that value, not just your own. It can introduce unnecessary risk, whereas appending only what you know should have changed is a more pragmatic approach. In addition, it adds code complexity for a case that often doesn't need to be generalized to that point.
# This will update the PATH variable for the current process
$env:PATH += ";C:\Path\To\New\Program\Folder;"

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.

Reload environment variables inside powershell window

In my Powershell script I'm setting machine-level environment variables with:
[Environment]::SetEnvironmentVariable("MY_VARIABLE", "MY_VALUE", "Machine")
Inside this same script, I am calling another application that will look for the variable above. Is there a way for me to reload the current session with the variables I created above?
No but you can also set the environment variables for the current process i.e. $env:MY_VARIABLE = 'MY_VALUE'. Any application you start should inherit the environment variables set in this fashion.

Setting a Environment Variables via Powershell for another User

I am trying to set the environment variables of a user XYZ from the powershell of admin user ABC. I am using Start-Process to launch the powershell of user XYZ but i am not able to capture the output. All this process needs to be done in Java.
Can someone help me out.
Thanks
Ajax
When you change environment variables, the change affects only the current PowerShell session (like if you were using SET command in a Windows cmd). To make the changes permanent, you have to change them with a utility like SETX. You must also have permission to change the values.
Check this TechNet article on it: https://technet.microsoft.com/en-us/library/ff730964.aspx
Basically, you're going to want to set it using the .NET method at the machine scope:
[Environment]::SetEnvironmentVariable("TestVariable","Test Value","Machine")
You'll need to restart your Powershell session to be able to access the new environment variable after creating it.

Why is the Powershell Environment PATH different to the System Environment PATH?

I'm having this weird situation :
My user's and system's PATH variable is different than the PATH in powershell.
When I do :
PS C:\$env:path
C:\Windows\System32\WindowsPowerShell\v1.0\;c:\oldpath
However this is not correct, it looks like it stuck on some old PATH variable of my system, so none of the udpates I've done on it didn't change this variable (I do restart after every change to test).
Why is this happening? Do I have to set a PATH variable just for powershell?
The change might be "delayed", so try one or more of these solutions:
Log off and on again;
Task Manager > Restart "Windows Explorer" (explorer.exe)
Restart your launcher app (launchy, SlickRun, etc)
Reboot
Explanation:
Powershell will inherit the environment of the process that launched it (which depends on how you launch it). This is usually the interactive shell (explorer.exe). When you modify the environment from computer properties, you modify the environment of explorer.exe, so if you launch powershell from explorer.exe, (for example from the start menu) you should see the new environment.
However, if you launch it from something else (say a cmd.exe shell that you already had opened), then you won't since that process was launched under the old environment.
In other words: be careful how you are launching things.
In my case, I installed an app that incorrectly added itself to the PATH by creating a powershell profile that would override $env:PATH and blow out the existing configuration every time I started powershell.
Check if you have profile at USER\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1 and if it's doing anything fishy like setting $env:PATH.