0

I develop a Python script to list all installed apps in my Windows 11 system. I would like to replicate the same results displayed in Settings > App > Installed Apps. Optionally, I would like to list apps installed not only for my user but in the entire system. Surfing the web, I found these four commands:

cmd1 = "Get-WmiObject -Class Win32_Product | Select-Object Name, Version, Architecture | Sort-Object Name"
cmd2 = "Get-AppxPackage | Select-Object Name, Version, Architecture | Sort-Object Name"
path = f"HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*"
cmd3 = f"Get-ItemProperty -Path {path} | Select-Object DisplayName, DisplayVersion, Architecture | Sort-Object Name"
path = f"HKLM:\\Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*"
cmd4 = f"Get-ItemProperty -Path {path} | Select-Object DisplayName, DisplayVersion, Architecture | Sort-Object Name"

I develop a simple code to run these four commands sequentially:

def list_installed_apps(cmd: str): 
 # Run the PowerShell command and capture output
 result = subprocess.run(["powershell", "-Command", cmd], capture_output=True, text=True) 
 # Split the result into lines and filter out unwanted lines
 installed_apps = result.stdout.splitlines() 
 return installed_apps
##############################
# CMD 1 
##############################
cmd = "Get-WmiObject -Class Win32_Product | Select-Object Name, Version, Architecture | Sort-Object Name"
apps_1 = list_installed_apps(cmd)
# Find the index of the header line and column label boundaries
header_index = [i for i, app in enumerate(apps_1) if app.find("Version") != -1][0]
header = apps_1[header_index]
bound_1 = header.find("Version")
bound_2 = header.find("Architecture")
# Remove the header and any empty lines
apps_1 = [app.strip() for app in apps_1 if app.strip() and "Name" not in app]
apps_1 = apps_1[1:] #removes the line of -------
apps_1_ordered = [
 {"name": app[:bound_1], "version": app[bound_1:bound_2], "architecture": app[bound_2:]}
 for app in apps_1
]
##############################
# CMD 2
##############################
cmd = "Get-AppxPackage | Select-Object Name, Version, Architecture | Sort-Object Name"
apps_2 = list_installed_apps(cmd)
# Find the index of the header line and column label boundaries
header_index = [i for i, app in enumerate(apps_2) if app.find("Version") != -1][0]
header = apps_2[header_index]
bound_1 = header.find("Version")
bound_2 = header.find("Architecture")
# Remove the header and any empty lines
apps_2 = [app.strip() for app in apps_2 if app.strip() and "Name" not in app]
apps_2 = apps_2[1:] #removes the line of -------
apps_2_ordered = [
 {"name": app[:bound_1], "version": app[bound_1:bound_2], "architecture": app[bound_2:]}
 for app in apps_2
]
##############################
# CMD 3
##############################
path = f"HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*"
cmd = f"Get-ItemProperty -Path {path} | Select-Object DisplayName, DisplayVersion, Architecture | Sort-Object Name"
apps_3 = list_installed_apps(cmd)
# Find the index of the header line and column label boundaries
header_index = [i for i, app in enumerate(apps_3) if app.find("DisplayVersion") != -1][0]
header = apps_3[header_index]
bound_1 = header.find("DisplayVersion")
bound_2 = header.find("Architecture")
# Remove the header and any empty lines
apps_3 = [app.strip() for app in apps_3 if app.strip() and "DisplayName" not in app]
apps_3 = apps_3[1:] #removes the line of -------
apps_3_ordered = [
 {"name": app[:bound_1], "version": app[bound_1:bound_2], "architecture": app[bound_2:]}
 for app in apps_3
]
##############################
# CMD 4
##############################
path = f"HKLM:\\Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*"
cmd = f"Get-ItemProperty -Path {path} | Select-Object DisplayName, DisplayVersion, Architecture | Sort-Object Name"
apps_4 = list_installed_apps(cmd)
# Find the index of the header line and column label boundaries
header = apps_4[1]
bound_1 = header.find("DisplayVersion")
bound_2 = header.find("Architecture")
# Remove the header and any empty lines
apps_4 = [app.strip() for app in apps_4 if app.strip() and "DisplayName" not in app]
apps_4 = apps_4[1:] #removes the line of -------
apps_4_ordered = [
 {"name": app[:bound_1], "version": app[bound_1:bound_2], "architecture": app[bound_2:]}
 for app in apps_4
]
##############################
# SUM RESULTS OF ALL COMMANDS AND SAVE
##############################
all_apps = apps_1_ordered + apps_2_ordered + apps_3_ordered + apps_4_ordered
fileName = "" # SET YOUR NAME
with open(fileName, "w") as f:
 for app in all_apps:
 f.write(f"{app['name']} --- {app['version']} --- {app['architecture']}\n")

