Make ShellExecuteEx use STA (#4362)
This commit is contained in:
parent
b7b3b69a74
commit
e38f0e7149
|
@ -23,13 +23,8 @@ using System.Text;
|
||||||
|
|
||||||
using TypeTable = System.Management.Automation.Runspaces.TypeTable;
|
using TypeTable = System.Management.Automation.Runspaces.TypeTable;
|
||||||
|
|
||||||
#if CORECLR
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Microsoft.Win32.SafeHandles;
|
using Microsoft.Win32.SafeHandles;
|
||||||
#else
|
|
||||||
using System.Security.Principal;
|
|
||||||
using PSUtils = System.Management.Automation.PsUtils;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace System.Management.Automation
|
namespace System.Management.Automation
|
||||||
{
|
{
|
||||||
|
@ -242,43 +237,9 @@ namespace System.Management.Automation
|
||||||
|
|
||||||
internal static string GetApplicationBase(string shellId)
|
internal static string GetApplicationBase(string shellId)
|
||||||
{
|
{
|
||||||
#if CORECLR
|
|
||||||
// Use the location of SMA.dll as the application base.
|
// Use the location of SMA.dll as the application base.
|
||||||
Assembly assembly = typeof(PSObject).GetTypeInfo().Assembly;
|
Assembly assembly = typeof(PSObject).GetTypeInfo().Assembly;
|
||||||
return Path.GetDirectoryName(assembly.Location);
|
return Path.GetDirectoryName(assembly.Location);
|
||||||
#else
|
|
||||||
// This code path applies to Windows FullCLR inbox deployments. All CoreCLR
|
|
||||||
// implementations should use the location of SMA.dll since it must reside in PSHOME.
|
|
||||||
//
|
|
||||||
// try to get the path from the registry first
|
|
||||||
string result = GetApplicationBaseFromRegistry(shellId);
|
|
||||||
if (result != null)
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The default keys aren't installed, so try and use the entry assembly to
|
|
||||||
// get the application base. This works for managed apps like minishells...
|
|
||||||
Assembly assem = Assembly.GetEntryAssembly();
|
|
||||||
if (assem != null)
|
|
||||||
{
|
|
||||||
// For minishells, we just return the executable path.
|
|
||||||
return Path.GetDirectoryName(assem.Location);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For unmanaged host apps, look for the SMA dll, if it's not GAC'ed then
|
|
||||||
// use it's location as the application base...
|
|
||||||
assem = typeof(PSObject).GetTypeInfo().Assembly;
|
|
||||||
string gacRootPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "Microsoft.Net\\assembly");
|
|
||||||
if (!assem.Location.StartsWith(gacRootPath, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
// For other hosts.
|
|
||||||
return Path.GetDirectoryName(assem.Location);
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise, just give up...
|
|
||||||
return "";
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string[] s_productFolderDirectories;
|
private static string[] s_productFolderDirectories;
|
||||||
|
@ -324,11 +285,7 @@ namespace System.Management.Automation
|
||||||
// TODO: #1184 will resolve this work-around
|
// TODO: #1184 will resolve this work-around
|
||||||
// Side-by-side versions of PowerShell use modules from their application base, not
|
// Side-by-side versions of PowerShell use modules from their application base, not
|
||||||
// the system installation path.
|
// the system installation path.
|
||||||
#if CORECLR
|
|
||||||
progFileDir = Path.Combine(appBase, "Modules");
|
progFileDir = Path.Combine(appBase, "Modules");
|
||||||
#else
|
|
||||||
progFileDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WindowsPowerShell", "Modules");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(progFileDir))
|
if (!string.IsNullOrEmpty(progFileDir))
|
||||||
{
|
{
|
||||||
|
@ -522,64 +479,6 @@ namespace System.Management.Automation
|
||||||
return AllowedEditionValues.Contains(editionValue, StringComparer.OrdinalIgnoreCase);
|
return AllowedEditionValues.Contains(editionValue, StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !CORECLR
|
|
||||||
/// <summary>
|
|
||||||
/// Checks whether current monad session supports NetFrameworkVersion specified
|
|
||||||
/// by checkVersion. The specified version is treated as the the minimum required
|
|
||||||
/// version of .NET framework.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="checkVersion">Version to check</param>
|
|
||||||
/// <param name="higherThanKnownHighestVersion">true if version to check is higher than the known highest version</param>
|
|
||||||
/// <returns>true if supported, false otherwise</returns>
|
|
||||||
internal static bool IsNetFrameworkVersionSupported(Version checkVersion, out bool higherThanKnownHighestVersion)
|
|
||||||
{
|
|
||||||
higherThanKnownHighestVersion = false;
|
|
||||||
bool isSupported = false;
|
|
||||||
|
|
||||||
if (checkVersion == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct a temporary version number with build number and revision number set to 0.
|
|
||||||
// This is done so as to re-use the version specifications in PSUtils.FrameworkRegistryInstallation
|
|
||||||
Version tempVersion = new Version(checkVersion.Major, checkVersion.Minor, 0, 0);
|
|
||||||
|
|
||||||
// Win8: 840038 - For any version above the highest known .NET version (4.5 for Windows 8), we can't make a call as to
|
|
||||||
// whether the requirement is satisfied or not because we can't detect that version of .NET.
|
|
||||||
// We end up erring on the side of app compat by letting it through.
|
|
||||||
// We will write a message in the Verbose output saying that we cannot detect the specified version of the .NET Framework.
|
|
||||||
if (checkVersion > PsUtils.FrameworkRegistryInstallation.KnownHighestNetFrameworkVersion)
|
|
||||||
{
|
|
||||||
isSupported = true;
|
|
||||||
higherThanKnownHighestVersion = true;
|
|
||||||
}
|
|
||||||
// For a script to have a valid .NET version, the specified version or atleast one of its compatible versions must be installed on the machine.
|
|
||||||
else if (PSUtils.FrameworkRegistryInstallation.CompatibleNetFrameworkVersions.ContainsKey(tempVersion))
|
|
||||||
{
|
|
||||||
if (PSUtils.FrameworkRegistryInstallation.IsFrameworkInstalled(tempVersion.Major, tempVersion.Minor, 0))
|
|
||||||
{
|
|
||||||
// If the specified version is installed on the machine, then we return true.
|
|
||||||
isSupported = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// If any of the compatible versions are installed on the machine, then we return true.
|
|
||||||
HashSet<Version> compatibleVersions = PSUtils.FrameworkRegistryInstallation.CompatibleNetFrameworkVersions[tempVersion];
|
|
||||||
foreach (Version compatibleVersion in compatibleVersions)
|
|
||||||
{
|
|
||||||
if (PSUtils.FrameworkRegistryInstallation.IsFrameworkInstalled(compatibleVersion.Major, compatibleVersion.Minor, 0))
|
|
||||||
{
|
|
||||||
isSupported = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return isSupported;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -590,11 +489,7 @@ namespace System.Management.Automation
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is used to construct the profile path.
|
/// This is used to construct the profile path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
#if CORECLR
|
|
||||||
internal static string ProductNameForDirectory = Platform.IsInbox ? "WindowsPowerShell" : "PowerShell";
|
internal static string ProductNameForDirectory = Platform.IsInbox ? "WindowsPowerShell" : "PowerShell";
|
||||||
#else
|
|
||||||
internal const string ProductNameForDirectory = "WindowsPowerShell";
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The subdirectory of module paths
|
/// The subdirectory of module paths
|
||||||
|
@ -1450,7 +1345,7 @@ namespace System.Management.Automation
|
||||||
internal static readonly UTF8Encoding utf8NoBom =
|
internal static readonly UTF8Encoding utf8NoBom =
|
||||||
new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
|
new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
|
||||||
|
|
||||||
#if !CORECLR // TODO:CORECLR - WindowsIdentity.Impersonate() is not available. Use WindowsIdentity.RunImplemented to replace it.
|
#if !CORECLR // TODO:CORECLR - WindowsIdentity.Impersonate() is not available. Use WindowsIdentity.RunImpersonated to replace it.
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queues a CLR worker thread with impersonation of provided Windows identity.
|
/// Queues a CLR worker thread with impersonation of provided Windows identity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1600,12 +1495,21 @@ namespace System.Management.Automation.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if CORECLR && !UNIX
|
#if !UNIX
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Helper to start process using ShellExecuteEx. This is used only in PowerShell Core on Full Windows.
|
/// Helper to start process using ShellExecuteEx. This is used only in PowerShell Core on Full Windows.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class ShellExecuteHelper
|
internal class ShellExecuteHelper
|
||||||
{
|
{
|
||||||
|
private NativeMethods.ShellExecuteInfo _executeInfo;
|
||||||
|
private int _errorCode;
|
||||||
|
private bool _succeeded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor for ShellExecuteHelper
|
||||||
|
/// </summary>
|
||||||
|
private ShellExecuteHelper(NativeMethods.ShellExecuteInfo executeInfo) { _executeInfo = executeInfo; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Start a process using ShellExecuteEx with default settings about WindowStyle and Verb.
|
/// Start a process using ShellExecuteEx with default settings about WindowStyle and Verb.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1617,21 +1521,6 @@ namespace System.Management.Automation.Internal
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Start a process using ShellExecuteEx
|
/// Start a process using ShellExecuteEx
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
|
||||||
/// Quoted from MSDN:
|
|
||||||
/// "Because ShellExecuteEx can delegate execution to Shell extensions (data sources, context menu handlers, verb implementations)
|
|
||||||
/// that are activated using Component Object Model (COM), COM should be initialized before ShellExecuteEx is called. Some Shell
|
|
||||||
/// extensions require the COM single-threaded apartment (STA) type. In that case, COM should be initialized as shown here:
|
|
||||||
/// CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)
|
|
||||||
/// There are instances where ShellExecuteEx does not use one of these types of Shell extension and those instances would not require
|
|
||||||
/// COM to be initialized at all. Nonetheless, it is good practice to always initalize COM before using this function."
|
|
||||||
///
|
|
||||||
/// TODO: In .NET Core, managed threads are all eagerly initialized with MTA mode, so to call 'ShellExecuteEx' from a STA thread, we
|
|
||||||
/// need to create a native thread using 'CreateThread' function and initialize COM with STA on that thread. Currently we are calling
|
|
||||||
/// ShellExecuteEx directly on MTA thread, and it works for things like openning a folder in File Explorer, openning a PDF/DOCX file,
|
|
||||||
/// openning URL in web browser and etc, but it's not guaranteed to work in all ShellExecution scenarios. Github issue #2969 is used
|
|
||||||
/// to track the "invoke-on-STA-thread" work.
|
|
||||||
/// </remarks>
|
|
||||||
internal static Process Start(ProcessStartInfo startInfo, ProcessWindowStyle windowStyle, string verb)
|
internal static Process Start(ProcessStartInfo startInfo, ProcessWindowStyle windowStyle, string verb)
|
||||||
{
|
{
|
||||||
var shellExecuteInfo = new NativeMethods.ShellExecuteInfo();
|
var shellExecuteInfo = new NativeMethods.ShellExecuteInfo();
|
||||||
|
@ -1666,34 +1555,17 @@ namespace System.Management.Automation.Internal
|
||||||
shellExecuteInfo.lpDirectory = Marshal.StringToHGlobalUni(startInfo.WorkingDirectory);
|
shellExecuteInfo.lpDirectory = Marshal.StringToHGlobalUni(startInfo.WorkingDirectory);
|
||||||
|
|
||||||
shellExecuteInfo.fMask |= NativeMethods.SEE_MASK_FLAG_DDEWAIT;
|
shellExecuteInfo.fMask |= NativeMethods.SEE_MASK_FLAG_DDEWAIT;
|
||||||
|
ShellExecuteHelper helper = new ShellExecuteHelper(shellExecuteInfo);
|
||||||
if (!NativeMethods.ShellExecuteEx(shellExecuteInfo))
|
if (!helper.ExecuteOnSTAThread())
|
||||||
{
|
{
|
||||||
int errorCode = Marshal.GetLastWin32Error();
|
if(helper.ErrorCode == NativeMethods.ERROR_BAD_EXE_FORMAT || helper.ErrorCode == NativeMethods.ERROR_EXE_MACHINE_TYPE_MISMATCH)
|
||||||
if (errorCode == 0)
|
|
||||||
{
|
{
|
||||||
switch ((long)shellExecuteInfo.hInstApp)
|
throw new Win32Exception(helper.ErrorCode, "InvalidApplication");
|
||||||
{
|
|
||||||
case NativeMethods.SE_ERR_FNF: errorCode = NativeMethods.ERROR_FILE_NOT_FOUND; break;
|
|
||||||
case NativeMethods.SE_ERR_PNF: errorCode = NativeMethods.ERROR_PATH_NOT_FOUND; break;
|
|
||||||
case NativeMethods.SE_ERR_ACCESSDENIED: errorCode = NativeMethods.ERROR_ACCESS_DENIED; break;
|
|
||||||
case NativeMethods.SE_ERR_OOM: errorCode = NativeMethods.ERROR_NOT_ENOUGH_MEMORY; break;
|
|
||||||
case NativeMethods.SE_ERR_DDEFAIL:
|
|
||||||
case NativeMethods.SE_ERR_DDEBUSY:
|
|
||||||
case NativeMethods.SE_ERR_DDETIMEOUT: errorCode = NativeMethods.ERROR_DDE_FAIL; break;
|
|
||||||
case NativeMethods.SE_ERR_SHARE: errorCode = NativeMethods.ERROR_SHARING_VIOLATION; break;
|
|
||||||
case NativeMethods.SE_ERR_NOASSOC: errorCode = NativeMethods.ERROR_NO_ASSOCIATION; break;
|
|
||||||
case NativeMethods.SE_ERR_DLLNOTFOUND: errorCode = NativeMethods.ERROR_DLL_NOT_FOUND; break;
|
|
||||||
default: errorCode = (int)shellExecuteInfo.hInstApp; break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if(errorCode == NativeMethods.ERROR_BAD_EXE_FORMAT || errorCode == NativeMethods.ERROR_EXE_MACHINE_TYPE_MISMATCH)
|
|
||||||
{
|
{
|
||||||
throw new Win32Exception(errorCode, "InvalidApplication");
|
throw new Win32Exception(helper.ErrorCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Win32Exception(errorCode);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
@ -1719,6 +1591,56 @@ namespace System.Management.Automation.Internal
|
||||||
return processToReturn;
|
return processToReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ShellExecuteFunction()
|
||||||
|
{
|
||||||
|
if (!(_succeeded = NativeMethods.ShellExecuteEx(_executeInfo)))
|
||||||
|
{
|
||||||
|
_errorCode = Marshal.GetLastWin32Error();
|
||||||
|
if (_errorCode == 0)
|
||||||
|
{
|
||||||
|
switch ((long)_executeInfo.hInstApp)
|
||||||
|
{
|
||||||
|
case NativeMethods.SE_ERR_FNF: _errorCode = NativeMethods.ERROR_FILE_NOT_FOUND; break;
|
||||||
|
case NativeMethods.SE_ERR_PNF: _errorCode = NativeMethods.ERROR_PATH_NOT_FOUND; break;
|
||||||
|
case NativeMethods.SE_ERR_ACCESSDENIED: _errorCode = NativeMethods.ERROR_ACCESS_DENIED; break;
|
||||||
|
case NativeMethods.SE_ERR_OOM: _errorCode = NativeMethods.ERROR_NOT_ENOUGH_MEMORY; break;
|
||||||
|
case NativeMethods.SE_ERR_DDEFAIL:
|
||||||
|
case NativeMethods.SE_ERR_DDEBUSY:
|
||||||
|
case NativeMethods.SE_ERR_DDETIMEOUT: _errorCode = NativeMethods.ERROR_DDE_FAIL; break;
|
||||||
|
case NativeMethods.SE_ERR_SHARE: _errorCode = NativeMethods.ERROR_SHARING_VIOLATION; break;
|
||||||
|
case NativeMethods.SE_ERR_NOASSOC: _errorCode = NativeMethods.ERROR_NO_ASSOCIATION; break;
|
||||||
|
case NativeMethods.SE_ERR_DLLNOTFOUND: _errorCode = NativeMethods.ERROR_DLL_NOT_FOUND; break;
|
||||||
|
default: _errorCode = (int)_executeInfo.hInstApp; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ExecuteOnSTAThread()
|
||||||
|
{
|
||||||
|
if (Thread.CurrentThread.GetApartmentState() != System.Threading.ApartmentState.STA)
|
||||||
|
{
|
||||||
|
ThreadStart threadStart = new ThreadStart(this.ShellExecuteFunction);
|
||||||
|
Thread thread = new Thread(threadStart);
|
||||||
|
thread.SetApartmentState(System.Threading.ApartmentState.STA);
|
||||||
|
thread.Start();
|
||||||
|
thread.Join();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ShellExecuteFunction();
|
||||||
|
}
|
||||||
|
return _succeeded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ErrorCode
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _errorCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static int GetProcessIdFromHandle(SafeProcessHandle processHandle)
|
private static int GetProcessIdFromHandle(SafeProcessHandle processHandle)
|
||||||
{
|
{
|
||||||
NativeMethods.NtProcessBasicInfo info = new NativeMethods.NtProcessBasicInfo();
|
NativeMethods.NtProcessBasicInfo info = new NativeMethods.NtProcessBasicInfo();
|
||||||
|
|
Loading…
Reference in a new issue