none
.NET: Set new path with SetEnvironmentVariable RRS feed

  • Pregunta

  • I'm trying to update the path variable in the system environment.

    Here is a snip of the code I'm trying to use:

    String varPath = System.Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine);
    varPath += @";D:\My folder\My project\bin";
    System.Environment.SetEnvironmentVariable("Path", varPath, EnvironmentVariableTarget.Machine);

    The variable is inserted to the Path on my Windows 7 64-bit, when I'm looking in: System -> Advanved system settings -> Environment variables. I have som .bat files inside the bin-directory, let's say app.bat. After I run the code and verified that the path is updated in system, I start up a fresh, new console and try to run the command "app". But I does not work!

    If I change something, e.g. removing the last semicolon, in the Path from System properties and press "ok", I can start a new console and run the command "app". That's strange in my world!

    It's like something is missing.. hope you can help me?

    miércoles, 12 de mayo de 2010 18:38

Respuestas

  • Read the document I linked more carefully, then alter two lines in your code:

    [DllImport("user32.dll", SetLastError = true, CharSet=CharSet.Auto)]
        private static extern IntPtr SendMessageTimeout(IntPtr hWnd, int Msg, IntPtr wParam, string lParam, uint fuFlags, uint uTimeout, IntPtr lpdwResult);

    SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, IntPtr.Zero, "Environment", SMTO_ABORTIFHUNG, 100, IntPtr.Zero);

    • Marcado como respuesta DennisMadsen domingo, 16 de mayo de 2010 19:15
    sábado, 15 de mayo de 2010 11:10
  • Make sure that the registry value for "PATH" is of type REG_EXPAND_SZ after you alter it.
    • Marcado como respuesta DennisMadsen domingo, 16 de mayo de 2010 19:14
    domingo, 16 de mayo de 2010 16:58

