diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs
index f359b83004..6023199fcc 100644
--- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs
+++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs
@@ -1665,18 +1665,6 @@ namespace Microsoft.PowerShell.Commands
private ManualResetEvent _waithandle = null;
private bool _isDefaultSetParameterSpecified = false;
- private bool _useShellExecute;
- private readonly bool _isOnFullWinSku;
-
- ///
- /// Constructor
- ///
- public StartProcessCommand()
- {
- _isOnFullWinSku = Platform.IsWindows && !Platform.IsNanoServer && !Platform.IsIoT;
- _useShellExecute = _isOnFullWinSku;
- }
-
#region Parameters
///
@@ -1877,7 +1865,7 @@ namespace Microsoft.PowerShell.Commands
string message = string.Empty;
// -Verb and -WindowStyle are not supported on non-Windows platforms as well as Windows headless SKUs
- if (_isOnFullWinSku)
+ if (Platform.IsWindowsDesktop)
{
// Parameters '-NoNewWindow' and '-WindowStyle' are both valid on full windows SKUs.
if (_nonewwindow && _windowstyleSpecified)
@@ -1908,6 +1896,8 @@ namespace Microsoft.PowerShell.Commands
//create an instance of the ProcessStartInfo Class
ProcessStartInfo startInfo = new ProcessStartInfo();
+ //use ShellExecute by default if we are running on full windows SKUs
+ startInfo.UseShellExecute = Platform.IsWindowsDesktop;
//Path = Mandatory parameter -> Will not be empty.
try
@@ -1959,7 +1949,6 @@ namespace Microsoft.PowerShell.Commands
{
if (_isDefaultSetParameterSpecified)
{
- _useShellExecute = false;
startInfo.UseShellExecute = false;
}
@@ -1970,10 +1959,10 @@ namespace Microsoft.PowerShell.Commands
LoadEnvironmentVariable(startInfo, Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Machine));
LoadEnvironmentVariable(startInfo, Environment.GetEnvironmentVariables(EnvironmentVariableTarget.User));
}
-#if !CORECLR
+
//WindowStyle
startInfo.WindowStyle = _windowstyle;
-#endif
+
//NewWindow
if (_nonewwindow)
{
@@ -2054,7 +2043,6 @@ namespace Microsoft.PowerShell.Commands
}
}
}
-#if !CORECLR // Properties 'Verb' and 'WindowStyle' are missing in CoreCLR
else if (ParameterSetName.Equals("UseShellExecute"))
{
//Verb
@@ -2062,7 +2050,7 @@ namespace Microsoft.PowerShell.Commands
//WindowStyle
startInfo.WindowStyle = _windowstyle;
}
-#endif
+
//Starts the Process
Process process = Start(startInfo);
@@ -2212,7 +2200,7 @@ namespace Microsoft.PowerShell.Commands
return process;
#else
Process process = null;
- if (_useShellExecute)
+ if (startInfo.UseShellExecute)
{
process = StartWithShellExecute(startInfo);
}
@@ -2467,7 +2455,7 @@ namespace Microsoft.PowerShell.Commands
lpStartupInfo.dwFlags |= 0x00000001;
// On headless SKUs like NanoServer and IoT, window style can only be the default value 'Normal'.
- switch (WindowStyle)
+ switch (startinfo.WindowStyle)
{
case ProcessWindowStyle.Normal:
//SW_SHOWNORMAL
@@ -2595,11 +2583,7 @@ namespace Microsoft.PowerShell.Commands
Process result = null;
try
{
-#if CORECLR
- result = ShellExecuteHelper.Start(startInfo, WindowStyle, Verb);
-#else
result = Process.Start(startInfo);
-#endif
}
catch (Win32Exception ex)
{
diff --git a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs
index 81df0316e9..fae4d8c7fe 100644
--- a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs
+++ b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs
@@ -148,10 +148,29 @@ namespace System.Management.Automation
}
}
+ ///
+ /// True if underlying system is Windows Desktop.
+ ///
+ public static bool IsWindowsDesktop
+ {
+ get
+ {
+#if UNIX
+ return false;
+#else
+ if (_isWindowsDesktop.HasValue) { return _isWindowsDesktop.Value; }
+
+ _isWindowsDesktop = !IsNanoServer && !IsIoT;
+ return _isWindowsDesktop.Value;
+#endif
+ }
+ }
+
#if !UNIX
private static bool? _isNanoServer = null;
private static bool? _isIoT = null;
private static bool? _isInbox = null;
+ private static bool? _isWindowsDesktop = null;
#endif
// format files
diff --git a/src/System.Management.Automation/engine/NativeCommandProcessor.cs b/src/System.Management.Automation/engine/NativeCommandProcessor.cs
index 677a0a64e5..18c36b9667 100644
--- a/src/System.Management.Automation/engine/NativeCommandProcessor.cs
+++ b/src/System.Management.Automation/engine/NativeCommandProcessor.cs
@@ -304,7 +304,16 @@ namespace System.Management.Automation
this.NativeParameterBinderController.BindParameters(arguments);
}
- InitNativeProcess();
+ try
+ {
+ InitNativeProcess();
+ }
+ catch (Exception)
+ {
+ // Do cleanup in case of exception
+ CleanUp();
+ throw;
+ }
}
///
@@ -312,12 +321,21 @@ namespace System.Management.Automation
///
internal override void ProcessRecord()
{
- while (Read())
+ try
{
- _inputWriter.Add(Command.CurrentPipelineObject);
- }
+ while (Read())
+ {
+ _inputWriter.Add(Command.CurrentPipelineObject);
+ }
- ConsumeAvailableNativeProcessOutput(blocking: false);
+ ConsumeAvailableNativeProcessOutput(blocking: false);
+ }
+ catch (Exception)
+ {
+ // Do cleanup in case of exception
+ CleanUp();
+ throw;
+ }
}
///
@@ -342,6 +360,12 @@ namespace System.Management.Automation
///
private bool _isRunningInBackground;
+ ///
+ /// Indicate if we have called 'NotifyBeginApplication()' on the host, so that
+ /// we can call the counterpart 'NotifyEndApplication' as approriate.
+ ///
+ private bool _hasNotifiedBeginApplication;
+
///
/// This output queue helps us keep the output and error (if redirected) order correct.
/// We could do a blocking read in the Complete block instead,
@@ -401,9 +425,10 @@ namespace System.Management.Automation
// If this process is being run standalone, tell the host, which might want
// to save off the window title or other such state as might be tweaked by
// the native process
- if (!redirectOutput)
+ if (_runStandAlone)
{
this.Command.Context.EngineHostInterface.NotifyBeginApplication();
+ _hasNotifiedBeginApplication = true;
// Also, store the Raw UI coordinates so that we can scrape the screen after
// if we are transcribing.
@@ -436,21 +461,28 @@ namespace System.Management.Automation
throw new PipelineStoppedException();
}
+ if (!Platform.IsWindows && startInfo.UseShellExecute)
+ {
+ // UseShellExecute is not properly supported on Unix. It runs the file with '/bin/sh'.
+ // Before the behavior is improved (tracked by dotnet/corefx#19956), we use xdg-open/open as the default programs
+ string executable = Platform.IsLinux ? "xdg-open" : /* OS X */ "open";
+ startInfo.Arguments = "\"" + startInfo.FileName + "\" " + startInfo.Arguments;
+ startInfo.FileName = executable;
+ startInfo.UseShellExecute = false;
+ }
+
try
{
- _nativeProcess = new Process();
- _nativeProcess.StartInfo = startInfo;
+ _nativeProcess = new Process() { StartInfo = startInfo };
_nativeProcess.Start();
}
catch (Win32Exception)
{
-#if CORECLR // Shell doesn't exist on OneCore, so a file cannot be associated with an executable,
- // and we cannot run an executable as 'ShellExecute' either.
- throw;
-#else
- // See if there is a file association for this command. If so
- // then we'll use that. If there's no file association, then
- // try shell execute...
+ // On Unix platforms, nothing can be further done, so just throw
+ // On headless Windows SKUs, there is no shell to fall back to, so just throw
+ if (!Platform.IsWindowsDesktop) { throw; }
+
+ // on Windows desktops, see if there is a file association for this command. If so then we'll use that.
string executable = FindExecutable(startInfo.FileName);
bool notDone = true;
if (!String.IsNullOrEmpty(executable))
@@ -495,7 +527,6 @@ namespace System.Management.Automation
throw;
}
}
-#endif
}
}
@@ -724,11 +755,7 @@ namespace System.Management.Automation
}
finally
{
- if (!_nativeProcess.StartInfo.RedirectStandardOutput)
- {
- this.Command.Context.EngineHostInterface.NotifyEndApplication();
- }
- // Do the clean up...
+ // Do some cleanup
CleanUp();
}
@@ -931,11 +958,7 @@ namespace System.Management.Automation
[ArchitectureSensitive]
private static bool IsWindowsApplication(string fileName)
{
-#if UNIX
- return false;
-#else
- if (Platform.IsNanoServer)
- return false;
+ if (!Platform.IsWindowsDesktop) { return false; }
SHFILEINFO shinfo = new SHFILEINFO();
IntPtr type = SHGetFileInfo(fileName, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_EXETYPE);
@@ -955,7 +978,6 @@ namespace System.Management.Automation
// anything else - is a windows program...
return true;
}
-#endif
}
#endregion checkForConsoleApplication
@@ -994,8 +1016,15 @@ namespace System.Management.Automation
///
private void CleanUp()
{
+ // We need to call 'NotifyEndApplication' as appropriate during cleanup
+ if (_hasNotifiedBeginApplication)
+ {
+ this.Command.Context.EngineHostInterface.NotifyEndApplication();
+ }
+
try
{
+ // Dispose the process if it's already created
if (_nativeProcess != null)
{
_nativeProcess.Dispose();
@@ -1104,11 +1133,14 @@ namespace System.Management.Automation
}
else
{
-#if CORECLR // Shell doesn't exist on OneCore, so documents cannot be associated with an application.
- // Therefore, we cannot run document directly on OneCore.
- throw InterpreterError.NewInterpreterException(this.Path, typeof(RuntimeException),
- this.Command.InvocationExtent, "CantActivateDocumentInPowerShellCore", ParserStrings.CantActivateDocumentInPowerShellCore, this.Path);
-#else
+ if (Platform.IsNanoServer || Platform.IsIoT)
+ {
+ // Shell doesn't exist on headless SKUs, so documents cannot be associated with an application.
+ // Therefore, we cannot run document in this case.
+ throw InterpreterError.NewInterpreterException(this.Path, typeof(RuntimeException),
+ this.Command.InvocationExtent, "CantActivateDocumentInPowerShellCore", ParserStrings.CantActivateDocumentInPowerShellCore, this.Path);
+ }
+
// We only want to ShellExecute something that is standalone...
if (!soloCommand)
{
@@ -1117,7 +1149,6 @@ namespace System.Management.Automation
}
startInfo.UseShellExecute = true;
-#endif
}
//For minishell value of -outoutFormat parameter depends on value of redirectOutput.
@@ -1176,7 +1207,6 @@ namespace System.Management.Automation
redirectOutput = true;
redirectError = true;
-
// Figure out if we're going to run this process "standalone" i.e. without
// redirecting anything. This is a bit tricky as we always run redirected so
// we have to see if the redirection is actually being done at the topmost level or not.
@@ -1243,11 +1273,10 @@ namespace System.Management.Automation
redirectOutput = true;
redirectError = true;
}
-#if !CORECLR // UI doesn't exist on OneCore, so all applications running on an OneCore client should be console applications.
- // The powershell on the OneCore client should already have a console attached.
- else if (IsConsoleApplication(this.Path))
+ else if (Platform.IsWindowsDesktop && IsConsoleApplication(this.Path))
{
- // Allocate a console if there isn't one attached already...
+ // On Windows desktops, if the command to run is a console application,
+ // then allocate a console if there isn't one attached already...
ConsoleVisibility.AllocateHiddenConsole();
if (ConsoleVisibility.AlwaysCaptureApplicationIO)
@@ -1256,9 +1285,8 @@ namespace System.Management.Automation
redirectError = true;
}
}
-#endif
- if (!(redirectInput || redirectOutput))
- _runStandAlone = true;
+
+ _runStandAlone = !redirectInput && !redirectOutput && !redirectError;
}
private bool ValidateExtension(string path)
@@ -1288,7 +1316,6 @@ namespace System.Management.Automation
return false;
}
-#if !UNIX // Shell doesn't exist on OneCore, so documents cannot be associated with applications.
#region Interop for FindExecutable...
// Constant used to determine the buffer size for a path
@@ -1368,7 +1395,6 @@ namespace System.Management.Automation
ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags);
#endregion
-#endif
#region Minishell Interop
@@ -1843,8 +1869,6 @@ namespace System.Management.Automation
}
}
-#if !CORECLR // There is no GUI application on OneCore, so powershell on OneCore should always have a console attached.
-
///
/// Static class that allows you to show and hide the console window
/// associated with this process.
@@ -1989,7 +2013,6 @@ namespace System.Management.Automation
}
}
}
-#endif
///
/// Exception used to wrap the error coming from
diff --git a/src/System.Management.Automation/engine/Utils.cs b/src/System.Management.Automation/engine/Utils.cs
index dd935c4ff3..0c1e39de86 100644
--- a/src/System.Management.Automation/engine/Utils.cs
+++ b/src/System.Management.Automation/engine/Utils.cs
@@ -1494,242 +1494,4 @@ namespace System.Management.Automation.Internal
}
}
}
-
-#if !UNIX
- ///
- /// Helper to start process using ShellExecuteEx. This is used only in PowerShell Core on Full Windows.
- ///
- internal class ShellExecuteHelper
- {
- private NativeMethods.ShellExecuteInfo _executeInfo;
- private int _errorCode;
- private bool _succeeded;
-
- ///
- /// Constructor for ShellExecuteHelper
- ///
- private ShellExecuteHelper(NativeMethods.ShellExecuteInfo executeInfo) { _executeInfo = executeInfo; }
-
- ///
- /// Start a process using ShellExecuteEx with default settings about WindowStyle and Verb.
- ///
- internal static Process Start(ProcessStartInfo startInfo)
- {
- return Start(startInfo, ProcessWindowStyle.Normal, string.Empty);
- }
-
- ///
- /// Start a process using ShellExecuteEx
- ///
- internal static Process Start(ProcessStartInfo startInfo, ProcessWindowStyle windowStyle, string verb)
- {
- var shellExecuteInfo = new NativeMethods.ShellExecuteInfo();
- shellExecuteInfo.fMask = NativeMethods.SEE_MASK_NOCLOSEPROCESS;
- shellExecuteInfo.fMask |= NativeMethods.SEE_MASK_FLAG_NO_UI;
-
- switch (windowStyle)
- {
- case ProcessWindowStyle.Hidden:
- shellExecuteInfo.nShow = NativeMethods.SW_HIDE;
- break;
- case ProcessWindowStyle.Minimized:
- shellExecuteInfo.nShow = NativeMethods.SW_SHOWMINIMIZED;
- break;
- case ProcessWindowStyle.Maximized:
- shellExecuteInfo.nShow = NativeMethods.SW_SHOWMAXIMIZED;
- break;
- default:
- shellExecuteInfo.nShow = NativeMethods.SW_SHOWNORMAL;
- break;
- }
-
- try
- {
- if (startInfo.FileName.Length != 0)
- shellExecuteInfo.lpFile = Marshal.StringToHGlobalUni(startInfo.FileName);
- if (!string.IsNullOrEmpty(verb))
- shellExecuteInfo.lpVerb = Marshal.StringToHGlobalUni(verb);
- if (startInfo.Arguments.Length != 0)
- shellExecuteInfo.lpParameters = Marshal.StringToHGlobalUni(startInfo.Arguments);
- if (startInfo.WorkingDirectory.Length != 0)
- shellExecuteInfo.lpDirectory = Marshal.StringToHGlobalUni(startInfo.WorkingDirectory);
-
- shellExecuteInfo.fMask |= NativeMethods.SEE_MASK_FLAG_DDEWAIT;
- ShellExecuteHelper helper = new ShellExecuteHelper(shellExecuteInfo);
- if (!helper.ExecuteOnSTAThread())
- {
- if(helper.ErrorCode == NativeMethods.ERROR_BAD_EXE_FORMAT || helper.ErrorCode == NativeMethods.ERROR_EXE_MACHINE_TYPE_MISMATCH)
- {
- throw new Win32Exception(helper.ErrorCode, "InvalidApplication");
- }
- else
- {
- throw new Win32Exception(helper.ErrorCode);
- }
- }
- }
- finally
- {
- if (shellExecuteInfo.lpFile != (IntPtr)0) Marshal.FreeHGlobal(shellExecuteInfo.lpFile);
- if (shellExecuteInfo.lpVerb != (IntPtr)0) Marshal.FreeHGlobal(shellExecuteInfo.lpVerb);
- if (shellExecuteInfo.lpParameters != (IntPtr)0) Marshal.FreeHGlobal(shellExecuteInfo.lpParameters);
- if (shellExecuteInfo.lpDirectory != (IntPtr)0) Marshal.FreeHGlobal(shellExecuteInfo.lpDirectory);
- }
-
- Process processToReturn = null;
- if (shellExecuteInfo.hProcess != IntPtr.Zero)
- {
- var handle = new SafeProcessHandle(shellExecuteInfo.hProcess, true);
- try {
- int processId = GetProcessIdFromHandle(handle);
- processToReturn = Process.GetProcessById(processId);
- } finally {
- handle.Dispose();
- }
- }
-
- 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)
- {
- NativeMethods.NtProcessBasicInfo info = new NativeMethods.NtProcessBasicInfo();
- int status = NativeMethods.NtQueryInformationProcess(processHandle, NativeMethods.NtQueryProcessBasicInfo, info, (int)Marshal.SizeOf(info), null);
- if (status != 0) {
- throw new InvalidOperationException("CantGetProcessId", new Win32Exception(status));
- }
- // We should change the signature of this function and ID property in process class.
- return info.UniqueProcessId.ToInt32();
- }
-
- private static class NativeMethods
- {
- public const int SEE_MASK_NOCLOSEPROCESS = 0x00000040;
- public const int SEE_MASK_FLAG_NO_UI = 0x00000400;
- public const int SEE_MASK_FLAG_DDEWAIT = 0x00000100;
-
- public const int SW_HIDE = 0;
- public const int SW_SHOWMINIMIZED = 2;
- public const int SW_SHOWMAXIMIZED = 3;
- public const int SW_SHOWNORMAL = 1;
-
- public const int SE_ERR_FNF = 2;
- public const int SE_ERR_PNF = 3;
- public const int SE_ERR_ACCESSDENIED = 5;
- public const int SE_ERR_OOM = 8;
- public const int SE_ERR_DLLNOTFOUND = 32;
- public const int SE_ERR_SHARE = 26;
- public const int SE_ERR_DDETIMEOUT = 28;
- public const int SE_ERR_DDEFAIL = 29;
- public const int SE_ERR_DDEBUSY = 30;
- public const int SE_ERR_NOASSOC = 31;
-
- public const int ERROR_FILE_NOT_FOUND = 2;
- public const int ERROR_PATH_NOT_FOUND = 3;
- public const int ERROR_ACCESS_DENIED = 5;
- public const int ERROR_NOT_ENOUGH_MEMORY = 8;
- public const int ERROR_SHARING_VIOLATION = 32;
- public const int ERROR_OPERATION_ABORTED = 995;
- public const int ERROR_NO_ASSOCIATION = 1155;
- public const int ERROR_DLL_NOT_FOUND = 1157;
- public const int ERROR_DDE_FAIL = 1156;
-
- public const int ERROR_BAD_EXE_FORMAT = 193;
- public const int ERROR_EXE_MACHINE_TYPE_MISMATCH = 216;
-
- public const int NtQueryProcessBasicInfo = 0;
-
- [StructLayout(LayoutKind.Sequential)]
- internal class ShellExecuteInfo
- {
- public int cbSize = 0;
- public int fMask = 0;
- public IntPtr hwnd = (IntPtr)0;
- public IntPtr lpVerb = (IntPtr)0;
- public IntPtr lpFile = (IntPtr)0;
- public IntPtr lpParameters = (IntPtr)0;
- public IntPtr lpDirectory = (IntPtr)0;
- public int nShow = 0;
- public IntPtr hInstApp = (IntPtr)0;
- public IntPtr lpIDList = (IntPtr)0;
- public IntPtr lpClass = (IntPtr)0;
- public IntPtr hkeyClass = (IntPtr)0;
- public int dwHotKey = 0;
- public IntPtr hIcon = (IntPtr)0;
- public IntPtr hProcess = (IntPtr)0;
-
- public ShellExecuteInfo()
- {
- cbSize = Marshal.SizeOf(this);
- }
- }
-
- [StructLayout(LayoutKind.Sequential)]
- internal class NtProcessBasicInfo {
- public int ExitStatus = 0;
- public IntPtr PebBaseAddress = (IntPtr)0;
- public IntPtr AffinityMask = (IntPtr)0;
- public int BasePriority = 0;
- public IntPtr UniqueProcessId = (IntPtr)0;
- public IntPtr InheritedFromUniqueProcessId = (IntPtr)0;
- }
-
- [DllImport("Shell32", CharSet=CharSet.Unicode, SetLastError=true)]
- public static extern bool ShellExecuteEx(ShellExecuteInfo info);
-
- [DllImport("Ntdll", CharSet=CharSet.Unicode)]
- public static extern int NtQueryInformationProcess(SafeProcessHandle processHandle, int query, NtProcessBasicInfo info, int size, int[] returnedSize);
- }
- }
-#endif
}
diff --git a/src/System.Management.Automation/help/HelpCommands.cs b/src/System.Management.Automation/help/HelpCommands.cs
index 1b39db326c..eedaf0ee67 100644
--- a/src/System.Management.Automation/help/HelpCommands.cs
+++ b/src/System.Management.Automation/help/HelpCommands.cs
@@ -642,7 +642,7 @@ namespace Microsoft.PowerShell.Commands
browserProcess.StartInfo.FileName = Platform.IsLinux ? "xdg-open" : /* OS X */ "open";
browserProcess.StartInfo.Arguments = uriToLaunch.OriginalString;
browserProcess.Start();
-#elif CORECLR
+#else
if (Platform.IsNanoServer || Platform.IsIoT)
{
// We cannot open the URL in browser on headless SKUs.
@@ -653,11 +653,9 @@ namespace Microsoft.PowerShell.Commands
{
// We can call ShellExecute directly on Full Windows.
browserProcess.StartInfo.FileName = uriToLaunch.OriginalString;
- ShellExecuteHelper.Start(browserProcess.StartInfo);
+ browserProcess.StartInfo.UseShellExecute = true;
+ browserProcess.Start();
}
-#else
- browserProcess.StartInfo.FileName = uriToLaunch.OriginalString;
- browserProcess.Start();
#endif
}
catch (InvalidOperationException ioe)
diff --git a/src/System.Management.Automation/namespaces/FileSystemProvider.cs b/src/System.Management.Automation/namespaces/FileSystemProvider.cs
index 7205120bdd..4e38dbdf45 100644
--- a/src/System.Management.Automation/namespaces/FileSystemProvider.cs
+++ b/src/System.Management.Automation/namespaces/FileSystemProvider.cs
@@ -1307,14 +1307,6 @@ namespace Microsoft.PowerShell.Commands
///
protected override void InvokeDefaultAction(string path)
{
-#if UNIX
- // Error code 13 -- Permission denied
- const int NOT_EXECUTABLE = 13;
-#else
- // Error code 193 -- BAD_EXE_FORMAT (not a valid Win32 application)
- const int NOT_EXECUTABLE = 193;
-#endif
-
if (String.IsNullOrEmpty(path))
{
throw PSTraceSource.NewArgumentException("path");
@@ -1330,11 +1322,11 @@ namespace Microsoft.PowerShell.Commands
{
var invokeProcess = new System.Diagnostics.Process();
invokeProcess.StartInfo.FileName = path;
+#if UNIX
bool invokeDefaultProgram = false;
-
- if (Directory.Exists(path) && !Platform.IsNanoServer && !Platform.IsIoT)
+ if (Directory.Exists(path))
{
- // Path points to a directory and it's not NanoServer or IoT, so we can opne the file explorer
+ // Path points to a directory. We have to use xdg-open/open on Linux/OSX.
invokeDefaultProgram = true;
}
else
@@ -1344,18 +1336,16 @@ namespace Microsoft.PowerShell.Commands
// Try Process.Start first. This works for executables on Win/Unix platforms
invokeProcess.Start();
}
- catch (Win32Exception ex) when (ex.NativeErrorCode == NOT_EXECUTABLE)
+ catch (Win32Exception ex) when (ex.NativeErrorCode == 13)
{
- // The file is possibly not an executable. If it's headless SKUs, rethrow.
- if (Platform.IsNanoServer || Platform.IsIoT) { throw; }
- // Otherwise, try invoking the default program that handles this file.
+ // Error code 13 -- Permission denied
+ // The file is possibly not an executable. We try xdg-open/open on Linux/OSX.
invokeDefaultProgram = true;
}
}
if (invokeDefaultProgram)
{
-#if UNIX
const string quoteFormat = "\"{0}\"";
invokeProcess.StartInfo.FileName = Platform.IsLinux ? "xdg-open" : /* OS X */ "open";
if (NativeCommandParameterBinder.NeedQuotes(path))
@@ -1364,10 +1354,12 @@ namespace Microsoft.PowerShell.Commands
}
invokeProcess.StartInfo.Arguments = path;
invokeProcess.Start();
-#else
- ShellExecuteHelper.Start(invokeProcess.StartInfo);
-#endif
}
+#else
+ // Use ShellExecute when it's not a headless SKU
+ invokeProcess.StartInfo.UseShellExecute = Platform.IsWindowsDesktop;
+ invokeProcess.Start();
+#endif
}
} // InvokeDefaultAction
diff --git a/test/powershell/Language/Scripting/NativeExecution/NativeCommandProcessor.Tests.ps1 b/test/powershell/Language/Scripting/NativeExecution/NativeCommandProcessor.Tests.ps1
index 24678d46d6..f09c672743 100644
--- a/test/powershell/Language/Scripting/NativeExecution/NativeCommandProcessor.Tests.ps1
+++ b/test/powershell/Language/Scripting/NativeExecution/NativeCommandProcessor.Tests.ps1
@@ -119,5 +119,102 @@ Describe "Native Command Processor" -tags "Feature" {
$ps.Dispose()
}
}
+}
+Describe "Open a text file with NativeCommandProcessor" -tags @("Feature", "RequireAdminOnWindows") {
+ BeforeAll {
+ if ($IsWindows) {
+ $TestFile = Join-Path -Path $TestDrive -ChildPath "TextFileTest.foo"
+ } else {
+ $TestFile = Join-Path -Path $TestDrive -ChildPath "TextFileTest.txt"
+ }
+ Set-Content -Path $TestFile -Value "Hello" -Force
+ $supportedEnvironment = $true
+
+ if ($IsLinux) {
+ $appFolder = "$HOME/.local/share/applications"
+ $supportedEnvironment = Test-Path $appFolder
+ if ($supportedEnvironment) {
+ $mimeDefault = xdg-mime query default text/plain
+ Remove-Item $HOME/nativeCommandProcessor.Success -Force -ErrorAction SilentlyContinue
+ Set-Content -Path "$appFolder/nativeCommandProcessor.desktop" -Force -Value @"
+[Desktop Entry]
+Version=1.0
+Name=nativeCommandProcessor
+Comment=Validate_native_command_processor_open_text_file
+Exec=/bin/sh -c 'echo %u > ~/nativeCommandProcessor.Success'
+Icon=utilities-terminal
+Terminal=true
+Type=Application
+Categories=Application;
+"@
+ xdg-mime default nativeCommandProcessor.desktop text/plain
+ }
+ }
+ elseif ($IsWindows) {
+ $supportedEnvironment = [System.Management.Automation.Platform]::IsWindowsDesktop
+ if ($supportedEnvironment) {
+ cmd /c assoc .foo=foofile
+ cmd /c ftype foofile=cmd /c echo %1^> $TestDrive\foo.txt
+ Remove-Item $TestDrive\foo.txt -Force -ErrorAction SilentlyContinue
+ }
+ }
+ }
+
+ AfterAll {
+ Remove-Item -Path $TestFile -Force -ErrorAction SilentlyContinue
+
+ if ($IsLinux -and $supportedEnvironment) {
+ xdg-mime default $mimeDefault text/plain
+ Remove-Item $appFolder/nativeCommandProcessor.desktop -Force -ErrorAction SilentlyContinue
+ Remove-Item $HOME/nativeCommandProcessor.Success -Force -ErrorAction SilentlyContinue
+ }
+ elseif ($IsWindows -and $supportedEnvironment) {
+ cmd /c assoc .foo=
+ cmd /c ftype foofile=
+ }
+ }
+
+ It "Should open text file without error" -Skip:(!$supportedEnvironment) {
+ if ($IsOSX) {
+ $expectedTitle = Split-Path $TestFile -Leaf
+ open -F -a TextEdit
+ $beforeCount = [int]('tell application "TextEdit" to count of windows' | osascript)
+ & $TestFile
+ $startTime = Get-Date
+ $title = [String]::Empty
+ while (((Get-Date) - $startTime).TotalSeconds -lt 30 -and ($title -ne $expectedTitle)) {
+ Start-Sleep -Milliseconds 100
+ $title = 'tell application "TextEdit" to get name of front window' | osascript
+ }
+ $afterCount = [int]('tell application "TextEdit" to count of windows' | osascript)
+ $afterCount | Should Be ($beforeCount + 1)
+ $title | Should Be $expectedTitle
+ "tell application ""TextEdit"" to close window ""$expectedTitle""" | osascript
+ 'tell application "TextEdit" to quit' | osascript
+ }
+ elseif ($IsLinux) {
+ # Validate on Linux by reassociating default app for text file
+ & $TestFile
+ $startTime = Get-Date
+ # It may take time for handler to start
+ while (((Get-Date) - $startTime).TotalSeconds -lt 10 -and (-not (Test-Path "$HOME/nativeCommandProcessor.Success"))) {
+ Start-Sleep -Milliseconds 100
+ }
+ Get-Content $HOME/nativeCommandProcessor.Success | Should Be $TestFile
+ }
+ else {
+ & $TestFile
+ $startTime = Get-Date
+ while (((Get-Date) - $startTime).TotalSeconds -lt 10 -and (!(Test-Path $TestDrive\foo.txt))) {
+ Start-Sleep -Milliseconds 100
+ }
+ "$TestDrive\foo.txt" | Should Exist
+ Get-Content $TestDrive\foo.txt | Should BeExactly $TestFile
+ }
+ }
+
+ It "Opening a file with an unregistered extension on Windows should fail" -Skip:(!$IsWindows) {
+ { $dllFile = "$PSHOME\System.Management.Automation.dll"; & $dllFile } | ShouldBeErrorId "NativeCommandFailed"
+ }
}
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Invoke-Item.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Invoke-Item.Tests.ps1
index 2a1fc0f25f..ce97092b8c 100644
--- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Invoke-Item.Tests.ps1
+++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Invoke-Item.Tests.ps1
@@ -51,17 +51,31 @@ Describe "Invoke-Item basic tests" -Tags "Feature" {
}
It "Should invoke an executable file without error" {
- $executable = Get-Command "ping" -CommandType Application | ForEach-Object Source
+ $ping = Get-Command "ping" -CommandType Application | ForEach-Object Source
$redirectFile = Join-Path -Path $TestDrive -ChildPath "redirect2.txt"
if ($IsWindows) {
- ## 'ping.exe' on Windows writes out usage to stdout.
- & $powershell -noprofile -c "Invoke-Item '$executable'" > $redirectFile
+ if ([System.Management.Automation.Platform]::IsNanoServer -or [System.Management.Automation.Platform]::IsIoT) {
+ ## On headless SKUs, we use `UseShellExecute = false`
+ ## 'ping.exe' on Windows writes out usage to stdout.
+ & $powershell -noprofile -c "Invoke-Item '$ping'" > $redirectFile
+ Get-Content $redirectFile -Raw | Should Match "usage: ping"
+ } else {
+ ## On full desktop, we use `UseShellExecute = true` to align with Windows PowerShell
+ $notepad = Get-Command "notepad.exe" -CommandType Application | ForEach-Object Source
+ $notepadProcessName = "notepad"
+ Get-Process -Name $notepadProcessName | Stop-Process -Force
+ Invoke-Item -Path $notepad
+ $notepadProcess = Get-Process -Name $notepadProcessName
+ $notepadProcess.Name | Should Be $notepadProcessName
+ Stop-Process -InputObject $notepadProcess
+ }
} else {
+ ## On Unix, we use `UseShellExecute = false`
## 'ping' on Unix write out usage to stderr
- & $powershell -noprofile -c "Invoke-Item '$executable'" 2> $redirectFile
+ & $powershell -noprofile -c "Invoke-Item '$ping'" 2> $redirectFile
+ Get-Content $redirectFile -Raw | Should Match "usage: ping"
}
- Get-Content $redirectFile -Raw | Should Match "usage: ping"
}
Context "Invoke a folder" {