"""Unattend.xml parser, builder, and form-data extractor. Round-trips a Windows ``FlatUnattendW10.xml`` between the disk format and a flat dict the editor template can render. ``parse_unattend()`` reads disk + returns a dict; ``build_unattend_xml()`` takes the dict + returns a string for writing back; ``extract_form_data()`` converts a Flask ``request.form`` MultiDict into the dict format that ``build_unattend_xml()`` expects. """ import os from lxml import etree import config UNATTEND_TEMPLATE = """\ """ def qn(tag): """Return a tag qualified with the default unattend namespace.""" return f"{{{config.UNATTEND_NS}}}{tag}" def qwcm(attr): """Return an attribute qualified with the wcm namespace.""" return f"{{{config.WCM_NS}}}{attr}" def _find_or_create(parent, tag): """Find the first child with *tag* or create it.""" el = parent.find(tag, namespaces={"": config.UNATTEND_NS}) if el is None: el = etree.SubElement(parent, qn(tag.split("}")[-1]) if "}" not in tag else tag) return el def _settings_pass(root, pass_name): """Return the element, creating if needed.""" for s in root.findall(qn("settings")): if s.get("pass") == pass_name: return s s = etree.SubElement(root, qn("settings")) s.set("pass", pass_name) return s def parse_unattend(xml_path): """Parse an unattend.xml and return a dict of editable data.""" data = { "driver_paths": [], "computer_name": "", "registered_organization": "", "registered_owner": "", "time_zone": "", "specialize_commands": [], "oobe": { "HideEULAPage": "true", "HideOEMRegistrationScreen": "true", "HideOnlineAccountScreens": "true", "HideWirelessSetupInOOBE": "true", "HideLocalAccountScreen": "true", "NetworkLocation": "Work", "ProtectYourPC": "3", "SkipUserOOBE": "true", "SkipMachineOOBE": "true", }, "firstlogon_commands": [], "user_accounts": [], "autologon": { "enabled": "", "username": "", "password": "", "plain_text": "true", "logon_count": "", }, "intl": { "input_locale": "", "system_locale": "", "ui_language": "", "user_locale": "", }, "oobe_timezone": "", "raw_xml": "", } if not os.path.isfile(xml_path): data["raw_xml"] = UNATTEND_TEMPLATE return data with open(xml_path, "r", encoding="utf-8") as fh: raw = fh.read() data["raw_xml"] = raw try: root = etree.fromstring(raw.encode("utf-8")) except etree.XMLSyntaxError: return data ns = {"u": config.UNATTEND_NS} # --- offlineServicing: DriverPaths --- for dp_el in root.xpath( "u:settings[@pass='offlineServicing']//u:PathAndCredentials/u:Path", namespaces=ns, ): if dp_el.text: data["driver_paths"].append(dp_el.text.strip()) # --- specialize: Shell-Setup --- for comp in root.xpath("u:settings[@pass='specialize']/u:component", namespaces=ns): comp_name = comp.get("name", "") if "Shell-Setup" in comp_name: for tag, key in [ ("ComputerName", "computer_name"), ("RegisteredOrganization", "registered_organization"), ("RegisteredOwner", "registered_owner"), ("TimeZone", "time_zone"), ]: el = comp.find(qn(tag)) if el is not None and el.text: data[key] = el.text.strip() # --- specialize: RunSynchronous commands --- for cmd in root.xpath( "u:settings[@pass='specialize']//u:RunSynchronousCommand", namespaces=ns, ): order_el = cmd.find(qn("Order")) path_el = cmd.find(qn("Path")) desc_el = cmd.find(qn("Description")) data["specialize_commands"].append({ "order": order_el.text.strip() if order_el is not None and order_el.text else "", "path": path_el.text.strip() if path_el is not None and path_el.text else "", "description": desc_el.text.strip() if desc_el is not None and desc_el.text else "", }) # --- oobeSystem --- for comp in root.xpath("u:settings[@pass='oobeSystem']/u:component", namespaces=ns): comp_name = comp.get("name", "") if "International-Core" in comp_name: for tag, key in [ ("InputLocale", "input_locale"), ("SystemLocale", "system_locale"), ("UILanguage", "ui_language"), ("UserLocale", "user_locale"), ]: el = comp.find(qn(tag)) if el is not None and el.text: data["intl"][key] = el.text.strip() if "OOBE" in comp_name or "Shell-Setup" in comp_name: oobe_el = comp.find(qn("OOBE")) if oobe_el is not None: for child in oobe_el: local = etree.QName(child).localname if local in data["oobe"] and child.text: data["oobe"][local] = child.text.strip() ua = comp.find(qn("UserAccounts")) if ua is not None: la_container = ua.find(qn("LocalAccounts")) if la_container is not None: for acct in la_container.findall(qn("LocalAccount")): name_el = acct.find(qn("Name")) group_el = acct.find(qn("Group")) display_el = acct.find(qn("DisplayName")) pw_el = acct.find(qn("Password")) pw_val = "" pw_plain = "true" if pw_el is not None: v = pw_el.find(qn("Value")) p = pw_el.find(qn("PlainText")) if v is not None and v.text: pw_val = v.text.strip() if p is not None and p.text: pw_plain = p.text.strip() data["user_accounts"].append({ "name": name_el.text.strip() if name_el is not None and name_el.text else "", "password": pw_val, "plain_text": pw_plain, "group": group_el.text.strip() if group_el is not None and group_el.text else "Administrators", "display_name": display_el.text.strip() if display_el is not None and display_el.text else "", }) al = comp.find(qn("AutoLogon")) if al is not None: enabled_el = al.find(qn("Enabled")) user_el = al.find(qn("Username")) count_el = al.find(qn("LogonCount")) pw_el = al.find(qn("Password")) pw_val = "" pw_plain = "true" if pw_el is not None: v = pw_el.find(qn("Value")) p = pw_el.find(qn("PlainText")) if v is not None and v.text: pw_val = v.text.strip() if p is not None and p.text: pw_plain = p.text.strip() data["autologon"] = { "enabled": enabled_el.text.strip() if enabled_el is not None and enabled_el.text else "", "username": user_el.text.strip() if user_el is not None and user_el.text else "", "password": pw_val, "plain_text": pw_plain, "logon_count": count_el.text.strip() if count_el is not None and count_el.text else "", } flc = comp.find(qn("FirstLogonCommands")) if flc is not None: for sync in flc.findall(qn("SynchronousCommand")): order_el = sync.find(qn("Order")) cl_el = sync.find(qn("CommandLine")) desc_el = sync.find(qn("Description")) data["firstlogon_commands"].append({ "order": order_el.text.strip() if order_el is not None and order_el.text else "", "commandline": cl_el.text.strip() if cl_el is not None and cl_el.text else "", "description": desc_el.text.strip() if desc_el is not None and desc_el.text else "", }) tz_el = comp.find(qn("TimeZone")) if tz_el is not None and tz_el.text: data["oobe_timezone"] = tz_el.text.strip() return data def build_unattend_xml(form_data): """Build a complete unattend.xml string from form data dict.""" root = etree.Element(qn("unattend"), nsmap=config.NSMAP) _settings_pass(root, "windowsPE") # --- offlineServicing: DriverPaths --- offline = _settings_pass(root, "offlineServicing") driver_paths = form_data.get("driver_paths", []) if driver_paths: comp = etree.SubElement(offline, qn("component")) comp.set("name", "Microsoft-Windows-PnpCustomizationsNonWinPE") comp.set("processorArchitecture", "amd64") comp.set("publicKeyToken", "31bf3856ad364e35") comp.set("language", "neutral") comp.set("versionScope", "nonSxS") dp_container = etree.SubElement(comp, qn("DriverPaths")) for idx, dp in enumerate(driver_paths, start=1): if not dp.strip(): continue pac = etree.SubElement(dp_container, qn("PathAndCredentials")) pac.set(qwcm("action"), "add") pac.set(qwcm("keyValue"), str(idx)) path_el = etree.SubElement(pac, qn("Path")) path_el.text = dp.strip() # --- specialize --- spec = _settings_pass(root, "specialize") shell_comp = etree.SubElement(spec, qn("component")) shell_comp.set("name", "Microsoft-Windows-Shell-Setup") shell_comp.set("processorArchitecture", "amd64") shell_comp.set("publicKeyToken", "31bf3856ad364e35") shell_comp.set("language", "neutral") shell_comp.set("versionScope", "nonSxS") for tag, key in [ ("ComputerName", "computer_name"), ("RegisteredOrganization", "registered_organization"), ("RegisteredOwner", "registered_owner"), ("TimeZone", "time_zone"), ]: val = form_data.get(key, "").strip() if val: el = etree.SubElement(shell_comp, qn(tag)) el.text = val spec_cmds = form_data.get("specialize_commands", []) if spec_cmds: deploy_comp = etree.SubElement(spec, qn("component")) deploy_comp.set("name", "Microsoft-Windows-Deployment") deploy_comp.set("processorArchitecture", "amd64") deploy_comp.set("publicKeyToken", "31bf3856ad364e35") deploy_comp.set("language", "neutral") deploy_comp.set("versionScope", "nonSxS") rs = etree.SubElement(deploy_comp, qn("RunSynchronous")) for idx, cmd in enumerate(spec_cmds, start=1): if not cmd.get("path", "").strip(): continue rsc = etree.SubElement(rs, qn("RunSynchronousCommand")) rsc.set(qwcm("action"), "add") order_el = etree.SubElement(rsc, qn("Order")) order_el.text = str(idx) path_el = etree.SubElement(rsc, qn("Path")) path_el.text = cmd["path"].strip() desc_el = etree.SubElement(rsc, qn("Description")) desc_el.text = cmd.get("description", "").strip() # --- oobeSystem --- oobe_settings = _settings_pass(root, "oobeSystem") intl = form_data.get("intl", {}) if any(v.strip() for v in intl.values() if v): intl_comp = etree.SubElement(oobe_settings, qn("component")) intl_comp.set("name", "Microsoft-Windows-International-Core") intl_comp.set("processorArchitecture", "amd64") intl_comp.set("publicKeyToken", "31bf3856ad364e35") intl_comp.set("language", "neutral") intl_comp.set("versionScope", "nonSxS") for tag, key in [ ("InputLocale", "input_locale"), ("SystemLocale", "system_locale"), ("UILanguage", "ui_language"), ("UserLocale", "user_locale"), ]: val = intl.get(key, "").strip() if val: el = etree.SubElement(intl_comp, qn(tag)) el.text = val oobe_comp = etree.SubElement(oobe_settings, qn("component")) oobe_comp.set("name", "Microsoft-Windows-Shell-Setup") oobe_comp.set("processorArchitecture", "amd64") oobe_comp.set("publicKeyToken", "31bf3856ad364e35") oobe_comp.set("language", "neutral") oobe_comp.set("versionScope", "nonSxS") oobe_el = etree.SubElement(oobe_comp, qn("OOBE")) oobe_data = form_data.get("oobe", {}) for key in [ "HideEULAPage", "HideOEMRegistrationScreen", "HideOnlineAccountScreens", "HideWirelessSetupInOOBE", "HideLocalAccountScreen", "NetworkLocation", "ProtectYourPC", "SkipUserOOBE", "SkipMachineOOBE", ]: val = oobe_data.get(key, "") if val: child = etree.SubElement(oobe_el, qn(key)) child.text = str(val) accounts = form_data.get("user_accounts", []) if accounts: ua = etree.SubElement(oobe_comp, qn("UserAccounts")) la_container = etree.SubElement(ua, qn("LocalAccounts")) for acct in accounts: if not acct.get("name", "").strip(): continue la = etree.SubElement(la_container, qn("LocalAccount")) la.set(qwcm("action"), "add") pw = etree.SubElement(la, qn("Password")) pw_val = etree.SubElement(pw, qn("Value")) pw_val.text = acct.get("password", "") pw_plain = etree.SubElement(pw, qn("PlainText")) pw_plain.text = acct.get("plain_text", "true") name_el = etree.SubElement(la, qn("Name")) name_el.text = acct["name"].strip() group_el = etree.SubElement(la, qn("Group")) group_el.text = acct.get("group", "Administrators").strip() display_el = etree.SubElement(la, qn("DisplayName")) display_el.text = acct.get("display_name", acct["name"]).strip() autologon = form_data.get("autologon", {}) if autologon.get("username", "").strip(): al = etree.SubElement(oobe_comp, qn("AutoLogon")) al_pw = etree.SubElement(al, qn("Password")) al_pw_val = etree.SubElement(al_pw, qn("Value")) al_pw_val.text = autologon.get("password", "") al_pw_plain = etree.SubElement(al_pw, qn("PlainText")) al_pw_plain.text = autologon.get("plain_text", "true") al_enabled = etree.SubElement(al, qn("Enabled")) al_enabled.text = autologon.get("enabled", "true") al_user = etree.SubElement(al, qn("Username")) al_user.text = autologon["username"].strip() logon_count = autologon.get("logon_count", "").strip() if logon_count: al_count = etree.SubElement(al, qn("LogonCount")) al_count.text = logon_count fl_cmds = form_data.get("firstlogon_commands", []) if fl_cmds: flc = etree.SubElement(oobe_comp, qn("FirstLogonCommands")) for idx, cmd in enumerate(fl_cmds, start=1): if not cmd.get("commandline", "").strip(): continue sc = etree.SubElement(flc, qn("SynchronousCommand")) sc.set(qwcm("action"), "add") order_el = etree.SubElement(sc, qn("Order")) order_el.text = str(idx) cl_el = etree.SubElement(sc, qn("CommandLine")) cl_el.text = cmd["commandline"].strip() desc_el = etree.SubElement(sc, qn("Description")) desc_el.text = cmd.get("description", "").strip() oobe_tz = form_data.get("oobe_timezone", "").strip() if oobe_tz: tz_el = etree.SubElement(oobe_comp, qn("TimeZone")) tz_el.text = oobe_tz xml_bytes = etree.tostring( root, pretty_print=True, xml_declaration=True, encoding="utf-8", ) return xml_bytes.decode("utf-8") def extract_form_data(form): """Pull structured data from the submitted form.""" data = {} dp_list = form.getlist("driver_path[]") data["driver_paths"] = [p for p in dp_list if p.strip()] data["computer_name"] = form.get("computer_name", "") data["registered_organization"] = form.get("registered_organization", "") data["registered_owner"] = form.get("registered_owner", "") data["time_zone"] = form.get("time_zone", "") spec_paths = form.getlist("spec_cmd_path[]") spec_descs = form.getlist("spec_cmd_desc[]") data["specialize_commands"] = [] for i in range(len(spec_paths)): if spec_paths[i].strip(): data["specialize_commands"].append({ "path": spec_paths[i], "description": spec_descs[i] if i < len(spec_descs) else "", }) data["oobe"] = {} for key in [ "HideEULAPage", "HideOEMRegistrationScreen", "HideOnlineAccountScreens", "HideWirelessSetupInOOBE", "HideLocalAccountScreen", "SkipUserOOBE", "SkipMachineOOBE", ]: data["oobe"][key] = form.get(f"oobe_{key}", "false") data["oobe"]["NetworkLocation"] = form.get("oobe_NetworkLocation", "Work") data["oobe"]["ProtectYourPC"] = form.get("oobe_ProtectYourPC", "3") fl_cls = form.getlist("fl_cmd_commandline[]") fl_descs = form.getlist("fl_cmd_desc[]") data["firstlogon_commands"] = [] for i in range(len(fl_cls)): if fl_cls[i].strip(): data["firstlogon_commands"].append({ "commandline": fl_cls[i], "description": fl_descs[i] if i < len(fl_descs) else "", }) accounts = [] i = 0 while form.get(f"account_name_{i}"): accounts.append({ "name": form.get(f"account_name_{i}", ""), "password": form.get(f"account_password_{i}", ""), "plain_text": form.get(f"account_plaintext_{i}", "true"), "group": form.get(f"account_group_{i}", "Administrators"), "display_name": form.get(f"account_display_{i}", ""), }) i += 1 data["user_accounts"] = accounts data["autologon"] = { "enabled": form.get("autologon_enabled", ""), "username": form.get("autologon_username", ""), "password": form.get("autologon_password", ""), "plain_text": form.get("autologon_plaintext", "true"), "logon_count": form.get("autologon_logoncount", ""), } data["intl"] = { "input_locale": form.get("intl_input_locale", ""), "system_locale": form.get("intl_system_locale", ""), "ui_language": form.get("intl_ui_language", ""), "user_locale": form.get("intl_user_locale", ""), } data["oobe_timezone"] = form.get("oobe_timezone", "") return data