Shell.cs 29 KB

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