Setting User level environment variables with Chocolatey - powershell

I'm writing a Chocolately package that needs to install my program and then set a User level environment variable that the program needs.
As recommended in the documentation, I've installed Chocolatey from a PowerShell terminal with elevated privileges. In my chocolatelyinstall.ps1 script I can set the environment variable with this command:
Install-ChocolateyEnvironmentVariable -VariableName "my_env_var" -VariableValue "Wibble" -VariableType User
However, when I install the package: choco install my_package -s . the environment variable is set at User level for the administrator account, rather than the standard user account.
Installing the package in a regular (non-elevated) PowerShell process, simply fails with:
Access to the path 'C:\ProgramData\chocolatey\lib\my_package\tools' is denied.
Is there any way to set the Env var on the standard user account, rather than the admin account?
All assistance is welcome!

Indeed (to recap), if your elevated process uses a different (of necessity administrative) user account than the current window-station user (the user that started the current OS user session), you cannot define environment variables for the windows-station user using the usual methods that target the HKEY_CURRENT_USER hive, as it reflects the elevating user's data.
Conversely, this means that if your window-station user is an administrator and therefore allowed to run with elevation themselves, the problem will not arise.
Workaround (takes the place of your Install-ChocolateyEnvironmentVariable call):
Determine the identify of the window-station user in terms of its SID (security identify).
Use the SID to target the window-station user's specific registry hive, under HKEY_USERS.
Use a dummy user-level [Environment]::SetEnvironmentVariable() call so as to broadcast a notification of the environment change (modifying the registry directly doesn't do that), notably so that the Windows (GUI) shell refreshes its environment.
# Get the window station user and split into domain name and user name.
$domain, $user = (Get-CimInstance Win32_ComputerSystem).UserName -split '\\'
# Obtain their SID.
$sid = [System.Security.Principal.NTAccount]::new(
$domain,
$user
).Translate([System.Security.Principal.SecurityIdentifier]).Value
# Set an environment variable for them.
Set-ItemProperty "registry::HKEY_USERS\$sid\Environment" my_env_var Wibble
# Set and remove a dummy variable for the *current user*,
# so as to notify the GUI shell that the environment changed.
('unused', $null).ForEach({
[Environment]::SetEnvironmentVariable("_PowerShell_$PID", $_, 'User')
})

Related

How to use Powershell to modify System Variable in the user account?

I am trying to use powershell to change the path environment variable in my account for my company laptop.
I have tried to use [System.Environment]::SetEnvironmentVariable('Path','mypath','User')
It will only set the path under User Variable
When I tried [System.Environment]::SetEnvironmentVariable('Path','mypath','Machine')
I got an exception saying "Requested registry access is not allowed"
However, when I do start>Edit environment variables for your account> I can click and change the System Variable without issue.
Is there a way to do the same as above from powershell?
EDIT: there are 2 options from the start panel, "Edit environment variables for your account" and "Edit the system environment variables". I do not have the Admin right, so I can only access the first option which I can edit system variables from there.

Powershell GetEnvironmentVariable vs $Env

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

Is there any way to persist VS Code extensions in VDI?

Every day when I log into VDI my vscode extensions get removed on a daily basis.
So I need to install them every day. Is there any walk around to keep the extensions with persistence and that I don't have to download/install it again on a daily basis when ever I log in.
Any help would be much appreciated and thanks in advance.
It seems that you are using a nonpersistent VDI, so you should ask your IT to install the vscode extensions that you need in the image stored in the servers.
Excerpt retrieved here:
There are two main approaches to VDI: persistent and nonpersistent. Persistent VDI provides each user with his or her own desktop image, which can be customized and saved for future use, much like a traditional physical desktop. Nonpersistent VDI provides a pool of uniform desktops that users can access when needed. Nonpersistent desktops revert to their original state each time the user logs out.
Found this on GitHub and it is down near the bottom... https://github.com/microsoft/vscode/issues/17691
Create an environment variable named VSCODE_EXTENSIONS. Set the path you wish the extensions to be stored. We used a network share in our implementation to keep extensions persistent in a non-persistent VDI. (e.g VSCODE_EXTENSIONS = \\Server\Share\%USERNAME%\.vscode)
This environment variable must be in place before VSCode launches. We are utilizing this with VSCode 1.52.1 and it is working for us.
This is how I got it to work in my environment. You need to install the extensions using the .vis format, and then copy the extensions from that local profile to a location any user can access. After that, create a GPO that will run this script at every logon and set the scope to your VDI access AD group. The logon GPO is located at User Configuration > Policies > Windows Settings > Scripts > Logon > Powershell This may be a crude way of doing it, but it's working in my environment.
#This will not work unless there are extensions on the root of the default user
#folder. Install Visual Studio Code and its extensions first, then copy the
#entire "\.vscode" folder from the user profile you installed it into and onto
#the default user profile root folder.
#This script tests to see if the extensions already exist in the root folder of
#the user logging in using the path described in the below variable.
$vscodeextensions = "$env:USERPROFILE\.vscode\extensions\ms-vscode.cpptools-
1.13.2"
#This will just allow the script to run.
Set-ExecutionPolicy bypass
If(-not(Test-Path $vscodeextensions)){
Copy-Item -Path "C:\Users\Default\.vscode\" -Destination "$env:USERPROFILE\"
-Force -Recurse
}
else{
Write-Host "Extensions already copied"
}

Run Executable File Without UAC Popup as Administrator

I am running a large study where we have staff in various countries collecting information on tablet computers running Windows 10 Enterprise. Each staff member is assigned to a tablet and they log into the tablet with their standard username and password. These users do not have local admin rights on the machines, but all tablets have a single Administrator Username and Password which I know and these are uniform across the tablets.
Each night, users invoke a program on their tablets that uploads data to our servers and then we pass information back to the tablet during this synchronization process. Otherwise, they are disconnected from the internet. At the end of the synchronization process a program is executed that allows me to run any script I like, but the script executes under the standard user account (i.e. without elevated privileges).
I need to update all the tablets with a bug fix for software that they use on the tablets and I'd like to do this during the synchronization process. The bug fix is contained in a simple executable file that can be easily pushed to the staff memebers' tablets along with any code I like during the sync. If users were running the synchronization program as administrators, this wouldn't a problem as I could simply run the executable via a script at the end of the synchronization. But they aren't, so I'm trying to find a way that I could run a script (I don't really care what it is. It could be a windows batch file, a vbs script, VB.NET, powershell, etc.) and have that script execute with administrative privileges and run the installation without the UAC prompt interfering.
I don't even mind supplying the admin password in plaintext to be honest, since these users are all our employees and they can't really do anything really concerning to us with it (and I could always deploy a subsequent file through the synchronization process to delete the program that has the password in it). I realize this sounds somewhat complicated, but in a nutshell, I'd like to carry out these steps:
Send the bug update executable to the tablet computer (I can do this now)
Develop custom code, that will pass admin credentials to the tablet and install the executable in 1 without having the UAC appear (I can send the script to the tablet during sync but do not know how to execute it as the Admin without getting the UAC prompt).
Any ideas how I can do this? I've explored this all day with minimal success using PowerShell scripts like the ones described here and here. This was the closest I got after storing the credentials in $cred, but it continued to give me the UAC prompt:
Start-Process PowerShell.exe -Cred $cred -ArgumentList '-command &{Start-Process -FilePath C:\MySyncPath\BugFix32.exe -Verb runas}]
UPDATE
After some additional work, I think I'd be able to get this to run if I could somehow disable to UAC control with a script that can run under the regular user's account and pass the admin credentials to it. Any idea how I might be able to accomplish this? If I could get this to work, even with a reboot, I'd be able to accomplish what I need.
The actual issue you're having is that you want to update your application, but the application is in the Program Files folder (or some other location that standard users are not allowed to modify).
In order to allow any user the ability to update your program, you must grant all users Full Control to your folder. Ideally your application's installer would have done this adjustment to the DACL during installation (when the installer was running as an administrator).
For now you will have to settle for a final one-time requirement that the users elevate to administrator. Then you can disable all security on your application - allowing any user (malicious or not) to modify your application at will.
GrantEveryoneFullControlToFileOrFolder("C:\Program Files\Contoso");
with a pseudocode implementation of:
void GrantAllUsersFullControlToFileOrFolder(String path)
{
PACL oldDACL;
PACL newDACL;
PSECURITY_DESCRIPTOR sd;
//Get the current DALC (Discretionary Access Control List) and Security Descriptor
GetNamedSecurityInfo(path, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION,
nil, nil, ref oldDACL, nil, ref sd);
//Create an SID for the "Users" group
PSID usersSid = StringToSid("S-1-5-32-545");
// Initialize an EXPLICIT_ACCESS structure for the new Access Control Entry (ACE)
EXPLICIT_ACCESS ea;
ZeroMemory(#ea, SizeOf(EXPLICIT_ACCESS));
ea.grfAccessPermissions = GENERIC_ALL;
ea.grfAccessMode = GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE;
ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea.Trustee.TrusteeType = TRUSTEE_IS_GROUP;
ea.Trustee.ptstrName = PChar(usersSID);
// Create a new ACL that merges the new ACE into the existing ACL.
// SetEntriesInAcl takes care of adding the ACE in the correct order in the list
SetEntriesInAcl(1, #ea, oldDACL, ref newDACL); //use LocalFree to free returned newDACL
//Attach the new ACL as the object's new DACL
SetNamedSecurityInfo(path, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION,
nil, nil, newDACL, nil);
LocalFree(HLOCAL(sd));
LocalFree(HLOCAL(newDACL));
FreeSid(usersSID);
}
It's not completely unheard of for applications to be modifiable by any user: Most MMOs install updates while you play. MMOs usually have a shim applied by Microsoft that gives all users control of the application folder.
run the script as a domain admin account... and set execution policy before the script is run, then run as administrator... some applications are picky about UAC still, but Set-ExecutionPolicy [bypass/remotesigned] will ensure that you're not prompted. however, sharing and permissions may still be an issue if the domain admin account doesn't have access to the share. psexec does this, but it's literally a matter of doing what i just mentioned and the psexec file essentially writes out the permissions by the end of the script. the intent was to make sure that passwords weren't written in clear text, it hashes the password value. either way, if you want this done securely, using a GPO and making sure your file permissions/share is at the highest level might iterate out the prompt. that's why you'll see some batch files use %1 %2 %3 %4 %5 %6 %7 %8 %9 .... that's because it's automatically requesting elevation and will loop in an iterative cycle until the UAC prompt isn't necessary.
i know i'm bumping an old thread, but this is what i've found, trying to mix and match legacy cmd batches with powershell ... lots to consider about the execution policy leading into the call vs during the call...
This question is in the category of "when people ask for security holes as features".
You cannot bypass (or, if you prefer this phrasing, "programmatically accept") the UAC prompt and automatically elevate without interactive confirmation. UAC is designed specifically to prevent this. (If this were possible, all malware would do it.)
This isn't a PowerShell thing but a general windows 10 thing. You'd need to disable UAC for this. No experience with it on Windows 10 yet though.
You can try setting the EnableLUA registry key to 0. The key can be found in:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System
This will probably need a reboot to be active though.

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.