Shell.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772
  1. using SmartCoalApplication.Base;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.ComponentModel;
  5. using System.Diagnostics;
  6. using System.Drawing;
  7. using System.Globalization;
  8. using System.IO;
  9. using System.Linq;
  10. using System.Runtime.InteropServices;
  11. using System.Security;
  12. using System.Text;
  13. using System.Threading;
  14. using System.Threading.Tasks;
  15. using System.Windows.Forms;
  16. namespace SmartCoalApplication.SystemLayer
  17. {
  18. public static class Shell
  19. {
  20. public static bool ReplaceMissingFiles(string[] missingFiles)
  21. {
  22. StringBuilder missingFilesSB = new StringBuilder();
  23. for (int i = 0; i < missingFiles.Length; ++i)
  24. {
  25. missingFilesSB.Append(missingFiles[i]);
  26. if (i != missingFiles.Length - 1)
  27. {
  28. missingFilesSB.Append(", ");
  29. }
  30. }
  31. try
  32. {
  33. // If they are not an admin and have no possibility of elevating, such as for a standard User
  34. // in XP, then give them an error. Unfortunately we do not know if we can even load text
  35. // resources at this point, and so must provide an English-only error message.
  36. if (!Security.IsAdministrator && !Security.CanElevateToAdministrator)
  37. {
  38. MessageBox.Show(
  39. null,
  40. "Metis Vision has detected that some important installation files are missing. Repairing " +
  41. "this requires administrator privilege. Please run the 'PdnRepair.exe' program in the installation " +
  42. "directory after logging in with a user that has administrator privilege." + Environment.NewLine +
  43. Environment.NewLine +
  44. "The missing files are: " + missingFilesSB.ToString(),
  45. "Metis Vision",
  46. MessageBoxButtons.OK,
  47. MessageBoxIcon.Error);
  48. return false;
  49. }
  50. const int hMargin = 8;
  51. const int vMargin = 8;
  52. Form form = new Form();
  53. form.Text = "Metis Vision";
  54. form.ClientSize = new Size(400, 10);
  55. form.StartPosition = FormStartPosition.CenterScreen;
  56. Label infoLabel = new Label();
  57. form.Controls.Add(infoLabel);
  58. infoLabel.Text =
  59. "Metis Vision has detected that some important installation files are missing. If you click " +
  60. "the Repair button it will attempt to repair this and then continue loading." + Environment.NewLine +
  61. Environment.NewLine +
  62. "The missing files are: " + missingFilesSB.ToString();
  63. #if DEBUG
  64. infoLabel.Text += Environment.NewLine + Environment.NewLine +
  65. "*** Since this is a DEBUG build, you should probably add /skipRepairAttempt to the command-line.";
  66. #endif
  67. infoLabel.Location = new Point(hMargin, vMargin);
  68. infoLabel.Width = form.ClientSize.Width - hMargin * 2;
  69. infoLabel.Height = infoLabel.GetPreferredSize(new Size(infoLabel.Width, 1)).Height;
  70. Button repairButton = new Button();
  71. form.Controls.Add(repairButton);
  72. repairButton.Text = "&Repair";
  73. Exception exception = null;
  74. repairButton.Click +=
  75. delegate (object sender, EventArgs e)
  76. {
  77. form.DialogResult = DialogResult.Yes;
  78. repairButton.Enabled = false;
  79. try
  80. {
  81. Shell.Execute(form, "PdnRepair.exe", "/noPause", ExecutePrivilege.AsInvokerOrAsManifest, ExecuteWaitType.WaitForExit);
  82. }
  83. catch (Exception ex)
  84. {
  85. exception = ex;
  86. }
  87. };
  88. repairButton.AutoSize = true;
  89. repairButton.PerformLayout();
  90. repairButton.Width += 20;
  91. repairButton.Location = new Point((form.ClientSize.Width - repairButton.Width) / 2, infoLabel.Bottom + vMargin * 2);
  92. repairButton.FlatStyle = FlatStyle.System;
  93. UI.EnableShield(repairButton, true);
  94. form.FormBorderStyle = FormBorderStyle.FixedDialog;
  95. form.MinimizeBox = false;
  96. form.MaximizeBox = false;
  97. form.ShowInTaskbar = true;
  98. form.Icon = null;
  99. form.ClientSize = new Size(form.ClientRectangle.Width, repairButton.Bottom + vMargin);
  100. DialogResult result = form.ShowDialog(null);
  101. form.Dispose();
  102. form = null;
  103. if (result == DialogResult.Yes)
  104. {
  105. return true;
  106. }
  107. else if (exception == null)
  108. {
  109. return false;
  110. }
  111. else
  112. {
  113. throw new Exception("", exception); // ("Error while attempting to repair", exception);
  114. }
  115. }
  116. catch (Exception ex)
  117. {
  118. throw new Exception("", ex); //
  119. //("Could not repair installation after it was determined that the following files are missing: " +
  120. //missingFilesSB.ToString(), ex);
  121. }
  122. }
  123. /// <summary>
  124. /// Opens the requested directory in the shell's file/folder browser.
  125. /// </summary>
  126. /// <param name="parent">The window that is currently in the foreground.</param>
  127. /// <param name="folderPath">The folder to open.</param>
  128. /// <remarks>
  129. /// This UI is presented modelessly, in another process, and in the foreground.
  130. /// Error handling and messaging (error dialogs) will be handled by the shell,
  131. /// and these errors will not be communicated to the caller of this method.
  132. /// </remarks>
  133. public static void BrowseFolder(IWin32Window parent, string folderPath)
  134. {
  135. NativeStructs.SHELLEXECUTEINFO sei = new NativeStructs.SHELLEXECUTEINFO();
  136. sei.cbSize = (uint)Marshal.SizeOf(typeof(NativeStructs.SHELLEXECUTEINFO));
  137. sei.fMask = NativeConstants.SEE_MASK_NO_CONSOLE;
  138. sei.lpVerb = "open";
  139. sei.lpFile = folderPath;
  140. sei.nShow = NativeConstants.SW_SHOWNORMAL;
  141. sei.hwnd = parent.Handle;
  142. bool bResult = NativeMethods.ShellExecuteExW(ref sei);
  143. if (bResult)
  144. {
  145. if (sei.hProcess != IntPtr.Zero)
  146. {
  147. SafeNativeMethods.CloseHandle(sei.hProcess);
  148. sei.hProcess = IntPtr.Zero;
  149. }
  150. }
  151. else
  152. {
  153. NativeMethods.ThrowOnWin32Error("ShellExecuteW returned FALSE");
  154. }
  155. GC.KeepAlive(parent);
  156. }
  157. #if false
  158. [Obsolete("Do not use this method.", true)]
  159. public static void Execute(
  160. IWin32Window parent,
  161. string exePath,
  162. string args,
  163. bool requireAdmin)
  164. {
  165. Execute(parent, exePath, args, requireAdmin ? ExecutePrivilege.RequireAdmin : ExecutePrivilege.AsInvokerOrAsManifest, ExecuteWaitType.ReturnImmediately);
  166. }
  167. #endif
  168. private const string updateExeFileName = "UpdateMonitor.exe";
  169. private delegate int ExecuteHandOff(IWin32Window parent, string exePath, string args, out IntPtr hProcess);
  170. /// <summary>
  171. /// Uses the shell to execute the command.
  172. /// and not by plugins.
  173. /// </summary>
  174. /// <param name="parent">
  175. /// The window that is currently in the foreground. This may be null if requireAdmin
  176. /// is false and the executable that exePath refers to is not marked (e.g. via a
  177. /// manifest) as requiring administrator privilege.
  178. /// </param>
  179. /// <param name="exePath">
  180. /// The path to the executable to launch.
  181. /// </param>
  182. /// <param name="args">
  183. /// The command-line arguments for the executable.
  184. /// </param>
  185. /// <param name="execPrivilege">
  186. /// The privileges to execute the new process with.
  187. /// If the executable is already marked as requiring administrator privilege
  188. /// (e.g. via a "requiresAdministrator" UAC manifest), this parameter should be
  189. /// set to AsInvokerOrAsManifest.
  190. /// </param>
  191. /// <remarks>
  192. /// If administrator privilege is required, a consent UI may be displayed asking the
  193. /// user to approve the action. A parent window must be provided in this case so that
  194. /// the consent UI will know where to position itself. Administrator privilege is
  195. /// required if execPrivilege is set to RequireAdmin, or if the executable being launched
  196. /// has a manifest declaring that it requires this privilege and if the operating
  197. /// system recognizes the manifest.
  198. /// </remarks>
  199. /// <exception cref="ArgumentException">
  200. /// execPrivilege was RequireAdmin, but parent was null.
  201. /// </exception>
  202. /// <exception cref="SecurityException">
  203. /// execPrivilege was RequireAdmin, but the user does not have this privilege, nor do they
  204. /// have the ability to acquire or elevate to obtain this privilege.
  205. /// </exception>
  206. /// <exception cref="Win32Exception">
  207. /// There was an error launching the program.
  208. /// </exception>
  209. public static void Execute(
  210. IWin32Window parent,
  211. string exePath,
  212. string args,
  213. ExecutePrivilege execPrivilege,
  214. ExecuteWaitType execWaitType)
  215. {
  216. if (exePath == null)
  217. {
  218. throw new ArgumentNullException("exePath");
  219. }
  220. if (execPrivilege == ExecutePrivilege.RequireAdmin && parent == null)
  221. {
  222. throw new ArgumentException("If requireAdmin is true, a parent window must be provided");
  223. }
  224. // If this action requires admin privilege, but the user does not have this
  225. // privilege and is not capable of acquiring this privilege, then we will
  226. // throw an exception.
  227. if (execPrivilege == ExecutePrivilege.RequireAdmin &&
  228. !Security.IsAdministrator &&
  229. !Security.CanElevateToAdministrator)
  230. {
  231. throw new SecurityException("Executable requires administrator privilege, but user is not an administrator and cannot elevate");
  232. }
  233. ExecuteHandOff executeHandOff = null;
  234. switch (execPrivilege)
  235. {
  236. case ExecutePrivilege.AsInvokerOrAsManifest:
  237. executeHandOff = new ExecuteHandOff(ExecAsInvokerOrAsManifest);
  238. break;
  239. case ExecutePrivilege.RequireAdmin:
  240. executeHandOff = new ExecuteHandOff(ExecRequireAdmin);
  241. break;
  242. case ExecutePrivilege.RequireNonAdminIfPossible:
  243. if (Security.CanLaunchNonAdminProcess)
  244. {
  245. executeHandOff = new ExecuteHandOff(ExecRequireNonAdmin);
  246. }
  247. else
  248. {
  249. executeHandOff = new ExecuteHandOff(ExecAsInvokerOrAsManifest);
  250. }
  251. break;
  252. default:
  253. throw new InvalidEnumArgumentException("ExecutePrivilege");
  254. }
  255. string updateMonitorExePath = null;
  256. if (execWaitType == ExecuteWaitType.RelaunchPdnOnExit)
  257. {
  258. RelaunchPdnHelperPart1(out updateMonitorExePath);
  259. }
  260. IntPtr hProcess = IntPtr.Zero;
  261. int nResult = executeHandOff(parent, exePath, args, out hProcess);
  262. if (nResult == NativeConstants.ERROR_SUCCESS)
  263. {
  264. if (execWaitType == ExecuteWaitType.WaitForExit)
  265. {
  266. SafeNativeMethods.WaitForSingleObject(hProcess, NativeConstants.INFINITE);
  267. }
  268. else if (execWaitType == ExecuteWaitType.RelaunchPdnOnExit)
  269. {
  270. bool bResult2 = SafeNativeMethods.SetHandleInformation(
  271. hProcess,
  272. NativeConstants.HANDLE_FLAG_INHERIT,
  273. NativeConstants.HANDLE_FLAG_INHERIT);
  274. RelaunchPdnHelperPart2(updateMonitorExePath, hProcess);
  275. // Ensure that we don't close the process handle right away in the next few lines of code.
  276. // It must be inherited by the child process. Yes, this is technically a leak but we are
  277. // planning to terminate in just a moment anyway.
  278. hProcess = IntPtr.Zero;
  279. }
  280. else if (execWaitType == ExecuteWaitType.ReturnImmediately)
  281. {
  282. }
  283. if (hProcess != IntPtr.Zero)
  284. {
  285. SafeNativeMethods.CloseHandle(hProcess);
  286. hProcess = IntPtr.Zero;
  287. }
  288. }
  289. else
  290. {
  291. if (nResult == NativeConstants.ERROR_CANCELLED ||
  292. nResult == NativeConstants.ERROR_TIMEOUT)
  293. {
  294. // no problem
  295. }
  296. else
  297. {
  298. NativeMethods.ThrowOnWin32Error("ExecuteHandoff failed", nResult);
  299. }
  300. if (updateMonitorExePath != null)
  301. {
  302. try
  303. {
  304. File.Delete(updateMonitorExePath);
  305. }
  306. catch (Exception)
  307. {
  308. }
  309. updateMonitorExePath = null;
  310. }
  311. }
  312. GC.KeepAlive(parent);
  313. }
  314. private static int ExecAsInvokerOrAsManifest(IWin32Window parent, string exePath, string args, out IntPtr hProcess)
  315. {
  316. return ExecShellExecuteEx(parent, exePath, args, null, out hProcess);
  317. }
  318. private static int ExecRequireAdmin(IWin32Window parent, string exePath, string args, out IntPtr hProcess)
  319. {
  320. const string runAs = "runas";
  321. string verb;
  322. if (Security.IsAdministrator)
  323. {
  324. verb = null;
  325. }
  326. else
  327. {
  328. verb = runAs;
  329. }
  330. return ExecShellExecuteEx(parent, exePath, args, verb, out hProcess);
  331. }
  332. private static int ExecRequireNonAdmin(IWin32Window parent, string exePath, string args, out IntPtr hProcess)
  333. {
  334. int nError = NativeConstants.ERROR_SUCCESS;
  335. string commandLine = "\"" + exePath + "\"" + (args == null ? "" : (" " + args));
  336. string dir;
  337. try
  338. {
  339. dir = Path.GetDirectoryName(exePath);
  340. }
  341. catch (Exception)
  342. {
  343. dir = null;
  344. }
  345. IntPtr hWndShell = IntPtr.Zero;
  346. IntPtr hShellProcess = IntPtr.Zero;
  347. IntPtr hShellProcessToken = IntPtr.Zero;
  348. IntPtr hTokenCopy = IntPtr.Zero;
  349. IntPtr bstrExePath = IntPtr.Zero;
  350. IntPtr bstrCommandLine = IntPtr.Zero;
  351. IntPtr bstrDir = IntPtr.Zero;
  352. NativeStructs.PROCESS_INFORMATION procInfo = new NativeStructs.PROCESS_INFORMATION();
  353. try
  354. {
  355. hWndShell = SafeNativeMethods.FindWindowW("Progman", null);
  356. if (hWndShell == IntPtr.Zero)
  357. {
  358. NativeMethods.ThrowOnWin32Error("FindWindowW() returned NULL");
  359. }
  360. uint dwPID;
  361. uint dwThreadId = SafeNativeMethods.GetWindowThreadProcessId(hWndShell, out dwPID);
  362. if (0 == dwPID)
  363. {
  364. NativeMethods.ThrowOnWin32Error("GetWindowThreadProcessId returned 0", NativeErrors.ERROR_FILE_NOT_FOUND);
  365. }
  366. hShellProcess = NativeMethods.OpenProcess(NativeConstants.PROCESS_QUERY_INFORMATION, false, dwPID);
  367. if (IntPtr.Zero == hShellProcess)
  368. {
  369. NativeMethods.ThrowOnWin32Error("OpenProcess() returned NULL");
  370. }
  371. bool optResult = NativeMethods.OpenProcessToken(
  372. hShellProcess,
  373. NativeConstants.TOKEN_ASSIGN_PRIMARY | NativeConstants.TOKEN_DUPLICATE | NativeConstants.TOKEN_QUERY,
  374. out hShellProcessToken);
  375. if (!optResult)
  376. {
  377. NativeMethods.ThrowOnWin32Error("OpenProcessToken() returned FALSE");
  378. }
  379. bool dteResult = NativeMethods.DuplicateTokenEx(
  380. hShellProcessToken,
  381. NativeConstants.MAXIMUM_ALLOWED,
  382. IntPtr.Zero,
  383. NativeConstants.SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
  384. NativeConstants.TOKEN_TYPE.TokenPrimary,
  385. out hTokenCopy);
  386. if (!dteResult)
  387. {
  388. NativeMethods.ThrowOnWin32Error("DuplicateTokenEx() returned FALSE");
  389. }
  390. bstrExePath = Marshal.StringToBSTR(exePath);
  391. bstrCommandLine = Marshal.StringToBSTR(commandLine);
  392. bstrDir = Marshal.StringToBSTR(dir);
  393. bool cpwtResult = NativeMethods.CreateProcessWithTokenW(
  394. hTokenCopy,
  395. 0,
  396. bstrExePath,
  397. bstrCommandLine,
  398. 0,
  399. IntPtr.Zero,
  400. bstrDir,
  401. IntPtr.Zero,
  402. out procInfo);
  403. if (cpwtResult)
  404. {
  405. hProcess = procInfo.hProcess;
  406. procInfo.hProcess = IntPtr.Zero;
  407. nError = NativeConstants.ERROR_SUCCESS;
  408. }
  409. else
  410. {
  411. hProcess = IntPtr.Zero;
  412. nError = Marshal.GetLastWin32Error();
  413. }
  414. }
  415. catch (Win32Exception ex)
  416. {
  417. Tracing.Ping(ex.ToString());
  418. nError = ex.ErrorCode;
  419. hProcess = IntPtr.Zero;
  420. }
  421. finally
  422. {
  423. if (bstrExePath != IntPtr.Zero)
  424. {
  425. Marshal.FreeBSTR(bstrExePath);
  426. bstrExePath = IntPtr.Zero;
  427. }
  428. if (bstrCommandLine != IntPtr.Zero)
  429. {
  430. Marshal.FreeBSTR(bstrCommandLine);
  431. bstrCommandLine = IntPtr.Zero;
  432. }
  433. if (bstrDir != IntPtr.Zero)
  434. {
  435. Marshal.FreeBSTR(bstrDir);
  436. bstrDir = IntPtr.Zero;
  437. }
  438. if (hShellProcess != IntPtr.Zero)
  439. {
  440. SafeNativeMethods.CloseHandle(hShellProcess);
  441. hShellProcess = IntPtr.Zero;
  442. }
  443. if (hShellProcessToken != IntPtr.Zero)
  444. {
  445. SafeNativeMethods.CloseHandle(hShellProcessToken);
  446. hShellProcessToken = IntPtr.Zero;
  447. }
  448. if (hTokenCopy != IntPtr.Zero)
  449. {
  450. SafeNativeMethods.CloseHandle(hTokenCopy);
  451. hTokenCopy = IntPtr.Zero;
  452. }
  453. if (procInfo.hThread != IntPtr.Zero)
  454. {
  455. SafeNativeMethods.CloseHandle(procInfo.hThread);
  456. procInfo.hThread = IntPtr.Zero;
  457. }
  458. if (procInfo.hProcess != IntPtr.Zero)
  459. {
  460. SafeNativeMethods.CloseHandle(procInfo.hProcess);
  461. procInfo.hProcess = IntPtr.Zero;
  462. }
  463. }
  464. return nError;
  465. }
  466. private static int ExecShellExecuteEx(IWin32Window parent, string exePath, string args, string verb, out IntPtr hProcess)
  467. {
  468. string dir;
  469. try
  470. {
  471. dir = Path.GetDirectoryName(exePath);
  472. }
  473. catch (Exception)
  474. {
  475. dir = null;
  476. }
  477. NativeStructs.SHELLEXECUTEINFO sei = new NativeStructs.SHELLEXECUTEINFO();
  478. sei.cbSize = (uint)Marshal.SizeOf(typeof(NativeStructs.SHELLEXECUTEINFO));
  479. sei.fMask =
  480. NativeConstants.SEE_MASK_NOCLOSEPROCESS |
  481. NativeConstants.SEE_MASK_NO_CONSOLE |
  482. NativeConstants.SEE_MASK_FLAG_DDEWAIT;
  483. sei.lpVerb = verb;
  484. sei.lpDirectory = dir;
  485. sei.lpFile = exePath;
  486. sei.lpParameters = args;
  487. sei.nShow = NativeConstants.SW_SHOWNORMAL;
  488. if (parent != null)
  489. {
  490. sei.hwnd = parent.Handle;
  491. }
  492. bool bResult = NativeMethods.ShellExecuteExW(ref sei);
  493. hProcess = sei.hProcess;
  494. sei.hProcess = IntPtr.Zero;
  495. int nResult = NativeConstants.ERROR_SUCCESS;
  496. if (!bResult)
  497. {
  498. nResult = Marshal.GetLastWin32Error();
  499. }
  500. return nResult;
  501. }
  502. private static void RelaunchPdnHelperPart1(out string updateMonitorExePath)
  503. {
  504. string srcDir = Application.StartupPath;
  505. string srcPath = Path.Combine(srcDir, updateExeFileName);
  506. string srcPath2 = srcPath + ".config";
  507. string dstDir = Environment.ExpandEnvironmentVariables(@"%TEMP%\PdnSetup");
  508. string dstPath = Path.Combine(dstDir, updateExeFileName);
  509. string dstPath2 = dstPath + ".config";
  510. if (!Directory.Exists(dstDir))
  511. {
  512. Directory.CreateDirectory(dstDir);
  513. }
  514. File.Copy(srcPath, dstPath, true);
  515. File.Copy(srcPath2, dstPath2, true);
  516. updateMonitorExePath = dstPath;
  517. }
  518. private static void RelaunchPdnHelperPart2(string updateMonitorExePath, IntPtr hProcess)
  519. {
  520. string args = hProcess.ToInt64().ToString(CultureInfo.InstalledUICulture);
  521. ProcessStartInfo psi = new ProcessStartInfo(updateMonitorExePath, args);
  522. psi.UseShellExecute = false;
  523. psi.WindowStyle = ProcessWindowStyle.Hidden;
  524. Process process = Process.Start(psi);
  525. process.Dispose();
  526. }
  527. /// <summary>
  528. /// 用默认浏览器打开Url
  529. /// </summary>
  530. /// <param name="url">url地址,最长长度512</param>
  531. public static bool LaunchUrl(IWin32Window owner, string url)
  532. {
  533. if (url.Length > 512)
  534. {
  535. throw new ArgumentOutOfRangeException("url.Length must be <= 512");
  536. }
  537. bool success = false;
  538. string quotedUrl = "\"" + url + "\"";
  539. ExecutePrivilege executePrivilege;
  540. if (!Security.IsAdministrator || (Security.IsAdministrator && !Security.CanLaunchNonAdminProcess))
  541. {
  542. executePrivilege = ExecutePrivilege.AsInvokerOrAsManifest;
  543. }
  544. else
  545. {
  546. executePrivilege = ExecutePrivilege.RequireNonAdminIfPossible;
  547. }
  548. // Method 1. Just launch the url, and hope that the shell figures out the association correctly.
  549. // This method will not work with ExecutePrivilege.RequireNonAdmin though.
  550. if (!success && executePrivilege != ExecutePrivilege.RequireNonAdminIfPossible)
  551. {
  552. try
  553. {
  554. Execute(owner, quotedUrl, null, executePrivilege, ExecuteWaitType.ReturnImmediately);
  555. success = true;
  556. }
  557. catch (Exception ex)
  558. {
  559. Tracing.Ping("Exception while using method 1 to launch url, " + quotedUrl + ", :" + ex.ToString());
  560. success = false;
  561. }
  562. }
  563. // Method 2. Launch the url through explorer
  564. if (!success)
  565. {
  566. const string shellFileLoc = @"%WINDIR%\explorer.exe";
  567. string shellExePath = "(n/a)";
  568. try
  569. {
  570. shellExePath = Environment.ExpandEnvironmentVariables(shellFileLoc);
  571. Execute(owner, shellExePath, quotedUrl, executePrivilege, ExecuteWaitType.ReturnImmediately);
  572. success = true;
  573. }
  574. catch (Exception ex)
  575. {
  576. Tracing.Ping("Exception while using method 2 to launch url through '" + shellExePath + "', " + quotedUrl + ", : " + ex.ToString());
  577. success = false;
  578. }
  579. }
  580. return success;
  581. }
  582. public static void AddToRecentDocumentsList(string fileName)
  583. {
  584. // Apparently SHAddToRecentDocs can block for a very long period of time when certain
  585. // conditions are met: so we just stick it on "the backburner."
  586. ThreadPool.QueueUserWorkItem(new WaitCallback(AddToRecentDocumentsListImpl), fileName);
  587. }
  588. private static void AddToRecentDocumentsListImpl(object fileNameObj)
  589. {
  590. string fileName = (string)fileNameObj;
  591. IntPtr bstrFileName = IntPtr.Zero;
  592. try
  593. {
  594. bstrFileName = Marshal.StringToBSTR(fileName);
  595. NativeMethods.SHAddToRecentDocs(NativeConstants.SHARD_PATHW, bstrFileName);
  596. }
  597. finally
  598. {
  599. if (bstrFileName != IntPtr.Zero)
  600. {
  601. Marshal.FreeBSTR(bstrFileName);
  602. bstrFileName = IntPtr.Zero;
  603. }
  604. }
  605. }
  606. private static T2 Map<T1, T2>(T1 mapFrom, Pair<T1, T2>[] mappings)
  607. {
  608. foreach (Pair<T1, T2> mapping in mappings)
  609. {
  610. if (mapping.First.Equals(mapFrom))
  611. {
  612. return mapping.Second;
  613. }
  614. }
  615. throw new KeyNotFoundException();
  616. }
  617. private static string GetCSIDLPath(int csidl, bool tryCreateIfAbsent)
  618. {
  619. // First, try calling SHGetFolderPathW with the "CSIDL_FLAG_CREATE" flag. However, if it
  620. // returns an error then ignore it. We've had some crash logs with "access denied" coming
  621. // from this function.
  622. int csidlWithFlags = csidl | (tryCreateIfAbsent ? NativeConstants.CSIDL_FLAG_CREATE : 0);
  623. StringBuilder sbWithFlags = new StringBuilder(NativeConstants.MAX_PATH);
  624. Do.TryBool(() => NativeMethods.SHGetFolderPathW(IntPtr.Zero, csidlWithFlags, IntPtr.Zero, NativeConstants.SHGFP_TYPE_CURRENT, sbWithFlags));
  625. StringBuilder sb = new StringBuilder(NativeConstants.MAX_PATH);
  626. NativeMethods.SHGetFolderPathW(IntPtr.Zero, csidl, IntPtr.Zero, NativeConstants.SHGFP_TYPE_CURRENT, sb);
  627. // If we get back something like 'Z:' then we need to put a backslash on it.
  628. // Otherwise other path-related functions will freak out.
  629. if (sb.Length == 2 && sb[1] == ':')
  630. {
  631. sb.Append(Path.DirectorySeparatorChar);
  632. }
  633. string path = sb.ToString();
  634. return path;
  635. }
  636. private static readonly Pair<VirtualFolderName, int>[] pathMappings = new Pair<VirtualFolderName, int>[]
  637. {
  638. Pair.Create(VirtualFolderName.SystemProgramFiles, NativeConstants.CSIDL_PROGRAM_FILES),
  639. Pair.Create(VirtualFolderName.UserDesktop, NativeConstants.CSIDL_DESKTOP_DIRECTORY),
  640. Pair.Create(VirtualFolderName.UserDocuments, NativeConstants.CSIDL_PERSONAL),
  641. Pair.Create(VirtualFolderName.UserLocalAppData, NativeConstants.CSIDL_LOCAL_APPDATA),
  642. Pair.Create(VirtualFolderName.UserPictures, NativeConstants.CSIDL_MYPICTURES),
  643. Pair.Create(VirtualFolderName.UserRoamingAppData, NativeConstants.CSIDL_APPDATA)
  644. };
  645. public static string GetVirtualPath(VirtualFolderName folderName, bool tryCreateIfAbsent)
  646. {
  647. try
  648. {
  649. int csidl = Map(folderName, pathMappings);
  650. string path = GetCSIDLPath(csidl, tryCreateIfAbsent);
  651. return path;
  652. }
  653. catch (KeyNotFoundException)
  654. {
  655. throw new InvalidEnumArgumentException("folderName", (int)folderName, typeof(VirtualFolderName));
  656. }
  657. }
  658. }
  659. }