Set current user environment variable from c++ code visible to other process like cmds - c++

I need to set a local environment variable for current user and it shoukd be visible to other processes like a new command prompt. I need it for windows. I have tried options like putenv and editing the registry from C++ code but the new cmd prompt see the old values. Primarily i need to edit PATH variable along with few custom env variables. Will appreciate if i can get a working sample code.
Please note that the environment variable need to persist past program execution.
My requirement is for windows. I even tried running setx from C++ code and it works fine but for PATH variable it trims it down to 1024 character and i lose the update. Is there a workaround to this?
IF my wording looks confusing about the requirement. I need exactly same behavior as if i am using setx.
Thanks in advance.

If you start Cmd.exe from your process you can control its environment. The environment variables are inherited from the parent process. They can also be overridden when you call CreateProcess.
If you change the users/system environment configuration in the registry(HKCU\Environment/HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment) and log off/reboot then the first process will use these new defaults.
If you update the registry you can tell other applications to refresh their environments without logging off by broadcasting a message:
BroadcastSystemMessage(0, 0, WM_SETTINGCHANGE, 0, (LPARAM)TEXT("Environment"));
In reality it is only Explorer.exe that reacts to this message but that is enough to affect new applications started from the taskbar/start menu.

The setx command is actually an executable that sets values in the registry. If you are looking to simulate the behavior where you can set an environment variable that will last longer than the current process you will need to write it to the HKCU\Environment key. The HKCU is for the the current user and can be written to without elevated permissions.
Use RegEdit.exe or reg.exe query HKCU\Environment to view the current settings. From C/C++ you can use the Registry functions. If you can, I recommend using the ATL CRegKey class as it follows RAII and ensures handles are properly cleaned up.

Related

How to return a command from a c++ application to parent terminal?

Is it possible to run a c++ application from a terminal and on certain conditions return a command back into the terminal from which it was called from? For instance, if I were to run an application within my terminal and after my selections; my application needs to change my PATH by running an export command such as:
(USING BASH)
export PATH=.:/home/User/application/bin:$PATH
After I'm done and before my application completely closes can I make the application change my terminals local environment variables with the above command? Does Qt offer a way of doing this? Thanks in advance for any help!
No, you cannot change parent application environment.
Why? When your parent app started yours (probably using system()), it actually fork()ed - child process was born as to be almost exact replica of parent, and then that child used execve() call, which completely replaced executable image of that process with executable image of your application (for scripts it would be image of interpreter like bash).
In addition to that, that process also prepared few more things. One is list of open files, starting with file handles 0,1,2 (stdin, stdout, stderr). Also, it created memory block (which belongs to child process address space) which contains environment variables (as key=value pairs).
Because environment block belongs to your process, you can change your own environment as you please. But, it is impossible for your process to change environment memory block of parent (or any other process for that matter). The only way to possibly achieve this would be to use IPC (inter-process communication) and gently ask parent to do this task inside of it, but parent must be actively listening (on local or network socket) and be willing to fulfill such request from somebody, and child is not any special compared to any other process in that regard.
This also reason why you can change environment in bash using some shell script, but ONLY using source or . bash macro - because it is processed by bash itself, without starting any external process.
However, you cannot change environment by executing any other program or script for reasons stated above.
The common solution is to have your application print the result to standard output, then have the invoker pass it to its environment. A textbook example is ssh-agent which prints an environment variable assigment; you usually invoke it with eval $(ssh-agent)

C++ getenv always returns null value

