MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.ComponentModel;5using System.Diagnostics;6using System.Globalization;7using System.IO;8using System.Management.Automation.Remoting;9using System.Text;10 11namespace System.Management.Automation.Runspaces12{13 /// <summary>14 /// This class represents a PowerShell process that is used for an out-of-process remote Runspace.15 /// </summary>16 public sealed class PowerShellProcessInstance : IDisposable17 {18 #region Fields19 20 private readonly ProcessStartInfo _startInfo;21 private RunspacePool _runspacePool;22 private readonly object _syncObject = new object();23 private bool _started;24 private bool _isDisposed;25 private bool _processExited;26 27 internal static readonly string PwshExePath;28 internal static readonly string WinPwshExePath;29 30 #endregion Fields31 32 #region Constructors33 34 static PowerShellProcessInstance()35 {36#if UNIX37 PwshExePath = Path.Combine(Utils.DefaultPowerShellAppBase, "pwsh");38#else39 PwshExePath = Path.Combine(Utils.DefaultPowerShellAppBase, "pwsh.exe");40 var winPowerShellDir = Utils.GetApplicationBaseFromRegistry(Utils.DefaultPowerShellShellID);41 WinPwshExePath = string.IsNullOrEmpty(winPowerShellDir) ? null : Path.Combine(winPowerShellDir, "powershell.exe");42#endif43 }44 45 /// <summary>46 /// Initializes a new instance of the <see cref="PowerShellProcessInstance"/> class. Initializes the underlying dotnet process class.47 /// </summary>48 /// <param name="powerShellVersion">Specifies the version of powershell.</param>49 /// <param name="credential">Specifies a user account credentials.</param>50 /// <param name="initializationScript">Specifies a script that will be executed when the powershell process is initialized.</param>51 /// <param name="useWow64">Specifies if the powershell process will be 32-bit.</param>52 /// <param name="workingDirectory">Specifies the initial working directory for the new powershell process.</param>53 public PowerShellProcessInstance(Version powerShellVersion, PSCredential credential, ScriptBlock initializationScript, bool useWow64, string workingDirectory)54 {55 string exePath = PwshExePath;56 bool startingWindowsPowerShell51 = false;57#if !UNIX58 // if requested PS version was "5.1" then we start Windows PS instead of PS Core59 startingWindowsPowerShell51 = (powerShellVersion != null) && (powerShellVersion.Major == 5) && (powerShellVersion.Minor == 1);60 if (startingWindowsPowerShell51)61 {62 if (WinPwshExePath == null)63 {64 throw new PSInvalidOperationException(RemotingErrorIdStrings.WindowsPowerShellNotPresent);65 }66 67 exePath = WinPwshExePath;68 69 if (useWow64)70 {71 string procArch = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE");72 73 if ((!string.IsNullOrEmpty(procArch)) && (procArch.Equals("amd64", StringComparison.OrdinalIgnoreCase) ||74 procArch.Equals("ia64", StringComparison.OrdinalIgnoreCase)))75 {76 exePath = WinPwshExePath.ToLowerInvariant().Replace("\\system32\\", "\\syswow64\\");77 78 if (!File.Exists(exePath))79 {80 string message = PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.WowComponentNotPresent, exePath);81 throw new PSInvalidOperationException(message);82 }83 }84 }85 }86#endif87 // 'WindowStyle' is used only if 'UseShellExecute' is 'true'. Since 'UseShellExecute' is set88 // to 'false' in our use, we can ignore the 'WindowStyle' setting in the initialization below.89 _startInfo = new ProcessStartInfo90 {91 FileName = exePath,92 UseShellExecute = false,93 RedirectStandardInput = true,94 RedirectStandardOutput = true,95 RedirectStandardError = true,96 CreateNoWindow = true,97#if !UNIX98 LoadUserProfile = true,99#endif100 };101#if !UNIX102 if (startingWindowsPowerShell51)103 {104 _startInfo.ArgumentList.Add("-Version");105 _startInfo.ArgumentList.Add("5.1");106 107 // if starting Windows PowerShell, need to remove PowerShell specific segments of PSModulePath108 _startInfo.Environment["PSModulePath"] = ModuleIntrinsics.GetWindowsPowerShellModulePath();109 }110#endif111 _startInfo.ArgumentList.Add("-s");112 _startInfo.ArgumentList.Add("-NoLogo");113 _startInfo.ArgumentList.Add("-NoProfile");114 115 if (!string.IsNullOrWhiteSpace(workingDirectory) && !startingWindowsPowerShell51)116 {117 _startInfo.ArgumentList.Add("-wd");118 _startInfo.ArgumentList.Add(workingDirectory);119 }120 121 if (initializationScript != null)122 {123 var scriptBlockString = initializationScript.ToString();124 if (!string.IsNullOrEmpty(scriptBlockString))125 {126 var encodedCommand = Convert.ToBase64String(Encoding.Unicode.GetBytes(scriptBlockString));127 _startInfo.ArgumentList.Add("-EncodedCommand");128 _startInfo.ArgumentList.Add(encodedCommand);129 }130 }131 132 if (credential != null)133 {134 Net.NetworkCredential netCredential = credential.GetNetworkCredential();135 136 _startInfo.UserName = netCredential.UserName;137 _startInfo.Domain = string.IsNullOrEmpty(netCredential.Domain) ? "." : netCredential.Domain;138 _startInfo.Password = credential.Password;139 }140 141 Process = new Process { StartInfo = _startInfo, EnableRaisingEvents = true };142 }143 144 /// <summary>145 /// Initializes a new instance of the <see cref="PowerShellProcessInstance"/> class. Initializes the underlying dotnet process class.146 /// </summary>147 /// <param name="powerShellVersion">Specifies the version of powershell.</param>148 /// <param name="credential">Specifies a user account credentials.</param>149 /// <param name="initializationScript">Specifies a script that will be executed when the powershell process is initialized.</param>150 /// <param name="useWow64">Specifies if the powershell process will be 32-bit.</param>151 public PowerShellProcessInstance(Version powerShellVersion, PSCredential credential, ScriptBlock initializationScript, bool useWow64) : this(powerShellVersion, credential, initializationScript, useWow64, workingDirectory: null)152 {153 }154 155 /// <summary>156 /// Initializes a new instance of the <see cref="PowerShellProcessInstance"/> class. Default initializes the underlying dotnet process class.157 /// </summary>158 public PowerShellProcessInstance() : this(powerShellVersion: null, credential: null, initializationScript: null, useWow64: false, workingDirectory: null)159 {160 }161 162 /// <summary>163 /// Gets a value indicating whether the associated process has been terminated.164 /// true if the operating system process referenced by the Process component has terminated; otherwise, false.165 /// </summary>166 public bool HasExited167 {168 get169 {170 // When process is exited, there is some delay in receiving ProcessExited event and HasExited property on process object.171 // Using HasExited property on started process object to determine if powershell process has exited.172 //173 return _processExited || (_started && Process != null && Process.HasExited);174 }175 }176 177 #endregion Constructors178 179 #region Dispose180 181 /// <summary>182 /// Release all resources.183 /// </summary>184 public void Dispose()185 {186 if (_isDisposed)187 {188 return;189 }190 191 lock (_syncObject)192 {193 if (_isDisposed)194 {195 return;196 }197 198 _isDisposed = true;199 }200 201 try202 {203 if (Process != null && !Process.HasExited)204 Process.Kill();205 }206 catch (InvalidOperationException)207 {208 }209 catch (Win32Exception)210 {211 }212 catch (NotSupportedException)213 {214 }215 }216 217 #endregion Dispose218 219 #region Public Properties220 221 /// <summary>222 /// Gets the process object of the remote target.223 /// </summary>224 public Process Process { get; }225 226 #endregion Public Properties227 228 #region Internal Members229 230 internal RunspacePool RunspacePool231 {232 get233 {234 lock (_syncObject)235 {236 return _runspacePool;237 }238 }239 240 set241 {242 lock (_syncObject)243 {244 _runspacePool = value;245 }246 }247 }248 249 internal OutOfProcessTextWriter StdInWriter { get; set; }250 251 internal void Start()252 {253 // To fix the deadlock, we should not call Process.HasExited by holding the sync lock as Process.HasExited can raise ProcessExited event254 //255 if (HasExited)256 {257 throw new InvalidOperationException();258 }259 260 lock (_syncObject)261 {262 if (_started)263 {264 return;265 }266 267 _started = true;268 Process.Exited += ProcessExited;269 }270 271 Process.Start();272 }273 274 private void ProcessExited(object sender, EventArgs e)275 {276 lock (_syncObject)277 {278 _processExited = true;279 }280 }281 282 #endregion Internal Members283 }284}285 