mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
276 lines
9.7 KiB
C#
276 lines
9.7 KiB
C#
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT License.
|
|
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Management.Automation.Remoting;
|
|
using System.Text;
|
|
|
|
namespace System.Management.Automation.Runspaces
|
|
{
|
|
/// <summary>
|
|
/// </summary>
|
|
public sealed class PowerShellProcessInstance : IDisposable
|
|
{
|
|
#region Fields
|
|
|
|
private readonly ProcessStartInfo _startInfo;
|
|
private RunspacePool _runspacePool;
|
|
private readonly object _syncObject = new object();
|
|
private bool _started;
|
|
private bool _isDisposed;
|
|
private bool _processExited;
|
|
|
|
internal static readonly string PwshExePath;
|
|
internal static readonly string WinPwshExePath;
|
|
|
|
#endregion Fields
|
|
|
|
#region Constructors
|
|
|
|
/// <summary>
|
|
/// </summary>
|
|
static PowerShellProcessInstance()
|
|
{
|
|
#if UNIX
|
|
PwshExePath = Path.Combine(Utils.DefaultPowerShellAppBase, "pwsh");
|
|
#else
|
|
PwshExePath = Path.Combine(Utils.DefaultPowerShellAppBase, "pwsh.exe");
|
|
WinPwshExePath = Path.Combine(Utils.GetApplicationBaseFromRegistry(Utils.DefaultPowerShellShellID), "powershell.exe");
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="PowerShellProcessInstance"/> class. Initializes the underlying dotnet process class.
|
|
/// </summary>
|
|
/// <param name="powerShellVersion">Specifies the version of powershell.</param>
|
|
/// <param name="credential">Specifies a user account credentials.</param>
|
|
/// <param name="initializationScript">Specifies a script that will be executed when the powershell process is initialized.</param>
|
|
/// <param name="useWow64">Specifies if the powershell process will be 32-bit.</param>
|
|
/// <param name="workingDirectory">Specifies the initial working directory for the new powershell process.</param>
|
|
public PowerShellProcessInstance(Version powerShellVersion, PSCredential credential, ScriptBlock initializationScript, bool useWow64, string workingDirectory)
|
|
{
|
|
string exePath = PwshExePath;
|
|
bool startingWindowsPowerShell51 = false;
|
|
#if !UNIX
|
|
// if requested PS version was "5.1" then we start Windows PS instead of PS Core
|
|
startingWindowsPowerShell51 = (powerShellVersion != null) && (powerShellVersion.Major == 5) && (powerShellVersion.Minor == 1);
|
|
if (startingWindowsPowerShell51)
|
|
{
|
|
exePath = WinPwshExePath;
|
|
|
|
if (useWow64)
|
|
{
|
|
string procArch = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE");
|
|
|
|
if ((!string.IsNullOrEmpty(procArch)) && (procArch.Equals("amd64", StringComparison.OrdinalIgnoreCase) ||
|
|
procArch.Equals("ia64", StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
exePath = WinPwshExePath.ToLowerInvariant().Replace("\\system32\\", "\\syswow64\\");
|
|
|
|
if (!File.Exists(exePath))
|
|
{
|
|
string message = PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.WowComponentNotPresent, exePath);
|
|
throw new PSInvalidOperationException(message);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
// 'WindowStyle' is used only if 'UseShellExecute' is 'true'. Since 'UseShellExecute' is set
|
|
// to 'false' in our use, we can ignore the 'WindowStyle' setting in the initialization below.
|
|
_startInfo = new ProcessStartInfo
|
|
{
|
|
FileName = exePath,
|
|
UseShellExecute = false,
|
|
RedirectStandardInput = true,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
CreateNoWindow = true,
|
|
#if !UNIX
|
|
LoadUserProfile = true,
|
|
#endif
|
|
};
|
|
|
|
if (startingWindowsPowerShell51)
|
|
{
|
|
_startInfo.ArgumentList.Add("-Version");
|
|
_startInfo.ArgumentList.Add("5.1");
|
|
}
|
|
|
|
_startInfo.ArgumentList.Add("-s");
|
|
_startInfo.ArgumentList.Add("-NoLogo");
|
|
_startInfo.ArgumentList.Add("-NoProfile");
|
|
|
|
if (!string.IsNullOrWhiteSpace(workingDirectory) && !startingWindowsPowerShell51)
|
|
{
|
|
_startInfo.ArgumentList.Add("-wd");
|
|
_startInfo.ArgumentList.Add(workingDirectory);
|
|
}
|
|
|
|
if (initializationScript != null)
|
|
{
|
|
var scriptBlockString = initializationScript.ToString();
|
|
if (!string.IsNullOrEmpty(scriptBlockString))
|
|
{
|
|
var encodedCommand = Convert.ToBase64String(Encoding.Unicode.GetBytes(scriptBlockString));
|
|
_startInfo.ArgumentList.Add("-EncodedCommand");
|
|
_startInfo.ArgumentList.Add(encodedCommand);
|
|
}
|
|
}
|
|
|
|
if (credential != null)
|
|
{
|
|
Net.NetworkCredential netCredential = credential.GetNetworkCredential();
|
|
|
|
_startInfo.UserName = netCredential.UserName;
|
|
_startInfo.Domain = string.IsNullOrEmpty(netCredential.Domain) ? "." : netCredential.Domain;
|
|
_startInfo.Password = credential.Password;
|
|
}
|
|
|
|
Process = new Process { StartInfo = _startInfo, EnableRaisingEvents = true };
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="PowerShellProcessInstance"/> class. Initializes the underlying dotnet process class.
|
|
/// </summary>
|
|
/// <param name="powerShellVersion"></param>
|
|
/// <param name="credential"></param>
|
|
/// <param name="initializationScript"></param>
|
|
/// <param name="useWow64"></param>
|
|
public PowerShellProcessInstance(Version powerShellVersion, PSCredential credential, ScriptBlock initializationScript, bool useWow64) : this(powerShellVersion, credential, initializationScript, useWow64, workingDirectory: null)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="PowerShellProcessInstance"/> class. Default initializes the underlying dotnet process class.
|
|
/// </summary>
|
|
public PowerShellProcessInstance() : this(powerShellVersion: null, credential: null, initializationScript: null, useWow64: false, workingDirectory: null)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether the associated process has been terminated.
|
|
/// true if the operating system process referenced by the Process component has terminated; otherwise, false.
|
|
/// </summary>
|
|
public bool HasExited
|
|
{
|
|
get
|
|
{
|
|
// When process is exited, there is some delay in receiving ProcessExited event and HasExited property on process object.
|
|
// Using HasExited property on started process object to determine if powershell process has exited.
|
|
//
|
|
return _processExited || (_started && Process != null && Process.HasExited);
|
|
}
|
|
}
|
|
|
|
#endregion Constructors
|
|
|
|
#region Dispose
|
|
/// <summary>
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
/// <summary>
|
|
/// </summary>
|
|
/// <param name="disposing"></param>
|
|
private void Dispose(bool disposing)
|
|
{
|
|
if (_isDisposed) return;
|
|
lock (_syncObject)
|
|
{
|
|
if (_isDisposed) return;
|
|
_isDisposed = true;
|
|
}
|
|
|
|
if (disposing)
|
|
{
|
|
try
|
|
{
|
|
if (Process != null && !Process.HasExited)
|
|
Process.Kill();
|
|
}
|
|
catch (InvalidOperationException)
|
|
{
|
|
}
|
|
catch (Win32Exception)
|
|
{
|
|
}
|
|
catch (NotSupportedException)
|
|
{
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion Dispose
|
|
|
|
#region Public Properties
|
|
/// <summary>
|
|
/// </summary>
|
|
public Process Process { get; }
|
|
|
|
#endregion Public Properties
|
|
|
|
#region Internal Members
|
|
|
|
internal RunspacePool RunspacePool
|
|
{
|
|
get
|
|
{
|
|
lock (_syncObject)
|
|
{
|
|
return _runspacePool;
|
|
}
|
|
}
|
|
|
|
set
|
|
{
|
|
lock (_syncObject)
|
|
{
|
|
_runspacePool = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal OutOfProcessTextWriter StdInWriter { get; set; }
|
|
|
|
internal void Start()
|
|
{
|
|
// To fix the deadlock, we should not call Process.HasExited by holding the sync lock as Process.HasExited can raise ProcessExited event
|
|
//
|
|
if (HasExited)
|
|
{
|
|
throw new InvalidOperationException();
|
|
}
|
|
|
|
lock (_syncObject)
|
|
{
|
|
if (_started)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_started = true;
|
|
Process.Exited += ProcessExited;
|
|
}
|
|
|
|
Process.Start();
|
|
}
|
|
|
|
private void ProcessExited(object sender, EventArgs e)
|
|
{
|
|
lock (_syncObject)
|
|
{
|
|
_processExited = true;
|
|
}
|
|
}
|
|
|
|
#endregion Internal Members
|
|
}
|
|
}
|