//
you're reading...
Programming Issues/Tips

Programmatically Adding/Removing Paths to/from the System Path

1. Introduction.

1.1 Recently, my development team had to programmatically add a new path to the system path.

1.2 After doing some research, we successfully wrote a small program that performs this small feat.

1.3 In this post, I share the code that was used to accomplish this task.

2. Altering the System Path.

2.1 The system path is stored in the system registry under the following key :

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\Path

registry_path

2.2 It is a string comprised of individual paths separated by semi-colons.

2.3 To add a new path, we simply append this string with the new path. To remove a path, we remove it from the string. The new string is then set as the new value of the “Path” environment value.

2.4 To do this in Windows 7, you will need to be logged in as an Administrator or under an account that allows for modification of the system registry.

3. Programmatically Changing the System Path.

3.1 In this section, we present a C# program that allows for changing the system path. The following is a brief synopsis of the program :

  • The program name is SystemPathModifier.exe.
  • It is a console program written in C#.
  • It takes 2 arguments : the first argument is a flag that indicates whether the program is to add or remove a path. The second is the path itself.

3.2 Listed below is the complete listing of the code of this program :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SystemPathModifier
{
    class Program
    {
        #region Pre-run Checks
        enum Action
        {
            NO_ACTION = 0,
            ADD_PATH = 1,
            REMOVE_PATH = 2
        }

        // DisplayUsage() displays instructions on the command line
        // of this program.
        static void DisplayUsage()
        {
            Console.WriteLine("Usage : SystemPathModifier [/a or /r] [Path].");
        }

        // CheckArguments() interprets the command line of the program
        // and sets values for the variables of this program.
        static bool CheckArguments(string[] args, out Action action, out string strSpecifiedPath)
        {
            // Assign receiver to default value.
            action = Action.NO_ACTION;
            strSpecifiedPath = "";

            // Ensure that there are at least 2 arguments.
            if (args.Length < 2)
            {
                DisplayUsage();
                return false;
            }

            if (args[0] == "/a")
            {
                action = Action.ADD_PATH;
                strSpecifiedPath = args[1];
                return true;
            }
            else if (args[0] == "/r")
            {
                action = Action.REMOVE_PATH;
                strSpecifiedPath = args[1];
                return true;
            }
            else
            {
                Console.WriteLine("Invalid flag : {0:S}.", args[0]);
                DisplayUsage();
                return false;
            }
        }
        #endregion

        #region System Path-related Helper functions.
        // GetCurrentSystemPaths() obtains the system path from the registry setting :
        // HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment
        // After obtaining this registry setting (which is a string), this function 
        // splits up the string into its constutuent parts (separated by a ';') and
        // stores each sub-string into a string array.
        static string[] GetCurrentSystemPaths()
        {
            string strPath = System.Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine);

            string[] split = strPath.Split(new Char[] { ';' });

            return split;
        }

        // AddPath() adds a new path to the system path.
        static void AddPath(string strNewPath)
        {
            string[] strPaths = GetCurrentSystemPaths();

            string strFoundPath = Array.Find(strPaths, new PathMatcherPredicate(strNewPath).MatchPath);

            if (strFoundPath == null)
            {
                // Path not found. We now add it in.
                int iSize = strPaths.Length;
                // We increment the strPaths array by one more element.
                Array.Resize(ref strPaths, iSize + 1);
                // Append strNewPath into the strPaths array.
                strPaths[iSize] = strNewPath;

                // We construct the system path string by concatenating
                // all elements from the strPaths array, separated by a ';'.
                string strNewSystemPath = ConcatStringArray(ref strPaths);

                // Replace the current system path with strNewSystemPath.
                System.Environment.SetEnvironmentVariable("Path", strNewSystemPath, EnvironmentVariableTarget.Machine);
                Console.WriteLine("New path successfully added to system.");
            }
            else
            {
                Console.WriteLine("New path already in system.");
            }
        }

        // RemovePath() removes a path from the system path.
        static void RemovePath(string strRemovalPath)
        {
            string[] strPaths = GetCurrentSystemPaths();

            string strFoundPath = Array.Find(strPaths, new PathMatcherPredicate(strRemovalPath).MatchPath);

            if (strFoundPath == null)
            {
                Console.WriteLine("Path is not in system.");
            }
            else
            {
                // We construct the new system path string by concatenating
                // all elements from the strPaths array, separated by a ';'.
                string strNewSystemPath = ConcatStringArray(ref strPaths, strRemovalPath);

                // Replace the current system path with strNewSystemPath.
                System.Environment.SetEnvironmentVariable("Path", strNewSystemPath, EnvironmentVariableTarget.Machine);
                Console.WriteLine("Path [{0:S}] successfully removed from system.", strRemovalPath);
            }
        }

        // ConcatStringArray() concatenates all strings from
        // the input strArray. Each string separated by a ';'.
        static string ConcatStringArray(ref string[] strArray)
        {
            int iSize = strArray.Length;
            int i = 0;
            string strRet = strArray[0];

            for (i = 1; i < iSize; i++)
            {
                strRet += String.Format(";{0:S}", strArray[i]);
            }

            return strRet;
        }

        // This overload of ConcatStringArray() concatenates all strings from
        // the input strArray but will ensure that strExclude is excluded. 
        // Each string separated by a ';'.
        static string ConcatStringArray(ref string[] strArray, string strExclude)
        {
            string strRet = "";

            foreach (string str in strArray)
            {
                if (String.Compare(str, strExclude, true) != 0)
                {
                    if (strRet != "")
                    {
                        strRet += ";";
                    }

                    strRet += str;
                }
            }

            return strRet;
        }

        // The PathMatcherPredicate class serves as the functor
        // for the Predicate delegate of the Array.Find() method.
        class PathMatcherPredicate
        {
            public PathMatcherPredicate(string strSpecifiedPath)
            {
                m_strSpecifiedPath = strSpecifiedPath;
            }

            public bool MatchPath(string strTestPath)
            {
                if (String.Compare(strTestPath, m_strSpecifiedPath, true) == 0)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }

            private string m_strSpecifiedPath;
        }

        #endregion

        static void Main(string[] args)
        {
            // Check the arguments for correctness.
            // Note that if the specified path contains spaces,
            // the path must be enclosed in quotations, e.g. :
            // "c:\my path"
            Action action;
            string strSpecifiedPath;

            if (CheckArguments(args, out action, out strSpecifiedPath) == false)
            {
                return;
            }

            if (action == Action.ADD_PATH)
            {
                AddPath(strSpecifiedPath);
            }
            else if (action == Action.REMOVE_PATH)
            {
                RemovePath(strSpecifiedPath);
            }
        }
    }
}

3.3 We shall omit discussing some of the peripheral code like CheckArguments(), DisplayUsage() and even Main(). These are easily understood and I leave it to the reader to digest them.

3.4 We will instead begin by studying AddPath() and RemovePath(). Along the way, we will analyze the supporting code that allow both functions to achieve their ends.

4. The AddPath() Function.

4.1 Listed below is the source codes of the AddPath() function :

static void AddPath(string strNewPath)
{
    string[] strPaths = GetCurrentSystemPaths();

    string strFoundPath = Array.Find(strPaths, new PathMatcherPredicate(strNewPath).MatchPath);

    if (strFoundPath == null)
    {
        // Path not found. We now add it in.
        int iSize = strPaths.Length;
        // We increment the strPaths array by one more element.
        Array.Resize(ref strPaths, iSize + 1);
        // Append strNewPath into the strPaths array.
        strPaths[iSize] = strNewPath;

        // We construct the system path string by concatenating
        // all elements from the strPaths array, separated by a ';'.
        string strNewSystemPath = ConcatStringArray(ref strPaths);

        // Replace the current system path with strNewSystemPath.
        System.Environment.SetEnvironmentVariable("Path", strNewSystemPath, EnvironmentVariableTarget.Machine);
        Console.WriteLine("New path successfully added to system.");
    }
    else
    {
        Console.WriteLine("New path already in system.");
    }
}

4.2 The AddPath() function uses the GetCurrentSystemPaths() function to obtain the current system paths which is returned as an array of strings.

4.3 After obtaining this array of path strings (i.e. strPaths), AddPath() uses the Array.Find() function to search for the presence of the target path among the elements of strPaths. We shall later study more in-depth how Array.Find() performs its search using the predicate supplied by the PathMatcherPredicate class.

4.4 If the target path is found, nothing further is performed and an appropriate message indicating that the target path is already in the system path will be displayed.

4.5 If the target path is not found, strPaths is increased in size by one more element to store the target path.

4.6 The strings in strPaths are then concatenated together by the ConcatStringArray() function with each path separated by a ‘;’.

4.7 The System.Environment.SetEnvironmentVariable() function is then used to set the system path to a new string (which includes the new target path).

4.8 And that’s it for AddPath(). We follow up with a study of the helper functions of AddPath().

5. GetCurrentSystemPaths().

5.1 Listed below is the source codes for the GetCurrentSystemPaths() function :

static string[] GetCurrentSystemPaths()
{
    string strPath = System.Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine);

    string[] split = strPath.Split(new Char[] { ';' });

    return split;
}

5.2 It uses the System.Environment.GetEnvironmentVariable() function to obtain the system path. It then uses the String class’ Split() method to separate the paths which are delimited by ‘;’.

5.3 The String.Split() method returns an array of strings which is then returned to the caller. What the caller receives then is an array of strings each of which is a component path of the system path.

6. The PathMatcherPredicate Class.

6.1 The source codes for the PathMatcherPredicate class is listed below :

class PathMatcherPredicate
{
    public PathMatcherPredicate(string strSpecifiedPath)
    {
        m_strSpecifiedPath = strSpecifiedPath;
    }

    public bool MatchPath(string strTestPath)
    {
        if (String.Compare(strTestPath, m_strSpecifiedPath, true) == 0)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    private string m_strSpecifiedPath;
}

6.2 It is meant to be instantiated in a call to Array.Find() in order to supply the search Predicate delegate. In this case, the Predicate delegate supplied is PathMatcherPredicate::MatchPath().

6.3 We create an instance of PathMatcherPredicate in the call to Array.Find() so that we can pass to this instance the path to be added to the system path (i.e. m_strSpecifiedPath). This path is passed as a parameter to the constructor for PathMatcherPredicate.

6.4 Array.Find() iterates through the string elements of the strPaths array and calls the MatchPath() function for each iteration, passing the string element as parameter to MatchPath().

6.5 The Predicate function MatchPath() returns true to indicate its input string parameter matches m_strSpecifiedPath. It returns false otherwise. By returning true, Array.Find() stops its iteration and returns the found string element. By returning false, the iteration continues.

6.6 MatchPath() uses the String.Compare() function to do the string comparison.

6.7 Special attention must be drawn to the way that the MatchPath() function gets specified as the Predicate for Array.Find(). The following is a summary :

  • First observe the syntax of the Array.Find() call in the AddPath() function :
string strFoundPath = Array.Find(strPaths, new PathMatcherPredicate(strNewPath).MatchPath);
  • The PathMatcherPredicate is instantiated with strNewPath as constructor parameter. Along with this, the MatchPath() function is added to the code.
  • What this means is that a new PathMatcherPredicate instance is created and specified with a path to compare. Thereafter, its MatchPath() function is called.
  • This technique is similar to using the C++ functor as part of a call to an STL algorithm.

7. ConcatStringArray().

7.1 The source codes for the ConcatStringArray() function is listed below :

static string ConcatStringArray(ref string[] strArray)
{
    int iSize = strArray.Length;
    int i = 0;
    string strRet = strArray[0];

    for (i = 1; i < iSize; i++)
    {
        strRet += String.Format(";{0:S}", strArray[i]);
    }

    return strRet;
}

7.2 ConcatStringArray() simply iterates through the string elements of its input strArray array and combines them all into one long string with each sub-string separated by a semi-colon (‘;’).

8. The RemovePath() Function.

8.1 Listed below is the source codes of the RemovePath() function :

static void RemovePath(string strRemovalPath)
{
    string[] strPaths = GetCurrentSystemPaths();

    string strFoundPath = Array.Find(strPaths, new PathMatcherPredicate(strRemovalPath).MatchPath);

    if (strFoundPath == null)
    {
        Console.WriteLine("Path is not in system.");
    }
    else
    {
        // We construct the new system path string by concatenating
        // all elements from the strPaths array, separated by a ';'.
        string strNewSystemPath = ConcatStringArray(ref strPaths, strRemovalPath);

        // Replace the current system path with strNewSystemPath.
        System.Environment.SetEnvironmentVariable("Path", strNewSystemPath, EnvironmentVariableTarget.Machine);
        Console.WriteLine("Path [{0:S}] successfully removed from system.", strRemovalPath);
    }
}

8.2 RemovePath() is the exact opposite of AddPath() although it uses some of the same functions that AddPath() uses.

8.3 It begins by calling GetCurrentSystemPaths() to obtain the current system paths stored in an array of strings.

8.4 It then calls Array.Find() and uses the familiar PathMatcherPredicate class’ MatchPath() predicate to check if strRemovalPath is among the paths stored inside the array of current system paths (i.e. strPaths).

8.5 If strRemovalPath is not among strPaths, then nothing further is done. A message indicating that the path to be removed is not in the system path will be displayed.

8.6 If strRemovalPath is found among strPaths, then RemovePath() will construct a new system path string by concatenating all the elements from the strPaths array, with the exception of strRemovalPath. Each path is separated by a ‘;’.

8.7 This is done by calling the version of ConcatStringArray() which takes 2 parameters :

static string ConcatStringArray(ref string[] strArray, string strExclude)
{
    string strRet = "";

    foreach (string str in strArray)
    {
        if (String.Compare(str, strExclude, true) != 0)
        {
            if (strRet != "")
            {
                strRet += ";";
            }

            strRet += str;
        }
    }

    return strRet;
}

8.8 This function iterates through each element from the strArray array and constructs the string of paths each separated by a ‘;’. If it finds strExclude, it will not include it in the string of paths.

8.9 The new system path string thus created will be updated to the registry via System.Environment.SetEnvironmentVariable().

9. In Conclusion.

9.1 I hope this little utility program will be useful to the reader.

9.2 Remember that if you run this program under Windows 7, you will need to run it as an Administrator or under an account that allows for updating into the system registry.

Advertisements

About Lim Bio Liong

I've been in software development for nearly 20 years specializing in C , COM and C#. It's truly an exicting time we live in, with so much resources at our disposal to gain and share knowledge. I hope my blog will serve a small part in this global knowledge sharing network. For many years now I've been deeply involved with C development work. However since circa 2010, my current work has required me to use more and more on C# with a particular focus on COM interop. I've also written several articles for CodeProject. However, in recent years I've concentrated my time more on helping others in the MSDN forums. Please feel free to leave a comment whenever you have any constructive criticism over any of my blog posts.

Discussion

No comments yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: