获取系统中安装的应用程序

96

如何使用 C# 代码获取系统中安装的应用程序?

15个回答

125

遍历注册表键“SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall”似乎可以提供已安装应用程序的全面列表。

除了下面的示例,您还可以找到类似于我所做的此处的版本。

这只是一个简单的示例,您可能需要像第二个链接中提供的那样去掉空行。

string registry_key = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
using(Microsoft.Win32.RegistryKey key = Registry.LocalMachine.OpenSubKey(registry_key))
{
    foreach(string subkey_name in key.GetSubKeyNames())
    {
        using(RegistryKey subkey = key.OpenSubKey(subkey_name))
        {
            Console.WriteLine(subkey.GetValue("DisplayName"));
        }
    }
}

或者,您可以使用已经提到的WMI:

ManagementObjectSearcher mos = new ManagementObjectSearcher("SELECT * FROM Win32_Product");
foreach(ManagementObject mo in mos.Get())
{
    Console.WriteLine(mo["Name"]);
}

但是这种方法执行起来相对较慢,我听说它可能只列出在“ALLUSERS”下安装的程序,但这可能是不正确的。此外,它也会忽略Windows组件和更新,而这可能对你有用。


27
值得注意的是,如果您计划重复运行这个查询,使用WMI Win32_Product类是不明智的。请参考微软的这篇KB文章:http://support.microsoft.com/kb/974524/EN-US核心问题是,(a) Win32_Product非常慢,且(b)它会为系统上每个安装的产品生成“Windows Installer重新配置了该产品。”事件日志消息...每次运行查询都会如此。哎呀!这篇文章建议使用Win32reg_AddRemovePrograms类...但这个类只有在安装了SMS后才会出现。哎呀!因此,最好还是坚持使用注册表查询。 - Simon Gillbee
15
嗯,这就是为什么我的回答中首先提到了注册表的例子。WMI只是作为一种替代方案被简单地介绍了一下,甚至我还指出了它执行较慢等其他缺点。请从开头仔细阅读回答。;) - Xiaofu
@Dhru'soni,如果您知道需要哪些程序,那么这可能不是最好的方法;如果您知道特定程序所需的注册表键(例如“SOFTWARE\JavaSoft\Java Development Kit”或其他),那么速度会更快。如果您查看注册表中位于“卸载”节点下的条目,如此答案所述,您将看到它们的键是GUID(类似GUID),因此您别无选择,只能遍历整个列表以找到所需内容。 - LeeCambl
1
有点奇怪,但如果您卸载一个程序并重新安装它,然后尝试使用注册表键找到它,除非您重新启动计算机,否则无法找到它。 - Yar
4
回答我的问题: https://dev59.com/Zobca4cB1Zd3GeqPZbdD 虽然有点麻烦,但你可能需要查询64位和32位。 - Robert Koernke
显示剩余9条评论

17

我想要提取一个应用程序列表,就像它们在开始菜单中显示的那样。使用注册表,我得到的条目并没有出现在开始菜单中。

我还想找到exe路径,并提取图标以最终制作一个漂亮的启动器。不幸的是,使用注册表方法有点靠运气,因为我的观察结果是这些信息不是可靠地可用的。

我的替代方法基于shell:AppsFolder,您可以通过运行explorer.exe shell:appsFolder来访问它,它列出了所有应用程序,包括当前安装并可通过开始菜单访问的商店应用程序。问题是这是一个虚拟文件夹,无法使用System.IO.Directory访问。相反,您需要使用本机shell32命令。幸运的是,Microsoft在Nuget上发布了Microsoft.WindowsAPICodePack-Shell,它是前面命令的一个包装器。话说到此为止,这里是代码:

// GUID taken from https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid
var FOLDERID_AppsFolder = new Guid("{1e87508d-89c2-42f0-8a7e-645a0f50ca58}");
ShellObject appsFolder = (ShellObject)KnownFolderHelper.FromKnownFolderId(FOLDERID_AppsFolder);

foreach (var app in (IKnownFolder)appsFolder)
{
    // The friendly app name
    string name = app.Name;
    // The ParsingName property is the AppUserModelID
    string appUserModelID = app.ParsingName; // or app.Properties.System.AppUserModel.ID
    // You can even get the Jumbo icon in one shot
    ImageSource icon =  app.Thumbnail.ExtraLargeBitmapSource;
}

就是这样了。您还可以使用以下方式启动应用程序

