Environment Variables Go "Poof"

S

Scott McNair

Hi,

I'm trying to set an environment variable programmatically. I've added
these three lines to my code:

System.Environment.SetEnvironmentVariable("TCS", "C:\Program Files\TCS\")
InstallPath = System.Environment.GetEnvironmentVariable("TCS")
MsgBox(InstallPath)

And, as expected, the MsgBox contains "C:\Program Files\TCS\".

However the variable seems to exist only for that instance of the app. For
example, if I open a cmd window while the app is running and type "echo %
TCS%", it just returns "%TCS%". If I exit the app, remark out the first
line, and then re-run the app, the MsgBox returns an empty box.

I'm guessing that any environment variables you set using
System.Environment.SetEnvironmentVariable are volatile; that is, they don't
exist outside that currently running application.

How can I set it programmatically, and have it STAY there?

Regards,
Scott
 
T

Tom Shelton

Hi,

I'm trying to set an environment variable programmatically. I've added
these three lines to my code:

System.Environment.SetEnvironmentVariable("TCS", "C:\Program Files\TCS\")
InstallPath = System.Environment.GetEnvironmentVariable("TCS")
MsgBox(InstallPath)

And, as expected, the MsgBox contains "C:\Program Files\TCS\".

However the variable seems to exist only for that instance of the app. For
example, if I open a cmd window while the app is running and type "echo %
TCS%", it just returns "%TCS%". If I exit the app, remark out the first
line, and then re-run the app, the MsgBox returns an empty box.

I'm guessing that any environment variables you set using
System.Environment.SetEnvironmentVariable are volatile; that is, they don't
exist outside that currently running application.

How can I set it programmatically, and have it STAY there?

Regards,
Scott

Your correct, Environment variables created in this way are volatile -
they only last for the life time of the process and only for that
process. If you want to make them system wide, then there are two
things you need to do...

1) Add the variable to the
HKLM\System\CurrentControlSet\Control\Session Manager\Environment key in
the registry. You can do this with the Microsoft.Win32.Registry class.

2) Send out a broadcast message to with SendMessage (your going to need
to use P/Invoke here) with a value of WM_SETTINGCHANGE (&H1A) and the lParam
set to the string Environment

HTH
 
S

Scott McNair

1) Add the variable to the
HKLM\System\CurrentControlSet\Control\Session Manager\Environment key in
the registry. You can do this with the Microsoft.Win32.Registry class.

Thanks for your help. I've added the key using the registry, and although
it shows up via regedit, I still can't access the key either
programmatically or thru the command prompt. I'm assuming I need to log
off/on to reload these variables?

Also I have a secondary question about volatile environment variables.
Let's say I have an application, Foo.exe, that sets a volatile environment
variable and then calls Bar.exe. Does Bar have access to the key set with
Foo since it was spawned by Foo, or does the variable exist only within the
scope of Foo itself?
 
T

Tom Shelton

Thanks for your help. I've added the key using the registry, and although
it shows up via regedit, I still can't access the key either
programmatically or thru the command prompt. I'm assuming I need to log
off/on to reload these variables?

Did you broadcast the wm_settingchange message (call to SendMessage with
a hwnd of -1)?
Also I have a secondary question about volatile environment variables.
Let's say I have an application, Foo.exe, that sets a volatile environment
variable and then calls Bar.exe. Does Bar have access to the key set with
Foo since it was spawned by Foo, or does the variable exist only within the
scope of Foo itself?

Hmmm... I can't remember for sure if they are inherited by spawned
processes. I know it's possible to cause a spawned process to inherit
some things via a call to createprocess - but, I don't think you need to
do that :)

The way I would approach that is to add the varialbe (if it doesn't
already exist) to the Process.StartInfo.EnvironmentVariables collection.
 
S

Scott McNair

Did you broadcast the wm_settingchange message (call to SendMessage with
a hwnd of -1)?

Ah, I had misunderstood your prior post... I was thinking you were giving a
choice of two things to try, rather than a list of things to do, in order.

I'm trying to find some documentation for P/Invoke for VB.NET, but I'm not
having much luck. Could you provide a sample line of code (or a link) to
point me in the right direction?

Regards,
Scott
 
T

Tom Shelton

Ah, I had misunderstood your prior post... I was thinking you were giving a
choice of two things to try, rather than a list of things to do, in order.

I'm trying to find some documentation for P/Invoke for VB.NET, but I'm not
having much luck. Could you provide a sample line of code (or a link) to
point me in the right direction?

Regards,
Scott

Sure... Off the top of my head :)

Private Const WM_SETTINGCHANGE As Integer = &H1A
Private Shared ReadOnly HWND_BROADCAST As New IntPtr (-1)

Private Declare Auto Function SendMessage Lib "User32" ( _
ByVal hWnd As IntPtr, _
ByVal Msg As Integer, _
ByVal wParam As IntPtr, _
ByVal lParam As String) As Integer

