PageRenderTime 49ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/src/SystemLayer/Shell.cs

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