System.Diagnostics.Process.Start("explorer.exe", @" shell:appsFolder\" + appModelUserID);

这适用于常规的Win32应用程序和UWP商店应用程序。这些苹果怎么样。

既然您有兴趣列出所有已安装的应用程序,那么您可能还希望监视新应用程序或已卸载的应用程序,您可以使用ShellObjectWatcher实现:

ShellObjectWatcher sow = new ShellObjectWatcher(appsFolder, false);
sow.AllEvents += (s, e) => DoWhatever();
sow.Start();

编辑:有人可能会对知道上面提到的AppUserModelID感兴趣,它是 Windows用来分组任务栏窗口的唯一标识符.

2022: 在Windows 11中测试,仍然运行良好。 Windows 11似乎也缓存了并非直接安装的应用程序,例如不需要安装的便携式应用程序。 它们会出现在开始菜单的搜索结果中,还可以从shell:appsFolder检索。


1
谢谢!我实际上正在做同样的事情;这也帮助了我解决其他问题,例如“如何发现刚安装的应用程序的主可执行文件”的问题-> https://stackoverflow.com/questions/60440044/how-to-discover-the-main-executable-file-of-an-application那么感谢您! :) - forlayo
有没有一种方法可以使用参数启动该进程? - Starwave
不幸的是,这种方法会极大地占用内存 - 我的应用程序使用了57 MB的内存,当我使用这种方法获取应用程序列表时,它飙升到了882 MB。请注意 - 只有在我同时获取图标数据时才会发生这种情况。 - Starwave
如果您还要显示图标,请使用“SmallBitmapSource”来加速集合的创建并减少内存使用。 - Starwave
“SmallBitmapSource” 图标增加了 5 MB 的内存使用量(因此我的应用程序的总使用量为 62 MB),这并不算什么(我的应用程序文件夹中有 217 个项目)。 - Starwave
显示剩余5条评论

10

我认为枚举注册表键是最好的方法。

注意,提供的键 @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"将列出32位Windows安装中的所有应用程序以及64位Windows安装中的64位应用程序。

为了查看在Windows 64位安装上安装的32位应用程序,您还需要枚举键@"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"


你确定吗?在我的 Windows 10 Enterprise 64 位系统中,这两个列表看起来很相似,并且 x86 应用程序都会显示出来。 - Florian Straub
谢谢,这对我有用,我找到了我正在寻找的程序。 - Xtian11
regedit 中看起来是这样的。然而,在一个32位程序(在64位Windows上)中,两个列表与 regedit 中的 WOW6432Node 列表相同。 - Meow Cat 2012

8
您可以查看这篇文章。它利用注册表读取已安装应用程序的列表。
public void GetInstalledApps()
{
    string uninstallKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
    using (RegistryKey rk = Registry.LocalMachine.OpenSubKey(uninstallKey))
    {
        foreach (string skName in rk.GetSubKeyNames())
        {
            using (RegistryKey sk = rk.OpenSubKey(skName))
            {
                try
                {
                    lstInstalled.Items.Add(sk.GetValue("DisplayName"));
                }
                catch (Exception ex)
                { }
            }
        }
    }
}

我不需要整个列表,我只需要一些选定的安装程序,那我该怎么做呢?谢谢。 - Dhru 'soni

7

虽然已经有接受的解决方案可用,但它并不完整。远远不够。

如果您想获取所有密钥,则需要考虑两个因素:

x86和x64应用程序无法访问相同的注册表。 基本上,x86无法正常访问x64注册表。而某些 应用程序仅向x64注册表注册。

某些应用程序实际上会安装到CurrentUser注册表中,而不是LocalMachine

考虑到这一点,我使用以下代码成功获取了没有使用WMI的所有已安装应用程序

以下是代码:

List<string> installs = new List<string>();
List<string> keys = new List<string>() {
  @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
  @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
};

// The RegistryView.Registry64 forces the application to open the registry as x64 even if the application is compiled as x86 
FindInstalls(RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64), keys, installs);
FindInstalls(RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64), keys, installs);

installs = installs.Where(s => !string.IsNullOrWhiteSpace(s)).Distinct().ToList();
installs.Sort(); // The list of ALL installed applications



private void FindInstalls(RegistryKey regKey, List<string> keys, List<string> installed)
{
  foreach (string key in keys)
  {
    using (RegistryKey rk = regKey.OpenSubKey(key))
    {
      if (rk == null)
      {
        continue;
      }
      foreach (string skName in rk.GetSubKeyNames())
      {
        using (RegistryKey sk = rk.OpenSubKey(skName))
        {
          try
          {
            installed.Add(Convert.ToString(sk.GetValue("DisplayName")));
          }
          catch (Exception ex)
          { }
        }
      }
    }
  }
}

