diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs
index 576e7cb38b9..2ba50c23459 100644
--- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs
+++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs
@@ -2348,22 +2348,8 @@ private static byte[] ConvertEnvVarsToByteArray(StringDictionary sd)
return bytes;
}
- ///
- /// This method will be used on all windows platforms, both full desktop and headless SKUs.
- ///
- private Process StartWithCreateProcess(ProcessStartInfo startinfo)
+ private void SetStartupInfo(ProcessStartInfo startinfo, ref ProcessNativeMethods.STARTUPINFO lpStartupInfo, ref int creationFlags)
{
- ProcessNativeMethods.STARTUPINFO lpStartupInfo = new ProcessNativeMethods.STARTUPINFO();
- SafeNativeMethods.PROCESS_INFORMATION lpProcessInformation = new SafeNativeMethods.PROCESS_INFORMATION();
- int error = 0;
- GCHandle pinnedEnvironmentBlock = new GCHandle();
- string message = string.Empty;
-
- // building the cmdline with the file name given and it's arguments
- StringBuilder cmdLine = BuildCommandLine(startinfo.FileName, startinfo.Arguments);
-
- try
- {
// RedirectionStandardInput
if (_redirectstandardinput != null)
{
@@ -2375,6 +2361,7 @@ private Process StartWithCreateProcess(ProcessStartInfo startinfo)
{
lpStartupInfo.hStdInput = new SafeFileHandle(ProcessNativeMethods.GetStdHandle(-10), false);
}
+
// RedirectionStandardOutput
if (_redirectstandardoutput != null)
{
@@ -2386,6 +2373,7 @@ private Process StartWithCreateProcess(ProcessStartInfo startinfo)
{
lpStartupInfo.hStdOutput = new SafeFileHandle(ProcessNativeMethods.GetStdHandle(-11), false);
}
+
// RedirectionStandardError
if (_redirectstandarderror != null)
{
@@ -2397,11 +2385,10 @@ private Process StartWithCreateProcess(ProcessStartInfo startinfo)
{
lpStartupInfo.hStdError = new SafeFileHandle(ProcessNativeMethods.GetStdHandle(-12), false);
}
+
// STARTF_USESTDHANDLES
lpStartupInfo.dwFlags = 0x100;
- int creationFlags = 0;
-
if (startinfo.CreateNoWindow)
{
// No new window: Inherit the parent process's console window
@@ -2411,6 +2398,7 @@ private Process StartWithCreateProcess(ProcessStartInfo startinfo)
{
// CREATE_NEW_CONSOLE
creationFlags |= 0x00000010;
+
// STARTF_USESHOWWINDOW
lpStartupInfo.dwFlags |= 0x00000001;
@@ -2438,15 +2426,41 @@ private Process StartWithCreateProcess(ProcessStartInfo startinfo)
// Create the new process suspended so we have a chance to get a corresponding Process object in case it terminates quickly.
creationFlags |= 0x00000004;
+ }
- IntPtr AddressOfEnvironmentBlock = IntPtr.Zero;
- var environmentVars = startinfo.EnvironmentVariables;
- if (environmentVars != null)
+ ///
+ /// 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();
+ SafeNativeMethods.PROCESS_INFORMATION lpProcessInformation = new SafeNativeMethods.PROCESS_INFORMATION();
+ int error = 0;
+ GCHandle pinnedEnvironmentBlock = new GCHandle();
+ IntPtr AddressOfEnvironmentBlock = IntPtr.Zero;
+ string message = string.Empty;
+
+ // building the cmdline with the file name given and it's arguments
+ StringBuilder cmdLine = BuildCommandLine(startinfo.FileName, startinfo.Arguments);
+
+ try
+ {
+ int creationFlags = 0;
+
+ SetStartupInfo(startinfo, ref lpStartupInfo, ref creationFlags);
+
+ // We follow the logic:
+ // - Ignore `UseNewEnvironment` when we run a process as another user.
+ // Setting initial environment variables makes sense only for current user.
+ // - Set environment variables if they present in ProcessStartupInfo.
+ if (!UseNewEnvironment)
{
- if (this.UseNewEnvironment)
+ var environmentVars = startinfo.EnvironmentVariables;
+ if (environmentVars != null)
{
// 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();
}
@@ -2456,6 +2470,7 @@ private Process StartWithCreateProcess(ProcessStartInfo startinfo)
if (_credential != null)
{
+ // Run process as another user.
ProcessNativeMethods.LogonFlags logonFlags = 0;
if (startinfo.LoadUserProfile)
{
@@ -2504,6 +2519,22 @@ private Process StartWithCreateProcess(ProcessStartInfo startinfo)
}
}
+ // Run process as current user.
+ if (UseNewEnvironment)
+ {
+ // All Windows Operating Systems that we support are Windows NT systems, so we use Unicode for environment.
+ creationFlags |= 0x400;
+
+ IntPtr token = WindowsIdentity.GetCurrent().Token;
+ if (!ProcessNativeMethods.CreateEnvironmentBlock(out AddressOfEnvironmentBlock, token, false))
+ {
+ Win32Exception win32ex = new Win32Exception(error);
+ message = StringUtil.Format(ProcessResources.InvalidStartProcess, win32ex.Message);
+ var errorRecord = new ErrorRecord(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null);
+ ThrowTerminatingError(errorRecord);
+ }
+ }
+
ProcessNativeMethods.SECURITY_ATTRIBUTES lpProcessAttributes = new ProcessNativeMethods.SECURITY_ATTRIBUTES();
ProcessNativeMethods.SECURITY_ATTRIBUTES lpThreadAttributes = new ProcessNativeMethods.SECURITY_ATTRIBUTES();
flag = ProcessNativeMethods.CreateProcess(null, cmdLine, lpProcessAttributes, lpThreadAttributes, true, creationFlags, AddressOfEnvironmentBlock, startinfo.WorkingDirectory, lpStartupInfo, lpProcessInformation);
@@ -2531,6 +2562,10 @@ private Process StartWithCreateProcess(ProcessStartInfo startinfo)
{
pinnedEnvironmentBlock.Free();
}
+ else
+ {
+ ProcessNativeMethods.DestroyEnvironmentBlock(AddressOfEnvironmentBlock);
+ }
lpStartupInfo.Dispose();
lpProcessInformation.Dispose();
@@ -2720,6 +2755,14 @@ public static extern FileNakedHandle CreateFileW(
System.IntPtr hTemplateFile
);
+ [DllImport("userenv.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit);
+
+ [DllImport("userenv.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment);
+
[Flags]
internal enum LogonFlags
{
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 0669e78426a..c557df36608 100644
--- a/test/powershell/Modules/Microsoft.PowerShell.Management/Start-Process.Tests.ps1
+++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Start-Process.Tests.ps1
@@ -1,5 +1,6 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
+
Describe "Start-Process" -Tag "Feature","RequireAdminOnWindows" {
BeforeAll {
@@ -19,7 +20,7 @@ Describe "Start-Process" -Tag "Feature","RequireAdminOnWindows" {
$pingParam = "-n 2 localhost"
}
elseif ($IsLinux -Or $IsMacOS) {
- $pingParam = "-c 2 localhost"
+ $pingParam = "-c 2 localhost"
}
}
@@ -27,7 +28,7 @@ Describe "Start-Process" -Tag "Feature","RequireAdminOnWindows" {
# This has been fixed on Linux, but not on macOS
It "Should process arguments without error" {
- $process = Start-Process ping -ArgumentList $pingParam -PassThru -RedirectStandardOutput "$TESTDRIVE/output" @extraArgs
+ $process = Start-Process ping -ArgumentList $pingParam -PassThru -RedirectStandardOutput "$TESTDRIVE/output" @extraArgs
$process.Length | Should -Be 1
$process.Id | Should -BeGreaterThan 1
@@ -35,7 +36,7 @@ Describe "Start-Process" -Tag "Feature","RequireAdminOnWindows" {
}
It "Should work correctly when used with full path name" {
- $process = Start-Process $pingCommand -ArgumentList $pingParam -PassThru -RedirectStandardOutput "$TESTDRIVE/output" @extraArgs
+ $process = Start-Process $pingCommand -ArgumentList $pingParam -PassThru -RedirectStandardOutput "$TESTDRIVE/output" @extraArgs
$process.Length | Should -Be 1
$process.Id | Should -BeGreaterThan 1
@@ -43,7 +44,7 @@ Describe "Start-Process" -Tag "Feature","RequireAdminOnWindows" {
}
It "Should invoke correct path when used with FilePath argument" {
- $process = Start-Process -FilePath $pingCommand -ArgumentList $pingParam -PassThru -RedirectStandardOutput "$TESTDRIVE/output" @extraArgs
+ $process = Start-Process -FilePath $pingCommand -ArgumentList $pingParam -PassThru -RedirectStandardOutput "$TESTDRIVE/output" @extraArgs
$process.Length | Should -Be 1
$process.Id | Should -BeGreaterThan 1
@@ -51,18 +52,18 @@ Describe "Start-Process" -Tag "Feature","RequireAdminOnWindows" {
}
It "Should invoke correct path when used with Path alias argument" {
- $process = Start-Process -Path $pingCommand -ArgumentList $pingParam -PassThru -RedirectStandardOutput "$TESTDRIVE/output" @extraArgs
+ $process = Start-Process -Path $pingCommand -ArgumentList $pingParam -PassThru -RedirectStandardOutput "$TESTDRIVE/output" @extraArgs
- $process.Length | Should -Be 1
- $process.Id | Should -BeGreaterThan 1
+ $process.Length | Should -Be 1
+ $process.Id | Should -BeGreaterThan 1
}
It "Should wait for command completion if used with Wait argument" {
- $process = Start-Process ping -ArgumentList $pingParam -Wait -PassThru -RedirectStandardOutput "$TESTDRIVE/output" @extraArgs
+ $process = Start-Process ping -ArgumentList $pingParam -Wait -PassThru -RedirectStandardOutput "$TESTDRIVE/output" @extraArgs
}
It "Should work correctly with WorkingDirectory argument" {
- $process = Start-Process ping -WorkingDirectory $pingDirectory -ArgumentList $pingParam -PassThru -RedirectStandardOutput "$TESTDRIVE/output" @extraArgs
+ $process = Start-Process ping -WorkingDirectory $pingDirectory -ArgumentList $pingParam -PassThru -RedirectStandardOutput "$TESTDRIVE/output" @extraArgs
$process.Length | Should -Be 1
$process.Id | Should -BeGreaterThan 1
@@ -70,7 +71,7 @@ Describe "Start-Process" -Tag "Feature","RequireAdminOnWindows" {
}
It "Should handle stderr redirection without error" {
- $process = Start-Process ping -ArgumentList $pingParam -PassThru -RedirectStandardError $tempFile -RedirectStandardOutput "$TESTDRIVE/output" @extraArgs
+ $process = Start-Process ping -ArgumentList $pingParam -PassThru -RedirectStandardError $tempFile -RedirectStandardOutput "$TESTDRIVE/output" @extraArgs
$process.Length | Should -Be 1
$process.Id | Should -BeGreaterThan 1
@@ -78,16 +79,16 @@ Describe "Start-Process" -Tag "Feature","RequireAdminOnWindows" {
}
It "Should handle stdout redirection without error" {
- $process = Start-Process ping -ArgumentList $pingParam -Wait -RedirectStandardOutput $tempFile @extraArgs
- $dirEntry = get-childitem $tempFile
- $dirEntry.Length | Should -BeGreaterThan 0
+ $process = Start-Process ping -ArgumentList $pingParam -Wait -RedirectStandardOutput $tempFile @extraArgs
+ $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 handle stdin redirection without error" -Pending {
- $process = Start-Process sort -Wait -RedirectStandardOutput $tempFile -RedirectStandardInput $assetsFile @extraArgs
- $dirEntry = get-childitem $tempFile
- $dirEntry.Length | Should -BeGreaterThan 0
+ $process = Start-Process sort -Wait -RedirectStandardOutput $tempFile -RedirectStandardInput $assetsFile @extraArgs
+ $dirEntry = get-childitem $tempFile
+ $dirEntry.Length | Should -BeGreaterThan 0
}
## -Verb is supported in PowerShell on Windows full desktop.
@@ -169,3 +170,30 @@ Describe "Start-Process tests requiring admin" -Tags "Feature","RequireAdminOnWi
Get-Content $testdrive\foo.txt | Should -BeExactly $fooFile
}
}
+
+Describe "Start-Process" -Tags "Feature" {
+
+ It "UseNewEnvironment parameter should reset environment variables for child process" {
+
+ $PWSH = (Get-Process -Id $PID).MainModule.FileName
+ $outputFile = Join-Path -Path $TestDrive -ChildPath output.txt
+
+ $env:TestEnvVariable | Should -BeNullOrEmpty
+
+ $env:TestEnvVariable = 1
+ $userName = $env:USERNAME
+
+ try {
+ Start-Process $PWSH -ArgumentList '-NoProfile','-Command Write-Output \"$($env:TestEnvVariable);$($env:USERNAME)\"' -RedirectStandardOutput $outputFile -Wait
+ Get-Content -LiteralPath $outputFile | Should -BeExactly "1;$userName"
+
+ # Check that:
+ # 1. Environment variables is resetted (TestEnvVariable is removed)
+ # 2. Environment variables comes from current user profile
+ Start-Process $PWSH -ArgumentList '-NoProfile','-Command Write-Output \"$($env:TestEnvVariable);$($env:USERNAME)\"' -RedirectStandardOutput $outputFile -Wait -UseNewEnvironment
+ Get-Content -LiteralPath $outputFile | Should -BeExactly ";$userName"
+ } finally {
+ $env:TestEnvVariable = $null
+ }
+ }
+}