Todas las respuestas

  • There is a difference between the environment variables associated with your process (which are inherited by any processes you start) and the master copies that you are seeing in the Control Panel.

    The master copies are stored in the registry.  Two sets of master variables can come into play, one set associated with the current user and another that is systemwide.  You can use Microsoft.Win32.Registry class to edit these.  Of course, be really careful about how you do this.

    See this for the registry keys:

    http://support.microsoft.com/default.aspx/kb/104011

    miércoles, 12 de mayo de 2010 22:46
  • When running my code, the variable "HKEY_LOCAL_MACHINE \ SYSTEM \ CurrentControlSet \ Control \ Session Manager \ Environment \ Path" is changed currently. As I mention before, just go to the Control Panel and change something unimportant in the Path, and press ok will do the trick. But if I do the same with the Path-variable from the RegEdit, it does not help.

    It's like that it is not commited or something. Like the Environment has to reload or something. Can that be true? I've tried to restart my machine, but it does not help :(

    jueves, 13 de mayo de 2010 10:03
  • The WM_SETTINGCHANGE broadcast doesn't happen when you just change the registry. Running programs don't pick up changes unless they handle that message.  There might be a bit more to it than that, but that's one part of it. Installers typically broadcast this message after an install to update this kind of thing, which is why you see a desktop flicker.
    Phil Wilson
    jueves, 13 de mayo de 2010 22:12
  • Peter Ritchie provides an example on how to perform a WM_SETTINGCHANGE broadcast here:

    http://social.msdn.microsoft.com/Forums/en-US/winforms/thread/ce540c7d-a113-4f39-956e-0af6bc91abd3/

     

    jueves, 13 de mayo de 2010 22:32
  • I've tried this before, but it does not help. Here is a sample of my code:

    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using System.Data;
    using System.Linq;
    using System.Windows;
    using System.Runtime.InteropServices;
    
    namespace glinit
    {
    
      public partial class App : Application
      {
    
        public App()
        {
    	  this.install();
          Environment.Exit(0);
        }
    
        private void install()
        {
          String gl_home = System.Environment.CurrentDirectory;
          String path = @"%GL_HOME%\bin";
    
          System.Environment.SetEnvironmentVariable("GL_HOME", gl_home, EnvironmentVariableTarget.Machine);
    
          String varPath = System.Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine);
          if (!varPath.Contains(path))
          {
            varPath += @";" + @path;
    
            System.Environment.SetEnvironmentVariable("Path", varPath, EnvironmentVariableTarget.Machine);
          }
    
          this.notifyProcesses();
    	}
    
        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr SendMessageTimeout(IntPtr hWnd, int Msg, IntPtr wParam, string lParam, uint fuFlags, uint uTimeout, IntPtr lpdwResult);
    
        private static readonly IntPtr HWND_BROADCAST = new IntPtr(0xffff);
        private const int WM_SETTINGCHANGE = 0x1a;
        private const int SMTO_ABORTIFHUNG = 0x0002;
    
        private void notifyProcesses()
        {
          SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, IntPtr.Zero, null, SMTO_ABORTIFHUNG, 100, IntPtr.Zero);
        }
      }
    }

    It's the exact same situation. I still have to go to the Control Panel and eg. add a semicolon to the Path and press ok. A logout or restart does not help me.

     

    viernes, 14 de mayo de 2010 10:58
  • As was already explained, you cannot use System.Environment.SetEnvironmentVariable to alter the system-wide environment.

    When an application is launched by the OS, it creates a copy of the system-wide (user-wide actually) environment and associates the copy with the process. Any changes the process makes are only made to the copy. Any processes spawned by that first process will inherit the copy.

    viernes, 14 de mayo de 2010 13:30
  • viernes, 14 de mayo de 2010 13:37
  • Well, the article says that I only has the option to change it by the HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment registry key and broadcast like I do in the code example. How can I edit the registers from C#?
    viernes, 14 de mayo de 2010 19:52
  • Here is my new code example:

    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using System.Data;
    using System.Linq;
    using System.Windows;
    using System.Runtime.InteropServices;
    using Microsoft.Win32;
    
    namespace glinit
    {
      public partial class App : Application
      {
        public App()
        {
    	  this.install();
          Environment.Exit(0);
        }
    
        private void install()
        {
          String gl_home = System.Environment.CurrentDirectory;
          String path = @"%GL_HOME%\bin";
    
          RegistryKey environment = Registry.LocalMachine.CreateSubKey(@"System\CurrentControlSet\Control\Session Manager\Environment\");
    
          environment.SetValue("GL_HOME", gl_home);
    
          String varPath = (String)environment.GetValue("Path");
    
          if (!varPath.Contains(path))
          {
            varPath += @";" + @path;
    
            environment.SetValue("Path", varPath);
          }
    
          this.notifyProcesses();
        }
    
        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr SendMessageTimeout(IntPtr hWnd, int Msg, IntPtr wParam, string lParam, uint fuFlags, uint uTimeout, IntPtr lpdwResult);
    
        private static readonly IntPtr HWND_BROADCAST = new IntPtr(0xffff);
        private const int WM_SETTINGCHANGE = 0x1a;
        private const int SMTO_ABORTIFHUNG = 0x0002;
    
        private void notifyProcesses()
        {
          SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, IntPtr.Zero, null, SMTO_ABORTIFHUNG, 100, IntPtr.Zero);
        }
      }
    }

    This time there is a difference. I have to login and logout, before I can see the changes by eg. "echo %GL_HOME%". But still, it does not work, before I've been into the Control Panel, eg. added a semicolon and press ok. :(

    viernes, 14 de mayo de 2010 20:44
  • Read the document I linked more carefully, then alter two lines in your code:

    [DllImport("user32.dll", SetLastError = true, CharSet=CharSet.Auto)]
        private static extern IntPtr SendMessageTimeout(IntPtr hWnd, int Msg, IntPtr wParam, string lParam, uint fuFlags, uint uTimeout, IntPtr lpdwResult);

    SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, IntPtr.Zero, "Environment", SMTO_ABORTIFHUNG, 100, IntPtr.Zero);

    • Marcado como respuesta DennisMadsen domingo, 16 de mayo de 2010 19:15
    sábado, 15 de mayo de 2010 11:10
  • To understand why, run cmd.exe and then run Process Explorer (http://technet.microsoft.com/en-us/sysinternals/bb896653.aspx). Notice that cmd.exe is a child of Explorer.exe.

    Remember that when a process starts up, it inherits a copy of the environment. When that process starts a new process, the child inherits a copy of its parent's copy, and so on.

    So when you launch cmd.exe, it gets a copy of its parent's (explorer.exe) environment. Explorer is already a running process. The only way that it can know that the system environment has changed is through the WM_SETTINGCHANGE "Environment" message. Applications that are WM_SETTINGCHANGE aware (like Explorer) will reload their own environment strings in response to that message. It is only from that point on that new processes spawned will get an updated copy of the environment.

    sábado, 15 de mayo de 2010 11:16
  • I just noticed that you copied and pasted code Peter Ritchie wrote in another thread. He got it wrong, not you.

    *edit - Actually, it wasn't Ritchie that got it wrong, his suggestion to use WM_SETTINGCHANGE was for a different reason, not changing environment strings.

    • Editado Tergiver sábado, 15 de mayo de 2010 11:34 correction
    sábado, 15 de mayo de 2010 11:22
  • I've changed the code like you told me:

    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using System.Data;
    using System.Linq;
    using System.Windows;
    using System.Runtime.InteropServices;
    using Microsoft.Win32;
    
    namespace glinit
    {
     public partial class App : Application
     {
      public App()
      {
       this.install();
       Environment.Exit(0);
      }
    
      private void install()
      {
       String gl_home = System.Environment.CurrentDirectory;
       String path = @"%GL_HOME%\bin";
    
       RegistryKey environment = Registry.LocalMachine.CreateSubKey(@"System\CurrentControlSet\Control\Session Manager\Environment\");
    
       environment.SetValue("GL_HOME", gl_home);
    
       String varPath = (String)environment.GetValue("Path");
    
       if (!varPath.Contains(path))
       {
        varPath += @";" + @path;
    
        environment.SetValue("Path", varPath);
       }
    
       this.notifyProcesses();
      }
    
      [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
      private static extern IntPtr SendMessageTimeout(IntPtr hWnd, int Msg, IntPtr wParam, string lParam, uint fuFlags, uint uTimeout, IntPtr lpdwResult);
    
      private static readonly IntPtr HWND_BROADCAST = new IntPtr(0xffff);
      private const int WM_SETTINGCHANGE = 0x1a;
      private const int SMTO_ABORTIFHUNG = 0x0002;
    
      private void notifyProcesses()
      {
      	SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, IntPtr.Zero, "Environment", SMTO_ABORTIFHUNG, 100, IntPtr.Zero);
      }
     }
    }

    This time I can go into the cmd right after the execution and type "echo %GL_HOME%". And the Path is like it should be. But I cannot run the bat-file from the bin-directory. When I go to the Control Panel and add e.g. a semicolon to the System Path and open a new cmd, it all works. I can go back and remove the semicolon again, and it still works. I have tried to both logout and login again and also to a restart windows. I does not help :(

    domingo, 16 de mayo de 2010 16:26
  • Make sure that the registry value for "PATH" is of type REG_EXPAND_SZ after you alter it.
    • Marcado como respuesta DennisMadsen domingo, 16 de mayo de 2010 19:14
    domingo, 16 de mayo de 2010 16:58
  • It's only REG_SZ. How can I make it REG_EXPAND_SZ?

    Thanks for your time!

    domingo, 16 de mayo de 2010 18:34
  • It's time for you to start helping yourself. That's why the MSDN Library exists.
    http://msdn.microsoft.com/en-us/library/k23f0345.aspx
    domingo, 16 de mayo de 2010 19:09
  • It's time for you to start helping yourself. That's why the MSDN Library exists.
    http://msdn.microsoft.com/en-us/library/k23f0345.aspx

    I found the solution:

    environment.SetValue("Path", varPath,RegistryValueKind.ExpandString);

    Thanks for your help!

    domingo, 16 de mayo de 2010 19:14
  • It looks like this has changed in Windows 8. Environment.SetEnvironmentVariable now knows how to change the appropriate registry key, in the case of changing a Machine-wide setting.

    Can't tell you if Windows 8 does the Windows message to notify existing windows though.


    -glenn-

    martes, 11 de junio de 2013 19:02