12

I'm trying to set a system environment variable in my application, but get an SecurityException. I tested everything I found in google - without success. Here is my code (note, that I'm administrator of my pc and run VS2012 as admin):

Attempt 1

new EnvironmentPermission(EnvironmentPermissionAccess.Write, "TEST1").Demand();
Environment.SetEnvironmentVariable("TEST1", "MyTest", EnvironmentVariableTarget.Machine);

Attempt 2

new EnvironmentPermission(EnvironmentPermissionAccess.Write, "TEST1").Demand();

using (var envKey = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\Session Manager\Environment", true))
{

  Contract.Assert(envKey != null, @"HKLM\System\CurrentControlSet\Control\Session Manager\Environment is missing!");
  envKey.SetValue("TEST1", "TestValue");
}

Attempt 3 Also I tried to fit out my app with administrator priviliges.

Do you have any other suggestions?

helb
  • 7,609
  • 8
  • 36
  • 58
alex555
  • 1,676
  • 4
  • 27
  • 45

1 Answers1

31

The documentation tells you how to do this.

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.

So, you need to write to the registry setting that you are already attempting to write to. And then broadcast a WM_SETTINGCHANGE message as detailed above. You will need to be running with elevated rights in order for this to succeed.

Some example code:

using Microsoft.Win32;
using System;
using System.Diagnostics.Contracts;
using System.Runtime.InteropServices;

namespace ConsoleApplication1
{
    class Program
    {
        const int HWND_BROADCAST = 0xffff;
        const uint WM_SETTINGCHANGE = 0x001a;

        [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        static extern bool SendNotifyMessage(IntPtr hWnd, uint Msg, 
            UIntPtr wParam, string lParam);

        static void Main(string[] args)
        {
            using (var envKey = Registry.LocalMachine.OpenSubKey(
                @"SYSTEM\CurrentControlSet\Control\Session Manager\Environment",
                true))
            {
                Contract.Assert(envKey != null, @"registry key is missing!");
                envKey.SetValue("TEST1", "TestValue");
                SendNotifyMessage((IntPtr)HWND_BROADCAST, WM_SETTINGCHANGE,
                    (UIntPtr)0, "Environment");
            }
        }
    }
}

However, whilst this code does work, the .net framework provides functionality to perform the same task much more simply.

Environment.SetEnvironmentVariable("TEST1", "TestValue", 
    EnvironmentVariableTarget.Machine);

The documentation for the three argument Environment.SetEnvironmentVariable overload says:

If target is EnvironmentVariableTarget.Machine, the environment variable is stored in the HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Session Manager\Environment key of the local computer's registry. It is also copied to all instances of File Explorer. The environment variable is then inherited by any new processes that are launched from File Explorer.

If target is User or Machine, other applications are notified of the set operation by a Windows WM_SETTINGCHANGE message.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • As I said, calling `OpenSubKey` function ends in an exception, too, although the key is available – alex555 Oct 31 '13 at 12:14
  • As I said, you need to run this elevated. When you do you will find that it works. I did test this code you know! – David Heffernan Oct 31 '13 at 12:21
  • Ah, it works! Thank you very much. Is there a command or parameter to confirm this UAC warning? – alex555 Oct 31 '13 at 12:41
  • I do not understand that question. – David Heffernan Oct 31 '13 at 12:44
  • when you run a program as administrator, an UAC warning appears, where you have to type admin's password and continue. I want to know if there are parameters to skip this step – alex555 Oct 31 '13 at 12:59
  • 4
    If there were such a thing, that would render UAC pointless wouldn't it. If you want to do admin tasks, you need to become admin. There's no getting away from that. – David Heffernan Oct 31 '13 at 13:07
  • The doc. excerpt is from C++ WinAPI, not C#! SetEnvironmentVariable method is different in C# than C++.. it could get a 3rd param which is EnvironmentVariableTarget!... If target is User or Machine, other applications are notified of the set operation by a Windows WM_SETTINGCHANGE message. – Learner Nov 02 '15 at 10:06
  • @Learner Nothing I wrote was wrong, and the WinAPI documentation is the important information. The .net runtime is of course built on top of WinAPI. But you are quite right that I overlooked the possibility of passing `EnvironmentVariableTarget.Machine`. Thank you for pointing that out. I've added that information to the answer. – David Heffernan Nov 02 '15 at 10:12
  • @Learner I don't think that's the right distinction. Both code samples above are are C#, both do the same thing, both go via WinAPI to do the actual work. I don't think it's realistic to ignore the existence of WinAPI when coding this kind of task on Windows. Ultimately any solution to the problem ends up writing to that registry key, and sending that broadcast message. – David Heffernan Nov 02 '15 at 10:54
  • No, was not wrong, but a bit misleading since someone might think the excerpt is about C# SetEnvironmentVariable (op asked for C#). I would have added "WinAPI" in the first line of the answer. – Learner Nov 02 '15 at 11:01
  • @Learner I cannot see anything in the answer that is not C# – David Heffernan Nov 02 '15 at 11:02
  • 2
    It is worth noting that SetEnvironmentVariabl() function in .Net Framework even in latest 4.6.2 version has a bug of not checking whether you are setting PATH variable -- it treats them all the same, and it will silently change the registry key type for PATH from REG_EXPAND_SZ to REG_SZ and thus kill the ability to use variable expansion in PATH. Needless to say, many programs (and computer users) rely on PATH expansion. – Igor Levicki Feb 16 '17 at 20:45
  • is it possible that this solution does not work properly on win server 2019? – yBother Mar 04 '22 at 12:11
  • @IgorLevicki very significant factor. Still broken on Win11 net 4.8 – Patrick Jul 31 '23 at 04:06
  • 1
    @Patrick Yes, and despite being reported by me 4 years ago, it wasn't even fixed in .Net 5, 6, 7 or 8. – Igor Levicki Aug 20 '23 at 11:01