Unfortunately, this file content doesn't completely match the apps sorted in the settings. How can I obtain that list?

asked Feb 10, 2025 at 12:54
4
  • Have you tested what those commands actually output? Commented Feb 10, 2025 at 12:55
  • I compare the output of all of them with what I would like to achieve (the list in Settings). Each command returns a partial list, so I decide to sum all outputs. The list obtained is still incomplete. "Microsoft Visual Studio Code" and "Miniconda" do not appear in the file, just to make an example. Commented Feb 10, 2025 at 12:59
  • 1
    Check List of installed programs Commented Feb 10, 2025 at 13:22
  • In addition to @buran's recommendation you might check out calling win32 MsiEnumProductsEx() learn.microsoft.com/en-us/windows/win32/api/msi/… Commented Feb 10, 2025 at 15:05

1 Answer 1

0

Thanks to @buran answer/link, I post here my entire code:

import subprocess, winreg
import pandas as pd
def remove_duplicates(data: list):
 # Create a set to store unique combinations of (name, version, architecture)
 seen = set()
 # Create a new list for unique elements
 unique_data = []
 # Iterate through the list of dictionaries
 for item in data:
 name = item["name"].strip()
 version = item["version"].strip()
 architecture = item["architecture"].strip()
 
 # Create a tuple of the values for name, version, and architecture
 identifier = (name, version, architecture)
 
 # If this combination hasn't been seen before, add it to the result list and mark it as seen
 if identifier not in seen:
 seen.add(identifier)
 unique_data.append({"name": name, "version": version, "architecture": architecture})
 return unique_data
def list_installed_apps(cmd: str): 
 # Run the PowerShell command and capture output
 result = subprocess.run(["powershell", "-Command", cmd], capture_output=True, text=True)
 
 # Split the result into lines and filter out unwanted lines
 all_apps = result.stdout.splitlines()
 
 # Find the index of the header line and column label boundaries
 header_index = [i for i, app in enumerate(all_apps) if app.find("Version") != -1][0]
 header = all_apps[header_index]
 bound_1 = header.find("Version")
 bound_2 = header.find("Architecture")
 #bound_3 = header.find("Publisher")
 # Remove the header and any empty lines
 all_apps = [app.strip() for app in all_apps if app.strip() and "Name" not in app]
 all_apps = all_apps[1:] #removes the line of -------
 all_apps_ordered = [
 {
 "name": app[:bound_1], 
 "version": app[bound_1:bound_2], 
 "architecture": app[bound_2:], 
 #"architecture": app[bound_2:bound_3], 
 #"publisher": app[bound_3:]
 }
 for app in all_apps
 ]
 
 return all_apps_ordered