I have just added the environment variable "DataDir", but the getenv function still returns null value.
Here is my code:
const char *ret = getenv("DataDir");
I restarted my computer and it done.
did you remember to export the variable before running the program? If you are using bash shell on linux, for example, you generally should use export DataDir="..."
On windows, if you set the environment variables using the system settings window, it will not immediately propagate to all of the running programs. If "I restarted my computer and it done." means "restarting the computer resolved the issue", then I believe that explains the problem. After changing the environment variable, try closing all programs and then start a CMD session (or visual studio) and run the program again
Are you running on Windows? Did you set the environment variable through the control panel? If so, that only affects processes that you start (programs that you launch) after you changed the setting. If you're running from a command prompt, and the command prompt didn't inherit the new environment variable, then your program won't inherit it either.
After rebooting, all new processes inherit the new environment variable.
On the other hand, if you set the variable and then run the program:
C:\>set DataDir=blah
C:\>.\my_program
then your program will inherit the variable (but it won't persist across a reboot).
Similar considerations apply on Linux and other systems, but the details differ.
Note that I'm only guessing, based on the symptoms you reported, what system you're using. In the future, it would be helpful to provide that information in the question (if it's not relevant we can ignore it).

Programmatically adding a directory to Windows PATH environment variable

I'm writing a Win32 DLL with a function that adds a directory to the Windows PATH environment variable (to be used in an installer).
Looking at the environment variables in Regedit or the Control Panel after the DLL has run shows me that my DLL has succeeded in adding the path to HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment and HKEY_CURRENT_USER\Environment.
But when I start up a new Command Prompt (after running the DLL), the directory I added does not show up in the output of echo %PATH% and I can not access the executable that lives in that directory by typing its name.
I think my program is not doing a good job of notifying the system that the PATH has changed, or maybe it is notifying them before the change has fully taken effect. I read an article by Microsoft that says to broadcast the WM_SETTINGCHANGE message after changing an environment variable, and I am doing that with this code:
DWORD result2 = 0;
LRESULT result = SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
(LPARAM)"Environment", SMTO_ABORTIFHUNG, 5000, &result2);
if (result == 0){ /* ... Display error message to user ... */ }
The order of my calls is: RegCreateKeyEx, RegSetValueEx, RegCloseKey, SendMessageTimeout
If I press "OK" in the Control Panel "Environment Variables" window, the changes made by my DLL to the PATH show up in newly-created command prompts, so there is something that the Control Panel is doing to propagate PATH changes; I want to figure out what it is and do the same thing.
Does anyone know what I should do?
I'm running 64-bit Windows Vista but I want this to work on all Windows XP, Vista and Windows 7 operating systems.
Update: The problem with the code I posted above is that I did not put the L prefix on the "Environment" string. Although it does not say it explicitly anywhere in the Microsoft documentation that I can find, the LPARAM needs to be a pointer to a WCHAR string (2-byte characters) as opposed to a CHAR string, which is what Visual Studio's compiler generates by default when I write a string literal. The solution to my problem was to change "Environment" to L"Environment". (I thought I already tried that before posting this question, but apparently I didn't try it correctly!) But anyone who wants a complete C++ solution for this task should look at Dan Moulding's answer.
It turns out there really isn't anything new under the sun. This has already been done before, at least once. By me. I created a DLL very much like what you describe for exactly the same purpose (for use in modifying the path from an NSIS installer). It gets used by the Visual Leak Detector installer.
The DLL is called editenv.dll. The source is available at github. I just tested the installer and it updated the system PATH environment variable, no problem. Based on what you've written, I don't see anything that stands out as being wrong. I also don't see anything obvious that's missing. But it may be worth a look at the editenv.dll source (you'd be most interested in EnvVar::set() in EnvVar.cpp, and possibly the pathAdd() and pathRemove() C APIs in editenv.cpp).
I have a program which calls the same Win32 API to yours to update the environment, and it works fine.
One thing to be careful of is how you are opening up the command prompt.
If you open up the command prompt by doing this:
Start -> Run -> cmd.exe
then the environment in the prompt shows that the new variable is set.
However, I also have a programmable function key on my keyboard which I have set to run the cmd.exe process. If I open a command prompt via that function key and then type env, it doesn't show the variable as being set.
I'm not sure why it works differently, but it must have something to do with the way the cmd.exe process is launched (although both are running under my user name, not SYSTEM).
How are you opening up the command prompt?

Is there a way to set the environment path programmatically in C++ on Windows?

Is there a way to set the global windows path environment variable programmatically (C++)?
As far as I can see, putenv sets it only for the current application.
Changing directly in the registry (HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment) is also an option though I would prefer API methods if there are?
MSDN Says:
Calling SetEnvironmentVariable has no
effect on the system environment
variables. To programmatically add or
modify system environment variables,
add them to the
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session
Manager\Environment registry key, then
broadcast a WM_SETTINGCHANGE message
with lParam set to the string
"Environment". This allows
applications, such as the shell, to
pick up your updates. Note that the
values of the environment variables
listed in this key are limited to 1024
characters.
As was pointed out earlier, to change the PATH at the machine level just change this registry entry:
HLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
But you can also set the PATH at the user level by changing this registry entry:
HKEY_CURRENT_USER\Environment\Path
And you can also set the PATH at the application level by adding the application\Path details to this registry entry:
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\
There is no API - changing the registry is the way to do it. The changed value will be used for processes starting after the change was made.
Also: Notice that running applications must actively process the settings changed message and many (most?) do not do so.
If you want to do it through the registry, you might want to look at the source code of this program.
Microsoft also provides a small command line utility called setx with its resource toolkits, which will let you do this. By the way, the regular set command just lets you define local environment variables I think.
Yes You are correct.
You also need to effect these settings without logging off
Send Message of borad casting to all windows SETTINGCHANGE for the parameter (LPARAM) "Environment" with SMTO_ABORTIFHUNG for some milliseconds(5000) using SendMessageTimeout API.
This is what setX.exe provided with resource Kit does.

How can I change Windows shell (cmd.exe) environment variables from C++?

I would like to write a program that sets an environment variable in an instance of the shell (cmd.exe) it was called from. The idea is that I could store some state in this variable and then use it again on a subsequent call.
I know there are commands like SetEnvironmentVariable, but my understanding is that those only change the variable for the current process and won't modify the calling shell's variables.
Specifically what I would like to be able to do is create a command that can bounce between two directories. Pushd/Popd can go to a directory and back, but don't have a way of returning a 2nd time to the originally pushed directory.
MSDN states the following:
Calling SetEnvironmentVariable has no
effect on the system environment
variables. To programmatically add or
modify system environment variables,
add them to the
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session
Manager\Environment registry key, then
broadcast a WM_SETTINGCHANGE message
with lParam set to the string
"Environment". This allows
applications, such as the shell, to
pick up your updates. Note that the
values of the environment variables
listed in this key are limited to 1024
characters.
Considering that there are two levels of environment - System and Process - changing those in the shell would constitute changing the environment of another process. I don't believe that this is possible.
A common techniques is the write an env file, that is then "call"ed from the script.
del env.var
foo.exe ## writes to env.var
call env.var
In Windows when one process creates another, it can simply let the child inherit the current environment strings, or it can give the new child process a modified, or even completely new environment.
See the full info for the CreateProccess() win32 API
There is no supported way for a child process to reach back to the parent process and change the parent's environment.
That being said, with CMD scripts and PowerShell, the parent command shell can take output from the child process and update its own environment. This is a common technique.
personly, I don't like any kind of complex CMD scripts - they are a bitch to write an debug. You may want to do this in PowerShell - there is a learning curve to be sure, but it is much richer.
There is a way...
Just inject your code into parent process and call SetEnvironmentVariableA inside
cmd's process memory. After injecting just free the allocated memory.