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.

670 lines
18 KiB

  1. // This is a part of the Microsoft Management Console.
  2. // Copyright (C) Microsoft Corporation, 1995 - 1999
  3. // All rights reserved.
  4. //
  5. // This source code is only intended as a supplement to the
  6. // Microsoft Management Console and related
  7. // electronic documentation provided with the interfaces.
  8. #include "stdafx.h"
  9. #include "resource.h"
  10. #include "initguid.h"
  11. #include "misc.h"
  12. #include <shlguid.h>
  13. #include <shlobj.h>
  14. #define __dwFILE__ __dwFILE_CERTMMC_SNAPIN_CPP__
  15. #define WSZCERTMMC_DLL "certmmc.dll"
  16. CComModule _Module;
  17. HINSTANCE g_hInstance = NULL;
  18. HMODULE g_hmodRichEdit = NULL;
  19. BEGIN_OBJECT_MAP(ObjectMap)
  20. OBJECT_ENTRY(CLSID_Snapin, CComponentDataPrimaryImpl)
  21. OBJECT_ENTRY(CLSID_About, CSnapinAboutImpl)
  22. END_OBJECT_MAP()
  23. #ifdef _DEBUG
  24. #undef THIS_FILE
  25. static char THIS_FILE[] = __FILE__;
  26. #endif
  27. void CreateRegEntries();
  28. void RemoveRegEntries();
  29. void CreateProgramGroupLink();
  30. void RemoveProgramGroupLink();
  31. // #define CERTMMC_DEBUG_REGSVR
  32. BOOL WINAPI
  33. DllMain(
  34. HINSTANCE hinstDLL, // handle to DLL module
  35. DWORD dwReason, // reason for calling function
  36. LPVOID /* lpvReserved */ )
  37. {
  38. switch (dwReason)
  39. {
  40. case DLL_PROCESS_ATTACH:
  41. {
  42. g_hInstance = hinstDLL;
  43. _Module.Init(ObjectMap, hinstDLL);
  44. DisableThreadLibraryCalls(hinstDLL);
  45. LogOpen(FALSE);
  46. break;
  47. }
  48. case DLL_PROCESS_DETACH:
  49. {
  50. // last call process should do this
  51. if (NULL != g_hmodRichEdit)
  52. {
  53. FreeLibrary(g_hmodRichEdit);
  54. }
  55. myFreeColumnDisplayNames();
  56. myFreeResourceStrings("certmmc.dll");
  57. delete g_pResources;
  58. _Module.Term();
  59. myRegisterMemDump();
  60. LogClose();
  61. DEBUG_VERIFY_INSTANCE_COUNT(CSnapin);
  62. DEBUG_VERIFY_INSTANCE_COUNT(CComponentDataImpl);
  63. break;
  64. }
  65. default:
  66. break;
  67. }
  68. return TRUE;
  69. }
  70. /////////////////////////////////////////////////////////////////////////////
  71. // Used to determine whether the DLL can be unloaded by OLE
  72. STDAPI DllCanUnloadNow(void)
  73. {
  74. AFX_MANAGE_STATE(AfxGetStaticModuleState());
  75. return (_Module.GetLockCount() == 0) ? S_OK : S_FALSE;
  76. }
  77. /////////////////////////////////////////////////////////////////////////////
  78. // Returns a class factory to create an object of the requested type
  79. STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
  80. {
  81. return _Module.GetClassObject(rclsid, riid, ppv);
  82. }
  83. /////////////////////////////////////////////////////////////////////////////
  84. // DllRegisterServer - Adds entries to the system registry
  85. STDAPI DllRegisterServer(void)
  86. {
  87. CreateRegEntries();
  88. CreateProgramGroupLink();
  89. // registers object, typelib and all interfaces in typelib
  90. return _Module.RegisterServer(FALSE);
  91. }
  92. /////////////////////////////////////////////////////////////////////////////
  93. // DllUnregisterServer - Removes entries from the system registry
  94. STDAPI DllUnregisterServer(void)
  95. {
  96. _Module.UnregisterServer();
  97. RemoveRegEntries();
  98. RemoveProgramGroupLink();
  99. return S_OK;
  100. }
  101. /////////////////////////////////////////////////////////////////////////////
  102. // Register/UnRegister nodetypes, etc
  103. typedef struct _REGVALUE
  104. {
  105. DWORD dwFlags;
  106. WCHAR const *pwszKeyName; // NULL implies place value under CA name key
  107. WCHAR const *pwszValueName;
  108. WCHAR const *pwszValueString; // NULL implies use REG_DWORD value (dwValue)
  109. DWORD dwValue;
  110. } REGVALUE;
  111. // Flags
  112. #define CERTMMC_REG_DELKEY 1 // delete this key on removal
  113. // Values Under "HKLM" from base to leaves
  114. REGVALUE g_arvCA[] =
  115. {
  116. // main snapin uuid
  117. #define IREG_SNAPINNAME 0
  118. { CERTMMC_REG_DELKEY, wszREGKEYMGMTSNAPINUUID1, NULL, NULL, 0},
  119. #define IREG_SNAPINNAMESTRING 1
  120. { 0, wszREGKEYMGMTSNAPINUUID1, wszSNAPINNAMESTRING, NULL, 0},
  121. #define IREG_SNAPINNAMESTRINGINDIRECT 2
  122. { 0, wszREGKEYMGMTSNAPINUUID1, wszSNAPINNAMESTRINGINDIRECT, NULL, 0},
  123. { 0, wszREGKEYMGMTSNAPINUUID1, wszSNAPINABOUT, wszSNAPINNODETYPE_ABOUT, 0},
  124. { CERTMMC_REG_DELKEY, wszREGKEYMGMTSNAPINUUID1_STANDALONE, NULL, NULL, 0}
  125. ,
  126. { CERTMMC_REG_DELKEY, wszREGKEYMGMTSNAPINUUID1_NODETYPES, NULL, NULL, 0},
  127. { CERTMMC_REG_DELKEY, wszREGKEYMGMTSNAPINUUID1_NODETYPES_1, NULL, NULL, 0},
  128. { CERTMMC_REG_DELKEY, wszREGKEYMGMTSNAPINUUID1_NODETYPES_2, NULL, NULL, 0},
  129. { CERTMMC_REG_DELKEY, wszREGKEYMGMTSNAPINUUID1_NODETYPES_3, NULL, NULL, 0},
  130. { CERTMMC_REG_DELKEY, wszREGKEYMGMTSNAPINUUID1_NODETYPES_4, NULL, NULL, 0},
  131. { CERTMMC_REG_DELKEY, wszREGKEYMGMTSNAPINUUID1_NODETYPES_5, NULL, NULL, 0},
  132. // register each snapin nodetype
  133. { CERTMMC_REG_DELKEY, wszREGKEYMGMTSNAPIN_NODETYPES_1, NULL, wszREGCERTSNAPIN_NODETYPES_1, 0},
  134. { CERTMMC_REG_DELKEY, wszREGKEYMGMTSNAPIN_NODETYPES_2, NULL, wszREGCERTSNAPIN_NODETYPES_2, 0},
  135. { CERTMMC_REG_DELKEY, wszREGKEYMGMTSNAPIN_NODETYPES_3, NULL, wszREGCERTSNAPIN_NODETYPES_3, 0},
  136. { CERTMMC_REG_DELKEY, wszREGKEYMGMTSNAPIN_NODETYPES_4, NULL, wszREGCERTSNAPIN_NODETYPES_4, 0},
  137. { CERTMMC_REG_DELKEY, wszREGKEYMGMTSNAPIN_NODETYPES_5, NULL, wszREGCERTSNAPIN_NODETYPES_5, 0},
  138. { 0, NULL, NULL, NULL, 0 }
  139. };
  140. HRESULT
  141. InitRegEntries(
  142. OPTIONAL IN OUT CString *pcstrName,
  143. OPTIONAL IN OUT CString *pcstrNameString,
  144. OPTIONAL IN OUT CString *pcstrNameStringIndirect)
  145. {
  146. HRESULT hr = S_OK;
  147. WCHAR const *pwsz;
  148. pwsz = NULL;
  149. if (NULL != pcstrName)
  150. {
  151. pcstrName->LoadString(IDS_CERTMMC_SNAPINNAME);
  152. if (pcstrName->IsEmpty())
  153. {
  154. hr = myHLastError();
  155. _PrintError(hr, "LoadString");
  156. }
  157. else
  158. {
  159. pwsz = (LPCWSTR) *pcstrName;
  160. }
  161. }
  162. g_arvCA[IREG_SNAPINNAME].pwszValueString = pwsz;
  163. pwsz = NULL;
  164. if (NULL != pcstrNameString)
  165. {
  166. pcstrNameString->LoadString(IDS_CERTMMC_SNAPINNAMESTRING);
  167. if (pcstrNameString->IsEmpty())
  168. {
  169. hr = myHLastError();
  170. _PrintError(hr, "LoadString");
  171. }
  172. else
  173. {
  174. pwsz = (LPCWSTR) *pcstrNameString;
  175. }
  176. }
  177. g_arvCA[IREG_SNAPINNAMESTRING].pwszValueString = pwsz;
  178. pwsz = NULL;
  179. if (NULL != pcstrNameStringIndirect)
  180. {
  181. pcstrNameStringIndirect->Format(wszSNAPINNAMESTRINGINDIRECT_TEMPLATE, L"CertMMC.dll", IDS_CERTMMC_SNAPINNAMESTRING);
  182. if (pcstrNameStringIndirect->IsEmpty())
  183. {
  184. hr = myHLastError();
  185. _PrintError(hr, "LoadString");
  186. }
  187. else
  188. {
  189. pwsz = (LPCWSTR) *pcstrNameStringIndirect;
  190. }
  191. }
  192. g_arvCA[IREG_SNAPINNAMESTRINGINDIRECT].pwszValueString = pwsz;
  193. //error:
  194. return(hr);
  195. }
  196. void CreateRegEntries()
  197. {
  198. DWORD err;
  199. HKEY hKeyThisValue = NULL;
  200. REGVALUE const *prv;
  201. CString cstrName;
  202. CString cstrNameString;
  203. CString cstrNameStringIndirect;
  204. InitRegEntries(&cstrName, &cstrNameString, &cstrNameStringIndirect);
  205. // run until not creating key or value
  206. for ( prv=g_arvCA;
  207. !(NULL == prv->pwszValueName && NULL == prv->pwszKeyName);
  208. prv++ )
  209. {
  210. DWORD dwDisposition;
  211. ASSERT(NULL != prv->pwszKeyName);
  212. if (NULL == prv->pwszKeyName)
  213. continue;
  214. #ifdef CERTMMC_DEBUG_REGSVR
  215. CString cstr;
  216. cstr.Format(L"RegCreateKeyEx: %ws\n", prv->pwszKeyName);
  217. OutputDebugString((LPCWSTR)cstr);
  218. #endif
  219. err = RegCreateKeyEx(HKEY_LOCAL_MACHINE,
  220. prv->pwszKeyName,
  221. 0,
  222. NULL,
  223. REG_OPTION_NON_VOLATILE,
  224. KEY_ALL_ACCESS,
  225. NULL,
  226. &hKeyThisValue,
  227. &dwDisposition);
  228. if (err != ERROR_SUCCESS)
  229. goto error;
  230. // for now, don't set any value if unnamed, unvalued string
  231. // UNDONE: can't set unnamed dword!
  232. if (NULL != prv->pwszValueName || NULL != prv->pwszValueString)
  233. {
  234. if (NULL != prv->pwszValueString)
  235. {
  236. #ifdef CERTMMC_DEBUG_REGSVR
  237. CString cstr;
  238. cstr.Format(L"RegSetValueEx: %ws : %ws\n", prv->pwszValueName, prv->pwszValueString);
  239. OutputDebugString((LPCWSTR)cstr);
  240. #endif
  241. err = RegSetValueEx(
  242. hKeyThisValue,
  243. prv->pwszValueName,
  244. 0,
  245. REG_SZ,
  246. (const BYTE *) prv->pwszValueString,
  247. WSZ_BYTECOUNT(prv->pwszValueString));
  248. }
  249. else
  250. {
  251. #ifdef CERTMMC_DEBUG_REGSVR
  252. CString cstr;
  253. cstr.Format(L"RegSetValueEx: %ws : %ul\n", prv->pwszValueName, prv->dwValue);
  254. OutputDebugString((LPCWSTR)cstr);
  255. #endif
  256. err = RegSetValueEx(
  257. hKeyThisValue,
  258. prv->pwszValueName,
  259. 0,
  260. REG_DWORD,
  261. (const BYTE *) &prv->dwValue,
  262. sizeof(prv->dwValue));
  263. }
  264. if (err != ERROR_SUCCESS)
  265. goto error;
  266. }
  267. if (NULL != hKeyThisValue)
  268. {
  269. RegCloseKey(hKeyThisValue);
  270. hKeyThisValue = NULL;
  271. }
  272. }
  273. error:
  274. if (hKeyThisValue)
  275. RegCloseKey(hKeyThisValue);
  276. InitRegEntries(NULL, NULL, NULL);
  277. return;
  278. }
  279. void RemoveRegEntries()
  280. {
  281. REGVALUE const *prv;
  282. // walk backwards through array until hit array start
  283. for ( prv= (&g_arvCA[ARRAYLEN(g_arvCA)]) - 2; // goto zero-based end AND skip {NULL}
  284. prv >= g_arvCA; // until we walk past beginning
  285. prv-- ) // walk backwards
  286. {
  287. if (prv->dwFlags & CERTMMC_REG_DELKEY)
  288. {
  289. ASSERT(prv->pwszKeyName != NULL);
  290. #ifdef CERTMMC_DEBUG_REGSVR
  291. CString cstr;
  292. cstr.Format(L"RegDeleteKey: %ws\n", prv->pwszKeyName);
  293. OutputDebugString((LPCWSTR)cstr);
  294. #endif
  295. RegDeleteKey(
  296. HKEY_LOCAL_MACHINE,
  297. prv->pwszKeyName);
  298. }
  299. }
  300. //error:
  301. return;
  302. }
  303. #include <shlobj.h> // CSIDL_ #defines
  304. #include <userenv.h>
  305. #include <userenvp.h> // CreateLinkFile API
  306. typedef struct _PROGRAMENTRY
  307. {
  308. UINT uiLinkName;
  309. UINT uiDescription;
  310. DWORD csidl; // special folder index
  311. WCHAR const *pwszExeName;
  312. WCHAR const *pwszArgs;
  313. } PROGRAMENTRY;
  314. PROGRAMENTRY const g_aProgramEntry[] = {
  315. {
  316. IDS_STARTMENU_CERTMMC_LINKNAME, // uiLinkName
  317. IDS_STARTMENU_CERTMMC_DESCRIPTION, // uiDescription
  318. CSIDL_COMMON_ADMINTOOLS, // "All Users\Start Menu\Programs\Administrative Tools"
  319. L"certsrv.msc", // pwszExeName
  320. L"", // pwszArgs
  321. },
  322. };
  323. #define CPROGRAMENTRY ARRAYSIZE(g_aProgramEntry)
  324. BOOL FFileExists(LPCWSTR szFile)
  325. {
  326. WIN32_FILE_ATTRIBUTE_DATA data;
  327. return(
  328. GetFileAttributesEx(szFile, GetFileExInfoStandard, &data) &&
  329. !(FILE_ATTRIBUTE_DIRECTORY & data.dwFileAttributes)
  330. );
  331. }
  332. HRESULT
  333. DeleteMatchingLinks(
  334. IN WCHAR const *pwszLinkDir, // C:\Documents...Administrative Tools
  335. IN WCHAR const *pwszLinkPath, // C:\ " \Certification Authority.lnk
  336. IN WCHAR const *pwszTargetPath, // C:\WINDOWS\system32\certsrv.msc
  337. IN WCHAR const *pwszArgs,
  338. IN WCHAR const *pwszDLLPath) // C:\WINDOWS\system32\certmmc.dll
  339. {
  340. HRESULT hr;
  341. WCHAR *pwszPattern = NULL;
  342. WCHAR *pwszFile = NULL;
  343. HANDLE hf = INVALID_HANDLE_VALUE;
  344. WIN32_FIND_DATA wfd;
  345. IShellLink *psl = NULL;
  346. IPersistFile *ppf = NULL;
  347. int i;
  348. hr = myBuildPathAndExt(pwszLinkDir, L"*.lnk", NULL, &pwszPattern);
  349. _JumpIfError(hr, error, "myBuildPathAndExt");
  350. hf = FindFirstFile(pwszPattern, &wfd);
  351. if (INVALID_HANDLE_VALUE == hf)
  352. {
  353. hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
  354. _JumpErrorStr2(hr, error, "no *.lnk files", pwszPattern, hr);
  355. }
  356. hr = HRESULT_FROM_WIN32(ERROR_DIRECTORY);
  357. do {
  358. WCHAR wszPath[MAX_PATH];
  359. if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
  360. {
  361. continue;
  362. }
  363. if (NULL != psl)
  364. {
  365. psl->Release();
  366. psl = NULL;
  367. }
  368. if (NULL != ppf)
  369. {
  370. ppf->Release();
  371. ppf = NULL;
  372. }
  373. if (NULL != pwszFile)
  374. {
  375. LocalFree(pwszFile);
  376. pwszFile = NULL;
  377. }
  378. hr = myBuildPathAndExt(pwszLinkDir, wfd.cFileName, NULL, &pwszFile);
  379. _JumpIfError(hr, error, "myBuildPathAndExt");
  380. if (0 == mylstrcmpiL(pwszLinkPath, pwszFile))
  381. {
  382. continue; // skip exact match
  383. }
  384. // Get a pointer to the IShellLink interface.
  385. hr = CoCreateInstance(CLSID_ShellLink, NULL,
  386. CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID *) &psl);
  387. if (S_OK != hr)
  388. psl = NULL;
  389. _JumpIfError(hr, error, "CoCreateInstance");
  390. // Query IShellLink for the IPersistFile interface for saving the
  391. // shortcut in persistent storage.
  392. hr = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);
  393. if (S_OK != hr)
  394. ppf = NULL;
  395. _JumpIfError(hr, error, "QI");
  396. // Load the link by calling IPersistFile::Load.
  397. hr = ppf->Load(pwszFile, STGM_READ);
  398. _JumpIfError(hr, error, "Load");
  399. hr = psl->GetPath(wszPath, ARRAYSIZE(wszPath), &wfd, SLGP_UNCPRIORITY);
  400. _JumpIfError(hr, error, "GetPath");
  401. if (0 != mylstrcmpiL(pwszTargetPath, wszPath))
  402. {
  403. continue; // different path: skip
  404. }
  405. hr = psl->GetArguments(wszPath, ARRAYSIZE(wszPath));
  406. _JumpIfError(hr, error, "GetArguments");
  407. if (0 != mylstrcmpiL(pwszArgs, wszPath))
  408. {
  409. continue; // different arguments: skip
  410. }
  411. hr = psl->GetIconLocation(wszPath, ARRAYSIZE(wszPath), &i);
  412. _JumpIfError(hr, error, "GetIconLocation");
  413. if (0 != mylstrcmpiL(pwszDLLPath, wszPath))
  414. {
  415. continue; // different icon DLL: skip
  416. }
  417. psl->Release();
  418. psl = NULL;
  419. ppf->Release();
  420. ppf = NULL;
  421. DeleteFile(pwszFile);
  422. } while (FindNextFile(hf, &wfd));
  423. hr = S_OK;
  424. error:
  425. if (INVALID_HANDLE_VALUE != hf)
  426. {
  427. FindClose(hf);
  428. }
  429. if (NULL != pwszFile)
  430. {
  431. LocalFree(pwszFile);
  432. }
  433. if (NULL != pwszPattern)
  434. {
  435. LocalFree(pwszPattern);
  436. }
  437. if (NULL != ppf)
  438. {
  439. ppf->Release();
  440. }
  441. if (NULL != psl)
  442. {
  443. psl->Release();
  444. }
  445. return(hr);
  446. }
  447. void CreateProgramGroupLink()
  448. {
  449. HRESULT hr;
  450. PROGRAMENTRY const *ppe;
  451. IShellLink *psl = NULL;
  452. IPersistFile *ppf = NULL;
  453. for (ppe = g_aProgramEntry; ppe < &g_aProgramEntry[CPROGRAMENTRY]; ppe++)
  454. {
  455. WCHAR wszPath[MAX_PATH];
  456. CString strDLLPath, strPath, strDescr, strLinkName, strLinkPath;
  457. LPWSTR pszTmp;
  458. if (NULL == (pszTmp = strPath.GetBuffer(MAX_PATH)))
  459. {
  460. hr = E_OUTOFMEMORY;
  461. _JumpError(hr, error, "GetBuffer");
  462. }
  463. GetSystemDirectory(pszTmp, MAX_PATH);
  464. strPath += L"\\";
  465. strDLLPath = strPath;
  466. strDLLPath += WSZCERTMMC_DLL;
  467. strPath += ppe->pwszExeName;
  468. // don't create link for file that doesn't exist
  469. if (!FFileExists(strPath))
  470. continue;
  471. strDescr.Format(L"@%s,-%d", strDLLPath.GetBuffer(), ppe->uiDescription);
  472. hr = SHGetFolderPath(
  473. NULL,
  474. ppe->csidl,
  475. NULL,
  476. SHGFP_TYPE_CURRENT,
  477. wszPath);
  478. _JumpIfError(hr, error, "SHGetFolderPath(Administrative Tools)");
  479. strLinkName.LoadString(ppe->uiLinkName);
  480. strLinkPath.Format(L"%s\\%s.lnk", wszPath, strLinkName);
  481. DeleteMatchingLinks(
  482. wszPath,
  483. strLinkPath,
  484. strPath,
  485. ppe->pwszArgs,
  486. strDLLPath);
  487. // Get a pointer to the IShellLink interface.
  488. hr = CoCreateInstance(CLSID_ShellLink, NULL,
  489. CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID *) &psl);
  490. if (S_OK != hr)
  491. psl = NULL;
  492. _JumpIfError(hr, error, "CoCreateInstance");
  493. strPath += ppe->pwszArgs;
  494. hr = psl->SetPath(strPath);
  495. _JumpIfError(hr, error, "SetPath");
  496. hr = psl->SetIconLocation(strDLLPath, 0);
  497. _JumpIfError(hr, error, "SetIconLocation");
  498. hr = psl->SetDescription(strDescr);
  499. _JumpIfError(hr, error, "SetDescription");
  500. // Query IShellLink for the IPersistFile interface for saving the
  501. // shortcut in persistent storage.
  502. hr = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);
  503. if (S_OK != hr)
  504. ppf = NULL;
  505. _JumpIfError(hr, error, "QI");
  506. // Save the link by calling IPersistFile::Save.
  507. hr = ppf->Save(strLinkPath.GetBuffer(), TRUE);
  508. _JumpIfError(hr, error, "Save");
  509. hr = SHSetLocalizedName(
  510. strLinkPath.GetBuffer(),
  511. strDLLPath,
  512. ppe->uiLinkName);
  513. _JumpIfError(hr, error, "SHSetLocalizedName");
  514. ppf->Release();
  515. ppf = NULL;
  516. psl->Release();
  517. psl = NULL;
  518. }
  519. hr = S_OK;
  520. error:
  521. if (NULL != ppf)
  522. ppf->Release();
  523. if (NULL != psl)
  524. psl->Release();
  525. return;
  526. }
  527. void RemoveProgramGroupLink()
  528. {
  529. HRESULT hr = S_OK;
  530. PROGRAMENTRY const *ppe;
  531. for (ppe = g_aProgramEntry; ppe < &g_aProgramEntry[CPROGRAMENTRY]; ppe++)
  532. {
  533. CString cstrLinkName;
  534. cstrLinkName.LoadString(ppe->uiLinkName);
  535. if (cstrLinkName.IsEmpty())
  536. {
  537. hr = myHLastError();
  538. _PrintError(hr, "LoadString");
  539. continue;
  540. }
  541. if (!DeleteLinkFile(
  542. ppe->csidl, // CSIDL_*
  543. NULL, // IN LPCSTR lpSubDirectory
  544. (LPCWSTR)cstrLinkName, // IN LPCSTR lpFileName
  545. FALSE)) // IN BOOL fDeleteSubDirectory
  546. {
  547. hr = myHLastError();
  548. _PrintError2(hr, "DeleteLinkFile", hr);
  549. }
  550. }
  551. //error:
  552. _PrintIfError2(hr, "RemoveProgramGroupLink", hr);
  553. return;
  554. }