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






4.99/5 (22投票s)
管理环境变量的最有效方法
更新 - 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
类及其 GetEnvironmentVariables
、GetEnvironmentVariable
、SetEnvironmentVariable
函数分别获取或设置值,或者您可以使用 Microsoft.Win32.Registry
和 Microsoft.Win32.RegistryKey
类直接访问注册表。
在 **NVM#** 中,EnVar
类封装了一个环境变量。由于环境变量本质上类似于键值对,EnVar
具有三个主要属性:
Key
代表环境变量的名称。Data
代表环境变量的值。Values
代表通过按分隔符 ';
' 分割Data
获得的字符串标记列表。
对于 EnVar
对象,Key
属性是不可变的,因此该属性构成了两个 EnVar
对象之间相等性的基础。EnVar
还具有几个 API,可以轻松操作 Data
和 Values
属性。
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 显示CommonOpenFileDialog
或CommonSaveFileDialog
,让用户选择要打开或保存的文件。这些对话框存在于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 发布