manifest.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  1. /*
  2. * Manifest parser for WUSA
  3. *
  4. * Copyright 2015 Michael Müller
  5. * Copyright 2015 Sebastian Lackner
  6. *
  7. * This library is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU Lesser General Public
  9. * License as published by the Free Software Foundation; either
  10. * version 2.1 of the License, or (at your option) any later version.
  11. *
  12. * This library is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public
  18. * License along with this library; if not, write to the Free Software
  19. * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  20. */
  21. #include <windows.h>
  22. #define COBJMACROS
  23. #include <initguid.h>
  24. #include <msxml.h>
  25. #include "wine/debug.h"
  26. #include "wine/list.h"
  27. #include "wusa.h"
  28. WINE_DEFAULT_DEBUG_CHANNEL(wusa);
  29. static struct dependency_entry *alloc_dependency(void)
  30. {
  31. struct dependency_entry *entry = heap_alloc_zero(sizeof(*entry));
  32. if (!entry) ERR("Failed to allocate memory for dependency\n");
  33. return entry;
  34. }
  35. static struct fileop_entry *alloc_fileop(void)
  36. {
  37. struct fileop_entry *entry = heap_alloc_zero(sizeof(*entry));
  38. if (!entry) ERR("Failed to allocate memory for fileop\n");
  39. return entry;
  40. }
  41. static struct registrykv_entry *alloc_registrykv(void)
  42. {
  43. struct registrykv_entry *entry = heap_alloc_zero(sizeof(*entry));
  44. if (!entry) ERR("Failed to allocate memory for registrykv\n");
  45. return entry;
  46. }
  47. static struct registryop_entry *alloc_registryop(void)
  48. {
  49. struct registryop_entry *entry = heap_alloc_zero(sizeof(*entry));
  50. if (!entry) ERR("Failed to allocate memory for registryop\n");
  51. else
  52. {
  53. list_init(&entry->keyvalues);
  54. }
  55. return entry;
  56. }
  57. static struct assembly_entry *alloc_assembly(void)
  58. {
  59. struct assembly_entry *entry = heap_alloc_zero(sizeof(*entry));
  60. if (!entry) ERR("Failed to allocate memory for assembly\n");
  61. else
  62. {
  63. list_init(&entry->dependencies);
  64. list_init(&entry->fileops);
  65. list_init(&entry->registryops);
  66. }
  67. return entry;
  68. }
  69. static void clear_identity(struct assembly_identity *entry)
  70. {
  71. heap_free(entry->name);
  72. heap_free(entry->version);
  73. heap_free(entry->architecture);
  74. heap_free(entry->language);
  75. heap_free(entry->pubkey_token);
  76. }
  77. void free_dependency(struct dependency_entry *entry)
  78. {
  79. clear_identity(&entry->identity);
  80. heap_free(entry);
  81. }
  82. static void free_fileop(struct fileop_entry *entry)
  83. {
  84. heap_free(entry->source);
  85. heap_free(entry->target);
  86. heap_free(entry);
  87. }
  88. static void free_registrykv(struct registrykv_entry *entry)
  89. {
  90. heap_free(entry->name);
  91. heap_free(entry->value_type);
  92. heap_free(entry->value);
  93. heap_free(entry);
  94. }
  95. static void free_registryop(struct registryop_entry *entry)
  96. {
  97. struct registrykv_entry *keyvalue, *keyvalue2;
  98. heap_free(entry->key);
  99. LIST_FOR_EACH_ENTRY_SAFE(keyvalue, keyvalue2, &entry->keyvalues, struct registrykv_entry, entry)
  100. {
  101. list_remove(&keyvalue->entry);
  102. free_registrykv(keyvalue);
  103. }
  104. heap_free(entry);
  105. }
  106. void free_assembly(struct assembly_entry *entry)
  107. {
  108. struct dependency_entry *dependency, *dependency2;
  109. struct fileop_entry *fileop, *fileop2;
  110. struct registryop_entry *registryop, *registryop2;
  111. heap_free(entry->filename);
  112. heap_free(entry->displayname);
  113. clear_identity(&entry->identity);
  114. LIST_FOR_EACH_ENTRY_SAFE(dependency, dependency2, &entry->dependencies, struct dependency_entry, entry)
  115. {
  116. list_remove(&dependency->entry);
  117. free_dependency(dependency);
  118. }
  119. LIST_FOR_EACH_ENTRY_SAFE(fileop, fileop2, &entry->fileops, struct fileop_entry, entry)
  120. {
  121. list_remove(&fileop->entry);
  122. free_fileop(fileop);
  123. }
  124. LIST_FOR_EACH_ENTRY_SAFE(registryop, registryop2, &entry->registryops, struct registryop_entry, entry)
  125. {
  126. list_remove(&registryop->entry);
  127. free_registryop(registryop);
  128. }
  129. heap_free(entry);
  130. }
  131. static WCHAR *get_xml_attribute(IXMLDOMElement *root, const WCHAR *name)
  132. {
  133. WCHAR *ret = NULL;
  134. VARIANT var;
  135. BSTR bstr;
  136. if ((bstr = SysAllocString(name)))
  137. {
  138. VariantInit(&var);
  139. if (SUCCEEDED(IXMLDOMElement_getAttribute(root, bstr, &var)))
  140. {
  141. ret = (V_VT(&var) == VT_BSTR) ? strdupW(V_BSTR(&var)) : NULL;
  142. VariantClear(&var);
  143. }
  144. SysFreeString(bstr);
  145. }
  146. return ret;
  147. }
  148. static BOOL check_xml_tagname(IXMLDOMElement *root, const WCHAR *tagname)
  149. {
  150. BOOL ret = FALSE;
  151. BSTR bstr;
  152. if (SUCCEEDED(IXMLDOMElement_get_tagName(root, &bstr)))
  153. {
  154. ret = !wcscmp(bstr, tagname);
  155. SysFreeString(bstr);
  156. }
  157. return ret;
  158. }
  159. static IXMLDOMElement *select_xml_node(IXMLDOMElement *root, const WCHAR *name)
  160. {
  161. IXMLDOMElement *ret = NULL;
  162. IXMLDOMNode *node;
  163. BSTR bstr;
  164. if ((bstr = SysAllocString(name)))
  165. {
  166. if (SUCCEEDED(IXMLDOMElement_selectSingleNode(root, bstr, &node)))
  167. {
  168. if (FAILED(IXMLDOMNode_QueryInterface(node, &IID_IXMLDOMElement, (void **)&ret)))
  169. ret = NULL;
  170. IXMLDOMNode_Release(node);
  171. }
  172. SysFreeString(bstr);
  173. }
  174. return ret;
  175. }
  176. static BOOL call_xml_callbacks(IXMLDOMElement *root, BOOL (*func)(IXMLDOMElement *child, WCHAR *tagname, void *context), void *context)
  177. {
  178. IXMLDOMNodeList *children;
  179. IXMLDOMElement *child;
  180. IXMLDOMNode *node;
  181. BSTR tagname;
  182. BOOL ret = TRUE;
  183. if (FAILED(IXMLDOMElement_get_childNodes(root, &children)))
  184. return FALSE;
  185. while (ret && IXMLDOMNodeList_nextNode(children, &node) == S_OK)
  186. {
  187. if (SUCCEEDED(IXMLDOMNode_QueryInterface(node, &IID_IXMLDOMElement, (void **)&child)))
  188. {
  189. if (SUCCEEDED(IXMLDOMElement_get_tagName(child, &tagname)))
  190. {
  191. ret = func(child, tagname, context);
  192. SysFreeString(tagname);
  193. }
  194. IXMLDOMElement_Release(child);
  195. }
  196. IXMLDOMNode_Release(node);
  197. }
  198. IXMLDOMNodeList_Release(children);
  199. return ret;
  200. }
  201. static IXMLDOMElement *load_xml(const WCHAR *filename)
  202. {
  203. IXMLDOMDocument *document = NULL;
  204. IXMLDOMElement *root = NULL;
  205. VARIANT_BOOL success;
  206. VARIANT variant;
  207. BSTR bstr;
  208. TRACE("Loading XML from %s\n", debugstr_w(filename));
  209. if (!(bstr = SysAllocString(filename)))
  210. return FALSE;
  211. if (SUCCEEDED(CoCreateInstance(&CLSID_DOMDocument, NULL, CLSCTX_INPROC_SERVER, &IID_IXMLDOMDocument, (void **)&document)))
  212. {
  213. VariantInit(&variant);
  214. V_VT(&variant) = VT_BSTR;
  215. V_BSTR(&variant) = bstr;
  216. if (SUCCEEDED(IXMLDOMDocument_load(document, variant, &success)) && success)
  217. {
  218. if (FAILED(IXMLDOMDocument_get_documentElement(document, &root)))
  219. root = NULL;
  220. }
  221. IXMLDOMDocument_Release(document);
  222. }
  223. SysFreeString(bstr);
  224. return root;
  225. }
  226. static BOOL read_identity(IXMLDOMElement *root, struct assembly_identity *identity)
  227. {
  228. memset(identity, 0, sizeof(*identity));
  229. if (!(identity->name = get_xml_attribute(root, L"name"))) goto error;
  230. if (!(identity->version = get_xml_attribute(root, L"version"))) goto error;
  231. if (!(identity->architecture = get_xml_attribute(root, L"processorArchitecture"))) goto error;
  232. if (!(identity->language = get_xml_attribute(root, L"language"))) goto error;
  233. if (!(identity->pubkey_token = get_xml_attribute(root, L"publicKeyToken"))) goto error;
  234. return TRUE;
  235. error:
  236. clear_identity(identity);
  237. return FALSE;
  238. }
  239. /* <assembly><dependency><dependentAssembly> */
  240. static BOOL read_dependent_assembly(IXMLDOMElement *root, struct assembly_identity *identity)
  241. {
  242. IXMLDOMElement *child = NULL;
  243. WCHAR *dependency_type;
  244. BOOL ret = FALSE;
  245. if (!(dependency_type = get_xml_attribute(root, L"dependencyType")))
  246. {
  247. WARN("Failed to get dependency type, assuming install\n");
  248. }
  249. if (dependency_type && wcscmp(dependency_type, L"install") && wcscmp(dependency_type, L"prerequisite"))
  250. {
  251. FIXME("Unimplemented dependency type %s\n", debugstr_w(dependency_type));
  252. goto error;
  253. }
  254. if (!(child = select_xml_node(root, L".//assemblyIdentity")))
  255. {
  256. FIXME("Failed to find assemblyIdentity child node\n");
  257. goto error;
  258. }
  259. ret = read_identity(child, identity);
  260. error:
  261. if (child) IXMLDOMElement_Release(child);
  262. heap_free(dependency_type);
  263. return ret;
  264. }
  265. /* <assembly><dependency> */
  266. static BOOL read_dependency(IXMLDOMElement *child, WCHAR *tagname, void *context)
  267. {
  268. struct assembly_entry *assembly = context;
  269. struct dependency_entry *entry;
  270. if (wcscmp(tagname, L"dependentAssembly"))
  271. {
  272. FIXME("Don't know how to handle dependency tag %s\n", debugstr_w(tagname));
  273. return FALSE;
  274. }
  275. if ((entry = alloc_dependency()))
  276. {
  277. if (read_dependent_assembly(child, &entry->identity))
  278. {
  279. TRACE("Found dependency %s\n", debugstr_w(entry->identity.name));
  280. list_add_tail(&assembly->dependencies, &entry->entry);
  281. return TRUE;
  282. }
  283. free_dependency(entry);
  284. }
  285. return FALSE;
  286. }
  287. static BOOL iter_dependency(IXMLDOMElement *root, struct assembly_entry *assembly)
  288. {
  289. return call_xml_callbacks(root, read_dependency, assembly);
  290. }
  291. /* <assembly><package><update><component> */
  292. /* <assembly><package><update><package> */
  293. static BOOL read_components(IXMLDOMElement *child, WCHAR *tagname, void *context)
  294. {
  295. struct assembly_entry *assembly = context;
  296. struct dependency_entry *entry;
  297. if (wcscmp(tagname, L"assemblyIdentity"))
  298. {
  299. FIXME("Ignoring unexpected tag %s\n", debugstr_w(tagname));
  300. return TRUE;
  301. }
  302. if ((entry = alloc_dependency()))
  303. {
  304. if (read_identity(child, &entry->identity))
  305. {
  306. TRACE("Found identity %s\n", debugstr_w(entry->identity.name));
  307. list_add_tail(&assembly->dependencies, &entry->entry);
  308. return TRUE;
  309. }
  310. free_dependency(entry);
  311. }
  312. return FALSE;
  313. }
  314. static BOOL iter_components(IXMLDOMElement *root, struct assembly_entry *assembly)
  315. {
  316. return call_xml_callbacks(root, read_components, assembly);
  317. }
  318. /* <assembly><package><update> */
  319. static BOOL read_update(IXMLDOMElement *child, WCHAR *tagname, void *context)
  320. {
  321. struct assembly_entry *assembly = context;
  322. if (!wcscmp(tagname, L"component"))
  323. return iter_components(child, assembly);
  324. if (!wcscmp(tagname, L"package"))
  325. return iter_components(child, assembly);
  326. if (!wcscmp(tagname, L"applicable"))
  327. return TRUE;
  328. FIXME("Ignoring unexpected tag %s\n", debugstr_w(tagname));
  329. return FALSE;
  330. }
  331. static BOOL iter_update(IXMLDOMElement *root, struct assembly_entry *assembly)
  332. {
  333. return call_xml_callbacks(root, read_update, assembly);
  334. }
  335. /* <assembly><package> */
  336. static BOOL read_package(IXMLDOMElement *child, WCHAR *tagname, void *context)
  337. {
  338. struct assembly_entry *assembly = context;
  339. if (!wcscmp(tagname, L"update"))
  340. return iter_update(child, assembly);
  341. if (!wcscmp(tagname, L"parent"))
  342. return TRUE;
  343. FIXME("Ignoring unexpected tag %s\n", debugstr_w(tagname));
  344. return TRUE;
  345. }
  346. static BOOL iter_package(IXMLDOMElement *root, struct assembly_entry *assembly)
  347. {
  348. return call_xml_callbacks(root, read_package, assembly);
  349. }
  350. /* <assembly><file> */
  351. static BOOL read_file(IXMLDOMElement *root, struct assembly_entry *assembly)
  352. {
  353. struct fileop_entry *entry;
  354. if (!(entry = alloc_fileop()))
  355. return FALSE;
  356. if (!(entry->source = get_xml_attribute(root, L"sourceName"))) goto error;
  357. if (!(entry->target = get_xml_attribute(root, L"destinationPath"))) goto error;
  358. TRACE("Found fileop %s -> %s\n", debugstr_w(entry->source), debugstr_w(entry->target));
  359. list_add_tail(&assembly->fileops, &entry->entry);
  360. return TRUE;
  361. error:
  362. free_fileop(entry);
  363. return FALSE;
  364. }
  365. /* <assembly><registryKeys><registryKey> */
  366. static BOOL read_registry_key(IXMLDOMElement *child, WCHAR *tagname, void *context)
  367. {
  368. struct registryop_entry *registryop = context;
  369. struct registrykv_entry *entry;
  370. if (!wcscmp(tagname, L"securityDescriptor")) return TRUE;
  371. if (!wcscmp(tagname, L"systemProtection")) return TRUE;
  372. if (wcscmp(tagname, L"registryValue"))
  373. {
  374. FIXME("Ignoring unexpected tag %s\n", debugstr_w(tagname));
  375. return TRUE;
  376. }
  377. if (!(entry = alloc_registrykv()))
  378. return FALSE;
  379. if (!(entry->value_type = get_xml_attribute(child, L"valueType"))) goto error;
  380. entry->name = get_xml_attribute(child, L"name"); /* optional */
  381. entry->value = get_xml_attribute(child, L"value"); /* optional */
  382. TRACE("Found registry %s -> %s\n", debugstr_w(entry->name), debugstr_w(entry->value));
  383. list_add_tail(&registryop->keyvalues, &entry->entry);
  384. return TRUE;
  385. error:
  386. free_registrykv(entry);
  387. return FALSE;
  388. }
  389. static BOOL iter_registry_key(IXMLDOMElement *root, struct registryop_entry *registryop)
  390. {
  391. return call_xml_callbacks(root, read_registry_key, registryop);
  392. }
  393. /* <assembly><registryKeys> */
  394. static BOOL read_registry_keys(IXMLDOMElement *child, WCHAR *tagname, void *context)
  395. {
  396. struct assembly_entry *assembly = context;
  397. struct registryop_entry *entry;
  398. WCHAR *keyname;
  399. if (wcscmp(tagname, L"registryKey"))
  400. {
  401. FIXME("Ignoring unexpected tag %s\n", debugstr_w(tagname));
  402. return TRUE;
  403. }
  404. if (!(keyname = get_xml_attribute(child, L"keyName")))
  405. {
  406. FIXME("RegistryKey tag doesn't specify keyName\n");
  407. return FALSE;
  408. }
  409. if ((entry = alloc_registryop()))
  410. {
  411. list_init(&entry->keyvalues);
  412. if (iter_registry_key(child, entry))
  413. {
  414. entry->key = keyname;
  415. TRACE("Found registryop %s\n", debugstr_w(entry->key));
  416. list_add_tail(&assembly->registryops, &entry->entry);
  417. return TRUE;
  418. }
  419. free_registryop(entry);
  420. }
  421. heap_free(keyname);
  422. return FALSE;
  423. }
  424. static BOOL iter_registry_keys(IXMLDOMElement *root, struct assembly_entry *assembly)
  425. {
  426. return call_xml_callbacks(root, read_registry_keys, assembly);
  427. }
  428. /* <assembly> */
  429. static BOOL read_assembly(IXMLDOMElement *child, WCHAR *tagname, void *context)
  430. {
  431. struct assembly_entry *assembly = context;
  432. if (!wcscmp(tagname, L"assemblyIdentity") && !assembly->identity.name)
  433. return read_identity(child, &assembly->identity);
  434. if (!wcscmp(tagname, L"dependency"))
  435. return iter_dependency(child, assembly);
  436. if (!wcscmp(tagname, L"package"))
  437. return iter_package(child, assembly);
  438. if (!wcscmp(tagname, L"file"))
  439. return read_file(child, assembly);
  440. if (!wcscmp(tagname, L"registryKeys"))
  441. return iter_registry_keys(child, assembly);
  442. if (!wcscmp(tagname, L"trustInfo"))
  443. return TRUE;
  444. if (!wcscmp(tagname, L"configuration"))
  445. return TRUE;
  446. if (!wcscmp(tagname, L"deployment"))
  447. return TRUE;
  448. FIXME("Ignoring unexpected tag %s\n", debugstr_w(tagname));
  449. return TRUE;
  450. }
  451. static BOOL iter_assembly(IXMLDOMElement *root, struct assembly_entry *assembly)
  452. {
  453. return call_xml_callbacks(root, read_assembly, assembly);
  454. }
  455. struct assembly_entry *load_manifest(const WCHAR *filename)
  456. {
  457. struct assembly_entry *entry = NULL;
  458. IXMLDOMElement *root = NULL;
  459. TRACE("Loading manifest %s\n", debugstr_w(filename));
  460. if (!(root = load_xml(filename))) return NULL;
  461. if (!check_xml_tagname(root, L"assembly"))
  462. {
  463. FIXME("Didn't find assembly root node?\n");
  464. goto done;
  465. }
  466. if ((entry = alloc_assembly()))
  467. {
  468. entry->filename = strdupW(filename);
  469. entry->displayname = get_xml_attribute(root, L"displayName");
  470. if (iter_assembly(root, entry)) goto done;
  471. free_assembly(entry);
  472. entry = NULL;
  473. }
  474. done:
  475. IXMLDOMElement_Release(root);
  476. return entry;
  477. }
  478. /* <unattend><servicing><package> */
  479. static BOOL read_update_package(IXMLDOMElement *child, WCHAR *tagname, void *context)
  480. {
  481. struct dependency_entry *entry;
  482. struct list *update_list = context;
  483. if (!wcscmp(tagname, L"source")) return TRUE;
  484. if (wcscmp(tagname, L"assemblyIdentity"))
  485. {
  486. TRACE("Ignoring unexpected tag %s\n", debugstr_w(tagname));
  487. return TRUE;
  488. }
  489. if ((entry = alloc_dependency()))
  490. {
  491. if (read_identity(child, &entry->identity))
  492. {
  493. TRACE("Found update %s\n", debugstr_w(entry->identity.name));
  494. list_add_tail(update_list, &entry->entry);
  495. return TRUE;
  496. }
  497. free_dependency(entry);
  498. }
  499. return FALSE;
  500. }
  501. static BOOL iter_update_package(IXMLDOMElement *root, struct list *update_list)
  502. {
  503. return call_xml_callbacks(root, read_update_package, update_list);
  504. }
  505. /* <unattend><servicing> */
  506. static BOOL read_servicing(IXMLDOMElement *child, WCHAR *tagname, void *context)
  507. {
  508. struct list *update_list = context;
  509. WCHAR *action;
  510. BOOL ret = TRUE;
  511. if (wcscmp(tagname, L"package"))
  512. {
  513. FIXME("Ignoring unexpected tag %s\n", debugstr_w(tagname));
  514. return TRUE;
  515. }
  516. if (!(action = get_xml_attribute(child, L"action")))
  517. {
  518. FIXME("Servicing tag doesn't specify action\n");
  519. return FALSE;
  520. }
  521. if (!wcscmp(action, L"install"))
  522. ret = iter_update_package(child, update_list);
  523. else
  524. FIXME("action %s not supported\n", debugstr_w(action));
  525. heap_free(action);
  526. return ret;
  527. }
  528. static BOOL iter_servicing(IXMLDOMElement *root, struct list *update_list)
  529. {
  530. return call_xml_callbacks(root, read_servicing, update_list);
  531. }
  532. /* <unattend> */
  533. static BOOL read_unattend(IXMLDOMElement *child, WCHAR *tagname, void *context)
  534. {
  535. struct list *update_list = context;
  536. if (wcscmp(tagname, L"servicing"))
  537. {
  538. FIXME("Ignoring unexpected tag %s\n", debugstr_w(tagname));
  539. return TRUE;
  540. }
  541. return iter_servicing(child, update_list);
  542. }
  543. static BOOL iter_unattend(IXMLDOMElement *root, struct list *update_list)
  544. {
  545. return call_xml_callbacks(root, read_unattend, update_list);
  546. }
  547. BOOL load_update(const WCHAR *filename, struct list *update_list)
  548. {
  549. IXMLDOMElement *root = NULL;
  550. BOOL ret = FALSE;
  551. TRACE("Reading update %s\n", debugstr_w(filename));
  552. if (!(root = load_xml(filename))) return FALSE;
  553. if (!check_xml_tagname(root, L"unattend"))
  554. {
  555. FIXME("Didn't find unattend root node?\n");
  556. goto done;
  557. }
  558. ret = iter_unattend(root, update_list);
  559. done:
  560. IXMLDOMElement_Release(root);
  561. return ret;
  562. }