Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

862 lines
22 KiB

  1. // Copyright (c) 1997-1999 Microsoft Corporation
  2. //
  3. // Post-operation shortcut (shell link) code
  4. //
  5. // 1 Dec 1999 sburns
  6. #include "headers.hxx"
  7. #include "ProgressDialog.hpp"
  8. #include "state.hpp"
  9. #include "resource.h"
  10. #include "..\dll\muiresource.h" // resIds for shortcuts
  11. // @@ need to make sure that, when deleting shortcuts, we consider the case
  12. // were the shortcuts may have been added by the adminpak from the 5.0 release
  13. // of the product, not by ourselves in a later release.
  14. //
  15. // This case is: promote with version 5.0, upgrade to later version, demote
  16. static const String SHORTCUT_DLL(L"dcpromo.dll");
  17. struct ShortcutParams
  18. {
  19. // resource id of the link file name and also the link name in the menu.
  20. // These are from muiresource.h. The string resources themselves are bound
  21. // to dcpromo.dll
  22. int linkNameResId;
  23. // resource id of the description string (also the info tip string) for
  24. // the shortcut. These are from muiresource.h. The string resources
  25. // themselves are bound to dcpromo.dll
  26. int descResId;
  27. const wchar_t* target;
  28. const wchar_t* params;
  29. const wchar_t* iconDll;
  30. };
  31. // "Add" from the point of view of promotion: these are removed on demotion.
  32. static ShortcutParams shortcutsToAdd[] =
  33. {
  34. {
  35. // Active Directory Sites and Services
  36. IDS_DS_SITE_LINK,
  37. IDS_DS_SITE_DESC,
  38. L"dssite.msc",
  39. L"",
  40. // the .msc file contains the proper icon, so we don't need to
  41. // specify a dll from whence to retrieve an icon.
  42. L""
  43. },
  44. {
  45. // Active Directory Users and Computers
  46. IDS_DS_USERS_LINK,
  47. IDS_DS_USERS_DESC,
  48. L"dsa.msc",
  49. L"",
  50. L""
  51. },
  52. {
  53. // Active Directory Domains and Trusts
  54. IDS_DS_DOMAINS_LINK,
  55. IDS_DS_DOMAINS_DESC,
  56. L"domain.msc",
  57. L"",
  58. L""
  59. },
  60. {
  61. // Domain Controller Security Policy
  62. // if you change this name, be sure to change the code in
  63. // PromoteConfigureToolShortcuts too
  64. IDS_DC_POLICY_LINK,
  65. IDS_DC_POLICY_DESC,
  66. L"dcpol.msc",
  67. L"",
  68. L""
  69. },
  70. {
  71. // Domain Security Policy
  72. // if you change this name, be sure to change the code in
  73. // PromoteConfigureToolShortcuts too
  74. IDS_DOMAIN_POLICY_LINK,
  75. IDS_DOMAIN_POLICY_DESC,
  76. L"dompol.msc",
  77. L"",
  78. L""
  79. }
  80. };
  81. // "Delete" from the point of view of promotion: these are added back again
  82. // on demotion.
  83. static ShortcutParams shortcutsToDelete[] =
  84. {
  85. {
  86. // Local Security Policy
  87. IDS_LOCAL_POLICY_LINK,
  88. IDS_LOCAL_POLICY_DESC,
  89. L"secpol.msc",
  90. L"/s",
  91. L"wsecedit.dll"
  92. }
  93. };
  94. // Extracts the target of a shortcut: that to which the shortcut points.
  95. // Returns S_OK on success, and sets result to that target. On error, a COM
  96. // error code is returned and result is empty.
  97. //
  98. // shellLink - pointer to instance of object implementing IShellLink, which
  99. // has been associated with a shortcut file.
  100. //
  101. // result - receives the result -- the shortcut target path -- on sucess.
  102. HRESULT
  103. GetShortcutTargetPath(
  104. const SmartInterface<IShellLink>& shellLink,
  105. String& result)
  106. {
  107. LOG_FUNCTION(GetShortcutTargetPath);
  108. ASSERT(shellLink);
  109. result.erase();
  110. WCHAR target[MAX_PATH + 1];
  111. // REVIEWED-2002/02/26-sburns correct byte count passed
  112. // remove superfluous '&' NTRAID#NTBUG9-540418-2002/03/12-sburns
  113. ::ZeroMemory(target, sizeof WCHAR * (MAX_PATH + 1));
  114. // REVIEWED-2002/02/26-sburns passing correct character count.
  115. HRESULT hr = shellLink->GetPath(target, MAX_PATH, 0, SLGP_SHORTPATH);
  116. if (SUCCEEDED(hr))
  117. {
  118. result = target;
  119. }
  120. return hr;
  121. }
  122. // Return true if the supplied target of a shortcut is such that it identifies
  123. // the shortcut as one of those installed on promote. Return false if not one
  124. // such.
  125. //
  126. // target - target path of the shortcut (i.e. path to that which the shortcut
  127. // points)
  128. bool
  129. IsAdminpakShortcut(const String& target)
  130. {
  131. LOG_FUNCTION2(IsAdminpakShortcut, target);
  132. // don't assert that target has a value. Some shortcuts don't, if they're
  133. // broken.
  134. //
  135. // ASSERT(!target.empty());
  136. // If the target is of the form %systemroot%\Installer\{guid}\foo.ico,
  137. // then it is one of the adminpak dcpromo shortcuts.
  138. static String baseNames[] =
  139. {
  140. L"DTMgmt.ico",
  141. L"ADSSMgr.ico",
  142. L"ADMgr.ico",
  143. L"ADDcPol.ico",
  144. L"ADDomPol.ico"
  145. };
  146. static String root(Win::GetSystemWindowsDirectory() + L"\\Installer\\{");
  147. bool result = false;
  148. String prefix(target, 0, root.length());
  149. if (root.icompare(prefix) == 0)
  150. {
  151. // the prefix matches.
  152. String leaf = FS::GetPathLeafElement(target);
  153. for (int i = 0; i < (sizeof(baseNames) / sizeof(String)) ; ++i)
  154. {
  155. if (leaf.icompare(baseNames[i]) == 0)
  156. {
  157. result = true;
  158. break;
  159. }
  160. }
  161. }
  162. LOG(
  163. String::format(
  164. L"%1 an adminpak shortcut",
  165. result ? L"is" : L"is not"));
  166. return result;
  167. }
  168. bool
  169. IsPromoteToolShortcut(const String& target)
  170. {
  171. LOG_FUNCTION2(IsPromoteToolShortcut, target);
  172. ASSERT(!target.empty());
  173. // Check target against the values we used to create the shortcuts. The
  174. // values we used specified a fully-qualified path to the system32 folder,
  175. // and we will compare the target to the full path.
  176. String targetPrefix = Win::GetSystemDirectory() + L"\\";
  177. for (
  178. int i = 0;
  179. i < sizeof(shortcutsToAdd) / sizeof(ShortcutParams);
  180. ++i)
  181. {
  182. if (target.icompare(targetPrefix + shortcutsToAdd[i].target) == 0)
  183. {
  184. return true;
  185. }
  186. }
  187. return false;
  188. }
  189. // Return true if the given shortcut one of those installed on promote.
  190. // Return false if not one of those shortcuts, or on error.
  191. //
  192. // shellLink - smart interface pointer to an object implementing IShellLink.
  193. //
  194. // lnkPath - full file path of the shortcut (.lnk) file to be evaluated.
  195. bool
  196. ShouldDeleteShortcut(
  197. const SmartInterface<IShellLink>& shellLink,
  198. const String& lnkPath)
  199. {
  200. LOG_FUNCTION2(ShouldDeleteShortcut, lnkPath);
  201. ASSERT(!lnkPath.empty());
  202. ASSERT(shellLink);
  203. // Shortcut file names are localized, so we can't delete them based on
  204. // their names. idea: Open the shortcut, see what it's target is,
  205. // and based on that, determine if it's one we should delete.
  206. HRESULT hr = S_OK;
  207. bool result = false;
  208. do
  209. {
  210. // Load the shortcut file
  211. // REVIEWED-2002/02/26-sburns we open with minimum access, and are only
  212. // reading
  213. SmartInterface<IPersistFile> ipf;
  214. hr = ipf.AcquireViaQueryInterface(shellLink);
  215. BREAK_ON_FAILED_HRESULT(hr);
  216. // ISSUE-2002/02/26-sburns should we specify STGM_SHARE_DENY_WRITE?
  217. hr = ipf->Load(lnkPath.c_str(), STGM_READ);
  218. BREAK_ON_FAILED_HRESULT(hr);
  219. // Get the target lnkPath
  220. String target;
  221. hr = GetShortcutTargetPath(shellLink, target);
  222. BREAK_ON_FAILED_HRESULT(hr);
  223. if (IsAdminpakShortcut(target))
  224. {
  225. result = true;
  226. break;
  227. }
  228. // Not an adminpak shortcut. Might be one of the ones created by
  229. // PromoteConfigureToolShortcuts (ourselves).
  230. if (IsPromoteToolShortcut(target))
  231. {
  232. result = true;
  233. break;
  234. }
  235. // if we make it here, the shortcut is not one we should delete.
  236. }
  237. while (0);
  238. LOG(
  239. String::format(
  240. L"%1 delete shortcut",
  241. result ? L"should" : L"should not"));
  242. return result;
  243. }
  244. HRESULT
  245. CreateShortcut(
  246. const SmartInterface<IShellLink>& shellLink,
  247. const String& destFolderPath,
  248. int linkNameResId,
  249. HINSTANCE linkNameResModule,
  250. int descResId,
  251. const String& target,
  252. const String& params,
  253. const String& iconDll)
  254. {
  255. LOG_FUNCTION2(CreateShortcut, target);
  256. ASSERT(shellLink);
  257. // the path should exist already: it's the one we grabbed at startup
  258. ASSERT(FS::PathExists(destFolderPath));
  259. ASSERT(!target.empty());
  260. ASSERT(linkNameResId);
  261. ASSERT(descResId);
  262. ASSERT(linkNameResModule);
  263. // params and iconDll may be empty
  264. HRESULT hr = S_OK;
  265. do
  266. {
  267. String sys32Folder = Win::GetSystemDirectory();
  268. String targetPath = sys32Folder + L"\\" + target;
  269. // REVIEWED-2002/05/06-sburns we're using full paths to the target
  270. hr = shellLink->SetPath(targetPath.c_str());
  271. BREAK_ON_FAILED_HRESULT(hr);
  272. hr = shellLink->SetWorkingDirectory(sys32Folder.c_str());
  273. BREAK_ON_FAILED_HRESULT(hr);
  274. hr =
  275. shellLink->SetDescription(
  276. String::format(
  277. // MUI-aware shortcuts take a description that is really a
  278. // pointer to a resource dll and a resource ID.
  279. // NTRAID#NTBUG9-185055-2001/06/21-sburns
  280. L"@%%systemroot%%\\system32\\dcpromo.dll,-%1!d!",
  281. descResId).c_str());
  282. BREAK_ON_FAILED_HRESULT(hr);
  283. hr = shellLink->SetArguments(params.c_str());
  284. BREAK_ON_FAILED_HRESULT(hr);
  285. if (!iconDll.empty())
  286. {
  287. hr =
  288. shellLink->SetIconLocation(
  289. (sys32Folder + L"\\" + iconDll).c_str(), 0);
  290. }
  291. SmartInterface<IPersistFile> ipf;
  292. hr = ipf.AcquireViaQueryInterface(shellLink);
  293. BREAK_ON_FAILED_HRESULT(hr);
  294. String destPath =
  295. destFolderPath
  296. + L"\\"
  297. + String::load(linkNameResId, linkNameResModule)
  298. + L".lnk";
  299. // REVIEWED-2002/02/27-sburns we are composing a full path to the file
  300. ASSERT(FS::IsValidPath(destPath));
  301. hr = ipf->Save(destPath.c_str(), TRUE);
  302. BREAK_ON_FAILED_HRESULT(hr);
  303. // MUI-aware shortcuts need a localized name.
  304. // NTRAID#NTBUG9-185055-2001/06/21-sburns
  305. hr =
  306. SHSetLocalizedName(
  307. const_cast<PWSTR>(destPath.c_str()),
  308. L"%systemroot%\\system32\\dcpromo.dll",
  309. linkNameResId);
  310. BREAK_ON_FAILED_HRESULT(hr);
  311. }
  312. while (0);
  313. LOG_HRESULT(hr);
  314. return hr;
  315. }
  316. HRESULT
  317. DeleteShortcut(
  318. const String& folder,
  319. int linkNameResId,
  320. HINSTANCE linkNameResModule)
  321. {
  322. LOG_FUNCTION(DeleteShortcut);
  323. ASSERT(!folder.empty());
  324. ASSERT(linkNameResId);
  325. ASSERT(linkNameResModule);
  326. HRESULT hr = S_OK;
  327. do
  328. {
  329. String linkPath =
  330. folder
  331. + L"\\"
  332. + String::load(linkNameResId, linkNameResModule)
  333. + L".lnk";
  334. LOG(linkPath);
  335. if (FS::PathExists(linkPath))
  336. {
  337. hr = Win::DeleteFile(linkPath);
  338. BREAK_ON_FAILED_HRESULT(hr);
  339. }
  340. }
  341. while (0);
  342. return hr;
  343. }
  344. // Remove the shortcuts to the DS administration tools that were installed on
  345. // promote.
  346. void
  347. DemoteConfigureToolShortcuts(ProgressDialog& dialog)
  348. {
  349. LOG_FUNCTION(DemoteConfigureToolShortcuts);
  350. HRESULT hr = S_OK;
  351. HMODULE dcpromoDll = 0;
  352. State& state = State::GetInstance();
  353. do
  354. {
  355. String path = state.GetAdminToolsShortcutPath();
  356. if (path.empty())
  357. {
  358. // We were unable to determine the path at startup.
  359. hr = Win32ToHresult(ERROR_PATH_NOT_FOUND);
  360. break;
  361. }
  362. // (may) Need to init com for this thread.
  363. AutoCoInitialize coInit;
  364. hr = coInit.Result();
  365. BREAK_ON_FAILED_HRESULT(hr);
  366. SmartInterface<IShellLink> shellLink;
  367. hr =
  368. shellLink.AcquireViaCreateInstance(
  369. CLSID_ShellLink,
  370. 0,
  371. CLSCTX_INPROC_SERVER);
  372. BREAK_ON_FAILED_HRESULT(hr);
  373. LOG(L"enumerating shortcuts");
  374. FS::Iterator iter(
  375. path + L"\\*.lnk",
  376. FS::Iterator::INCLUDE_FILES
  377. | FS::Iterator::RETURN_FULL_PATHS);
  378. String current;
  379. while ((hr = iter.GetCurrent(current)) == S_OK)
  380. {
  381. if (ShouldDeleteShortcut(shellLink, current))
  382. {
  383. LOG(String::format(L"Deleting %1", current.c_str()));
  384. // we don't bail out on an error here because we want to
  385. // try to delete as many shortcuts as possible.
  386. HRESULT unused = Win::DeleteFile(current);
  387. LOG_HRESULT(unused);
  388. }
  389. hr = iter.Increment();
  390. BREAK_ON_FAILED_HRESULT(hr);
  391. }
  392. // add the shortcut(s) removed during promote
  393. hr =
  394. Win::LoadLibraryEx(
  395. SHORTCUT_DLL,
  396. LOAD_LIBRARY_AS_DATAFILE,
  397. dcpromoDll);
  398. BREAK_ON_FAILED_HRESULT2(hr, L"Unable to load dcpromo.dll");
  399. for (
  400. int i = 0;
  401. i < sizeof(shortcutsToDelete) / sizeof(ShortcutParams);
  402. ++i)
  403. {
  404. // don't break on error -- push on to attempt to create the
  405. // entire set.
  406. CreateShortcut(
  407. shellLink,
  408. path,
  409. shortcutsToDelete[i].linkNameResId,
  410. dcpromoDll,
  411. shortcutsToDelete[i].descResId,
  412. shortcutsToDelete[i].target,
  413. shortcutsToDelete[i].params,
  414. shortcutsToDelete[i].iconDll);
  415. }
  416. }
  417. while (0);
  418. Win::FreeLibrary(dcpromoDll);
  419. if (FAILED(hr))
  420. {
  421. popup.Error(
  422. dialog.GetHWND(),
  423. hr,
  424. IDS_ERROR_CONFIGURING_SHORTCUTS);
  425. state.AddFinishMessage(
  426. String::load(IDS_SHORTCUTS_NOT_CONFIGURED));
  427. }
  428. }
  429. // Take a domain name in canonical (dotted) form, e.g. domain.foo.com, and
  430. // translate it to the fully-qualified DN form, e.g. DC=domain,DC=foo,DC=com
  431. //
  432. // domainCanonical - in, domain name in canonical form
  433. //
  434. // domainDN - out, domain name in DN form
  435. HRESULT
  436. CannonicalToDn(const String& domainCanonical, String& domainDN)
  437. {
  438. LOG_FUNCTION2(CannonicalToDn, domainCanonical);
  439. ASSERT(!domainCanonical.empty());
  440. domainDN.erase();
  441. HRESULT hr = S_OK;
  442. do
  443. {
  444. if (domainCanonical.empty())
  445. {
  446. hr = E_INVALIDARG;
  447. BREAK_ON_FAILED_HRESULT(hr);
  448. }
  449. // add a trailing '/' to signal DsCrackNames to do a syntactical
  450. // munge of the string, rather than hit the wire.
  451. // add 1 for the null terminator, 1 for the trailing '/'
  452. PWSTR name = new WCHAR[domainCanonical.length() + 2];
  453. // REVIEWED-2002/02/27-sburns correct byte count passed.
  454. ::ZeroMemory(name, (domainCanonical.length() + 2) * sizeof WCHAR);
  455. domainCanonical.copy(name, domainCanonical.length());
  456. name[domainCanonical.length()] = L'/';
  457. DS_NAME_RESULT* nameResult = 0;
  458. hr =
  459. Win32ToHresult(
  460. ::DsCrackNames(
  461. // no handle: this is a string munge
  462. reinterpret_cast<void*>(-1),
  463. DS_NAME_FLAG_SYNTACTICAL_ONLY,
  464. DS_CANONICAL_NAME,
  465. DS_FQDN_1779_NAME,
  466. 1,
  467. &name,
  468. &nameResult));
  469. delete[] name;
  470. BREAK_ON_FAILED_HRESULT(hr);
  471. ASSERT(nameResult);
  472. if (nameResult)
  473. {
  474. ASSERT(nameResult->cItems == 1);
  475. DS_NAME_RESULT_ITEM* items = nameResult->rItems;
  476. // if we don't get a struct back, then DsCrackNames is broken.
  477. ASSERT(items);
  478. if (items)
  479. {
  480. LOG(String::format(L"status : 0x%1!X!", items[0].status));
  481. LOG(String::format(L"pName : %1", items[0].pName));
  482. ASSERT(items[0].status == DS_NAME_NO_ERROR);
  483. if (items[0].pName)
  484. {
  485. domainDN = items[0].pName;
  486. }
  487. if (domainDN.empty())
  488. {
  489. hr = E_FAIL;
  490. }
  491. }
  492. ::DsFreeNameResult(nameResult);
  493. }
  494. }
  495. while (0);
  496. LOG_HRESULT(hr);
  497. return hr;
  498. }
  499. // Create all the admin tools shortcuts that are needed after a promote.
  500. //
  501. // path - in, where to create the shortcuts
  502. //
  503. // shellLink - in, initialized shellLink interface to create the shortcuts
  504. // with.
  505. HRESULT
  506. PromoteCreateShortcuts(
  507. const String& path,
  508. SmartInterface<IShellLink>& shellLink,
  509. HINSTANCE dcpromoDll)
  510. {
  511. LOG_FUNCTION(PromoteCreateShortcuts);
  512. ASSERT(!path.empty());
  513. ASSERT(shellLink);
  514. ASSERT(dcpromoDll);
  515. HRESULT hr = S_OK;
  516. do
  517. {
  518. State& state = State::GetInstance();
  519. // for the policy shortcuts, we will need to know the domain DN, so
  520. // determine that here.
  521. // NTRAID#NTBUG9-232442-2000/11/15-sburns
  522. String domainCanonical;
  523. State::Operation oper = state.GetOperation();
  524. if (
  525. oper == State::FOREST
  526. || oper == State::TREE
  527. || oper == State::CHILD)
  528. {
  529. domainCanonical = state.GetNewDomainDNSName();
  530. }
  531. else if (oper == State::REPLICA)
  532. {
  533. domainCanonical = state.GetReplicaDomainDNSName();
  534. }
  535. else
  536. {
  537. // we should not be calling this function on non-promote scenarios
  538. ASSERT(false);
  539. hr = E_FAIL;
  540. BREAK_ON_FAILED_HRESULT(hr);
  541. }
  542. String domainDn;
  543. bool skipPolicyShortcuts = false;
  544. hr = CannonicalToDn(domainCanonical, domainDn);
  545. if (FAILED(hr))
  546. {
  547. LOG(L"skipping install of policy shortcuts");
  548. skipPolicyShortcuts = true;
  549. }
  550. for (
  551. int i = 0;
  552. i < sizeof(shortcutsToAdd) / sizeof(ShortcutParams);
  553. ++i)
  554. {
  555. // set the correct parameters for domain and dc security policy tools.
  556. String params;
  557. if (shortcutsToAdd[i].linkNameResId == IDS_DC_POLICY_LINK)
  558. {
  559. if (skipPolicyShortcuts)
  560. {
  561. continue;
  562. }
  563. params =
  564. String::format(
  565. L"/gpobject:\"LDAP://CN={%1},CN=Policies,CN=System,%2\"",
  566. STR_DEFAULT_DOMAIN_CONTROLLER_GPO_GUID,
  567. domainDn.c_str());
  568. }
  569. else if (shortcutsToAdd[i].linkNameResId == IDS_DOMAIN_POLICY_LINK)
  570. {
  571. if (skipPolicyShortcuts)
  572. {
  573. continue;
  574. }
  575. params =
  576. String::format(
  577. L"/gpobject:\"LDAP://CN={%1},CN=Policies,CN=System,%2\"",
  578. STR_DEFAULT_DOMAIN_GPO_GUID,
  579. domainDn.c_str());
  580. }
  581. else
  582. {
  583. params = shortcutsToAdd[i].params;
  584. }
  585. // don't break on errors -- push on to attempt to create the
  586. // entire set.
  587. CreateShortcut(
  588. shellLink,
  589. path,
  590. shortcutsToAdd[i].linkNameResId,
  591. dcpromoDll,
  592. shortcutsToAdd[i].descResId,
  593. shortcutsToAdd[i].target,
  594. params,
  595. shortcutsToAdd[i].iconDll);
  596. }
  597. }
  598. while (0);
  599. LOG_HRESULT(hr);
  600. return hr;
  601. }
  602. void
  603. PromoteConfigureToolShortcuts(ProgressDialog& dialog)
  604. {
  605. LOG_FUNCTION(PromoteConfigureToolShortcuts);
  606. dialog.UpdateText(String::load(IDS_CONFIGURING_SHORTCUTS));
  607. HRESULT hr = S_OK;
  608. State& state = State::GetInstance();
  609. HMODULE dcpromoDll = 0;
  610. do
  611. {
  612. String path = state.GetAdminToolsShortcutPath();
  613. if (path.empty())
  614. {
  615. // We were unable to determine the path at startup.
  616. hr = Win32ToHresult(ERROR_PATH_NOT_FOUND);
  617. break;
  618. }
  619. // Need to init com for this thread.
  620. AutoCoInitialize coInit;
  621. hr = coInit.Result();
  622. BREAK_ON_FAILED_HRESULT(hr);
  623. SmartInterface<IShellLink> shellLink;
  624. hr =
  625. shellLink.AcquireViaCreateInstance(
  626. CLSID_ShellLink,
  627. 0,
  628. CLSCTX_INPROC_SERVER);
  629. BREAK_ON_FAILED_HRESULT(hr);
  630. hr =
  631. Win::LoadLibraryEx(
  632. SHORTCUT_DLL,
  633. LOAD_LIBRARY_AS_DATAFILE,
  634. dcpromoDll);
  635. BREAK_ON_FAILED_HRESULT2(hr, L"Unable to load dcpromo.dll");
  636. // add the shortcuts to the ds administration tools
  637. PromoteCreateShortcuts(path, shellLink, dcpromoDll);
  638. // remove the shortcuts to local tools
  639. for (
  640. int i = 0;
  641. i < sizeof(shortcutsToDelete) / sizeof(ShortcutParams);
  642. ++i)
  643. {
  644. // don't break on error -- push on to attempt to delete the
  645. // entire set.
  646. DeleteShortcut(
  647. path,
  648. shortcutsToDelete[i].linkNameResId,
  649. dcpromoDll);
  650. }
  651. }
  652. while (0);
  653. Win::FreeLibrary(dcpromoDll);
  654. if (FAILED(hr))
  655. {
  656. popup.Error(
  657. dialog.GetHWND(),
  658. hr,
  659. IDS_ERROR_CONFIGURING_SHORTCUTS);
  660. state.AddFinishMessage(
  661. String::load(IDS_SHORTCUTS_NOT_CONFIGURED));
  662. }
  663. }