/src/SystemLayer/Shell.cs
C# | 826 lines | 592 code | 116 blank | 118 comment | 79 complexity | 0563aff40d9334dfbc1f1d06f5002dbc MD5 | raw file
- /////////////////////////////////////////////////////////////////////////////////
- // Paint.NET //
- // Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
- // Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
- // See src/Resources/Files/License.txt for full licensing and attribution //
- // details. //
- // . //
- /////////////////////////////////////////////////////////////////////////////////
-
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Diagnostics;
- using System.Drawing;
- using System.Globalization;
- using System.IO;
- using System.Linq;
- using System.Runtime.InteropServices;
- using System.Security;
- using System.Text;
- using System.Threading;
- using System.Windows.Forms;
- using PaintDotNet.Base;
-
- namespace PaintDotNet.SystemLayer
- {
- public static class Shell
- {
- /// <summary>
- /// Repairs the installation of Paint.NET by replacing any files that have gone missing.
- /// This method should only be called after it has been determined that the files are missing,
- /// and not as a way to determine which files are missing.
- /// This is used, for instance, if the resource files, such as PaintDotNet.Strings.3.resources,
- /// cannot be found. This is actually a top support issue, and by automatically repairing
- /// this problem we save a lot of people a lot of trouble.
- /// </summary>
- /// <param name="missingFiles">
- /// Friendly names for the files that are missing. These will not be used as part of the
- /// repair process but rather as part of any UI presented to the user, or in an exception that
- /// will be thrown in the case of an error.
- /// </param>
- /// <returns>
- /// true if everything was successful, false if the user cancelled or does not have administrator
- /// privilege (and cannot elevate). An exception is thrown for errors.
- /// </returns>
- /// <remarks>
- /// Note to implementors: This may be implemented as a no-op. Just return true in this case.
- /// </remarks>
- public static bool ReplaceMissingFiles(string[] missingFiles)
- {
- // Generate a friendly, comma separated list of the missing file names
- var missingFilesSB = new StringBuilder();
-
- for (int i = 0; i < missingFiles.Length; ++i)
- {
- missingFilesSB.Append(missingFiles[i]);
-
- if (i != missingFiles.Length - 1)
- {
- missingFilesSB.Append(", ");
- }
- }
-
- try
- {
- // If they are not an admin and have no possibility of elevating, such as for a standard User
- // in XP, then give them an error. Unfortunately we do not know if we can even load text
- // resources at this point, and so must provide an English-only error message.
- if (!Security.IsAdministrator && !Security.CanElevateToAdministrator)
- {
- MessageBox.Show(
- null,
- "Paint.NET has detected that some important installation files are missing. Repairing " +
- "this requires administrator privilege. Please run the 'PdnRepair.exe' program in the installation " +
- "directory after logging in with a user that has administrator privilege." + Environment.NewLine +
- Environment.NewLine +
- "The missing files are: " + missingFilesSB,
- "Paint.NET",
- MessageBoxButtons.OK,
- MessageBoxIcon.Error);
-
- return false;
- }
-
- const int hMargin = 8;
- const int vMargin = 8;
- var form = new Form
- {
- Text = "Paint.NET",
- ClientSize = new Size(400, 10),
- StartPosition = FormStartPosition.CenterScreen
- };
-
- var infoLabel = new Label();
- form.Controls.Add(infoLabel);
- infoLabel.Text =
- "Paint.NET has detected that some important installation files are missing. If you click " +
- "the Repair button it will attempt to repair this and then continue loading." + Environment.NewLine +
- Environment.NewLine +
- "The missing files are: " + missingFilesSB;
-
- #if DEBUG
- infoLabel.Text += Environment.NewLine + Environment.NewLine +
- "*** Since this is a DEBUG build, you should probably add /skipRepairAttempt to the command-line.";
- #endif
-
- infoLabel.Location = new Point(hMargin, vMargin);
- infoLabel.Width = form.ClientSize.Width - hMargin * 2;
- infoLabel.Height = infoLabel.GetPreferredSize(new Size(infoLabel.Width, 1)).Height;
-
- var repairButton = new Button();
- form.Controls.Add(repairButton);
- repairButton.Text = "&Repair";
-
- Exception exception = null;
-
- repairButton.Click +=
- delegate
- {
- form.DialogResult = DialogResult.Yes;
- repairButton.Enabled = false;
-
- try
- {
- Execute(form, "PdnRepair.exe", "/noPause", ExecutePrivilege.AsInvokerOrAsManifest, ExecuteWaitType.WaitForExit);
- }
-
- catch (Exception ex)
- {
- exception = ex;
- }
- };
-
- repairButton.AutoSize = true;
- repairButton.PerformLayout();
- repairButton.Width += 20;
- repairButton.Location = new Point((form.ClientSize.Width - repairButton.Width) / 2, infoLabel.Bottom + vMargin * 2);
- repairButton.FlatStyle = FlatStyle.System;
- UI.EnableShield(repairButton, true);
-
- form.FormBorderStyle = FormBorderStyle.FixedDialog;
- form.MinimizeBox = false;
- form.MaximizeBox = false;
- form.ShowInTaskbar = true;
- form.Icon = null;
- form.ClientSize = new Size(form.ClientRectangle.Width, repairButton.Bottom + vMargin);
-
- DialogResult result = form.ShowDialog(null);
- form.Dispose();
- form = null;
-
- if (result == DialogResult.Yes)
- {
- return true;
- }
- if (exception == null)
- {
- return false;
- }
- throw new Exception("Error while attempting to repair", exception);
- }
-
- catch (Exception ex)
- {
- throw new Exception("Could not repair installation after it was determined that the following files are missing: " +
- missingFilesSB, ex);
- }
- }
-
- /// <summary>
- /// Opens the requested directory in the shell's file/folder browser.
- /// </summary>
- /// <param name="parent">The window that is currently in the foreground.</param>
- /// <param name="folderPath">The folder to open.</param>
- /// <remarks>
- /// This UI is presented modelessly, in another process, and in the foreground.
- /// Error handling and messaging (error dialogs) will be handled by the shell,
- /// and these errors will not be communicated to the caller of this method.
- /// </remarks>
- public static void BrowseFolder(IWin32Window parent, string folderPath)
- {
- var sei = new NativeStructs.SHELLEXECUTEINFO
- {
- cbSize = (uint) Marshal.SizeOf(typeof (NativeStructs.SHELLEXECUTEINFO)),
- fMask = NativeConstants.SEE_MASK_NO_CONSOLE,
- lpVerb = "open",
- lpFile = folderPath,
- nShow = NativeConstants.SW_SHOWNORMAL,
- hwnd = parent.Handle
- };
-
- bool bResult = NativeMethods.ShellExecuteExW(ref sei);
-
- if (bResult)
- {
- if (sei.hProcess != IntPtr.Zero)
- {
- SafeNativeMethods.CloseHandle(sei.hProcess);
- sei.hProcess = IntPtr.Zero;
- }
- }
- else
- {
- NativeMethods.ThrowOnWin32Error("ShellExecuteW returned FALSE");
- }
-
- GC.KeepAlive(parent);
- }
-
- #if false
- [Obsolete("Do not use this method.", true)]
- public static void Execute(
- IWin32Window parent,
- string exePath,
- string args,
- bool requireAdmin)
- {
- Execute(parent, exePath, args, requireAdmin ? ExecutePrivilege.RequireAdmin : ExecutePrivilege.AsInvokerOrAsManifest, ExecuteWaitType.ReturnImmediately);
- }
- #endif
-
- private const string UpdateExeFileName = "UpdateMonitor.exe";
-
- private delegate int ExecuteHandOff(IWin32Window parent, string exePath, string args, out IntPtr hProcess);
-
- /// <summary>
- /// Uses the shell to execute the command. This method must only be used by Paint.NET
- /// and not by plugins.
- /// </summary>
- /// <param name="parent">
- /// The window that is currently in the foreground. This may be null if requireAdmin
- /// is false and the executable that exePath refers to is not marked (e.g. via a
- /// manifest) as requiring administrator privilege.
- /// </param>
- /// <param name="exePath">
- /// The path to the executable to launch.
- /// </param>
- /// <param name="args">
- /// The command-line arguments for the executable.
- /// </param>
- /// <param name="execPrivilege">
- /// The privileges to execute the new process with.
- /// If the executable is already marked as requiring administrator privilege
- /// (e.g. via a "requiresAdministrator" UAC manifest), this parameter should be
- /// set to AsInvokerOrAsManifest.
- /// </param>
- /// <param name="execWaitType"></param>
- /// <remarks>
- /// If administrator privilege is required, a consent UI may be displayed asking the
- /// user to approve the action. A parent window must be provided in this case so that
- /// the consent UI will know where to position itself. Administrator privilege is
- /// required if execPrivilege is set to RequireAdmin, or if the executable being launched
- /// has a manifest declaring that it requires this privilege and if the operating
- /// system recognizes the manifest.
- /// </remarks>
- /// <exception cref="ArgumentException">
- /// execPrivilege was RequireAdmin, but parent was null.
- /// </exception>
- /// <exception cref="SecurityException">
- /// execPrivilege was RequireAdmin, but the user does not have this privilege, nor do they
- /// have the ability to acquire or elevate to obtain this privilege.
- /// </exception>
- /// <exception cref="Win32Exception">
- /// There was an error launching the program.
- /// </exception>
- public static void Execute(
- IWin32Window parent,
- string exePath,
- string args,
- ExecutePrivilege execPrivilege,
- ExecuteWaitType execWaitType)
- {
- if (exePath == null)
- {
- throw new ArgumentNullException("exePath");
- }
-
- if (execPrivilege == ExecutePrivilege.RequireAdmin && parent == null)
- {
- throw new ArgumentException("If requireAdmin is true, a parent window must be provided");
- }
-
- // If this action requires admin privilege, but the user does not have this
- // privilege and is not capable of acquiring this privilege, then we will
- // throw an exception.
- if (execPrivilege == ExecutePrivilege.RequireAdmin &&
- !Security.IsAdministrator &&
- !Security.CanElevateToAdministrator)
- {
- throw new SecurityException("Executable requires administrator privilege, but user is not an administrator and cannot elevate");
- }
-
- ExecuteHandOff executeHandOff;
- switch (execPrivilege)
- {
- case ExecutePrivilege.AsInvokerOrAsManifest:
- executeHandOff = new ExecuteHandOff(ExecAsInvokerOrAsManifest);
- break;
-
- case ExecutePrivilege.RequireAdmin:
- executeHandOff = new ExecuteHandOff(ExecRequireAdmin);
- break;
-
- case ExecutePrivilege.RequireNonAdminIfPossible:
- executeHandOff = Security.CanLaunchNonAdminProcess ? new ExecuteHandOff(ExecRequireNonAdmin) : new ExecuteHandOff(ExecAsInvokerOrAsManifest);
- break;
-
- default:
- throw new InvalidEnumArgumentException("ExecutePrivilege");
- }
-
- string updateMonitorExePath = null;
- if (execWaitType == ExecuteWaitType.RelaunchPdnOnExit)
- {
- RelaunchPdnHelperPart1(out updateMonitorExePath);
- }
-
- IntPtr hProcess;
- int nResult = executeHandOff(parent, exePath, args, out hProcess);
-
- if (nResult == NativeConstants.ERROR_SUCCESS)
- {
- switch (execWaitType)
- {
- case ExecuteWaitType.WaitForExit:
- SafeNativeMethods.WaitForSingleObject(hProcess, NativeConstants.INFINITE);
- break;
- case ExecuteWaitType.RelaunchPdnOnExit:
- {
- bool bResult2 = SafeNativeMethods.SetHandleInformation(
- hProcess,
- NativeConstants.HANDLE_FLAG_INHERIT,
- NativeConstants.HANDLE_FLAG_INHERIT);
-
- RelaunchPdnHelperPart2(updateMonitorExePath, hProcess);
-
- // Ensure that we don't close the process handle right away in the next few lines of code.
- // It must be inherited by the child process. Yes, this is technically a leak but we are
- // planning to terminate in just a moment anyway.
- hProcess = IntPtr.Zero;
- }
- break;
- case ExecuteWaitType.ReturnImmediately:
- break;
- }
-
- if (hProcess != IntPtr.Zero)
- {
- SafeNativeMethods.CloseHandle(hProcess);
- hProcess = IntPtr.Zero;
- }
- }
- else
- {
- if (nResult == NativeConstants.ERROR_CANCELLED ||
- nResult == NativeConstants.ERROR_TIMEOUT)
- {
- // no problem
- }
- else
- {
- NativeMethods.ThrowOnWin32Error("ExecuteHandoff failed", nResult);
- }
-
- if (updateMonitorExePath != null)
- {
- try
- {
- File.Delete(updateMonitorExePath);
- }
-
- catch (Exception)
- {
- }
-
- updateMonitorExePath = null;
- }
- }
-
- GC.KeepAlive(parent);
- }
-
- private static int ExecAsInvokerOrAsManifest(IWin32Window parent, string exePath, string args, out IntPtr hProcess)
- {
- return ExecShellExecuteEx(parent, exePath, args, null, out hProcess);
- }
-
- private static int ExecRequireAdmin(IWin32Window parent, string exePath, string args, out IntPtr hProcess)
- {
- const string runAs = "runas";
-
- string verb = Security.IsAdministrator ? null : runAs;
-
- return ExecShellExecuteEx(parent, exePath, args, verb, out hProcess);
- }
-
- private static int ExecRequireNonAdmin(IWin32Window parent, string exePath, string args, out IntPtr hProcess)
- {
- int nError = NativeConstants.ERROR_SUCCESS;
- string commandLine = "\"" + exePath + "\"" + (args == null ? "" : (" " + args));
-
- string dir;
-
- try
- {
- dir = Path.GetDirectoryName(exePath);
- }
-
- catch (Exception)
- {
- dir = null;
- }
-
- IntPtr hShellProcess = IntPtr.Zero;
- IntPtr hShellProcessToken = IntPtr.Zero;
- IntPtr hTokenCopy = IntPtr.Zero;
- IntPtr bstrExePath = IntPtr.Zero;
- IntPtr bstrCommandLine = IntPtr.Zero;
- IntPtr bstrDir = IntPtr.Zero;
- var procInfo = new NativeStructs.PROCESS_INFORMATION();
-
- try
- {
- IntPtr hWndShell = SafeNativeMethods.FindWindowW("Progman", null);
- if (hWndShell == IntPtr.Zero)
- {
- NativeMethods.ThrowOnWin32Error("FindWindowW() returned NULL");
- }
-
- uint dwPID;
- uint dwThreadId = SafeNativeMethods.GetWindowThreadProcessId(hWndShell, out dwPID);
- if (0 == dwPID)
- {
- NativeMethods.ThrowOnWin32Error("GetWindowThreadProcessId returned 0", NativeErrors.ERROR_FILE_NOT_FOUND);
- }
-
- hShellProcess = NativeMethods.OpenProcess(NativeConstants.PROCESS_QUERY_INFORMATION, false, dwPID);
- if (IntPtr.Zero == hShellProcess)
- {
- NativeMethods.ThrowOnWin32Error("OpenProcess() returned NULL");
- }
-
- bool optResult = NativeMethods.OpenProcessToken(
- hShellProcess,
- NativeConstants.TOKEN_ASSIGN_PRIMARY | NativeConstants.TOKEN_DUPLICATE | NativeConstants.TOKEN_QUERY,
- out hShellProcessToken);
-
- if (!optResult)
- {
- NativeMethods.ThrowOnWin32Error("OpenProcessToken() returned FALSE");
- }
-
- bool dteResult = NativeMethods.DuplicateTokenEx(
- hShellProcessToken,
- NativeConstants.MAXIMUM_ALLOWED,
- IntPtr.Zero,
- NativeConstants.SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
- NativeConstants.TOKEN_TYPE.TokenPrimary,
- out hTokenCopy);
-
- if (!dteResult)
- {
- NativeMethods.ThrowOnWin32Error("DuplicateTokenEx() returned FALSE");
- }
-
- bstrExePath = Marshal.StringToBSTR(exePath);
- bstrCommandLine = Marshal.StringToBSTR(commandLine);
- bstrDir = Marshal.StringToBSTR(dir);
-
- bool cpwtResult = NativeMethods.CreateProcessWithTokenW(
- hTokenCopy,
- 0,
- bstrExePath,
- bstrCommandLine,
- 0,
- IntPtr.Zero,
- bstrDir,
- IntPtr.Zero,
- out procInfo);
-
- if (cpwtResult)
- {
- hProcess = procInfo.hProcess;
- procInfo.hProcess = IntPtr.Zero;
- nError = NativeConstants.ERROR_SUCCESS;
- }
- else
- {
- hProcess = IntPtr.Zero;
- nError = Marshal.GetLastWin32Error();
- }
- }
-
- catch (Win32Exception ex)
- {
- Tracing.Ping(ex.ToString());
- nError = ex.ErrorCode;
- hProcess = IntPtr.Zero;
- }
-
- finally
- {
- if (bstrExePath != IntPtr.Zero)
- {
- Marshal.FreeBSTR(bstrExePath);
- bstrExePath = IntPtr.Zero;
- }
-
- if (bstrCommandLine != IntPtr.Zero)
- {
- Marshal.FreeBSTR(bstrCommandLine);
- bstrCommandLine = IntPtr.Zero;
- }
-
- if (bstrDir != IntPtr.Zero)
- {
- Marshal.FreeBSTR(bstrDir);
- bstrDir = IntPtr.Zero;
- }
-
- if (hShellProcess != IntPtr.Zero)
- {
- SafeNativeMethods.CloseHandle(hShellProcess);
- hShellProcess = IntPtr.Zero;
- }
-
- if (hShellProcessToken != IntPtr.Zero)
- {
- SafeNativeMethods.CloseHandle(hShellProcessToken);
- hShellProcessToken = IntPtr.Zero;
- }
-
- if (hTokenCopy != IntPtr.Zero)
- {
- SafeNativeMethods.CloseHandle(hTokenCopy);
- hTokenCopy = IntPtr.Zero;
- }
-
- if (procInfo.hThread != IntPtr.Zero)
- {
- SafeNativeMethods.CloseHandle(procInfo.hThread);
- procInfo.hThread = IntPtr.Zero;
- }
-
- if (procInfo.hProcess != IntPtr.Zero)
- {
- SafeNativeMethods.CloseHandle(procInfo.hProcess);
- procInfo.hProcess = IntPtr.Zero;
- }
- }
-
- return nError;
- }
-
- private static int ExecShellExecuteEx(IWin32Window parent, string exePath, string args, string verb, out IntPtr hProcess)
- {
- string dir;
-
- try
- {
- dir = Path.GetDirectoryName(exePath);
- }
-
- catch (Exception)
- {
- dir = null;
- }
-
- var sei = new NativeStructs.SHELLEXECUTEINFO
- {
- cbSize = (uint) Marshal.SizeOf(typeof (NativeStructs.SHELLEXECUTEINFO)),
- fMask = NativeConstants.SEE_MASK_NOCLOSEPROCESS |
- NativeConstants.SEE_MASK_NO_CONSOLE |
- NativeConstants.SEE_MASK_FLAG_DDEWAIT,
- lpVerb = verb,
- lpDirectory = dir,
- lpFile = exePath,
- lpParameters = args,
- nShow = NativeConstants.SW_SHOWNORMAL
- };
-
- if (parent != null)
- {
- sei.hwnd = parent.Handle;
- }
-
- bool bResult = NativeMethods.ShellExecuteExW(ref sei);
- hProcess = sei.hProcess;
- sei.hProcess = IntPtr.Zero;
-
- int nResult = NativeConstants.ERROR_SUCCESS;
-
- if (!bResult)
- {
- nResult = Marshal.GetLastWin32Error();
- }
-
- return nResult;
- }
-
- private static void RelaunchPdnHelperPart1(out string updateMonitorExePath)
- {
- string srcDir = Application.StartupPath;
- string srcPath = Path.Combine(srcDir, UpdateExeFileName);
- string srcPath2 = srcPath + ".config";
-
- string dstDir = Environment.ExpandEnvironmentVariables(@"%TEMP%\PdnSetup");
- string dstPath = Path.Combine(dstDir, UpdateExeFileName);
- string dstPath2 = dstPath + ".config";
-
- if (!Directory.Exists(dstDir))
- {
- Directory.CreateDirectory(dstDir);
- }
-
- File.Copy(srcPath, dstPath, true);
- File.Copy(srcPath2, dstPath2, true);
- updateMonitorExePath = dstPath;
- }
-
- private static void RelaunchPdnHelperPart2(string updateMonitorExePath, IntPtr hProcess)
- {
- string args = hProcess.ToInt64().ToString(CultureInfo.InstalledUICulture);
- var psi = new ProcessStartInfo(updateMonitorExePath, args)
- {
- UseShellExecute = false,
- WindowStyle = ProcessWindowStyle.Hidden
- };
- Process process = Process.Start(psi);
- process.Dispose();
- }
-
- /// <summary>
- /// Asynchronously restarts Paint.NET.
- /// </summary>
- /// <remarks>
- /// This method does not restart Paint.NET immediately. Instead, it waits
- /// for Paint.NET to terminate and then restarts it. It does not perform
- /// the termination or shutdown.
- /// </remarks>
- public static void RestartApplication()
- {
- string srcDir = Application.StartupPath;
- string updateExePath = Path.Combine(srcDir, UpdateExeFileName);
-
- Process thisProcess = Process.GetCurrentProcess();
- IntPtr hProcess = thisProcess.Handle;
-
- bool bResult = SafeNativeMethods.SetHandleInformation(
- hProcess,
- NativeConstants.HANDLE_FLAG_INHERIT,
- NativeConstants.HANDLE_FLAG_INHERIT);
-
- if (!bResult)
- {
- NativeMethods.ThrowOnWin32Error("SetHandleInformation() returned false");
- }
-
- RelaunchPdnHelperPart2(updateExePath, hProcess);
- }
-
- /// <summary>
- /// Launches the default browser and opens the given URL.
- /// </summary>
- /// <param name="owner"></param>
- /// <param name="url">The URL to show. The maximum length is 512 characters.</param>
- /// <remarks>
- /// This method will not present an error dialog if the URL could not be launched.
- /// Note: This method must only be used by Paint.NET, and not any plugins. It may
- /// change or be removed in future versions.
- /// </remarks>
- public static bool LaunchUrl(IWin32Window owner, string url)
- {
- if (url.Length > 512)
- {
- throw new ArgumentOutOfRangeException("url.Length must be <= 512");
- }
-
- bool success = false;
- string quotedUrl = "\"" + url + "\"";
- ExecutePrivilege executePrivilege;
-
- if (!Security.IsAdministrator || (Security.IsAdministrator && !Security.CanLaunchNonAdminProcess))
- {
- executePrivilege = ExecutePrivilege.AsInvokerOrAsManifest;
- }
- else
- {
- executePrivilege = ExecutePrivilege.RequireNonAdminIfPossible;
- }
-
- // Method 1. Just launch the url, and hope that the shell figures out the association correctly.
- // This method will not work with ExecutePrivilege.RequireNonAdmin though.
- if (executePrivilege != ExecutePrivilege.RequireNonAdminIfPossible)
- {
- try
- {
- Execute(owner, quotedUrl, null, executePrivilege, ExecuteWaitType.ReturnImmediately);
- success = true;
- }
-
- catch (Exception ex)
- {
- Tracing.Ping("Exception while using method 1 to launch url, " + quotedUrl + ", :" + ex.ToString());
- success = false;
- }
- }
-
- // Method 2. Launch the url through explorer
- if (!success)
- {
- const string shellFileLoc = @"%WINDIR%\explorer.exe";
- string shellExePath;
-
- try
- {
- shellExePath = Environment.ExpandEnvironmentVariables(shellFileLoc);
- Execute(owner, shellExePath, quotedUrl, executePrivilege, ExecuteWaitType.ReturnImmediately);
- success = true;
- }
-
- catch (Exception ex)
- {
- Tracing.Ping("Exception while using method 2 to launch url through '" + shellExePath + "', " + quotedUrl + ", : " + ex.ToString());
- success = false;
- }
- }
-
- return success;
- }
-
- [Obsolete("Use PdnInfo.OpenUrl() instead. Shell.LaunchUrl() must only be used by Paint.NET code, not by plugins.", true)]
- public static bool OpenUrl(IWin32Window owner, string url)
- {
- return LaunchUrl(owner, url);
- }
-
- public static void AddToRecentDocumentsList(string fileName)
- {
- // Apparently SHAddToRecentDocs can block for a very long period of time when certain
- // conditions are met: so we just stick it on "the backburner."
- ThreadPool.QueueUserWorkItem(AddToRecentDocumentsListImpl, fileName);
- }
-
- private static void AddToRecentDocumentsListImpl(object fileNameObj)
- {
- var fileName = (string)fileNameObj;
- IntPtr bstrFileName = IntPtr.Zero;
-
- try
- {
- bstrFileName = Marshal.StringToBSTR(fileName);
- NativeMethods.SHAddToRecentDocs(NativeConstants.SHARD_PATHW, bstrFileName);
- }
-
- finally
- {
- if (bstrFileName != IntPtr.Zero)
- {
- Marshal.FreeBSTR(bstrFileName);
- bstrFileName = IntPtr.Zero;
- }
- }
- }
-
- // TODO: convert to extension method in the 4.0 codebase, which can use .NET 3.5
- private static T2 Map<T1, T2>(T1 mapFrom, IEnumerable<Pair<T1, T2>> mappings)
- {
- foreach (Pair<T1, T2> mapping in mappings.Where(mapping => mapping.First.Equals(mapFrom)))
- {
- return mapping.Second;
- }
-
- throw new KeyNotFoundException();
- }
-
- private static string GetCSIDLPath(int csidl, bool tryCreateIfAbsent)
- {
- // First, try calling SHGetFolderPathW with the "CSIDL_FLAG_CREATE" flag. However, if it
- // returns an error then ignore it. We've had some crash logs with "access denied" coming
- // from this function.
- int csidlWithFlags = csidl | (tryCreateIfAbsent ? NativeConstants.CSIDL_FLAG_CREATE : 0);
- var sbWithFlags = new StringBuilder(NativeConstants.MAX_PATH);
- Do.TryBool(() => NativeMethods.SHGetFolderPathW(IntPtr.Zero, csidlWithFlags, IntPtr.Zero, NativeConstants.SHGFP_TYPE_CURRENT, sbWithFlags));
-
- var sb = new StringBuilder(NativeConstants.MAX_PATH);
- NativeMethods.SHGetFolderPathW(IntPtr.Zero, csidl, IntPtr.Zero, NativeConstants.SHGFP_TYPE_CURRENT, sb);
-
- // If we get back something like 'Z:' then we need to put a backslash on it.
- // Otherwise other path-related functions will freak out.
- if (sb.Length == 2 && sb[1] == ':')
- {
- sb.Append(Path.DirectorySeparatorChar);
- }
-
- string path = sb.ToString();
-
- return path;
- }
-
- private static readonly Pair<VirtualFolderName, int>[] PathMappings = new[]
- {
- Pair.Create(VirtualFolderName.SystemProgramFiles, NativeConstants.CSIDL_PROGRAM_FILES),
- Pair.Create(VirtualFolderName.UserDesktop, NativeConstants.CSIDL_DESKTOP_DIRECTORY),
- Pair.Create(VirtualFolderName.UserDocuments, NativeConstants.CSIDL_PERSONAL),
- Pair.Create(VirtualFolderName.UserLocalAppData, NativeConstants.CSIDL_LOCAL_APPDATA),
- Pair.Create(VirtualFolderName.UserPictures, NativeConstants.CSIDL_MYPICTURES),
- Pair.Create(VirtualFolderName.UserRoamingAppData, NativeConstants.CSIDL_APPDATA)
- };
-
- public static string GetVirtualPath(VirtualFolderName folderName, bool tryCreateIfAbsent)
- {
- try
- {
- int csidl = Map(folderName, PathMappings);
- string path = GetCSIDLPath(csidl, tryCreateIfAbsent);
- return path;
- }
-
- catch (KeyNotFoundException)
- {
- throw new InvalidEnumArgumentException("folderName", (int)folderName, typeof(VirtualFolderName));
- }
- }
- }
- }