MSI存储安装目录以供未来的卸载任务使用。
只有安装程序设置了ARPINSTALLLOCATION
属性,才能使用INSTALLPROPERTY_INSTALLLOCATION
属性(即"InstallLocation"
)。但是,该属性是可选的,几乎没有人使用它。
我如何检索安装目录?
MSI存储安装目录以供未来的卸载任务使用。
只有安装程序设置了ARPINSTALLLOCATION
属性,才能使用INSTALLPROPERTY_INSTALLLOCATION
属性(即"InstallLocation"
)。但是,该属性是可选的,几乎没有人使用它。
我如何检索安装目录?
如在帖子的其他地方所述,我通常会在HKLM中编写注册表键,以便能够轻松检索安装目录以供后续安装使用。
在处理未执行此操作的设置时,我使用内置的Windows Installer功能AppSearch:http://msdn.microsoft.com/en-us/library/aa367578(v=vs.85).aspx 通过指定要查找的文件签名来定位先前安装的目录。
文件签名可以由文件名、文件大小和文件版本以及其他文件属性组成。每个签名都可以以一定的灵活性指定,因此您可以通过指定要查找的版本范围来查找同一文件的不同版本。请查看SDK文档:http://msdn.microsoft.com/en-us/library/aa371853(v=vs.85).aspx
在大多数情况下,我使用主应用程序EXE并设置紧密签名,通过查找具有正确版本和日期的文件的狭窄版本范围来实现。
最近我需要通过 Ketarin 自动化安装 Natural Docs。我可以假设它已经安装到默认路径下 (%ProgramFiles(x86)%\Natural Docs
),但我决定采取一种安全的方法。不幸的是,即使安装程序在 HKLM\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall
上创建了一个键,但其中任何值都没有导致我找到安装目录。
Stein 的回答提出了 AppSearch MSI 函数,看起来很有趣,但不幸的是 Natural Docs MSI 安装程序没有提供 Signature 表,使其方法无法奏效。
所以我决定搜索注册表以找到任何与 Natural Docs 安装目录相关的引用,结果在 HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components
键中找到了一个。
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components
查找所有值,如果主应用程序可执行文件(NaturalDocs.exe)在其中一个子键值中找到,则提取它(C:\Program Files (x86)\Natural Docs\NaturalDocs.exe
变成C:\Program Files (x86)\Natural Docs
),并将其添加到系统环境变量%PATH%中(这样我就可以直接调用“NaturalDocs.exe”而不是使用完整路径)。System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo("NaturalDocs.exe", "-h");
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
var process = System.Diagnostics.Process.Start (startInfo);
process.WaitForExit();
if (process.ExitCode != 0)
{
string Components = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components";
bool breakFlag = false;
string hKeyName = "HKEY_LOCAL_MACHINE";
if (Environment.Is64BitOperatingSystem)
{
hKeyName = "HKEY_LOCAL_MACHINE64";
}
string[] subKeyNames = RegGetSubKeyNames(hKeyName, Components);
// Array.Reverse(subKeyNames);
for(int i = 0; i <= subKeyNames.Length - 1; i++)
{
string[] valueNames = RegGetValueNames(hKeyName, subKeyNames[i]);
foreach(string valueName in valueNames)
{
string valueKind = RegGetValueKind(hKeyName, subKeyNames[i], valueName);
switch(valueKind)
{
case "REG_SZ":
// case "REG_EXPAND_SZ":
// case "REG_BINARY":
string valueSZ = (RegGetValue(hKeyName, subKeyNames[i], valueName) as String);
if (valueSZ.IndexOf("NaturalDocs.exe") != -1)
{
startInfo = new System.Diagnostics.ProcessStartInfo("setx", "path \"%path%;" + System.IO.Path.GetDirectoryName(valueSZ) + "\" /M");
startInfo.Verb = "runas";
process = System.Diagnostics.Process.Start (startInfo);
process.WaitForExit();
if (process.ExitCode != 0)
{
Abort("SETX failed.");
}
breakFlag = true;
}
break;
/*
case "REG_MULTI_SZ":
string[] valueMultiSZ = (string[])RegGetValue("HKEY_CURRENT_USER", subKeyNames[i], valueKind);
for(int k = 0; k <= valueMultiSZ.Length - 1; k++)
{
Ketarin.Forms.LogDialog.Log("valueMultiSZ[" + k + "] = " + valueMultiSZ[k]);
}
break;
*/
default:
break;
}
if (breakFlag)
{
break;
}
}
if (breakFlag)
{
break;
}
}
}
即使您不使用Ketarin,也可以轻松地将函数粘贴并通过Visual Studio或CSC构建。
更一般的方法是使用RegClassVBS,它允许注册表键递归,并且不依赖于.NET Framework平台或构建过程。
请注意,枚举组件键的过程可能会占用CPU。上面的示例具有Length参数,您可以使用它来向用户显示一些进度(例如“i from (subKeysName.Length-1) keys remaining”-创意)。在RegClassVBS中也可以采用类似的方法。
RegClassCS和RegClassVBS两个类都有文档和示例可供参考,您可以在任何软件中使用它们并为其开发做出贡献,通过在git repo上进行提交,并在其github页面上打开问题(当然,如果您遇到无法解决的问题,我们可以尝试重现该问题以找出解决方案。=)