def list_installed_apps_from_registry(cmd: str): 
 # Run the PowerShell command and capture output
 result = subprocess.run(["powershell", "-Command", cmd], capture_output=True, text=True)
 
 # Split the result into lines and filter out unwanted lines
 all_apps = result.stdout.splitlines()
 
 # Find the index of the header line and column label boundaries
 header_index = [i for i, app in enumerate(all_apps) if app.find("DisplayVersion") != -1][0]
 header = all_apps[header_index]
 bound_1 = header.find("DisplayVersion")
 bound_2 = header.find("Architecture")
 #bound_3 = header.find("Publisher")
 # Remove the header and any empty lines
 all_apps = [app.strip() for app in all_apps if app.strip() and "DisplayName" not in app]
 all_apps = all_apps[1:] #removes the line of -------
 all_apps_ordered = [
 {
 "name": app[:bound_1], 
 "version": app[bound_1:bound_2], 
 "architecture": app[bound_2:], 
 #"architecture": app[bound_2:bound_3], 
 #"publisher": app[bound_3:]
 }
 for app in all_apps
 ]
 
 return all_apps_ordered
def query_registry(hive, flag):
 """
 Queries the registry for a list of installed software.
 Args:
 hive: a winreg constant specifying the registry hive to query.
 flag: a winreg constant specifying the access mode.
 Returns:
 A list of dictionaries, each containing the name, version, publisher, and
 architecture of a piece of software installed on the machine.
 """
 aReg = winreg.ConnectRegistry(None, hive)
 aKey = winreg.OpenKey(key=aReg, sub_key=r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
 reserved=0, access=winreg.KEY_READ | flag)
 count_subkey = winreg.QueryInfoKey(aKey)[0]
 software_list = []
 for i in range(count_subkey):
 software = {}
 try:
 asubkey_name = winreg.EnumKey(aKey, i)
 asubkey = winreg.OpenKey(key=aKey, sub_key=asubkey_name)
 software['name'] = winreg.QueryValueEx(asubkey, "DisplayName")[0]
 try:
 software['version'] = winreg.QueryValueEx(asubkey, "DisplayVersion")[0]
 except EnvironmentError:
 software['version'] = ""
 # try:
 # software['publisher'] = winreg.QueryValueEx(asubkey, "Publisher")[0]
 # except EnvironmentError:
 # software['publisher'] = ""
 try:
 software['architecture'] = winreg.QueryValueEx(asubkey, "Architecture")[0]
 except EnvironmentError:
 software['architecture'] = ""
 software_list.append(software)
 except EnvironmentError:
 continue
 return software_list
##############################
# CMD 1 
##############################
cmd = "Get-WmiObject -Class Win32_Product | Select-Object Name, Version, Architecture | Sort-Object Name"
apps_1 = list_installed_apps(cmd)
##############################
# CMD 2
##############################
cmd = "Get-AppxPackage | Select-Object Name, Version, Architecture | Sort-Object Name"
apps_2 = list_installed_apps(cmd)
##############################
# CMD 3
##############################
path = f"HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*"
cmd = f"Get-ItemProperty -Path {path} | Select-Object DisplayName, DisplayVersion, Architecture | Sort-Object Name"
apps_3 = list_installed_apps_from_registry(cmd)
##############################
# CMD 4
##############################
path = f"HKLM:\\Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*"
cmd = f"Get-ItemProperty -Path {path} | Select-Object DisplayName, DisplayVersion, Architecture | Sort-Object Name"
apps_4 = list_installed_apps_from_registry(cmd)
##############################
# CMD5, CM6, CM7
##############################
apps_5 = query_registry(winreg.HKEY_LOCAL_MACHINE, winreg.KEY_WOW64_32KEY)
apps_6 = query_registry(winreg.HKEY_LOCAL_MACHINE, winreg.KEY_WOW64_64KEY)
apps_7 = query_registry(winreg.HKEY_CURRENT_USER, 0)
##############################
# SUM RESULTS OF ALL COMMANDS
##############################
all_apps = apps_1 + apps_2 + apps_3 + apps_4 + apps_5 + apps_6 + apps_7
##############################################
# ELIMINATE DUPLICATES AND SORT BY NAME KEY
##############################################
# Output the unique list
all_apps_no_duplicates = remove_duplicates(all_apps)
all_apps_no_duplicates_sorted = sorted(all_apps_no_duplicates, key=lambda x: x["name"])
answered Feb 11, 2025 at 1:58
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.