247 lines
8.1 KiB
Python
Executable File
247 lines
8.1 KiB
Python
Executable File
#!/bin/env python3
|
|
|
|
import argparse
|
|
import pathlib
|
|
import re
|
|
import os
|
|
import xml.etree.ElementTree as ET
|
|
|
|
parser = argparse.ArgumentParser(description='Deals with DISM stuff.',
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
|
parser.add_argument('manifests',
|
|
help='DISM manifests directory',
|
|
type=pathlib.Path)
|
|
parser.add_argument('packages', nargs="*",
|
|
help='Packages to manipulate',
|
|
type=str)
|
|
|
|
args = parser.parse_args()
|
|
|
|
namespaces = {
|
|
"asm": "urn:schemas-microsoft-com:asm.v3"
|
|
}
|
|
|
|
known_tags = [
|
|
"{urn:schemas-microsoft-com:asm.v3}assemblyIdentity",
|
|
"{urn:schemas-microsoft-com:asm.v3}dependency",
|
|
"{urn:schemas-microsoft-com:asm.v3}file",
|
|
'{urn:schemas-microsoft-com:asm.v3}genericCommands',
|
|
"{urn:schemas-microsoft-com:asm.v3}registryKeys",
|
|
|
|
"{urn:schemas-microsoft-com:asm.v3}trustInfo",
|
|
"{urn:schemas-microsoft-com:asm.v3}localization",
|
|
"{urn:schemas-microsoft-com:asm.v3}instrumentation",
|
|
"{urn:schemas-microsoft-com:asm.v3}configuration",
|
|
'{urn:schemas-microsoft-com:wcs.v1}Application',
|
|
'{urn:schemas-microsoft-com:rescache.v1}rescache',
|
|
'{urn:schemas-microsoft-com:asm.v3}mof',
|
|
'{urn:schemas-microsoft-com:asm.v3}imaging',
|
|
'{urn:schemas-microsoft-com:asm.v3}deployment',
|
|
'{urn:schemas-microsoft-com:asm.v3}directories',
|
|
'{urn:schemas-microsoft-com:asm.v3}memberships',
|
|
'{urn:schemas-microsoft-com:asm.v3}migration',
|
|
'{urn:schemas-microsoft-com:asm.v3}unattendActions'
|
|
]
|
|
unknown_tags = []
|
|
|
|
|
|
variables = {
|
|
"runtime.system32": "C:\\windows\\system32",
|
|
"runtime.programfiles": "C:\\Program Files",
|
|
"runtime.windows": "C:\\windows",
|
|
"runtime.wbem": "C:\\windows\\wbem",
|
|
"runtime.commonfiles": "C:\\Program Files\\Common Files"
|
|
}
|
|
unknown_variables = []
|
|
|
|
known = {}
|
|
|
|
def resolve_variables_lookup(match) -> str:
|
|
capture = match.group(1)
|
|
if capture is None:
|
|
return "!!!ERROR!!!"
|
|
if capture.lower() in variables:
|
|
return variables[capture.lower()]
|
|
else:
|
|
if not capture.lower() in unknown_variables:
|
|
unknown_variables.append(capture.lower())
|
|
return "\033[91m${!!!UNKNOWN KEY: " + capture + "!!!}\033[0m"
|
|
|
|
def resolve_variables(input_str: str):
|
|
if input_str is None:
|
|
return None
|
|
return re.sub(r'\$\(([^)]+)\)', resolve_variables_lookup, input_str)
|
|
|
|
def print_deps(root, depth: int = 0, print_header: bool = True, print_all: bool = False):
|
|
dep = root.findall("asm:dependency/asm:dependentAssembly", namespaces)
|
|
|
|
if print_header and len(dep) > 0:
|
|
print(f'{" " * (depth * 2 + 1)}Depends on:')
|
|
depth += 1
|
|
|
|
for d in dep:
|
|
a_type = d.get("dependencyType")
|
|
|
|
x = d.find("asm:assemblyIdentity", namespaces)
|
|
name = x.get("name")
|
|
print(f'{" " * (depth * 2 + 1)}- {name} ({x.get("version")}) {a_type}')
|
|
if print_all:
|
|
if not name.lower() in known:
|
|
print(f'{" " * (depth * 2 + 3)}- \033[91m!!!MISSING!!!\033[0m')
|
|
continue
|
|
print_deps(known[name.lower()].getroot(), depth + 1, False, True)
|
|
|
|
def print_files(root, depth: int = 0, print_header: bool = True, print_all: bool = False, print_from: bool = False, print_info: bool = True):
|
|
files = root.findall("asm:file", namespaces)
|
|
|
|
if print_header and (len(files) > 0 or print_all):
|
|
print(f'{" " * (depth * 2 + 1)}Files:')
|
|
depth += 1
|
|
|
|
for x in files:
|
|
name = x.get("name")
|
|
if not print_from:
|
|
print(f'{" " * (depth * 2 + 1)}- {name}')
|
|
else:
|
|
identity = root.find("asm:assemblyIdentity", namespaces)
|
|
print(f'{" " * (depth * 2 + 1)}- {name} from {identity.get("name")} ({identity.get("version")})')
|
|
if print_info:
|
|
print(f'{" " * (depth * 2 + 3)} - To "{resolve_variables(x.get("destinationPath"))}" from "{x.get("sourcePath")}{x.get("sourceName")}"')
|
|
|
|
if not print_all:
|
|
return
|
|
|
|
for x in root.findall("asm:dependency/asm:dependentAssembly/asm:assemblyIdentity", namespaces):
|
|
name = x.get("name")
|
|
if not name.lower() in known:
|
|
print(f'{" " * (depth * 2 + 1)}- \033[91m!!!MISSING!!!\033[0m')
|
|
continue
|
|
print_files(known[name.lower()], depth, False, True, True)
|
|
|
|
value_types = []
|
|
|
|
def print_registry(root, depth: int = 0, print_header: bool = True, print_all: bool = False, print_from: bool = False, print_info: bool = True):
|
|
count = 0
|
|
values = 0
|
|
|
|
reg_keys = root.findall("asm:registryKeys/asm:registryKey", namespaces)
|
|
|
|
if print_header and (len(reg_keys) > 0 or print_all):
|
|
print(f'{" " * (depth * 2 + 1)}Registry:')
|
|
depth += 1
|
|
value_types.clear()
|
|
|
|
for key in reg_keys:
|
|
print(f'{" " * (depth * 2 + 3)}- [{key.get("keyName")}]')
|
|
|
|
reg_values = key.findall("asm:registryValue", namespaces)
|
|
for value in reg_values:
|
|
name = value.get("name")
|
|
if name == "":
|
|
name = "@"
|
|
else:
|
|
name = f"\"{name}\""
|
|
vtype = value.get("valueType")
|
|
if not vtype.upper() in value_types:
|
|
value_types.append(vtype.upper())
|
|
if vtype.upper() == "REG_SZ":
|
|
vtype = ""
|
|
print(f'{" " * (depth * 2 + 7)}{name} = {vtype}"{resolve_variables(value.get("value"))}"')
|
|
|
|
|
|
count += 1
|
|
values += len(reg_values)
|
|
#print(key.get("keyName"))
|
|
|
|
if not print_all:
|
|
return
|
|
|
|
for x in root.findall("asm:dependency/asm:dependentAssembly/asm:assemblyIdentity", namespaces):
|
|
name = x.get("name")
|
|
if not name.lower() in known:
|
|
print(f'{" " * (depth * 2 + 1)}- \033[91m!!!MISSING!!!\033[0m')
|
|
continue
|
|
print(f'{" " * (depth * 2 + 3)}- {name}')
|
|
add_count, add_values = print_registry(known[name.lower()], depth + 1, False, True, True)
|
|
count += add_count
|
|
values += add_values
|
|
|
|
if count > 0:
|
|
print(f'{" " * (depth * 2 + 1)}- Keys: {count}')
|
|
print(f'{" " * (depth * 2 + 1)}- Values: {values}')
|
|
if print_header:
|
|
print(f'{" " * (depth * 2 + 1)}- Value types: {value_types}')
|
|
|
|
return (count, values)
|
|
|
|
|
|
def check_unknown_tags(root, depth: int = 0, print_header: bool = True, do_print: bool = False):
|
|
for child in root:
|
|
if child.tag not in known_tags:
|
|
if do_print:
|
|
if print_header:
|
|
print(f'{" " * (depth * 2 + 1)} Unknown tags:')
|
|
depth += 1
|
|
print_header = False
|
|
print(f'{" " * (depth * 2 + 1)}- {child.tag}')
|
|
if child.tag not in unknown_tags:
|
|
unknown_tags.append(child.tag)
|
|
|
|
|
|
def print_known(name: str, print_all: bool):
|
|
tree = known[name.lower()]
|
|
root = tree.getroot()
|
|
identity = root.find("asm:assemblyIdentity", namespaces)
|
|
print(f'Package {identity.get("name")} ({identity.get("version")})')
|
|
print_deps(root, print_all=print_all)
|
|
print_files(root, print_all=print_all)
|
|
print_registry(root, print_all=print_all)
|
|
|
|
|
|
def parse_manifest(path: pathlib.Path, do_print: bool):
|
|
tree = None
|
|
try:
|
|
tree = ET.parse(path)
|
|
except Exception as e:
|
|
print("Exception parsing " + path.name)
|
|
print(e)
|
|
return
|
|
|
|
root = tree.getroot()
|
|
identity = root.find("asm:assemblyIdentity", namespaces)
|
|
|
|
if identity is None:
|
|
if do_print:
|
|
print(f"\033[91mMissing identity in: {path.name}\033[0m")
|
|
return
|
|
|
|
name = identity.get("name")
|
|
if name.lower() in known:
|
|
raise Exception("WTF - Duplicate name " + name)
|
|
known[name.lower()] = tree
|
|
if do_print:
|
|
print_known(name, False)
|
|
check_unknown_tags(root, do_print=do_print)
|
|
|
|
|
|
if __name__ != '__main__':
|
|
quit()
|
|
|
|
do_print = False if len(args.packages) > 0 else True
|
|
|
|
for entry in args.manifests.iterdir():
|
|
if entry.is_dir():
|
|
continue
|
|
parse_manifest(entry, do_print)
|
|
if do_print:
|
|
print()
|
|
|
|
if len(unknown_tags) > 0:
|
|
print("Unknown tags:")
|
|
for name in unknown_tags:
|
|
print(f" - {name}")
|
|
print()
|
|
|
|
for name in args.packages:
|
|
print_known(name, True)
|
|
print() |