返回

Windows应用筛选:仅显示搜索栏可见软件

windows

过滤安装软件:只显示 Windows 搜索栏可见应用

从Windows注册表中读取已安装软件信息并显示是很直接的做法,但经常会导致列表包含大量的系统组件、库以及其它用户通常不直接交互的条目,例如“Microsoft Visual C++ Redistributables”或者“WebView Runtime”。这些内容并不在Windows搜索栏可见,如何筛选掉它们呢?直接的匹配通常效果不佳,一个更为稳妥的方案是检查软件的“可搜索”特性。

问题分析

根本问题在于Windows注册表中的软件列表,包括了太多用户不直接使用的条目。要解决这个,必须找出用户在Windows搜索栏可以找到的软件与系统内部组件之间的区别。核心思路就是:Windows搜索栏展示的应用都有快捷方式入口,通常对应开始菜单中的项或者明确声明的App模型。这些入口使得搜索功能可以发现它们。

解决方案

针对上述问题,可以采用两个主要思路进行过滤:一是利用“可程序化应用程序注册”,二是检查开始菜单快捷方式。

方案一:检查程序注册表中的 SystemComponent

Windows在注册表中会标识哪些软件属于系统组件,这些系统组件通常不希望在用户应用列表中出现。通过查找注册表值SystemComponent可以判断软件是否属于此类。如果其值为 1,通常就意味着不应该在搜索结果中展示。

步骤:

  1. 修改现有的Python代码,添加对 SystemComponent 值的查询。
  2. 若存在该键且值为 1,则过滤该应用,不纳入结果列表中。

代码示例:

import winreg

