Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

NVM#: Effective Environment Variables Manager

0.00/5 (No votes)
7 Jan 2016 1  
The most effective way to manage your environment variables

Update - March 2017

NVMSharp project has been updated to dot Net Framework 4.6.2. The latest code is available in GitHub.

Introduction

After a long hiatus, it feels good to be back with a new article. 8 years ago, I wrote my first CodeProject article. It was NVM - eNvironment Variables Manager. It was a Windows Forms application written in C# on the dot Net 3.0 Framework. It provided simple and user-friendly features which made management of Environment Variables relatively easier. However, at the time of writing that article, I had planned several new features for it. But I could not implement them and slowly they were put on the back burner only to fade into oblivion with the passage of time.

With the launch of Windows 10, Microsoft brought forward a new user experience which I am very excited about. I decided it was time to dust up those old ideas, bring in some new ones and rewrite NVM from scratch and give the developer community a new Environment Variables Manager written using WPF & C# 6.0 on the dot Net 4.6 framework.

As a result NVM# came into existence and I shall be talking about it in detail in this article.

The Problem

You can view or modify the environment variables by selecting System from the Control Panel, selecting Advanced System Settings, and clicking Environment Variables.

The biggest problem with managing Windows Environment Variables is the User Experience. It is tedious, unintuitive & cumbersome and has remained unchanged since Windows XP!

In Windows 10, though Microsoft has made some minor improvements and added a few features but the user experience still does not feel effective!

The Solution

NVM# redefines the user experience for managing Environment Variables by providing a UI similar to the one provided by the Settings app in Windows 10. In future, Microsoft aims at integrating all the Control Panel features into the Settings app. So I thought, why not Environment Variables too??

I had initially planned to create a Universal Windows Platform (UWP) app for NVM# so that it can be available through the Windows App Store. But UWP apps cannot access the Registry as the apps are executed in a sandbox to protect the system. So I decided to create NVM# as a WPF desktop application which can be run as an administrator while having the look and feel of the Windows 10 Settings App.

Hamburger Menu

Another thing that was required in this app (though it is not present in the Windows 10 Settings App) is the SplitView Control for creating the Hamburger Menu. Now SplitView Control is not a WPF control. So I had to modify the RadioButton's ControlTemplate to emulate the look and feel of a Hamburger Menu.

The Views

The Hamburger Menu allows you to access 5 views of the application:

  • User allows you to view and manipulate the User Environment Variables.
  • System allows you to view and manipulate the System Environment Variables.
  • Import allows you to import Environment Variables from an XML file.
  • Export allows you to export Environment Variables to an XML file.
  • About shows the About screen.

Using the code

Environment Variables

An Environment Variable is similar to a Key-Value pair with the Environment Variable Name being the Key and the Environment Variable Value representing the Value of the pair. The Value can either contain a single token or have multiple tokens separated by semi-colon(;). There are two types of Environment variables:

  • User environment variables (set for each user)
  • System environment variables (set for everyone).

In the Registry, the User environment variables are stored at:

HKEY_CURRENT_USER\Environment

and the System environment variables are stored at:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment

So, you can either use the System.Environment class and its GetEnvironmentVariables, GetEnvironmentVariable, SetEnvironmentVariable functions to obtain or set the values, respectively, or you can use the Microsoft.Win32.Registry and Microsoft.Win32.RegistryKey classes to directly access the Registry.

In NVM#, the class EnVar encapsulates an Environment Variable. Since Environment Variable is basically similar to a Key-Value pair, EnVar has three main properties:

  • Key represents the name of the Environment Variable.
  • Data represents the value of the Environment Variable.
  • Values represents the list of string tokens obtained by splitting the Data by the separator ';'.

For an EnVar object, the Key property is immutable and thus this property forms the basis for equality between two EnVar objects. EnVar also has several APIs which allow easy manipulation of the Data and the Values properties.

namespace NVMSharp.Common
{
    /// <summary>
    /// This class encapsulates an Environment Variable
    /// </summary>
    internal class EnVar : IEquatable<EnVar>
    {
        #region Properties

        /// <summary>
        /// The Name of the Environment Variable
        /// </summary>
        public string Key { get; }
        /// <summary>
        /// The Value of the Environment Variable 
        /// </summary>
        public string Data { get; private set; }
        /// <summary>
        /// The Value of the Environment Variable converted
        /// to a list of string values (if the value contains
        /// multiple sub-values separated by ';')
        /// </summary>
        public List<string> Values => Data?.Split(new[] { Constants.SEPARATOR }, StringSplitOptions.RemoveEmptyEntries).ToList();

        #endregion

        #region Construction / Initialization

        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="key">The name of the Environment Variable</param>
        /// <param name="data">The value of the Environment Variable</param>
        public EnVar(string key, string data)
        {
            Key = key;
            Data = data;
        }

        #endregion

        #region APIs

        ...

        #endregion

        #region IEquatable Implementation

        /// <summary>
        /// Checks if the given EnVar object has the same key
        /// as this EnVar object
        /// </summary>
        /// <param name="other">EnVar object</param>
        /// <returns></returns>
        public bool Equals(EnVar other)
        {
            if (other == null)
                return false;

            return other.Key == Key;
        }

        #endregion

        #region Overrides

        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;

            var enObj = obj as EnVar;
            return (enObj != null) && Equals(enObj);
        }

        public override int GetHashCode()
        {
            return Key.GetHashCode();
        }

        public override string ToString()
        {
            return $"{Key}: {Data}";
        }

        #endregion

        #region Operator Overloads

        public static bool operator ==(EnVar varA, EnVar varB)
        {
            bool result;

            if (ReferenceEquals(varA, varB))
            {
                result = true;
            } else if (((object) varA == null) || ((object) varB == null))
            {
                result = false;
            }
            else
            {
                result = varA.Key == varB.Key;
            }

            return result;
        }

        public static bool operator !=(EnVar varA, EnVar varB)
        {
            return !(varA == varB);
        }

        #endregion
    }
}

MVVM and IOC

NVM#, being a WPF application, employs the MVVM and IOC concepts to ensure that the code is effectively decoupled. The CoreWindow class encapsulates the main View, the CoreViewModel class represents the ViewModel and the Model is the Registry.

The View communicates to ViewModel through DataBinding, whereas the ViewModel interacts with the View (especially when there is user interaction required e.g. showing a MessageBox, selecting a file for saving or opening etc) through the various service interfaces defined. The service interfaces are:

  • IDispatcherService provides access to the Dispatcher class. This service is used by the other services.
  • IMessageService allows the ViewModel to display a message to the user via the MessageBox class.
  • IFileService allows the ViewModel to show the CommonOpenFileDialog or the CommonSaveFileDialog to let the user select a file for opening or saving, respectively. These dialogs are present in the Microsoft.WindowsAPICodecPack.Shell package which is available through NuGet.
  • IModificationService allows the ViewModel to display a dialog for getting the user input while creating or editing an Environment Variable or its value.
  • IProgressService shows a progress dialog when the import or the export of Environment Variables is in progress.

These services are accessible through the ServiceManager class. The ViewModel accesses the Model (i.e. the Registry) through the RegistryManager class.

Environment Variable Manipulation via the Action Bar

As previously stated, the User and the System Views show the details of the User and System Environment Variables respectively. Each View is divided into two panes. The left pane shows the Environment Variable names (sorted alphabetically). The right pane shows the list of tokens (derived by splitting the Value) of the selected Environment Variable.

The Action Bar occupies the top most position in each view. It primarily consists of the View Title. For the User and System Views, it additionally contains a set of buttons representing the common tasks that can be performed with Environment Variables and their values. The buttons can be divided into two groups - one group (on the left) consists of tasks targeted at the Environment Variable and the other group (on the right) contains tasks focused on the Environment Variable value(s).

Each of the task causes the affected Environment Variable to be updated in the Registry. The method to update the Registry is an async method and until it completes, an indeterminate progress bar (FluidProgressBar), which is embedded in the Action Bar, is displayed.

Import & Export

In a production environment, normally it is required that all team members have the same environment variables to avoid potential issues and failures (I have experienced this scenario several times myself!). Or consider a scenario when a new member joins the team and s/he needs to set up the development machine, wouldn't it be easier to import all the necessary Environment Variables at the click of button (instead of manually entering each of them!)? NVM# allows you to do so, with its Export and Import features.

Exporting Environment Variables

In order to export your Environment Variables, you need to switch to the Export View (via the Hamburger Menu). The Export View shows you a list of User and System Environment Variables that can be exported from your machine. By default, all your Environment Variables are selected. You can deselect the variables by clicking on the checkbox shown on the left of each Environment Variable. If you want to select or deselect the entire User or System variables, click on the CheckBox next to the User or System Header, respectively.

Once you have made your selection, click on the Export button to export it. You will be prompted with a Save File Dialog where you have to provide the name and location of the XML file you want to export the Environment Variables.

Here is a sample of the content of the exported file:

<?xml version="1.0" encoding="utf-8"?>
<nvm>
  <version>2.0</version>
  <user>
    <variable>
      <name>TEMP</name>
      <value>C:\Users\Guest01\AppData\Local\Temp</value>
    </variable>
    <variable>
      <name>Test2</name>
      <value>C:\Windows\system32\regedit.exe</value>
    </variable>
    <variable>
      <name>TMP</name>
      <value>C:\Users\Guest01\AppData\Local\Temp</value>
    </variable>
  </user>
  <system>
    <variable>
      <name>ComSpec</name>
      <value>C:\WINDOWS\system32\cmd.exe</value>
    </variable>
    <variable>
      <name>NUMBER_OF_PROCESSORS</name>
      <value>8</value>
    </variable>
    <variable>
      <name>OS</name>
      <value>Windows_NT</value>
    </variable>
    <variable>
      <name>Path</name>
      <value>C:\Program Files (x86)\Intel\iCLS Client\;C:\Program Files\Intel\iCLS Client\;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\Intel\Intel(R) Management Engine Components\DAL;</value>
    </variable>
    <variable>
      <name>PATHEXT</name>
      <value>.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC</value>
    </variable>
    <variable>
      <name>PROCESSOR_ARCHITECTURE</name>
      <value>AMD64</value>
    </variable>
  </system>
</nvm>

 

Importing Environment Variables

In order to export your Environment Variables, you need to switch to the Import View (via the Hamburger Menu). Click on the Select File to Import button and select the XML file which you would like to import and click on Open. Once the file is parsed successfully, the Environment Variables that can be imported will be displayed. By default, all the Environment Variables will be shown selected. You can deselect the variables by clicking on the checkbox shown on the left of each Environment Variable. If you want to select or deselect the entire User or System variables, click on the CheckBox next to the User or System Header, respectively.

It is possible that an Environment Variable which you are trying to import already exists on your machine but has a different value. In such a scenario, a conflict is said to have occured. If an Environment Variable (which is to be imported) is conflicting with an existing Environment Variable, then a red dot​ is shown next to the Environment Variable name in the import list. At the bottom, a message is displayed stating the number of import conflicts found.

In case of a conflict, the user is given the following options:

  • Merge - This will merge the individual tokens within the new Value with the existing tokens in the old Value while avoiding duplicates. For e.g. if there exists an Environment Variable with Name - EV1 and Value A;B;C. Upon merging with an Environment Variable with Name - EV1 and Value - B;D;E;C;F, the result of the merge will update the Value to A;B;C;D;E;F
  • Replace - This will replace the existing Value of the Environment Variable with the new Value.
  • Discard - This will discard all conflicting Environment Variables and import only non-conflicting Environment Variables.
  • Clear - This will clear the list of Environment Variables that can be imported i.e. it will cancel the import.

In case of no conflicts, the user is given the option to either Import the selected Environment Variables or to Clear the list.

Window Style

The main window and the child windows are instances of SparkWindow (from my WPFSpark project). I have updated the SparkWindow (yes, I am working on WPFSpark v1.2 :-) ) so that it has the same look and feel as a Window in Windows 10. I was able to add the blur behind option (blurred glass effect) to the Window based on the excellent tip from the blog article by Rafael Rivera - Adding the "Aero Glass" blur to your Windows 10 apps.​

Points of Interest

Run as Administrator

NVM# is required to be run as Administrator, otherwise it will not be able to access the Environment Variables for CRUD operations. In order to enforce the app to be run only in Administrator mode, you need to add an app.manifest file to your project (Right click on your project in the Solution Explorer, click on Add > Add New Item. This will bring up a dialog in which you must select Application Manifest File).

<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
  <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
    <security>
      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
        <requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />
      </requestedPrivileges>
    </security>
  </trustInfo>
</assembly>

Single Instance Application

NVM# has now been updated to be Single Instance Application. Therefore only one instance NVM# will run at any given time. If the user tries to invoke another instance, the previously active NVM# window will be brought in focus. For more details about how to create a Single Instance WPF application check this article. (Thanks to Alfred Broderick!). You can check GitHub for the latest source code.

Visual Studio :Taskkill if process exists

In the Pre-Build Events of the project, I have added the following snippet

taskkill /F /IM $(TargetName).exe 2>&1 | exit /B 0

This snippet would automatically close the application (if it is running) before each build (thus preventing the build from being stuck due to the application running).

Check my blog for more details.

Note

As of now this app has been tested only on a Windows 10 Machine with .Net 4.6 framework. I have not tested it on Windows 8.1 (or below). It would be great if you could let me know if you face any issues in the older Windows OSes.

History

  • 20-Nov-2015 - Icons converted to vector so that they display correctly on other OSs (Windows 8.1 and below).
  • 18-Nov-2015 - NVM# updated to be a Single Instance Application.
  • 5-Nov-2015 - NVM# v2.0 Released

 

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here