Reading Environment Variables from Another Running Process

Recently I was working on some changes aimed to achieve better usability experiences for Eazfuscator.NET.

One of the common pitfalls is the requirement to restart Microsoft Visual Studio after the first Eazfuscator.NET installation. This requirement is commonly addressed by a message box during installation asking user to restart the running instances of Visual Studio. While it is a technically simple solution, it adds one additional screen (a message box) to the user interface and user should react to that message box by pressing OK button. This can worsen the user experience as this stands out of the expected installation workflow.

So I decided to implement another solution. Whenever user tries to protect a project the following screen appears:


As you can see, there is a large amount of space where the desired message can reside. And it computes pretty well to the user workflow, unobtrusively suggesting to restart Visual Studio to complete the installation of Eazfucator.NET.  Of course, there is nothing to complete; the restart of Visual Studio is required just to apply an updated set of environment variables Eazfuscator.NET integration relies on.

The next thing to decide is when to show that message.
And here comes the most interesting part, actually directly reflecting the topic.

The only reliable way to detect the necessity of Visual Studio restart is to analyze its current environment variables. Visual Studio generally runs as devenv.exe process, so we have to retrieve its environment variable values.

The first implementation, I naively thought it had to work out:

    class Program
    {
        static void Main(string[] args)
        {
            var processes = Process.GetProcessesByName("devenv");
            foreach (var process in processes)
            {
                var env = process.StartInfo.EnvironmentVariables;
 
                string path = env["PATH"];
                Console.WriteLine(path);
            }
        }
    }

While it runs without errors, the retrieved value of PATH environment variable is wrong. It corresponds to the current running process, not that remote devenv.exe process it supposed to be. How does this happen? Well, this is a flaw in System.Diagnostics.Process class from .NET Framework. While it seems like it has to work out, actually it does not, giving you the wrong values without raising exceptions.

After investigation, it became evident that Windows has no API to retrieve the environment variables of another running process in the system.

But there is a good workaround which allows to retrieve the desired values with the help of ntdll.dll API and ReadProcessMemory function.

The technical details and sample C++ code can be found here. Unfortunately, I did not find a decent implementation for .NET which would handle both 32 bit and 64 bit scenarios. That's why I created the component for reading the environment variables of a running process from scratch.

So, let me introduce the interface part of the component. It is very simple and everything is implemented inside one class:

    using System.Collections.Specialized;
    using System.Diagnostics;
 
    static class ProcessEnvironment
    {
        public static StringDictionary GetEnvironmentVariables(this Process process)
        {
            // The implementation goes here.
        }
 
        public static StringDictionary TryGetEnvironmentVariables(this Process process)
        {
            // The implementation goes here.
        }
    }

As you can see, ProcessEnvironment is a static class with two extension methods.

And this is how it can be used:

    class Program
    {
        static void Main(string[] args)
        {
            var processes = Process.GetProcessesByName("devenv");
            foreach (var process in processes)
            {
                var env = process.GetEnvironmentVariables();
 
                string path = env["PATH"];
                Console.WriteLine(path);
            }
        }
    }

Neatly done, isn't it?

The full sources of the component and sample usage can be found in the sample project.

Download sample project

Labels: