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.

564 lines
20 KiB

  1. //+----------------------------------------------------------------------------
  2. //
  3. // File: uninstall.cpp
  4. //
  5. // Module: CMSTP.EXE
  6. //
  7. // Synopsis: This source file contains most of the code to uninstall CM profiles.
  8. //
  9. // Copyright (c) 1997-1999 Microsoft Corporation
  10. //
  11. // Author: quintinb Created 07/14/98
  12. //
  13. //+----------------------------------------------------------------------------
  14. #include "cmmaster.h"
  15. const TCHAR* const c_pszCmPath = TEXT("%s\\SOFTWARE\\Microsoft\\Connection Manager");
  16. const TCHAR* const c_pszUserInfoPath = TEXT("%s\\SOFTWARE\\Microsoft\\Connection Manager\\UserInfo");
  17. const TCHAR* const c_pszProfileUserInfoPath = TEXT("%s\\SOFTWARE\\Microsoft\\Connection Manager\\UserInfo\\%s");
  18. //+----------------------------------------------------------------------------
  19. //
  20. // Function: PromptUserToUninstallProfile
  21. //
  22. // Synopsis: This function prompts the user to see if they wish to uninstall
  23. // the given profile.
  24. //
  25. // Arguments: HINSTANCE hInstance - Instance Handle to get resources with
  26. // LPCTSTR pszInfFile - full path to the profile inf file
  27. //
  28. // Returns: int - Return Value of the MessageBox prompt, IDNO signifies an
  29. // error or that the user didn't want to continue. IDYES
  30. // signifies that the install should continue.
  31. //
  32. // History: quintinb Created 6/28/99
  33. //
  34. //+----------------------------------------------------------------------------
  35. BOOL PromptUserToUninstallProfile(HINSTANCE hInstance, LPCTSTR pszInfFile)
  36. {
  37. BOOL bReturn = FALSE;
  38. //
  39. // On Legacy Platforms we need to prompt to see if the user wants to uninstall.
  40. // On NT5 this is taken care of by the connections folder. We also use no prompt
  41. // when uninstalling the old profile for a same name upgrade.
  42. //
  43. TCHAR szServiceName[MAX_PATH+1];
  44. TCHAR szMessage[MAX_PATH+1];
  45. TCHAR szTemp[MAX_PATH+1];
  46. TCHAR szTitle[MAX_PATH+1] = {TEXT("")};
  47. MYVERIFY(0 != LoadString(hInstance, IDS_CMSTP_TITLE, szTitle, MAX_PATH));
  48. MYDBGASSERT(TEXT('\0') != szTitle[0]);
  49. MYVERIFY(0 != GetPrivateProfileString(c_pszInfSectionStrings, c_pszCmEntryServiceName,
  50. TEXT(""), szServiceName, MAX_PATH, pszInfFile));
  51. if(TEXT('\0') != szServiceName[0])
  52. {
  53. MYVERIFY(0 != LoadString(hInstance, IDS_UNINSTALL_PROMPT, szTemp, MAX_PATH));
  54. MYDBGASSERT(TEXT('\0') != szTemp[0]);
  55. MYVERIFY(CELEMS(szMessage) > (UINT)wsprintf(szMessage, szTemp, szServiceName));
  56. bReturn = (IDYES == MessageBox(NULL, szMessage, szTitle, MB_YESNO | MB_DEFBUTTON2));
  57. }
  58. else
  59. {
  60. CMASSERTMSG(FALSE, TEXT("PromptUserToUninstallProfile: Failed to retrieve ServiceName from INF"));
  61. }
  62. return bReturn;
  63. }
  64. //+----------------------------------------------------------------------------
  65. //
  66. // Function: BuildUninstallDirKey
  67. //
  68. // Synopsis: Utility function to expand any environment strings in the passed
  69. // in Mappings Data value and then parse that path into the Install
  70. // dir value (basically remove the \<short service name>.cmp from
  71. // the full path to the cmp)
  72. //
  73. // Arguments: LPCTSTR pszMappingsData - Raw data from the mappings key, may
  74. // contain environment strings.
  75. // LPTSTR szInstallDir - Out buffer for the install dir
  76. //
  77. // Returns: Nothing
  78. //
  79. // History: quintinb Created Header 6/28/99
  80. //
  81. //+----------------------------------------------------------------------------
  82. void BuildUninstallDirKey(LPCTSTR pszMappingsData, LPTSTR szInstallDir)
  83. {
  84. TCHAR szCmp[MAX_PATH+1];
  85. ExpandEnvironmentStrings(pszMappingsData, szCmp, CELEMS(szCmp));
  86. CFileNameParts CmpParts(szCmp);
  87. wsprintf(szInstallDir, TEXT("%s%s"), CmpParts.m_Drive, CmpParts.m_Dir);
  88. if (TEXT('\\') == szInstallDir[lstrlen(szInstallDir) - 1])
  89. {
  90. szInstallDir[lstrlen(szInstallDir) - 1] = TEXT('\0');
  91. }
  92. }
  93. //+----------------------------------------------------------------------------
  94. //
  95. // Function: DeleteSafeNetPskOnUninstall
  96. //
  97. // Synopsis: Determines if a PSK was written to the PSK store on profile
  98. // install and then deletes it.
  99. //
  100. // Arguments:
  101. //
  102. // Returns: Nothing
  103. //
  104. // History: quintinb Created 09/16/01
  105. //
  106. //+----------------------------------------------------------------------------
  107. void DeleteSafeNetPskOnUninstall(LPCTSTR pszCmpFile)
  108. {
  109. if (pszCmpFile && pszCmpFile[0])
  110. {
  111. //
  112. // First check the CMP file to see if we wrote the key to remind ourselves
  113. // to delete the Psk on uninstall...
  114. //
  115. if ((BOOL)GetPrivateProfileInt(c_pszCmSection, c_pszDeletePskOnUninstall, 0, pszCmpFile))
  116. {
  117. //
  118. // Okay if we have the key then delete the PSK
  119. //
  120. SafeNetLinkageStruct SnLinkage = {0};
  121. if (IsSafeNetClientAvailable() && LinkToSafeNet(&SnLinkage))
  122. {
  123. const TCHAR* const c_pszDeletedPsk = TEXT("****************"); // unfortunately we cannot clear the PSK so this is the best we can do
  124. DATA_BLOB DataBlob = {0};
  125. DataBlob.cbData = (lstrlen(c_pszDeletedPsk) + 1)*sizeof(TCHAR);
  126. DataBlob.pbData = (BYTE*)c_pszDeletedPsk;
  127. if (SnLinkage.pfnSnPolicySet(SN_L2TPPRESHR, (void*)&DataBlob))
  128. {
  129. (void)SnLinkage.pfnSnPolicyReload();
  130. }
  131. }
  132. UnLinkFromSafeNet(&SnLinkage);
  133. }
  134. }
  135. }
  136. //+----------------------------------------------------------------------------
  137. //
  138. // Function: UninstallProfile
  139. //
  140. // Synopsis: This function uninstalls a CM profile.
  141. //
  142. // Arguments: HINSTANCE hInstance - Instance handle for resources
  143. // LPCTSTR szInfPath - full path of the INF to uninstall
  144. // BOOL bCleanUpCreds -- whether credential info in the registry
  145. // should be cleaned up or not
  146. //
  147. // Returns: HRESULT -- Standard COM Error Codes
  148. //
  149. // History: Created Header 7/14/98
  150. //
  151. //+----------------------------------------------------------------------------
  152. HRESULT UninstallProfile(HINSTANCE hInstance, LPCTSTR szInfFile, BOOL bCleanUpCreds)
  153. {
  154. TCHAR* pszPhonebook = NULL;
  155. TCHAR szSectionName[MAX_PATH+1];
  156. TCHAR szCmsFile[MAX_PATH+1];
  157. TCHAR szCmpFile[MAX_PATH+1];
  158. TCHAR szProfileDir[MAX_PATH+1];
  159. TCHAR szInstallDir[MAX_PATH+1];
  160. TCHAR szShortServiceName[MAX_PATH+1];
  161. TCHAR szPhonebookPath[MAX_PATH+1];
  162. TCHAR szTemp[MAX_PATH+1];
  163. TCHAR szLongServiceName[MAX_PATH+1];
  164. CPlatform plat;
  165. HANDLE hFile;
  166. BOOL bReturn = FALSE;
  167. BOOL bAllUserUninstall;
  168. HKEY hBaseKey;
  169. HKEY hKey;
  170. int nDesktopFolder;
  171. int iCmsVersion;
  172. HRESULT hr;
  173. const TCHAR* const c_pszRegGuidMappings = TEXT("SOFTWARE\\Microsoft\\Connection Manager\\Guid Mappings");
  174. ZeroMemory(szCmsFile, sizeof(szCmsFile));
  175. ZeroMemory(szLongServiceName, sizeof(szLongServiceName));
  176. ZeroMemory(szProfileDir, sizeof(szProfileDir));
  177. ZeroMemory(szPhonebookPath, sizeof(szPhonebookPath));
  178. //
  179. // Load the title in case IExpress needs to show error dialogs
  180. //
  181. TCHAR szTitle[MAX_PATH+1] = {TEXT("")};
  182. MYVERIFY(0 != LoadString(hInstance, IDS_CMSTP_TITLE, szTitle, MAX_PATH));
  183. MYDBGASSERT(TEXT('\0') != szTitle[0]);
  184. //
  185. // Get the Long Service Name
  186. //
  187. if (0 == GetPrivateProfileString(c_pszInfSectionStrings, c_pszCmEntryServiceName,
  188. TEXT(""), szLongServiceName, MAX_PATH, szInfFile))
  189. {
  190. CMASSERTMSG(FALSE, TEXT("UninstallProfile -- Unable to get Long Service Name. This situation will occur normally when cmstp.exe /u is called by hand on NT5."));
  191. return E_FAIL;
  192. }
  193. //
  194. // Determine if we are a private user profile or not
  195. //
  196. hr = HrIsCMProfilePrivate(szInfFile);
  197. if (FAILED(hr))
  198. {
  199. CMASSERTMSG(FALSE, TEXT("UninstallProfile: HrIsCMProfilePrivate failed"));
  200. goto exit;
  201. }
  202. else if (S_OK == hr)
  203. {
  204. //
  205. // Then we have a Private Profile, send Remove_Private as the uninstall command
  206. //
  207. lstrcpy(szSectionName, TEXT("Remove_Private"));
  208. //
  209. // All Registry access should be to the HKCU key
  210. //
  211. hBaseKey = HKEY_CURRENT_USER;
  212. //
  213. // Set these just in case we are on NT5 and will need them to remove the
  214. // Desktop and Start Menu shortcuts.
  215. //
  216. nDesktopFolder = CSIDL_DESKTOPDIRECTORY;
  217. //
  218. // We use this to determine if we need a phonebook path or if NULL will work
  219. //
  220. bAllUserUninstall = FALSE;
  221. }
  222. else
  223. {
  224. //
  225. // Then we have an All User profile, so send Remove as the uninstall command
  226. //
  227. lstrcpy(szSectionName, TEXT("Remove"));
  228. //
  229. // All Registry settings will be under HKLM
  230. //
  231. hBaseKey = HKEY_LOCAL_MACHINE;
  232. //
  233. // Set these just in case we are on NT5 and will need them to remove the
  234. // Desktop shortcut.
  235. //
  236. nDesktopFolder = CSIDL_COMMON_DESKTOPDIRECTORY;
  237. //
  238. // We use this to determine if we need a phonebook path or if NULL will work
  239. //
  240. bAllUserUninstall = TRUE;
  241. }
  242. //
  243. // Get the Short Service Name from the INF and use it to build the UninstallDir in the
  244. // registry.
  245. //
  246. MYVERIFY(0 != GetPrivateProfileString(c_pszInfSectionStrings, c_pszShortSvcName,
  247. TEXT(""), szShortServiceName, MAX_PATH, szInfFile));
  248. if (0 != szShortServiceName[0])
  249. {
  250. MYVERIFY(CELEMS(szTemp) > (UINT)wsprintf(szTemp,
  251. TEXT("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\%s"),
  252. szShortServiceName));
  253. DWORD dwDisposition = 0;
  254. LONG lResult = RegCreateKeyEx(hBaseKey, szTemp, 0, NULL, REG_OPTION_NON_VOLATILE,
  255. KEY_READ | KEY_WRITE, NULL, &hKey, &dwDisposition);
  256. if (ERROR_SUCCESS == lResult)
  257. {
  258. const TCHAR* const c_pszUninstallDir = TEXT("UninstallDir");
  259. DWORD dwType = REG_SZ;
  260. DWORD dwSize;
  261. //
  262. // If this value doesn't exist then the inf will fail, so we need to create it from the
  263. // cmp path value. We used to write it on install, but since we were always expanding
  264. // it and rewriting it anyway (because of single user profiles), it is just easier
  265. // to ignore the existing value and create it from scratch.
  266. //
  267. HKEY hMappingsKey;
  268. lResult = RegOpenKeyEx(hBaseKey, c_pszRegCmMappings, 0, KEY_READ, &hMappingsKey);
  269. if (ERROR_SUCCESS == lResult)
  270. {
  271. dwSize = CELEMS(szCmpFile);
  272. lResult = RegQueryValueEx(hMappingsKey, szLongServiceName, NULL,
  273. NULL, (LPBYTE)szCmpFile, &dwSize);
  274. if (ERROR_SUCCESS == lResult)
  275. {
  276. //
  277. // Now build the UninstallDir key
  278. //
  279. BuildUninstallDirKey(szCmpFile, szInstallDir);
  280. }
  281. else
  282. {
  283. MYVERIFY(ERROR_SUCCESS == RegCloseKey(hMappingsKey));
  284. MYVERIFY(ERROR_SUCCESS == RegCloseKey(hKey));
  285. CMASSERTMSG(FALSE, TEXT("UninstallProfile: Unable to find the Profile Entry in Mappings!"));
  286. goto exit;
  287. }
  288. RegCloseKey(hMappingsKey);
  289. }
  290. else
  291. {
  292. MYVERIFY(ERROR_SUCCESS == RegCloseKey(hKey));
  293. CMASSERTMSG(FALSE, TEXT("UninstallProfile: Unable to open the Mappings key!"));
  294. goto exit;
  295. }
  296. //
  297. // We need to write the UninstallDir key to the registry now that we
  298. // have created it. We do this because the inf processing code in advpack
  299. // doesn't understand environment strings and single user strings contain
  300. // the %userprofile% variable, thus we were always rewriting it anyway.
  301. // Note we only write this on install of All User profiles and only because
  302. // we cannot update the bits on win98 OSR1 and on IEAK5 machines (with CMAK).
  303. // Otherwise we don't write this at setup time anymore.
  304. //
  305. dwSize = lstrlen(szInstallDir) + 1;
  306. if (ERROR_SUCCESS != RegSetValueEx(hKey, c_pszUninstallDir, NULL, dwType,
  307. (CONST BYTE *)szInstallDir, dwSize))
  308. {
  309. CMASSERTMSG(FALSE, TEXT("UninstallProfile: Unable to set the UninstallDir key!"));
  310. goto exit;
  311. }
  312. //
  313. // szInstallDir currently contains the directory above the profile directory, add
  314. // the shortservice name on the end
  315. //
  316. UINT uCount = (UINT)wsprintf(szProfileDir, TEXT("%s\\%s"), szInstallDir, szShortServiceName);
  317. MYDBGASSERT(uCount <= CELEMS(szProfileDir));
  318. MYVERIFY(ERROR_SUCCESS == RegCloseKey(hKey));
  319. }
  320. else
  321. {
  322. //
  323. // If this key doesn't exist then the inf will fail, so exit.
  324. //
  325. CMASSERTMSG(FALSE, TEXT("UninstallProfile: Unable to open profile uninstall key."));
  326. goto exit;
  327. }
  328. }
  329. else
  330. {
  331. //
  332. // We shouldn't have a problem getting the short service name from a profile.
  333. // something is definitely wrong here.
  334. //
  335. CMASSERTMSG(FALSE, TEXT("UninstallProfile: Unable to retrieve the ShortServiceName"));
  336. goto exit;
  337. }
  338. //
  339. // Let's check to see if we should delete the user's pre-shared key from
  340. // the SafeNet PSK store. If so, we stored a reminder flag in the CMP file
  341. // at install time.
  342. //
  343. if (plat.IsWin9x() || plat.IsNT4())
  344. {
  345. DeleteSafeNetPskOnUninstall(szCmpFile);
  346. }
  347. //
  348. // Create the path to the cms file
  349. //
  350. MYVERIFY(CELEMS(szCmsFile) > (UINT)wsprintf(szCmsFile, TEXT("%s\\%s.cms"), szProfileDir,
  351. szShortServiceName));
  352. //
  353. // Remove the phonebook entry
  354. //
  355. if (TEXT('\0') != szLongServiceName[0])
  356. {
  357. if (plat.IsAtLeastNT5())
  358. {
  359. //
  360. // On NT5 we want to delete the hidden phonebook entry for
  361. // double dial profiles.
  362. //
  363. if (GetHiddenPhoneBookPath(szInstallDir , &pszPhonebook))
  364. {
  365. MYVERIFY(FALSE != RemovePhonebookEntry(szLongServiceName, pszPhonebook, !(plat.IsAtLeastNT5())));
  366. CmFree(pszPhonebook);
  367. }
  368. if (bAllUserUninstall)
  369. {
  370. //
  371. // On NT5 legacy profiles could be installed anywhere and the
  372. // install dir may not reflect the actual pbk path. Thus if it
  373. // is an all user install we want to force the directory to
  374. // the Cm All Users dir so we get the correct phonebook.
  375. //
  376. MYVERIFY(FALSE != GetAllUsersCmDir(szInstallDir, hInstance));
  377. }
  378. }
  379. if (GetPhoneBookPath(szInstallDir, &pszPhonebook, bAllUserUninstall))
  380. {
  381. //
  382. // Note that usually on NT5 we are called by RasCustomDeleteEntryNotify (in cmdial32.dll)
  383. // throw RasDeleteEntry. Thus we don't really need to delete the entry. However, it is
  384. // possible that someone would call cmstp.exe /u directly and not through the RAS API, thus
  385. // we want to delete the connectoid in that case. Since we are called after RAS has already
  386. // deleted the entry it shouldn't be a problem.
  387. // Note that on NT5 we only remove the exact entry to fix NTRAID 349749
  388. // otherwise we could end up deleting similarly named connectoids
  389. // and lose the users only interface to CM.
  390. //
  391. MYVERIFY(FALSE != RemovePhonebookEntry(szLongServiceName, pszPhonebook, !(plat.IsAtLeastNT5())));
  392. }
  393. CmFree(pszPhonebook);
  394. }
  395. //
  396. // Launch the uninstall INF
  397. //
  398. iCmsVersion = GetPrivateProfileInt(c_pszCmSectionProfileFormat, c_pszVersion,
  399. 0, szCmsFile);
  400. if (1 >= iCmsVersion)
  401. {
  402. //
  403. // Then we have an old 1.0 profile and we should remove the showicon.exe
  404. // postsetup command.
  405. //
  406. RemoveShowIconFromRunPostSetupCommands(szInfFile);
  407. }
  408. bReturn = SUCCEEDED(LaunchInfSection(szInfFile, szSectionName, szTitle, FALSE)); // bQuiet = FALSE
  409. //
  410. // On NT5 we need to delete the desktop shortcut
  411. //
  412. if (plat.IsAtLeastNT5())
  413. {
  414. DeleteNT5ShortcutFromPathAndName(hInstance, szLongServiceName, nDesktopFolder);
  415. }
  416. //
  417. // Finally delete the profile directory. (Not deleted because the inf file resides there.
  418. // The dir can't be removed because the inf file is still in it and in use, unless this
  419. // is a legacy profile). Not that we could cause cmstp to cause an Access Violation
  420. // if we ask it to delete an empty string.
  421. //
  422. if ((TEXT('\0') != szProfileDir[0]) && SetFileAttributes(szProfileDir, FILE_ATTRIBUTE_NORMAL))
  423. {
  424. SHFILEOPSTRUCT fOpStruct;
  425. ZeroMemory(&fOpStruct, sizeof(fOpStruct));
  426. fOpStruct.wFunc = FO_DELETE;
  427. fOpStruct.pFrom = szProfileDir;
  428. fOpStruct.fFlags = FOF_SILENT | FOF_NOCONFIRMATION;
  429. MYVERIFY(ERROR_SUCCESS == SHFileOperation(&fOpStruct));
  430. }
  431. //
  432. // We need to try to delete the following regkeys:
  433. // HKCU\\Software\\Microsoft\\Connection Manager\\<UserInfo/SingleUserInfo>
  434. // HKCU\\Software\\Microsoft\\Connection Manager\\Mappings
  435. // HKLM\\Software\\Microsoft\\Connection Manager\\Mappings
  436. // HKCU\\Software\\Microsoft\\Connection Manager
  437. // HKLM\\Software\\Microsoft\\Connection Manager
  438. //
  439. //
  440. // Registry Cleanup. We want to delete the UserInfo keys if they are empty.
  441. // We then want to delete the mappings keys if they don't contain any more
  442. // values. We also want to delete the CM registry keys if they don't contain
  443. // any subkeys. Also kill the GUID Mappings key (this was beta only but still
  444. // should be deleted). The problem here is that win95 infs delete keys recursively,
  445. // even if they have subkeys. Thus we must use code to safely delete these keys.
  446. // Please note that this does mean we could be unnecessarily deleting the Components
  447. // Checked value in HKLM\\...\\Connection Manager but this can't be helped. I
  448. // would rather take the small startup perf hit than leave the users registry
  449. // dirty.
  450. //
  451. if (bAllUserUninstall)
  452. {
  453. wsprintf(szTemp, TEXT("%s%s"), c_pszRegCmUserInfo, szLongServiceName);
  454. }
  455. else
  456. {
  457. wsprintf(szTemp, TEXT("%s%s"), c_pszRegCmSingleUserInfo, szLongServiceName);
  458. }
  459. //
  460. // Delete the User Data, note that we have to do this programatically because
  461. // of 1.0 profiles that don't know to delete their User Data on uninstall (no commands
  462. // in the 1.0 inf to do so). Note that we don't want to cleanup user data if this
  463. // is a same name upgrade uninstall (uninstall the 1.0 profile before installing the
  464. // new profile).
  465. //
  466. if (bCleanUpCreds)
  467. {
  468. CmDeleteRegKeyWithoutSubKeys(HKEY_CURRENT_USER, szTemp, TRUE);
  469. CmDeleteRegKeyWithoutSubKeys(HKEY_CURRENT_USER, c_pszRegCmUserInfo, TRUE);
  470. CmDeleteRegKeyWithoutSubKeys(HKEY_CURRENT_USER, c_pszRegCmSingleUserInfo, TRUE);
  471. }
  472. CmDeleteRegKeyWithoutSubKeys(HKEY_LOCAL_MACHINE, c_pszRegCmMappings, FALSE);
  473. CmDeleteRegKeyWithoutSubKeys(HKEY_CURRENT_USER, c_pszRegCmMappings, FALSE);
  474. HrRegDeleteKeyTree(HKEY_LOCAL_MACHINE, c_pszRegGuidMappings);
  475. CmDeleteRegKeyWithoutSubKeys(HKEY_LOCAL_MACHINE, c_pszRegCmRoot, TRUE);
  476. CmDeleteRegKeyWithoutSubKeys(HKEY_CURRENT_USER, c_pszRegCmRoot, TRUE);
  477. //
  478. // Refresh the desktop so Desktop GUIDS disappear
  479. //
  480. RefreshDesktop();
  481. exit:
  482. return (bReturn ? S_OK : E_FAIL);
  483. }