diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs
index 5ccf67c46c..d0264581fc 100644
--- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs
+++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs
@@ -1542,6 +1542,18 @@ 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
///
@@ -1692,13 +1704,9 @@ namespace Microsoft.PowerShell.Commands
[ValidateNotNullOrEmpty]
public string Verb { get; set; }
-#if !CORECLR
///
/// Window style of the process window
///
- ///
- /// The 'WindowStyle' is not supported in CoreCLR
- ///
[Parameter]
[ValidateNotNullOrEmpty]
public ProcessWindowStyle WindowStyle
@@ -1712,7 +1720,6 @@ namespace Microsoft.PowerShell.Commands
}
private ProcessWindowStyle _windowstyle = ProcessWindowStyle.Normal;
private bool _windowstyleSpecified = false;
-#endif
///
/// wait for th eprocess to terminate
@@ -1735,29 +1742,49 @@ namespace Microsoft.PowerShell.Commands
}
private SwitchParameter _UseNewEnvironment;
- private StreamWriter _outputWriter;
- private StreamWriter _errorWriter;
-
#endregion
#region overrides
///
- ///
+ /// BeginProcessing
///
protected override void BeginProcessing()
{
-#if CORECLR
- if(this.ParameterSetName.Equals("UseShellExecute"))
+ string message = string.Empty;
+
+ // -Verb and -WindowStyle are not supported on non-Windows platforms as well as Windows headless SKUs
+ if (_isOnFullWinSku)
{
- String errorMessage = StringUtil.Format(ProcessResources.ParameterNotSupportedOnPSEdition, "-Verb", "Start-Process");
- ErrorRecord er = new ErrorRecord(new NotSupportedException(errorMessage), "NotSupportedException", ErrorCategory.NotImplemented, null);
- ThrowTerminatingError(er);
+ // Parameters '-NoNewWindow' and '-WindowStyle' are both valid on full windows SKUs.
+ if (_nonewwindow && _windowstyleSpecified)
+ {
+ message = StringUtil.Format(ProcessResources.ContradictParametersSpecified, "-NoNewWindow", "-WindowStyle");
+ ErrorRecord er = new ErrorRecord(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null);
+ WriteError(er);
+ return;
+ }
}
-#endif
+ else
+ {
+ if (this.ParameterSetName.Equals("UseShellExecute"))
+ {
+ message = StringUtil.Format(ProcessResources.ParameterNotSupportedOnPSEdition, "-Verb", "Start-Process");
+ }
+ else if (_windowstyleSpecified)
+ {
+ message = StringUtil.Format(ProcessResources.ParameterNotSupportedOnPSEdition, "-WindowStyle", "Start-Process");
+ }
+
+ if (!string.IsNullOrEmpty(message))
+ {
+ ErrorRecord er = new ErrorRecord(new NotSupportedException(message), "NotSupportedException", ErrorCategory.NotImplemented, null);
+ ThrowTerminatingError(er);
+ }
+ }
+
//create an instance of the ProcessStartInfo Class
ProcessStartInfo startInfo = new ProcessStartInfo();
- string message = String.Empty;
//Path = Mandatory parameter -> Will not be empty.
try
@@ -1809,6 +1836,7 @@ namespace Microsoft.PowerShell.Commands
{
if (_isDefaultSetParameterSpecified)
{
+ _useShellExecute = false;
startInfo.UseShellExecute = false;
}
@@ -1819,16 +1847,7 @@ namespace Microsoft.PowerShell.Commands
LoadEnvironmentVariable(startInfo, Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Machine));
LoadEnvironmentVariable(startInfo, Environment.GetEnvironmentVariables(EnvironmentVariableTarget.User));
}
-
-#if !CORECLR // 'WindowStyle' not supported in CoreCLR
- if (_nonewwindow && _windowstyleSpecified)
- {
- message = StringUtil.Format(ProcessResources.ContradictParametersSpecified, "-NoNewWindow", "-WindowStyle");
- ErrorRecord er = new ErrorRecord(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null);
- WriteError(er);
- return;
- }
-
+#if !CORECLR
//WindowStyle
startInfo.WindowStyle = _windowstyle;
#endif
@@ -1837,13 +1856,10 @@ namespace Microsoft.PowerShell.Commands
{
startInfo.CreateNoWindow = _nonewwindow;
}
-
+#if !UNIX
//LoadUserProfile.
- if (Platform.IsWindows)
- {
- startInfo.LoadUserProfile = _loaduserprofile;
- }
-
+ startInfo.LoadUserProfile = _loaduserprofile;
+#endif
if (_credential != null)
{
//Gets NetworkCredentials
@@ -1919,47 +1935,19 @@ namespace Microsoft.PowerShell.Commands
}
}
}
-#if !CORECLR // 'UseShellExecute' is not supported in CoreCLR
+#if !CORECLR // Properties 'Verb' and 'WindowStyle' are missing in CoreCLR
else if (ParameterSetName.Equals("UseShellExecute"))
{
- startInfo.UseShellExecute = true;
//Verb
- if (Verb != null)
- {
- startInfo.Verb = Verb;
- }
-
+ if (Verb != null) { startInfo.Verb = Verb; }
//WindowStyle
startInfo.WindowStyle = _windowstyle;
}
#endif
//Starts the Process
- Process process;
- if (Platform.IsWindows)
- {
- process = start(startInfo);
- }
- else
- {
- process = new Process();
- process.StartInfo = startInfo;
- SetupInputOutputRedirection(process);
- process.Start();
- if (process.StartInfo.RedirectStandardOutput)
- {
- process.BeginOutputReadLine();
- }
- if (process.StartInfo.RedirectStandardError)
- {
- process.BeginErrorReadLine();
- }
- if (process.StartInfo.RedirectStandardInput)
- {
- WriteToStandardInput(process);
- }
- }
- //Wait and Passthru Implementation.
+ Process process = Start(startInfo);
+ //Wait and Passthru Implementation.
if (PassThru.IsPresent)
{
if (process != null)
@@ -1980,29 +1968,26 @@ namespace Microsoft.PowerShell.Commands
{
if (!process.HasExited)
{
- if (Platform.IsWindows)
- {
- _waithandle = new ManualResetEvent(false);
+#if UNIX
+ process.WaitForExit();
+#else
+ _waithandle = new ManualResetEvent(false);
- // Create and start the job object
- ProcessCollection jobObject = new ProcessCollection();
- if (jobObject.AssignProcessToJobObject(process))
- {
- // Wait for the job object to finish
- jobObject.WaitOne(_waithandle);
- }
- else if (!process.HasExited)
- {
- // WinBlue: 27537 Start-Process -Wait doesn't work in a remote session on Windows 7 or lower.
- process.Exited += new EventHandler(myProcess_Exited);
- process.EnableRaisingEvents = true;
- process.WaitForExit();
- }
- }
- else
+ // Create and start the job object
+ ProcessCollection jobObject = new ProcessCollection();
+ if (jobObject.AssignProcessToJobObject(process))
{
+ // Wait for the job object to finish
+ jobObject.WaitOne(_waithandle);
+ }
+ else if (!process.HasExited)
+ {
+ // WinBlue: 27537 Start-Process -Wait doesn't work in a remote session on Windows 7 or lower.
+ process.Exited += new EventHandler(myProcess_Exited);
+ process.EnableRaisingEvents = true;
process.WaitForExit();
}
+#endif
}
}
else
@@ -2024,11 +2009,35 @@ namespace Microsoft.PowerShell.Commands
}
}
+ #endregion
+
+ #region IDisposable Overrides
+
+ ///
+ /// Dispose WaitHandle used to honor -Wait parameter
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ System.GC.SuppressFinalize(this);
+ }
+
+ private void Dispose(bool isDisposing)
+ {
+ if (_waithandle != null)
+ {
+ _waithandle.Dispose();
+ _waithandle = null;
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
///
/// When Process exits the wait handle is set.
///
- ///
- ///
private void myProcess_Exited(object sender, System.EventArgs e)
{
if (_waithandle != null)
@@ -2037,7 +2046,11 @@ namespace Microsoft.PowerShell.Commands
}
}
- #region Private Methods
+ private string ResolveFilePath(string path)
+ {
+ string filepath = PathUtils.ResolveFilePath(path, this);
+ return filepath;
+ }
private void LoadEnvironmentVariable(ProcessStartInfo startinfo, IDictionary EnvironmentVariables)
{
@@ -2059,6 +2072,43 @@ namespace Microsoft.PowerShell.Commands
}
}
+ private Process Start(ProcessStartInfo startInfo)
+ {
+#if UNIX
+ Process process = new Process() { StartInfo = startInfo };
+ SetupInputOutputRedirection(process);
+ process.Start();
+ if (process.StartInfo.RedirectStandardOutput)
+ {
+ process.BeginOutputReadLine();
+ }
+ if (process.StartInfo.RedirectStandardError)
+ {
+ process.BeginErrorReadLine();
+ }
+ if (process.StartInfo.RedirectStandardInput)
+ {
+ WriteToStandardInput(process);
+ }
+ return process;
+#else
+ Process process = null;
+ if (_useShellExecute)
+ {
+ process = StartWithShellExecute(startInfo);
+ }
+ else
+ {
+ process = StartWithCreateProcess(startInfo);
+ }
+ return process;
+#endif
+ }
+
+#if UNIX
+ private StreamWriter _outputWriter;
+ private StreamWriter _errorWriter;
+
private void StdOutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
if (!String.IsNullOrEmpty(outLine.Data))
@@ -2156,7 +2206,89 @@ namespace Microsoft.PowerShell.Commands
}
writer.Dispose();
}
+#else
+ private SafeFileHandle GetSafeFileHandleForRedirection(string RedirectionPath, uint dwCreationDisposition)
+ {
+ System.IntPtr hFileHandle = System.IntPtr.Zero;
+ ProcessNativeMethods.SECURITY_ATTRIBUTES lpSecurityAttributes = new ProcessNativeMethods.SECURITY_ATTRIBUTES();
+
+ hFileHandle = ProcessNativeMethods.CreateFileW(RedirectionPath,
+ ProcessNativeMethods.GENERIC_READ | ProcessNativeMethods.GENERIC_WRITE,
+ ProcessNativeMethods.FILE_SHARE_WRITE | ProcessNativeMethods.FILE_SHARE_READ,
+ lpSecurityAttributes,
+ dwCreationDisposition,
+ ProcessNativeMethods.FILE_ATTRIBUTE_NORMAL,
+ System.IntPtr.Zero);
+ if (hFileHandle == System.IntPtr.Zero)
+ {
+ int error = Marshal.GetLastWin32Error();
+ Win32Exception win32ex = new Win32Exception(error);
+ string message = StringUtil.Format(ProcessResources.InvalidStartProcess, win32ex.Message);
+ ErrorRecord er = new ErrorRecord(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null);
+ ThrowTerminatingError(er);
+ }
+ SafeFileHandle sf = new SafeFileHandle(hFileHandle, true);
+ return sf;
+ }
+
+ private static StringBuilder BuildCommandLine(string executableFileName, string arguments)
+ {
+ StringBuilder builder = new StringBuilder();
+ string str = executableFileName.Trim();
+ bool flag = str.StartsWith("\"", StringComparison.Ordinal) && str.EndsWith("\"", StringComparison.Ordinal);
+ if (!flag)
+ {
+ builder.Append("\"");
+ }
+ builder.Append(str);
+ if (!flag)
+ {
+ builder.Append("\"");
+ }
+ if (!string.IsNullOrEmpty(arguments))
+ {
+ builder.Append(" ");
+ builder.Append(arguments);
+ }
+ return builder;
+ }
+
+ private static byte[] ConvertEnvVarsToByteArray(
+#if CORECLR
+ IDictionary sd)
+#else
+ StringDictionary sd)
+#endif
+ {
+ string[] array = new string[sd.Count];
+ byte[] bytes = null;
+ sd.Keys.CopyTo(array, 0);
+ string[] strArray2 = new string[sd.Count];
+ sd.Values.CopyTo(strArray2, 0);
+ Array.Sort(array, strArray2, StringComparer.OrdinalIgnoreCase);
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < sd.Count; i++)
+ {
+ builder.Append(array[i]);//
+ builder.Append('=');
+ builder.Append(strArray2[i]);
+ builder.Append('\0');
+ }
+ builder.Append('\0');
+
+ // Use Unicode encoding
+ bytes = Encoding.Unicode.GetBytes(builder.ToString());
+ if (bytes.Length > 0xffff)
+ {
+ throw new InvalidOperationException("EnvironmentBlockTooLong");
+ }
+ return bytes;
+ }
+
+ ///
+ /// This method will be used on all windows platforms, both full desktop and headless SKUs.
+ ///
private Process StartWithCreateProcess(ProcessStartInfo startinfo)
{
ProcessNativeMethods.STARTUPINFO lpStartupInfo = new ProcessNativeMethods.STARTUPINFO();
@@ -2220,11 +2352,8 @@ namespace Microsoft.PowerShell.Commands
//STARTF_USESHOWWINDOW
lpStartupInfo.dwFlags |= 0x00000001;
-#if CORECLR
- //SW_SHOWNORMAL
- lpStartupInfo.wShowWindow = 1;
-#else
- switch (startinfo.WindowStyle)
+ // On headless SKUs like NanoServer and IoT, window style can only be the default value 'Normal'.
+ switch (WindowStyle)
{
case ProcessWindowStyle.Normal:
//SW_SHOWNORMAL
@@ -2243,7 +2372,6 @@ namespace Microsoft.PowerShell.Commands
lpStartupInfo.wShowWindow = 0;
break;
}
-#endif
}
// Create the new process suspended so we have a chance to get a corresponding Process object in case it terminates quickly.
@@ -2255,13 +2383,9 @@ namespace Microsoft.PowerShell.Commands
{
if (this.UseNewEnvironment)
{
- bool unicode = false;
- if (ProcessManager.IsNt)
- {
- creationFlags |= 0x400;
- unicode = true;
- }
- pinnedEnvironmentBlock = GCHandle.Alloc(EnvironmentBlock.ToByteArray(environmentVars, unicode), GCHandleType.Pinned);
+ // All Windows Operating Systems that we support are Windows NT systems, so we use Unicode for environment.
+ creationFlags |= 0x400;
+ pinnedEnvironmentBlock = GCHandle.Alloc(ConvertEnvVarsToByteArray(environmentVars), GCHandleType.Pinned);
AddressOfEnvironmentBlock = pinnedEnvironmentBlock.AddrOfPinnedObject();
}
}
@@ -2353,176 +2477,33 @@ namespace Microsoft.PowerShell.Commands
}
}
+ ///
+ /// This method will be used only on Windows full desktop.
+ ///
private Process StartWithShellExecute(ProcessStartInfo startInfo)
{
- string message = String.Empty;
Process result = null;
try
{
+#if CORECLR
+ result = ShellExecuteHelper.Start(startInfo, WindowStyle, Verb);
+#else
result = Process.Start(startInfo);
+#endif
}
catch (Win32Exception ex)
{
- message = StringUtil.Format(ProcessResources.InvalidStartProcess, ex.Message);
+ string message = StringUtil.Format(ProcessResources.InvalidStartProcess, ex.Message);
ErrorRecord er = new ErrorRecord(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null);
ThrowTerminatingError(er);
}
- return result;
+ return result;
}
-
- private Process start(ProcessStartInfo startInfo)
- {
- Process process = null;
- if (startInfo.UseShellExecute)
- {
- process = StartWithShellExecute(startInfo);
- }
- else
- {
- process = StartWithCreateProcess(startInfo);
- }
- return process;
- }
- #endregion
-
- #endregion
-
- #region IDisposable Overrides
-
- ///
- /// Dispose WaitHandle used to honor -Wait parameter
- ///
- public void Dispose()
- {
- Dispose(true);
- System.GC.SuppressFinalize(this);
- }
-
- private void Dispose(bool isDisposing)
- {
- if (_waithandle != null)
- {
- _waithandle.Dispose();
- _waithandle = null;
- }
- }
-
- #endregion
-
- #region Private Methods
-
- private string ResolveFilePath(string path)
- {
- string filepath = PathUtils.ResolveFilePath(path, this);
- return filepath;
- }
-
- private SafeFileHandle GetSafeFileHandleForRedirection(string RedirectionPath, uint dwCreationDisposition)
- {
- System.IntPtr hFileHandle = System.IntPtr.Zero;
- ProcessNativeMethods.SECURITY_ATTRIBUTES lpSecurityAttributes = new ProcessNativeMethods.SECURITY_ATTRIBUTES();
-
-
- hFileHandle = ProcessNativeMethods.CreateFileW(RedirectionPath,
- ProcessNativeMethods.GENERIC_READ | ProcessNativeMethods.GENERIC_WRITE,
- ProcessNativeMethods.FILE_SHARE_WRITE | ProcessNativeMethods.FILE_SHARE_READ,
- lpSecurityAttributes,
- dwCreationDisposition,
- ProcessNativeMethods.FILE_ATTRIBUTE_NORMAL,
- System.IntPtr.Zero);
- if (hFileHandle == System.IntPtr.Zero)
- {
- int error = Marshal.GetLastWin32Error();
- Win32Exception win32ex = new Win32Exception(error);
- string message = StringUtil.Format(ProcessResources.InvalidStartProcess, win32ex.Message);
- ErrorRecord er = new ErrorRecord(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null);
- ThrowTerminatingError(er);
- }
- SafeFileHandle sf = new SafeFileHandle(hFileHandle, true);
- return sf;
- }
-
- internal static class ProcessManager
- {
- // Properties
- public static bool IsNt
- {
- get
- {
-#if CORECLR
- return true;
-#else
- return (Environment.OSVersion.Platform == PlatformID.Win32NT);
#endif
- }
- }
- }
-
- internal static class EnvironmentBlock
- {
-#if CORECLR
- public static byte[] ToByteArray(IDictionary sd, bool unicode)
-#else
- public static byte[] ToByteArray(StringDictionary sd, bool unicode)
-#endif
- {
- string[] array = new string[sd.Count];
- byte[] bytes = null;
- sd.Keys.CopyTo(array, 0);
- string[] strArray2 = new string[sd.Count];
- sd.Values.CopyTo(strArray2, 0);
- Array.Sort(array, strArray2, StringComparer.OrdinalIgnoreCase);
- StringBuilder builder = new StringBuilder();
- for (int i = 0; i < sd.Count; i++)
- {
- builder.Append(array[i]);//
- builder.Append('=');
- builder.Append(strArray2[i]);
- builder.Append('\0');
- }
- builder.Append('\0');
- if (unicode)
- {
- bytes = Encoding.Unicode.GetBytes(builder.ToString());
- }
- else
- {
- bytes = ClrFacade.GetDefaultEncoding().GetBytes(builder.ToString());
- }
- if (bytes.Length > 0xffff)
- {
- throw new InvalidOperationException("EnvironmentBlockTooLong");
- }
- return bytes;
- }
- }
-
-
- private static StringBuilder BuildCommandLine(string executableFileName, string arguments)
- {
- StringBuilder builder = new StringBuilder();
- string str = executableFileName.Trim();
- bool flag = str.StartsWith("\"", StringComparison.Ordinal) && str.EndsWith("\"", StringComparison.Ordinal);
- if (!flag)
- {
- builder.Append("\"");
- }
- builder.Append(str);
- if (!flag)
- {
- builder.Append("\"");
- }
- if (!string.IsNullOrEmpty(arguments))
- {
- builder.Append(" ");
- builder.Append(arguments);
- }
- return builder;
- }
-
#endregion
}
+#if !UNIX
///
/// ProcessCollection is a helper class used by Start-Process -Wait cmdlet to monitor the
/// child processes created by the main process hosted by the Start-process cmdlet.
@@ -2618,7 +2599,7 @@ namespace Microsoft.PowerShell.Commands
///
/// A variable-length array of process identifiers returned by this call.
- /// Array elements 0 through NumberOfProcessIdsInList– 1
+ /// Array elements 0 through NumberOfProcessIdsInList minus 1
/// contain valid process identifiers.
///
public IntPtr ProcessIdList;
@@ -2841,20 +2822,6 @@ namespace Microsoft.PowerShell.Commands
}
}
- [SuppressUnmanagedCodeSecurity]
- internal sealed class SafeThreadHandle : SafeHandleZeroOrMinusOneIsInvalid
- {
- // Methods
- internal SafeThreadHandle()
- : base(true)
- {
- }
- protected override bool ReleaseHandle()
- {
- return SafeNativeMethods.CloseHandle(base.handle);
- }
- }
-
[SuppressUnmanagedCodeSecurity, HostProtection(SecurityAction.LinkDemand, MayLeakOnAbort = true)]
internal sealed class SafeJobHandle : SafeHandleZeroOrMinusOneIsInvalid
{
@@ -2869,7 +2836,7 @@ namespace Microsoft.PowerShell.Commands
return SafeNativeMethods.CloseHandle(base.handle);
}
}
-
+#endif
#endregion
#region ProcessCommandException
diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs
index 9374d61c81..5fa6832883 100644
--- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs
+++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs
@@ -1,6 +1,7 @@
/********************************************************************++
Copyright (c) Microsoft Corporation. All rights reserved.
--********************************************************************/
+#if !UNIX // Not built on Unix
using System;
using System.Collections.Generic;
@@ -2419,3 +2420,4 @@ namespace Microsoft.PowerShell.Commands
#endregion NativeMethods
}
+#endif // Not built on Unix
diff --git a/src/System.Management.Automation/CoreCLR/CorePsStub.cs b/src/System.Management.Automation/CoreCLR/CorePsStub.cs
index b4bc1c57a2..8c4bc8ba89 100644
--- a/src/System.Management.Automation/CoreCLR/CorePsStub.cs
+++ b/src/System.Management.Automation/CoreCLR/CorePsStub.cs
@@ -545,6 +545,17 @@ namespace Microsoft.PowerShell.CoreClr.Stubs
NoZone = -1,
}
+ ///
+ /// Stub for ProcessWindowStyle
+ ///
+ public enum ProcessWindowStyle
+ {
+ Normal,
+ Hidden,
+ Minimized,
+ Maximized
+ }
+
///
/// Stub for MailAddress
///
diff --git a/src/System.Management.Automation/engine/Utils.cs b/src/System.Management.Automation/engine/Utils.cs
index e701738d44..f426f53941 100644
--- a/src/System.Management.Automation/engine/Utils.cs
+++ b/src/System.Management.Automation/engine/Utils.cs
@@ -20,10 +20,17 @@ using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Text;
-using System.Security.Principal;
using TypeTable = System.Management.Automation.Runspaces.TypeTable;
+
+#if CORECLR
+using System.Diagnostics;
+using Microsoft.Win32.SafeHandles;
+using Microsoft.PowerShell.CoreClr.Stubs;
+#else
+using System.Security.Principal;
using PSUtils = System.Management.Automation.PsUtils;
+#endif
namespace System.Management.Automation
{
@@ -1588,4 +1595,215 @@ namespace System.Management.Automation.Internal
}
}
}
+
+#if CORECLR && !UNIX
+ ///
+ /// Helper to start process using ShellExecuteEx. This is used only in PowerShell Core on Full Windows.
+ ///
+ internal class ShellExecuteHelper
+ {
+ ///
+ /// 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
+ ///
+ ///
+ /// 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.
+ ///
+ 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;
+
+ if (!NativeMethods.ShellExecuteEx(shellExecuteInfo))
+ {
+ int errorCode = Marshal.GetLastWin32Error();
+ if (errorCode == 0)
+ {
+ switch ((long)shellExecuteInfo.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)shellExecuteInfo.hInstApp; break;
+ }
+ }
+
+ if(errorCode == NativeMethods.ERROR_BAD_EXE_FORMAT || errorCode == NativeMethods.ERROR_EXE_MACHINE_TYPE_MISMATCH)
+ {
+ throw new Win32Exception(errorCode, "InvalidApplication");
+ }
+
+ throw new Win32Exception(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 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 3ed342367e..857dd5af5d 100644
--- a/src/System.Management.Automation/help/HelpCommands.cs
+++ b/src/System.Management.Automation/help/HelpCommands.cs
@@ -638,20 +638,17 @@ namespace Microsoft.PowerShell.Commands
browserProcess.StartInfo.Arguments = uriToLaunch.OriginalString;
browserProcess.Start();
#elif CORECLR
- // On FullCLR, ProcessStartInfo.UseShellExecute is true by default. This means that the shell will be used when starting the process.
- // On CoreCLR, UseShellExecute is not supported. To work around this, we check if there is a default browser in the system.
- // If there is, we lunch it to open the HelpURI. If there isn't, we error out.
- string webBrowserPath = GetDefaultWebBrowser();
- if (webBrowserPath == null)
+ if (Platform.IsNanoServer || Platform.IsIoT)
{
+ // We cannot open the URL in browser on headless SKUs.
wrapCaughtException = false;
exception = PSTraceSource.NewInvalidOperationException(HelpErrors.CannotLaunchURI, uriToLaunch.OriginalString);
}
else
{
- browserProcess.StartInfo = new ProcessStartInfo(webBrowserPath);
- browserProcess.StartInfo.Arguments = "\"" + uriToLaunch.OriginalString + "\"";
- browserProcess.Start();
+ // We can call ShellExecute directly on Full Windows.
+ browserProcess.StartInfo.FileName = uriToLaunch.OriginalString;
+ ShellExecuteHelper.Start(browserProcess.StartInfo);
}
#else
browserProcess.StartInfo.FileName = uriToLaunch.OriginalString;
@@ -676,46 +673,6 @@ namespace Microsoft.PowerShell.Commands
}
}
-#if !UNIX
- ///
- /// Gets the path to the default browser by querying the Windows registry.
- ///
- ///
- private string GetDefaultWebBrowser()
- {
- // Check if there is a default browser in the system.
- const string httpRegkey = @"HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice";
- object progId = Registry.GetValue(httpRegkey, "ProgId", null);
- if (progId != null)
- {
- // Query the registry to find the web browser path.
- using (RegistryKey browserRegKey = Registry.ClassesRoot.OpenSubKey(progId + "\\shell\\open\\command", false))
- {
- string browserPath = browserRegKey?.GetValue(null)?.ToString().Replace(/* remove the quotes */ "\"", "");
- if (!string.IsNullOrEmpty(browserPath))
- {
- const string exeExtension = ".exe";
- if (!browserPath.EndsWith(exeExtension, StringComparison.OrdinalIgnoreCase))
- {
- // Remove any extra chars in the path after ".exe".
- int extIndex = browserPath.LastIndexOf(exeExtension, StringComparison.OrdinalIgnoreCase);
- browserPath = extIndex > 0 ? browserPath.Substring(0, extIndex + exeExtension.Length) : string.Empty;
- }
-
- // Make sure the path to the default browser exists.
- if (File.Exists(browserPath))
- {
- return browserPath;
- }
- }
- }
- }
-
- // By default, return null.
- return null;
- }
-#endif
-
#endregion
private void HelpSystem_OnProgress(object sender, HelpProgressInfo arg)
diff --git a/src/System.Management.Automation/namespaces/FileSystemProvider.cs b/src/System.Management.Automation/namespaces/FileSystemProvider.cs
index e02d8551cc..706893c594 100644
--- a/src/System.Management.Automation/namespaces/FileSystemProvider.cs
+++ b/src/System.Management.Automation/namespaces/FileSystemProvider.cs
@@ -1330,7 +1330,19 @@ namespace Microsoft.PowerShell.Commands
invokeProcess.StartInfo.Arguments = path;
invokeProcess.Start();
#elif CORECLR
- throw new PlatformNotSupportedException();
+ try
+ {
+ // Try Process.Start first. This works for executables even on headless SKUs.
+ invokeProcess.StartInfo.FileName = path;
+ invokeProcess.Start();
+ }
+ catch (Win32Exception)
+ {
+ // If it's headless SKUs, rethrow.
+ if (Platform.IsNanoServer || Platform.IsIoT) { throw; }
+ // If it's full Windows, then try ShellExecute.
+ ShellExecuteHelper.Start(invokeProcess.StartInfo);
+ }
#else
invokeProcess.StartInfo.FileName = path;
invokeProcess.Start();
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Start-Process.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Start-Process.Tests.ps1
index 1b743e74ce..7ed13ff0e5 100644
--- a/test/powershell/Modules/Microsoft.PowerShell.Management/Start-Process.Tests.ps1
+++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Start-Process.Tests.ps1
@@ -1,86 +1,120 @@
+Import-Module $PSScriptRoot\..\..\Common\Test.Helpers.psm1
+
Describe "Start-Process" -Tags @("CI","SLOW") {
- $pingCommand = (Get-Command -CommandType Application ping)[0].Definition
- $pingDirectory = Split-Path $pingCommand -Parent
- $tempFile = Join-Path -Path $TestDrive -ChildPath PSTest
- $assetsFile = Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath assets) -ChildPath SortTest.txt
- if ($IsWindows) {
- $pingParam = "-n 2 localhost"
- }
- elseif ($IsLinux -Or $IsOSX) {
- $pingParam = "-c 2 localhost"
+
+ BeforeAll {
+ $isNanoServer = [System.Management.Automation.Platform]::IsNanoServer
+ $isIot = [System.Management.Automation.Platform]::IsIoT
+ $isFullWin = $IsWindows -and !$isNanoServer -and !$isIot
+
+ $pingCommand = (Get-Command -CommandType Application ping)[0].Definition
+ $pingDirectory = Split-Path $pingCommand -Parent
+ $tempFile = Join-Path -Path $TestDrive -ChildPath PSTest
+ $assetsFile = Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath assets) -ChildPath SortTest.txt
+ if ($IsWindows) {
+ $pingParam = "-n 2 localhost"
+ }
+ elseif ($IsLinux -Or $IsOSX) {
+ $pingParam = "-c 2 localhost"
+ }
}
# Note that ProcessName may still be `powershell` due to dotnet/corefx#5378
# This has been fixed on Linux, but not on OS X
It "Should process arguments without error" {
- $process = Start-Process ping -ArgumentList $pingParam -PassThru -RedirectStandardOutput "$TESTDRIVE/output"
+ $process = Start-Process ping -ArgumentList $pingParam -PassThru -RedirectStandardOutput "$TESTDRIVE/output"
- $process.Length | Should Be 1
- $process.Id | Should BeGreaterThan 1
- # $process.ProcessName | Should Be "ping"
+ $process.Length | Should Be 1
+ $process.Id | Should BeGreaterThan 1
+ # $process.ProcessName | Should Be "ping"
}
It "Should work correctly when used with full path name" {
- $process = Start-Process $pingCommand -ArgumentList $pingParam -PassThru -RedirectStandardOutput "$TESTDRIVE/output"
+ $process = Start-Process $pingCommand -ArgumentList $pingParam -PassThru -RedirectStandardOutput "$TESTDRIVE/output"
- $process.Length | Should Be 1
- $process.Id | Should BeGreaterThan 1
- # $process.ProcessName | Should Be "ping"
+ $process.Length | Should Be 1
+ $process.Id | Should BeGreaterThan 1
+ # $process.ProcessName | Should Be "ping"
}
It "Should invoke correct path when used with FilePath argument" {
- $process = Start-Process -FilePath $pingCommand -ArgumentList $pingParam -PassThru -RedirectStandardOutput "$TESTDRIVE/output"
+ $process = Start-Process -FilePath $pingCommand -ArgumentList $pingParam -PassThru -RedirectStandardOutput "$TESTDRIVE/output"
- $process.Length | Should Be 1
- $process.Id | Should BeGreaterThan 1
- # $process.ProcessName | Should Be "ping"
+ $process.Length | Should Be 1
+ $process.Id | Should BeGreaterThan 1
+ # $process.ProcessName | Should Be "ping"
}
It "Should wait for command completion if used with Wait argument" {
- $process = Start-Process ping -ArgumentList $pingParam -Wait -PassThru -RedirectStandardOutput "$TESTDRIVE/output"
+ $process = Start-Process ping -ArgumentList $pingParam -Wait -PassThru -RedirectStandardOutput "$TESTDRIVE/output"
}
It "Should work correctly with WorkingDirectory argument" {
- $process = Start-Process ping -WorkingDirectory $pingDirectory -ArgumentList $pingParam -PassThru -RedirectStandardOutput "$TESTDRIVE/output"
+ $process = Start-Process ping -WorkingDirectory $pingDirectory -ArgumentList $pingParam -PassThru -RedirectStandardOutput "$TESTDRIVE/output"
- $process.Length | Should Be 1
- $process.Id | Should BeGreaterThan 1
- # $process.ProcessName | Should Be "ping"
+ $process.Length | Should Be 1
+ $process.Id | Should BeGreaterThan 1
+ # $process.ProcessName | Should Be "ping"
}
- It "Should should handle stderr redirection without error" {
- $process = Start-Process ping -ArgumentList $pingParam -PassThru -RedirectStandardError $tempFile -RedirectStandardOutput "$TESTDRIVE/output"
+ It "Should handle stderr redirection without error" {
+ $process = Start-Process ping -ArgumentList $pingParam -PassThru -RedirectStandardError $tempFile -RedirectStandardOutput "$TESTDRIVE/output"
- $process.Length | Should Be 1
- $process.Id | Should BeGreaterThan 1
- # $process.ProcessName | Should Be "ping"
+ $process.Length | Should Be 1
+ $process.Id | Should BeGreaterThan 1
+ # $process.ProcessName | Should Be "ping"
}
- It "Should should handle stdout redirection without error" {
- $process = Start-Process ping -ArgumentList $pingParam -Wait -RedirectStandardOutput $tempFile
- $dirEntry = get-childitem $tempFile
- $dirEntry.Length | Should BeGreaterThan 0
+ It "Should handle stdout redirection without error" {
+ $process = Start-Process ping -ArgumentList $pingParam -Wait -RedirectStandardOutput $tempFile
+ $dirEntry = get-childitem $tempFile
+ $dirEntry.Length | Should BeGreaterThan 0
}
# Marking this test 'pending' to unblock daily builds. Filed issue : https://github.com/PowerShell/PowerShell/issues/2396
- It "Should should handle stdin redirection without error" -Pending {
- $process = Start-Process sort -Wait -RedirectStandardOutput $tempFile -RedirectStandardInput $assetsFile
- $dirEntry = get-childitem $tempFile
- $dirEntry.Length | Should BeGreaterThan 0
+ It "Should handle stdin redirection without error" -Pending {
+ $process = Start-Process sort -Wait -RedirectStandardOutput $tempFile -RedirectStandardInput $assetsFile
+ $dirEntry = get-childitem $tempFile
+ $dirEntry.Length | Should BeGreaterThan 0
}
- It "Should give an error when Verb parameter is used" -Skip:(-not $IsCoreClr) {
- try
- {
- Start-Process -Verb runas -FilePath $pingCommand -ArgumentList $pingParam
- throw "No Exception!"
- }
- catch
- {
- $_.FullyQualifiedErrorId | Should be "NotSupportedException,Microsoft.PowerShell.Commands.StartProcessCommand"
- $_.Exception.Message | Should match '-Verb'
- }
+ ## -Verb is supported in PowerShell core on Windows full desktop.
+ It "Should give an error when -Verb parameter is used" -Skip:$isFullWin {
+ { Start-Process -Verb runas -FilePath $pingCommand } | ShouldBeErrorId "NotSupportedException,Microsoft.PowerShell.Commands.StartProcessCommand"
}
+
+ ## -WindowStyle is supported in PowerShell core on Windows full desktop.
+ It "Should give an error when -WindowStyle parameter is used" -Skip:$isFullWin {
+ { Start-Process -FilePath $pingCommand -WindowStyle Normal } | ShouldBeErrorId "NotSupportedException,Microsoft.PowerShell.Commands.StartProcessCommand"
+ }
+
+ It "Should give an error when both -NoNewWindow and -WindowStyle are specified" -Skip:(!$isFullWin) {
+ { Start-Process -FilePath $pingCommand -NoNewWindow -WindowStyle Normal -ErrorAction Stop } | ShouldBeErrorId "InvalidOperationException,Microsoft.PowerShell.Commands.StartProcessCommand"
+ }
+
+ It "Should start cmd.exe with Verb 'open' and WindowStyle 'Minimized'" -Skip:(!$isFullWin) {
+ $fileToWrite = Join-Path $TestDrive "VerbTest.txt"
+ $process = Start-Process cmd.exe -ArgumentList "/c echo abc > $fileToWrite" -Verb open -WindowStyle Minimized -PassThru
+ $process.Name | Should Be "cmd"
+ $process.WaitForExit()
+ Test-Path $fileToWrite | Should Be $true
+ }
+
+ It "Should start notepad.exe with ShellExecute" -Skip:(!$isFullWin) {
+ $process = Start-Process notepad -PassThru -WindowStyle Normal
+ $process.Name | Should Be "notepad"
+ $process | Stop-Process
+ }
+
+ It "Should open the application that associates with extension '.txt'" -Skip:(!$isFullWin) {
+ $txtFile = Join-Path $TestDrive "TxtTest.txt"
+ New-Item $txtFile -ItemType File -Force
+ $process = Start-Process $txtFile -PassThru -WindowStyle Normal
+ $process.Name | Should Not BeNullOrEmpty
+ $process.Id | Should BeGreaterThan 1
+ $process | Stop-Process
+ }
+
Remove-Item -Path $tempFile -Force
}
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 ec7124a1b5..ce25b0d34d 100644
--- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Invoke-Item.Tests.ps1
+++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Invoke-Item.Tests.ps1
@@ -47,14 +47,16 @@ Describe "Invoke-Item on non-Windows" -Tags "CI" {
Describe "Invoke-Item tests on Windows" -Tags "CI","RequireAdminOnWindows" {
BeforeAll {
- if ($IsWindows) {
+ $isNanoServer = [System.Management.Automation.Platform]::IsNanoServer
+ $isIot = [System.Management.Automation.Platform]::IsIoT
+ $isFullWin = $IsWindows -and !$isNanoServer -and !$isIot
+
+ if ($isFullWin) {
$testfilename = "testfile.!!testext!!"
$testfilepath = Join-Path $TestDrive $testfilename
$renamedtestfilename = "renamedtestfile.!!testext!!"
$renamedtestfilepath = Join-Path $TestDrive $renamedtestfilename
- remove-item $testfilepath -ErrorAction SilentlyContinue
- remove-item $renamedtestfilepath -ErrorAction SilentlyContinue
- new-item $testfilepath | Out-Null
+
cmd.exe /c assoc .!!testext!!=!!testext!!.FileType | Out-Null
cmd.exe /c ftype !!testext!!.FileType=cmd.exe /c rename $testfilepath $renamedtestfilename | Out-Null
}
@@ -62,14 +64,21 @@ Describe "Invoke-Item tests on Windows" -Tags "CI","RequireAdminOnWindows" {
AfterAll {
if ($IsWindows) {
- remove-item $testfilepath -ErrorAction SilentlyContinue
- remove-item $renamedtestfilepath -ErrorAction SilentlyContinue
cmd.exe /c assoc !!testext!!=
cmd.exe /c ftype !!testext!!.FileType=
}
}
- It "Should invoke a file without error on Windows w/o .NET Core" -Skip:(-not $IsWindows -or ($IsWindows -and $IsCoreCLR)) {
+ BeforeEach {
+ New-Item $testfilepath -ItemType File | Out-Null
+ }
+
+ AfterEach {
+ Remove-Item $testfilepath -ErrorAction SilentlyContinue
+ Remove-Item $renamedtestfilepath -ErrorAction SilentlyContinue
+ }
+
+ It "Should invoke a file without error on Windows full SKUs" -Skip:(-not $isFullWin) {
invoke-item $testfilepath
# Waiting subprocess start and rename file
{
@@ -82,7 +91,8 @@ Describe "Invoke-Item tests on Windows" -Tags "CI","RequireAdminOnWindows" {
} | Should Not throw
}
- It "Should throw 'not supported' on Windows with .NET Core" -Skip:(-not ($IsWindows -and $IsCoreCLR)) {
- { Invoke-Item $testfilepath } | Should Throw "Operation is not supported on this platform."
+ It "Should start a file without error on Windows full SKUs" -Skip:(-not $isFullWin) {
+ Start-Process $testfilepath -Wait
+ Test-Path $renamedtestfilepath | Should Be $true
}
}