eljamba
eljamba

Reputation: 385

How to list all app/software/programs installed on Windows 11 using a Python script

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?

Upvotes: 0

Views: 100

Answers (1)

eljamba
eljamba

Reputation: 385

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"])

Upvotes: 0

Related Questions