dism-python/dism.py

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()