Monday, August 22, 2005

How to Create and Change Environment Variables using C# or .Net

Modifying environment variables seems like one of those easy tasks that you would expect .Net to support. Unfortunatley the System.Environment class only supports reading environment variables. So an alternative solution is needed. After a long search on the internet, I found one site that describes some options: Environment variable is handled. Unfortunately, this page is written in Japanese. An english translation is here. There are several options available, each with it's own limitations and issues. I tried each option and found they wouldn't work for me.

Windows Management Instrumentation (WMI) has the ability to easily read the environment. If an environment variable already exists, you can modify it using WMI. However, I could not find a good example of creating a new environment variable through WMI. I installed and ran Microsoft's WMI Tools (available here) to see if I could create an environment variable through the CIM Studio. No luck. There might be a way to use WMI to create a new environment variable, but I haven't seen it. Also, calling WMI causes a noticable performance hit on the first call.

Another solution is to modify the Registry (see MS KB Article) to create and modify environment variables. This seems to work, however the class Microsoft.Win32.RegistryKey doesn't provide the ability to create a REG_EXPAND_SZ value. Without REG_EXPAND_SZ, you can't use the %VAR% expansion! So using .Net framework falls short. (This is one example of my frustration with .Net, many framework interfaces are incomplete. Java never had this problem, instead Java tends to err on the side of giving you too much.)

The solution I found to work the best was to call Windows Scripting Host (WSH) Shell from .Net. This is described in Basic WSH Tasks: Manipulating the System Registry. I found an example of calling WSH from .Net in the article The Code Project - Creating Shell Links (Shortcuts) in .NET Programs Using WSH. Using WSH, it is easy to create and modify environment variables. Also, WSH allows you to set REG_EXPAND_SZ values. The final step after changing the environment variables in the registry is to broadcast the change by calling SendMessageTimeout as described in the KB article. I found this example calling SendMessageTimeout from C# on .Net 247.

So in short, here is the example code for creating and modifying environment variables from C#...

        public static void SetUserVariable( string name, string value, bool isRegExpandSz )
{
SetVariable( "HKEY_CURRENT_USER\\Environment\\" + name, value, isRegExpandSz );
}

public static void SetSystemVariable( string name, string value, bool isRegExpandSz )
{
SetVariable( "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment\\" + name, value, isRegExpandSz );
}

private static void SetVariable( string fullpath, string value, bool isRegExpandSz )
{
object objValue = value;
object objType = (isRegExpandSz) ? "REG_EXPAND_SZ" : "REG_SZ";
WshShell shell = new WshShell();
shell.RegWrite( fullpath, ref objValue, ref objType );

int result;
SendMessageTimeout( (System.IntPtr)HWND_BROADCAST,
WM_SETTINGCHANGE,0,"Environment",SMTO_BLOCK | SMTO_ABORTIFHUNG |
SMTO_NOTIMEOUTIFNOTHUNG, 5000, out result);
}

[DllImport("user32.dll",
CharSet=CharSet.Auto, SetLastError=true)]
[return:MarshalAs(UnmanagedType.Bool)]
public static extern bool
SendMessageTimeout(
IntPtr hWnd,
int Msg,
int wParam,
string lParam,
int fuFlags,
int uTimeout,
out int lpdwResult
);

public const int HWND_BROADCAST = 0xffff;
public const int WM_SETTINGCHANGE = 0x001A;
public const int SMTO_NORMAL = 0x0000;
public const int SMTO_BLOCK = 0x0001;
public const int SMTO_ABORTIFHUNG = 0x0002;
public const int SMTO_NOTIMEOUTIFNOTHUNG = 0x0008;




Call either SetUserVariable or SetSystemVariable with the name and value. Set the boolean argument isRegEpandSz true if you need REG_EXPAND_SZ or false for REG_SZ type.

No comments: