65.9K
CodeProject 正在变化。 阅读更多。
Home

NVM#: 高效的环境变量管理器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.99/5 (22投票s)

2015年11月5日

MIT

11分钟阅读

viewsIcon

43712

downloadIcon

463

管理环境变量的最有效方法

更新 - 2017年3月

NVMSharp 项目已更新至 .NET Framework 4.6.2。最新代码可在 GitHub 获取。

引言

经过漫长的停顿后,很高兴能带着新文章回归。8年前,我写了第一篇 CodeProject 文章。那是 NVM - 环境变量管理器。那是一个用 C# 编写的、基于 .NET 3.0 Framework 的 Windows Forms 应用程序。它提供了简单易用的功能,使环境变量的管理相对容易。然而,在写那篇文章时,我为它计划了几个新功能。但我未能实现它们,它们被慢慢地搁置,随着时间的推移而逐渐被遗忘。

随着 Windows 10 的发布,微软带来了一种全新的用户体验,我对此感到非常兴奋。我决定是时候重新拾起那些旧想法,加入一些新想法,并从头开始重写 NVM,为开发者社区带来一个使用 **WPF** 和 **C# 6.0** 在 .NET 4.6 框架上编写的新环境变量管理器。

因此,**NVM#** 应运而生,我将在本文中详细介绍它。

问题

您可以通过在 **控制面板** 中选择 **系统**,选择 **高级系统设置**,然后点击 **环境变量** 来查看或修改环境变量。

管理 Windows 环境变量最大的问题在于其 *用户体验*。它*繁琐、不直观且笨重*,并且*自 Windows XP 以来一直未改变*!

在 **Windows 10** 中,虽然微软做了一些小的改进并增加了一些功能,但用户体验仍然感觉不佳!

解决方案

**NVM#** 通过提供类似于 Windows 10 设置应用程序的 UI,重新定义了环境变量管理的用户体验。未来,微软的目标是将所有控制面板功能集成到设置应用程序中。所以我想,为什么环境变量不行呢??

我最初计划为 NVM# 创建一个 通用 Windows 平台 (UWP) 应用,以便通过 Windows 应用商店提供。但是 UWP 应用无法访问注册表,因为它们在沙箱中运行以保护系统。所以我决定将 NVM# 创建为一个 WPF 桌面应用程序,它可以作为管理员运行,同时拥有 Windows 10 设置应用程序的外观和感觉。

汉堡菜单

这个应用程序还需要另一项功能(虽然 Windows 10 设置应用程序中没有),那就是用于创建 汉堡菜单SplitView 控件。现在,SplitView 控件 不是 WPF 控件。所以我不得不修改 RadioButton 的 ControlTemplate 来模拟汉堡菜单的外观和感觉。

视图

汉堡菜单允许您访问应用程序的 5 个视图

  • **用户** 允许您查看和操作用户环境变量。
  • **系统** 允许您查看和操作系统环境变量。
  • **导入** 允许您从 XML 文件导入环境变量。
  • **导出** 允许您将环境变量导出到 XML 文件。
  • **关于** 显示关于屏幕。

使用代码

环境变量

环境变量类似于键值对,其中环境变量名称是键,环境变量值代表该对的值。值可以包含单个标记,也可以包含多个由分号 (;) 分隔的标记。有两种类型的环境变量:

  • **用户** 环境变量(为每个用户设置)
  • **系统** 环境变量(为所有人设置)。

在注册表中,用户环境变量存储在:

HKEY_CURRENT_USER\Environment

而在系统环境变量存储在:

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

因此,您可以使用 System.Environment 类及其 GetEnvironmentVariablesGetEnvironmentVariableSetEnvironmentVariable 函数分别获取或设置值,或者您可以使用 Microsoft.Win32.RegistryMicrosoft.Win32.RegistryKey 类直接访问注册表。

在 **NVM#** 中,EnVar 类封装了一个环境变量。由于环境变量本质上类似于键值对,EnVar 具有三个主要属性:

  • Key 代表环境变量的名称。
  • Data 代表环境变量的值。
  • Values 代表通过按分隔符 ';' 分割 Data 获得的字符串标记列表。

对于 EnVar 对象,Key 属性是不可变的,因此该属性构成了两个 EnVar 对象之间相等性的基础。EnVar 还具有几个 API,可以轻松操作 DataValues 属性。

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 和 IOC

**NVM#** 是一个 WPF 应用程序,它采用了 MVVM 和 IOC 概念,以确保代码有效解耦。CoreWindow 类封装了主视图,CoreViewModel 类代表 ViewModel,而 Model 是注册表。

视图通过数据绑定与 ViewModel 通信,而 ViewModel 则通过定义的各种服务接口与视图进行交互(尤其是在需要用户交互时,例如显示消息框、选择要保存或打开的文件等)。服务接口是:

  • IDispatcherService 提供对 Dispatcher 类的访问。其他服务使用此服务。
  • IMessageService 允许 ViewModel 通过 MessageBox 类向用户显示消息。
  • IFileService 允许 ViewModel 显示 CommonOpenFileDialogCommonSaveFileDialog,让用户选择要打开或保存的文件。这些对话框存在于 Microsoft.WindowsAPICodecPack.Shell 包中,该包可通过 NuGet 获取。
  • IModificationService 允许 ViewModel 显示一个对话框,在创建或编辑环境变量或其值时获取用户输入。
  • IProgressService 在环境变量导入或导出进行时显示进度对话框。

这些服务可通过 ServiceManager 类访问。ViewModel 通过 RegistryManager 类访问 Model(即注册表)。

通过操作栏进行环境变量操作

如前所述,用户系统 视图分别显示用户和系统环境变量的详细信息。每个视图分为两个窗格。左窗格显示环境变量名称(按字母顺序排序)。右窗格显示所选环境变量(通过分割值派生)的标记列表。

操作栏 位于每个视图的最顶部。它主要由视图标题组成。对于 用户系统 视图,它还包含一组按钮,代表可以对环境变量及其值执行的常见任务。按钮可分为两组 - 一组(在左侧)包含针对环境变量的任务,另一组(在右侧)包含关注环境变量值(或多个值)的任务。

每个任务都会导致受影响的环境变量在注册表中得到更新。更新注册表的方法是一个 async 方法,直到它完成,操作栏中嵌入的一个不确定进度条(FluidProgressBar)就会显示出来。

导入和导出

在生产环境中,通常要求所有团队成员拥有相同的环境变量,以避免潜在的问题和故障(我自己也经历过这种情况好几次!)。或者考虑这样一个场景:当新成员加入团队,需要设置开发机器,难道不应该比手动输入每个环境变量更容易,只需点击按钮即可导入所有必要环境变量吗?**NVM#** 通过其导出和导入功能允许您做到这一点。

导出环境变量

要导出您的环境变量,您需要切换到 导出 视图(通过汉堡菜单)。导出 视图显示了可以从您的计算机导出的用户和系统环境变量列表。默认情况下,所有环境变量都已选中。您可以通过点击每个环境变量左侧显示的复选框来取消选择变量。如果您想选择或取消选择所有用户或系统变量,请分别点击用户或系统标题旁边的复选框。

选择完毕后,点击 导出 按钮进行导出。您将看到一个 保存文件对话框,您需要在其中提供要导出环境变量的 XML 文件的名称和位置。

这是导出文件内容的示例:

<?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>

 

导入环境变量

要导入您的环境变量,您需要切换到 导入 视图(通过汉堡菜单)。点击 选择要导入的文件 按钮,然后选择您想要导入的 XML 文件,然后点击 打开。文件成功解析后,将显示可以导入的环境变量。默认情况下,所有环境变量都将显示为选中状态。您可以通过点击每个环境变量左侧显示的复选框来取消选择变量。如果您想选择或取消选择所有用户或系统变量,请分别点击用户或系统标题旁边的复选框。

您尝试导入的环境变量可能已经在您的计算机上存在,但具有不同的值。在这种情况下,会发生冲突。如果一个(要导入的)环境变量与现有环境变量发生冲突,则在导入列表中,环境变量名称旁边会显示一个**红点**。底部会显示一条消息,说明找到的导入冲突数量。

发生冲突时,用户将获得以下选项:

  • **合并** - 这将合并新值中与旧值中现有标记的各个标记,同时避免重复。例如,如果存在一个名为 EV1 且值为 A;B;C 的环境变量。与一个名为 EV1 且值为 B;D;E;C;F 的环境变量合并后,合并结果将更新值为 A;B;C;D;E;F
  • **替换** - 这将用新值替换环境变量的现有值。
  • **忽略** - 这将忽略所有冲突的环境变量,*仅*导入非冲突的环境变量。
  • **清除** - 这将清除可以导入的环境变量列表,即取消导入。

如果没有冲突,用户可以选择 导入 所选环境变量或 清除 列表。

窗口样式

主窗口和子窗口是 SparkWindow(来自我的 WPFSpark 项目)的实例。我已经更新了 SparkWindow(是的,我正在开发 WPFSpark v1.2 :-)),使其外观和感觉与 Windows 10 中的窗口相同。我能够在窗口上添加“背景模糊”选项(模糊玻璃效果),这得益于 Rafael Rivera 的博客文章 - 为您的 Windows 10 应用添加“Aero Glass”模糊效果。​

关注点

以管理员身份运行

**NVM#** 需要以管理员身份运行,否则它将无法访问环境变量进行 CRUD 操作。为了强制应用程序仅在管理员模式下运行,您需要在项目中添加一个 app.manifest 文件(在解决方案资源管理器中右键单击您的项目,然后选择 **添加** > **添加新项**。这将打开一个对话框,您必须在该对话框中选择 **应用程序清单文件**)。

<?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>

单实例应用程序

**NVM#** 已更新为 单实例应用程序。因此,任何时候只会运行一个 NVM# 实例。如果用户尝试启动另一个实例,则先前激活的 NVM# 窗口将被带到焦点。有关如何创建单实例 WPF 应用程序的更多详细信息,请查看 此文章。(感谢 Alfred Broderick!)。您可以在 GitHub 查看最新的源代码。

Visual Studio:如果进程存在则 Taskkill

在项目的预生成事件中,我添加了以下代码片段:

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

该代码片段将在每次生成之前自动关闭应用程序(如果正在运行)(从而防止应用程序运行时导致生成卡住)。

有关更多详细信息,请查看我的 博客

注意

到目前为止,此应用程序仅在安装了 .Net 4.6 框架的 Windows 10 计算机上进行了测试。我尚未在 Windows 8.1(或更低版本)上进行测试。如果您在旧版 Windows 操作系统中遇到任何问题,如果您能告诉我,那将非常有帮助。

历史

  • **2015年11月20日** - 图标已转换为矢量图,以便在其他操作系统(Windows 8.1 及更低版本)上正确显示。
  • **2015年11月18日** - NVM# 已更新为单实例应用程序。
  • **2015年11月5日** - NVM# v2.0 发布

 

© . All rights reserved.