SendMessage (HWND_BROADCAST, WM_SETTINGCHANGE, IntPtr.Zero,
"Environment")

Anyway, that should be pretty close. If you have any issues let me
know and I'll actually look up SendMessage and test some code :)
 
S

Scott McNair

If you have any issues let me know and I'll actually look up
SendMessage and test some code :)

Thanks, Tom.

I entered your code, and it seems to hang when I use SendMessage.
 
T

Tom Shelton

Thanks, Tom.

I entered your code, and it seems to hang when I use SendMessage.

I tested it here just now - and it is working. The call can take
a few moments, because it has to notify all the top level windows on the
system - and SendMessage will wait for all to return...

Are us sure it is hanging permanetly?
 
S

Scott McNair

Are us sure it is hanging permanetly?

Hi Tom,

Just to make sure, I started the app and let it sit there, to see how long
it would take (if ever) to execute. So far it's been sitting for about ten
minutes.

I use the term "hung up" loosely... it's not technically hung; it's almost
as if it's waiting for some input from some source that I can't see.

I put the following lines of code into my Form_Load:

Microsoft.Win32.Registry.SetValue( _
"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\" & _
"Session Manager\Environment\", "TCS", "C:\Program Files\TCS\")
Dim StartTime As DateTime = Now()
SendMessage(HWND_BROADCAST, WM_SETTINGCHANGE, IntPtr.Zero, _
"Environment")
MsgBox(DateDiff(DateInterval.Second, StartTime, Now()))

The first line works great; I can open regedit and see the key. The second
and fourth lines are throwaway, just to let me see the final execution
time. The problem therefore lies with the third line (SendMessage).

Just to verify I've got things right, I have the following declarations
inside the class, right above my Form_Load declaration:

Private Const WM_SETTINGCHANGE As Integer = &H1A
Private Shared ReadOnly HWND_BROADCAST As New IntPtr(-1)

Private Declare Auto Function SendMessage Lib "User32" ( _
ByVal hWnd As IntPtr, _
ByVal Msg As Integer, _
ByVal wParam As IntPtr, _
ByVal lParam As String) As Integer
 
S

Scott McNair

Just to make sure, I started the app and let it sit there, to see how
long it would take (if ever) to execute. So far it's been sitting for
about ten minutes.

As a followup:

Rather than run the app in debug mode has I had been doing, I compiled it
as a release, and then ran that app. It ran within a second, but it
returned nothing in the MsgBox that supposedly is polling the environment
variable.

I then ran it again, and it returned the proper string in the MsgBox. I
then went into the app and disabled the code that sets the variable,
effectively having the MsgBox be my first line; it still reported the
variable.

So I decided to run it again. I deleted the value from the registry and
reran the application. This time it reported the value properly on the
first execution.

I continued my experiment. I deleted the key and I remarked everything
out. I recompiled the app, and ran it. It still reported the key. "No
problem," I thought, "it just hasn't sent the environment refresh."

So this time I left the registry write remarked, but I unremarked the
SendMessage line. I ran it again, and it STILL reported C:\Program Files
\TCS\ as the value of the variable, even though it shouldn't be there.

The only explanation I can think of is that the SendMessage command only
updates and inserts, but it doesn't actually clear removed items from the
current environment.

Does that sound about right?
 
S

Scott McNair

The only explanation I can think of is that the SendMessage command
only updates and inserts, but it doesn't actually clear removed items
from the current environment.

Yet more followup:

If I run the app and declare the variable, it does not see the variable
within that instance of the app. I set a timer for five seconds to enable
immediately after SendMessage, and then MsgBoxed the variable; it showed a
blank.

If I then uncomment the code that creates the variable, and recompile the
app, the variable is there when I run it.

I'm guessing that's not typical execution, but I've run it about three or
four times, with the same result each time.
 
T

Tom Shelton

Yet more followup:

If I run the app and declare the variable, it does not see the variable
within that instance of the app. I set a timer for five seconds to enable
immediately after SendMessage, and then MsgBoxed the variable; it showed a
blank.

If I then uncomment the code that creates the variable, and recompile the
app, the variable is there when I run it.

I'm guessing that's not typical execution, but I've run it about three or
four times, with the same result each time.

Scott... I can play with this a little more - but, I want to ask before
we go much further (probably should have done this first!), but what is
the ultimate goal here? We maybe going down the wrong road :)
 
S

Scott McNair

Scott... I can play with this a little more - but, I want to ask before
we go much further (probably should have done this first!), but what is
the ultimate goal here? We maybe going down the wrong road :)

Hi Tom,

The goal is two-fold.

Firstly, we plan to use the path for installation of our suite of apps.

Secondly, we plan to use the environment variable as an encryption hash for
sensitive data, such as passwords stored within xml, database passwords
stored in the registry, etc.

There may be a better way to do these things, but my boss is absolutely
sold on the concept of using environment variables, so that's the way we're
going.

Regards,
Scott
 
T

Tom Shelton

Hi Tom,

The goal is two-fold.

Firstly, we plan to use the path for installation of our suite of apps.

Secondly, we plan to use the environment variable as an encryption hash for
sensitive data, such as passwords stored within xml, database passwords
stored in the registry, etc.

There may be a better way to do these things, but my boss is absolutely
sold on the concept of using environment variables, so that's the way we're
going.

Regards,
Scott

I guess what I'm asking is - are we trying to make this a permenant
system wide variable, or only need it to go to child processes?
Because, you can add them to you child environment when they are
starting if you use System.Diagnostics.Process class to start them...

Anyway - I think the behavior your seeing is consistant, with the way
things work. I'll try and post a complete sample here pretty soon.
By the way, if you only want the variable for the current user you
can put them in HKEY_CURRENT_USER\Environment.
 
S

Scott McNair

I guess what I'm asking is - are we trying to make this a permenant
system wide variable, or only need it to go to child processes?
Because, you can add them to you child environment when they are
starting if you use System.Diagnostics.Process class to start them...

The variable would be permanent and persistent.
Anyway - I think the behavior your seeing is consistant, with the way
things work. I'll try and post a complete sample here pretty soon.
By the way, if you only want the variable for the current user you
can put them in HKEY_CURRENT_USER\Environment.

It'd be a system-wide variable, so that any user with any login would have
access to this variable.

By the way, thanks for all your help, Tom. You've saved me hours of
fruitless googling.

Regards,
Scott
 
T

Tom Shelton

The variable would be permanent and persistent.


It'd be a system-wide variable, so that any user with any login would have
access to this variable.

By the way, thanks for all your help, Tom.  You've saved me hours of
fruitless googling.

Regards,
Scott

Option Explicit On
Option Strict On
Option Infer Off

Imports System
Imports System.Runtime.InteropServices
Imports Microsoft.Win32

Public Class MainForm
Private Shared ReadOnly HWND_BROADCAST As New IntPtr(-1)
Private Const WM_SETTINGCHANGE As Integer = &H1A

Private Declare Auto Function SendMessage Lib "user32" ( _
ByVal hWnd As IntPtr, _
ByVal Msg As Integer, _
ByVal wParam As IntPtr, _
ByVal lParam As String) As Integer

Private Sub MainForm_Load(ByVal sender As Object, ByVal e As
System.EventArgs) Handles Me.Load
LoadEnvironment()
End Sub

Private Sub LoadEnvironment()
Try
ListView1.BeginUpdate()

ListView1.Items.Clear()

Dim keys As New List(Of String)()
For Each target As EnvironmentVariableTarget In
DirectCast([Enum].GetValues(GetType(EnvironmentVariableTarget)),
EnvironmentVariableTarget())
For Each entry As DictionaryEntry In
Environment.GetEnvironmentVariables(target)
If Not keys.Contains(entry.Key.ToString()) Then
keys.Add(entry.Key.ToString())
Dim item As New ListViewItem(New String()
{entry.Key.ToString(), entry.Value.ToString()})
ListView1.Items.Add(item)
End If
Next
Next

Catch
Finally
ListView1.EndUpdate()
End Try
End Sub

Private Sub btnAdd_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnAdd.Click
Dim key As RegistryKey = OpenKey()
key.SetValue("Baloney", "Oscar Meyer",
RegistryValueKind.String)
key.Close()
SendMessage(HWND_BROADCAST, WM_SETTINGCHANGE, IntPtr.Zero,
"Environment")
End Sub

Private Sub btnDelete_Click(ByVal sender As System.Object, ByVal e
As System.EventArgs) Handles btnDelete.Click
Dim key As RegistryKey = OpenKey()
key.DeleteValue("Baloney", False)
key.Close()
SendMessage(HWND_BROADCAST, WM_SETTINGCHANGE, IntPtr.Zero,
"Environment")
End Sub

Private Function OpenKey() As RegistryKey
Return Registry.LocalMachine.OpenSubKey("System
\CurrentControlSet\Control\Session Manager\Environment", True)
End Function

Protected Overrides Sub WndProc(ByRef m As
System.Windows.Forms.Message)
If m.Msg = WM_SETTINGCHANGE AndAlso
Marshal.PtrToStringAuto(m.LParam) = "Environment" Then
LoadEnvironment()
End If
MyBase.WndProc(m)
End Sub

End Class

Basically a form with a listview and a couple of buttons. the
listview is in details view and has to columns, name and value. It
appears that the System.Environment.GetVariables() won't refresh
unless you pass in the target type. So, I do that, but make sure
their are no duplicates in the final list....

Kind of rough, but it seems to work ok :) At least on XP - I suspect
you might have permission issues on Vista ;)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top