def get_installed_apps():
    software_list = []
    hives = [(winreg.HKEY_LOCAL_MACHINE, winreg.KEY_WOW64_32KEY), 
             (winreg.HKEY_LOCAL_MACHINE, winreg.KEY_WOW64_64KEY), 
             (winreg.HKEY_CURRENT_USER, 0)]

    for hive, flag in hives:
        try:
            aReg = winreg.ConnectRegistry(None, hive)
            aKey = winreg.OpenKey(aReg, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", 0, winreg.KEY_READ | flag)
            count_subkey = winreg.QueryInfoKey(aKey)[0]

            for i in range(count_subkey):
                software = {}
                try:
                    asubkey_name = winreg.EnumKey(aKey, i)
                    asubkey = winreg.OpenKey(aKey, asubkey_name)
                    software['name'] = winreg.QueryValueEx(asubkey, "DisplayName")[0]

                    try:
                        software['version'] = winreg.QueryValueEx(asubkey, "DisplayVersion")[0]
                    except EnvironmentError:
                        software['version'] = 'undefined'
                    try:
                        software['publisher'] = winreg.QueryValueEx(asubkey, "Publisher")[0]
                    except EnvironmentError:
                        software['publisher'] = 'undefined'
                        
                    try:
                        is_system = winreg.QueryValueEx(asubkey, "SystemComponent")[0]
                        if is_system == 1:
                            continue # skip if system component
                    except EnvironmentError:
                        pass
                        
                    software_list.append(software)
                except EnvironmentError:
                    continue
        except Exception: # Catch registry-related errors, and proceed.
            continue


    return software_list


software_list = get_installed_apps()

for software in software_list:
     print('Name=%s, Version=%s, Publisher=%s' % (software['name'], software['version'], software['publisher']))

原理: 这种方法直接读取注册表,获取安装项中的 SystemComponent 值,1 代表是系统组件,跳过该应用,达到筛选目的。此方法运行速度快,较为可靠,也简单易行。

方案二:检查开始菜单快捷方式

另一种方案是检查开始菜单中是否有对应程序的快捷方式。 通常,用户可以通过 Windows 搜索栏直接启动的应用程序会在开始菜单中有对应的快捷方式项。因此,如果可以在开始菜单的快捷方式列表中找到某个安装程序的显示名称(Display Name)或相关路径,就说明该程序可搜索。

步骤:

  1. 定位到开始菜单快捷方式的路径。该路径通常位于 C:\ProgramData\Microsoft\Windows\Start Menu\Programs%APPDATA%\Microsoft\Windows\Start Menu\Programs
  2. 编写Python代码读取上述目录及其子目录中的.lnk 文件,这些是快捷方式文件。
  3. 提取快捷方式目标信息。比较其显示名称是否与安装列表的应用程序显示名称匹配。匹配成功表示应用应该展示在Windows 搜索栏里。
  4. 修改第一段代码逻辑,判断匹配快捷方式情况进行筛选。

代码示例:

import winreg
import os
import glob
import pythoncom
from win32com.shell import shell, shellcon

def get_start_menu_shortcuts():
  shortcut_paths = []
  for folder in [shell.SHGetFolderPath(0, shellcon.CSIDL_COMMON_STARTMENU, None, 0),
                 shell.SHGetFolderPath(0, shellcon.CSIDL_STARTMENU, None, 0)]:
    if os.path.exists(folder):
      for root, _, files in os.walk(folder):
        for file in files:
            if file.lower().endswith('.lnk'):
                shortcut_paths.append(os.path.join(root, file))
  return shortcut_paths


def get_target_from_shortcut(shortcut_path):
    try:
        shell = pythoncom.CoCreateInstance(shell.CLSID_ShellLink, None, pythoncom.CLSCTX_INPROC_SERVER, shell.IID_IShellLink)
        shell.QueryInterface(pythoncom.IID_IPersistFile).Load(shortcut_path)
        target_path = shell.GetPath()
        return target_path.lower() #Convert to lower case to ensure match.

    except Exception: # Avoid crashes on error parsing.
       return None


def get_installed_apps_with_shortcuts():
  software_list = []
  shortcut_targets = [get_target_from_shortcut(shortcut) for shortcut in get_start_menu_shortcuts()] #Generate valid lower-cased filepaths
  
  hives = [(winreg.HKEY_LOCAL_MACHINE, winreg.KEY_WOW64_32KEY),
           (winreg.HKEY_LOCAL_MACHINE, winreg.KEY_WOW64_64KEY),
           (winreg.HKEY_CURRENT_USER, 0)]
    

  for hive, flag in hives:
        try:
            aReg = winreg.ConnectRegistry(None, hive)
            aKey = winreg.OpenKey(aReg, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", 0, winreg.KEY_READ | flag)
            count_subkey = winreg.QueryInfoKey(aKey)[0]

            for i in range(count_subkey):
              software = {}
              try:
                    asubkey_name = winreg.EnumKey(aKey, i)
                    asubkey = winreg.OpenKey(aKey, asubkey_name)
                    
                    display_name = winreg.QueryValueEx(asubkey, "DisplayName")[0]
                    
                    try:
                        software['version'] = winreg.QueryValueEx(asubkey, "DisplayVersion")[0]
                    except EnvironmentError:
                        software['version'] = 'undefined'

                    try:
                      software['publisher'] = winreg.QueryValueEx(asubkey, "Publisher")[0]
                    except EnvironmentError:
                        software['publisher'] = 'undefined'


                    is_match = False
                    for shortcut_target in shortcut_targets:

                        if shortcut_target and (display_name.lower() in shortcut_target or display_name.lower() == os.path.basename(shortcut_target) ): 
                             is_match = True
                             break

                    if is_match :
                        software_list.append(software)

              except EnvironmentError:
                  continue
        except Exception:
          continue 
    

  return software_list



software_list = get_installed_apps_with_shortcuts()
for software in software_list:
      print('Name=%s, Version=%s, Publisher=%s' % (software['name'], software['version'], software['publisher']))

原理: 此方案核心是读取快捷方式的目标路径,然后使用该路径或其文件名与软件的 DisplayName 进行比较,这样可以识别到用户界面上可见的程序。这种方式考虑了多种可能性,因此会更全面。 使用Python win32comshleel模块来提取快捷方式。该方式依赖于系统API来解析快捷方式,因此更加准确和安全。

额外安全建议

  • 对从注册表和文件系统获取的数据,都要进行恰当的错误处理。保证程序的稳定和安全运行。
  • 尽量避免以系统管理员权限执行代码。采用权限最小化原则,能以普通用户权限获取结果的,避免请求更高权限。
  • 如果在生产环境中使用该程序,请进行充分测试。尤其是涉及解析外部链接和从不信任源中读取内容的情况。

这些方法提供了一个有效的手段来过滤 Windows 注册表中已安装软件列表。两种方案各有侧重,你可以结合实际场景选用或组合使用。方案一检查 SystemComponent 值快捷高效,方案二通过搜索快捷方式匹配提供更完善的检测方案。选择适合自己场景的方法,就能获取你需要的用户应用列表,摒弃多余的系统组件。