3
值得注意的是,Win32_Product WMI类表示产品安装方式为Windows Installer。并非所有应用程序都使用Windows Installer。
但是,“SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall”表示32位应用程序。对于64位,您还需要遍历“HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall”,由于不是每个软件都有64位版本,因此安装的总应用程序是两个位置上具有“UninstallString”值的键的联合。
但是最好的选择仍然是相同的。遍历注册表键是更好的方法,因为每个应用程序都在注册表中有一个条目[包括Windows Installer中的条目]。但是,注册表方法是不安全的,因为如果有人删除相应的键,则您将不知道应用程序条目。相反,更改HKEY_Classes_ROOT\Installers更棘手,因为它与许可问题(如Microsoft Office或其他产品)相关联。对于更强大的解决方案,您始终可以将注册表替代方案与WMI结合使用。

2
string[] registryKeys = new string[] {
@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
@"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" };

public class InstalledApp
{
    public string DisplayName { get; set; }
    public string DisplayIcon { get; set; }
    public string Version { get; set; }
    public string InstallLocation { get; set; }
}

private void AddInstalledAppToResultView(RegistryHive hive, RegistryView view, string registryKey,Dictionary<string,InstalledApp> resultView)
{
    using (var key = RegistryKey.OpenBaseKey(hive, view).OpenSubKey(registryKey))
    {
        foreach (string subKeyName in key.GetSubKeyNames())
        {
            using (RegistryKey subkey = key.OpenSubKey(subKeyName))
            {
                var displayName = subkey.GetValue("DisplayName");
                var displayIcon = subkey.GetValue("DisplayIcon");
                if (displayName == null || displayIcon == null)
                    continue;

                var app = new InstalledApp
                {
                    DisplayName = (string)displayName,
                    DisplayIcon = (string)displayIcon,
                    InstallLocation = (string)subkey.GetValue("InstallLocation"),
                    Version = (string)subkey.GetValue("DisplayVersion")
                };
                
                if(!resultView.ContainsKey(app.DisplayName))
                {
                    resultView.Add(app.DisplayName,app);
                }
            }
        }
    }
}


void Main()
{
    var result = new Dictionary<string,InstalledApp>();
    var view = Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Registry32;
    AddInstalledAppToResultView(RegistryHive.LocalMachine, view, registryKeys[0],result);
    AddInstalledAppToResultView(RegistryHive.CurrentUser, view, registryKeys[0],result);
    AddInstalledAppToResultView(RegistryHive.LocalMachine, RegistryView.Registry64, registryKeys[1],result);
    
    Console.WriteLine("==============" + result.Count + "=================");
    result.Values.ToList().ForEach(item => Console.WriteLine(item));
}

1

使用Windows Installer API!

它允许可靠地枚举所有程序。注册表不可靠,但WMI太重量级。


肯定是重量级的 - 如果重复运行,性能会像重量级一样下降。如果我的应用程序的某个功能依赖于另一个应用程序,并且知道是否已正确安装,则只需要卸载32位或64位的注册表键,仅当该应用程序也在64位中可用时。另一方面,如果必须使用WMI,则将通过智能属性技巧限制仅在应用程序期间使用一次。 - gg89
https://learn.microsoft.com/en-us/windows/desktop/msi/installer-function-reference - Brian Cannard

1

遍历"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"键并检查它们的"DisplayName"值。


1

正如其他人指出的那样,被接受的答案并没有返回x86和x64的安装。下面是我的解决方案。它创建了一个StringBuilder,将注册表值附加到其中(带有格式),并将其输出写入文本文件:

const string FORMAT = "{0,-100} {1,-20} {2,-30} {3,-8}\n";

private void LogInstalledSoftware()
{
    var line = string.Format(FORMAT, "DisplayName", "Version", "Publisher", "InstallDate");
    line += string.Format(FORMAT, "-----------", "-------", "---------", "-----------");
    var sb = new StringBuilder(line, 100000);
    ReadRegistryUninstall(ref sb, RegistryView.Registry32);
    sb.Append($"\n[64 bit section]\n\n{line}");
    ReadRegistryUninstall(ref sb, RegistryView.Registry64);
    File.WriteAllText(@"c:\temp\log.txt", sb.ToString());
}

   private static void ReadRegistryUninstall(ref StringBuilder sb, RegistryView view)
    {
        const string REGISTRY_KEY = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
        using var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, view);
        using var subKey = baseKey.OpenSubKey(REGISTRY_KEY);
        foreach (string subkey_name in subKey.GetSubKeyNames())
        {
            using RegistryKey key = subKey.OpenSubKey(subkey_name);
            if (!string.IsNullOrEmpty(key.GetValue("DisplayName") as string))
            {
                var line = string.Format(FORMAT,
                    key.GetValue("DisplayName"),
                    key.GetValue("DisplayVersion"),
                    key.GetValue("Publisher"),
                    key.GetValue("InstallDate"));
                sb.Append(line);
            }
            key.Close();
        }
        subKey.Close();
        baseKey.Close();
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接