# Copyright 2026 SigmaSRC, Inc. All Rights Reserved.

"""Gather script for Installed Applications."""

import os
import platform
import logging
import glob
if os.name == "nt":
    import winreg as _winreg

logger = logging.getLogger("gather.gather_installedapp")
basicos = platform.uname()[0]
if basicos in [ "Microsoft", "Microsoft Windows" ]:
    basicos = "Windows"
if basicos != "Windows" and basicos != "Darwin":
    # Unsupported OS (e.g. Linux). Import cleanly and let Gather() return an empty
    # result rather than raising at import: this gather is deployed to the broad
    # "unix" agent category (which the server's osx->unix umbrella mapping shares
    # with Linux), so a Linux agent inevitably receives it. Skipping quietly avoids
    # a spurious rule error for a gather it simply can't perform here.
    logger.debug("installed applications not supported on %r; gathering nothing", basicos)

def Gather(collector):
    # write header to output
    table = collector.table("installedapp", ["appname", "appversion"])
    if os.name == "nt":
        path = r"Software\Microsoft\Windows\CurrentVersion\Uninstall"
        applist = []
        try:
            rootkey = _winreg.OpenKey(getattr(_winreg, "HKEY_LOCAL_MACHINE"), path)
            try:
                nsubkeys, nvalues, mtime = _winreg.QueryInfoKey(rootkey)
                for i in range(nsubkeys):
                    try:
                        subkeyname = _winreg.EnumKey(rootkey, i)
                    except OSError as err:
                        continue # continue if possible
                    subkeypath = path + "\\" + subkeyname
                    try:
                        subkey = _winreg.OpenKey(rootkey, subkeyname)
                    except OSError as err:
                        continue # continue if possible
                    try:
                        nsubkeys, nvalues, mtime = _winreg.QueryInfoKey(subkey)
                        app, ver = "", ""
                        for i in range(nvalues):
                            nm, val, typ = _winreg.EnumValue(subkey, i)
                            if isinstance(nm, str):
                                pass
                            else:
                                nm = str(nm)
                            if isinstance(val, str):
                                pass
                            else:
                                val = str(val)
                            if nm.lower() == "displayversion":
                                ver = val
                            if nm.lower() == "displayname":
                                if val not in applist:
                                    applist.append(val)
                                    app = val
                        if app != "":
                            table.add([app, ver])

                    finally:
                        _winreg.CloseKey(subkey)
            finally:
                _winreg.CloseKey(rootkey)

            # now to search the WoW 32-bit applications
            path = r"Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
            rootkey = _winreg.OpenKey(getattr(_winreg, "HKEY_LOCAL_MACHINE"), path)
            try:
                nsubkeys, nvalues, mtime = _winreg.QueryInfoKey(rootkey)
                for i in range(nsubkeys):
                    try:
                        subkeyname = _winreg.EnumKey(rootkey, i)
                    except OSError as err:
                        continue  # continue if possible
                    subkeypath = path + "\\" + subkeyname
                    try:
                        subkey = _winreg.OpenKey(rootkey, subkeyname)
                    except OSError as err:
                        continue  # continue if possible
                    try:
                        nsubkeys, nvalues, mtime = _winreg.QueryInfoKey(subkey)
                        app, ver = "", "Undefined"
                        for i in range(nvalues):
                            nm, val, typ = _winreg.EnumValue(subkey, i)
                            if isinstance(nm, str):
                                pass
                            else:
                                nm = str(nm)
                            if isinstance(val, str):
                                pass
                            else:
                                val = str(val)
                            if nm.lower() == "displayversion":
                                ver = val
                            if nm.lower() == "displayname":
                                if val not in applist:
                                    applist.append(val)
                                    app = val
                        if app != "":
                            table.add([app, ver])

                    finally:
                        _winreg.CloseKey(subkey)
            finally:
                _winreg.CloseKey(rootkey)

        except OSError as err:
            pass
    else:
        ostype = os.uname()[0]
        if ostype == "Darwin":
            do_osx(table)
    

def do_osx(table):
    # get the list of all Info.plist files
    files = glob.glob("/Library/Receipts/*/Contents/Info.plist") 
    for aFile in files:
        # get rid of the extension .plist
        filename = aFile[:-6]
        try:
            command = "defaults read '" + filename + "' CFBundleName 2>&1"
            f = os.popen(command, "r")
            app = f.read()
            f.close()
            if (app.endswith("\n")):
                app = app[:-1]
            command = "defaults read '" + filename + "' CFBundleShortVersionString 2>&1"
            f = os.popen(command)
            ver = f.read()
            f.close()
            if (ver.endswith("\n")):
                ver = ver[:-1]
            if (app.find("does not exist") < 0):
                table.add([app, ver])
        except:
            pass


def _test():
    from sigma.gather import gathering
    gathering.test(Gather)

if __name__ == "__main__":
    _test()
