Skip to content

Commit

Permalink
Add working directory parameter to Start-Job (PowerShell#10324)
Browse files Browse the repository at this point in the history
  • Loading branch information
davinci26 authored and adityapatwardhan committed Sep 11, 2019
1 parent 096a78f commit f69f30b
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ internal static int Start(
{
ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("ServerMode");
ProfileOptimization.StartProfile("StartupProfileData-ServerMode");
System.Management.Automation.Remoting.Server.OutOfProcessMediator.Run(s_cpp.InitialCommand);
System.Management.Automation.Remoting.Server.OutOfProcessMediator.Run(s_cpp.InitialCommand, s_cpp.WorkingDirectory);
exitCode = 0;
}
else if (s_cpp.NamedPipeServerMode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,24 +41,38 @@ static PowerShellProcessInstance()
}

/// <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)
/// <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 processArguments = " -s -NoLogo -NoProfile";

if (!string.IsNullOrWhiteSpace(workingDirectory))
{
processArguments = string.Format(
CultureInfo.InvariantCulture,
"{0} -wd {1}",
processArguments,
workingDirectory);
}

if (initializationScript != null)
{
string scripBlockAsString = initializationScript.ToString();
if (!string.IsNullOrEmpty(scripBlockAsString))
{
string encodedCommand =
Convert.ToBase64String(Encoding.Unicode.GetBytes(scripBlockAsString));
processArguments = string.Format(CultureInfo.InvariantCulture,
"{0} -EncodedCommand {1}", processArguments, encodedCommand);
processArguments = string.Format(
CultureInfo.InvariantCulture,
"{0} -EncodedCommand {1}",
processArguments,
encodedCommand);
}
}

Expand Down Expand Up @@ -91,8 +105,20 @@ public PowerShellProcessInstance(Version powerShellVersion, PSCredential credent
}

/// <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(null, null, null, false)
public PowerShellProcessInstance() : this(powerShellVersion: null, credential: null, initializationScript: null, useWow64: false, workingDirectory: null)
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,13 @@ public virtual ScriptBlock InitializationScript

private ScriptBlock _initScript;

/// <summary>
/// Gets or sets an initial working directory for the powershell background job.
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty]
public string WorkingDirectory { get; set; }

/// <summary>
/// Launches the background job as a 32-bit process. This can be used on
/// 64-bit systems to launch a 32-bit wow process for the background job.
Expand Down Expand Up @@ -589,6 +596,18 @@ protected override void BeginProcessing()
ThrowTerminatingError(errorRecord);
}

if (WorkingDirectory != null && !Directory.Exists(WorkingDirectory))
{
string message = StringUtil.Format(RemotingErrorIdStrings.StartJobWorkingDirectoryNotFound, WorkingDirectory);
var errorRecord = new ErrorRecord(
new DirectoryNotFoundException(message),
"DirectoryNotFoundException",
ErrorCategory.InvalidOperation,
targetObject: null);

ThrowTerminatingError(errorRecord);
}

CommandDiscovery.AutoloadModulesWithJobSourceAdapters(this.Context, this.CommandOrigin);

if (ParameterSetName == DefinitionNameParameterSet)
Expand Down Expand Up @@ -628,6 +647,7 @@ protected override void CreateHelpersForSpecifiedComputerNames()
connectionInfo.InitializationScript = _initScript;
connectionInfo.AuthenticationMechanism = this.Authentication;
connectionInfo.PSVersion = this.PSVersion;
connectionInfo.WorkingDirectory = this.WorkingDirectory;

RemoteRunspace remoteRunspace = (RemoteRunspace)RunspaceFactory.CreateRunspace(connectionInfo, this.Host,
Utils.GetTypeTableFromExecutionContextTLS());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1514,6 +1514,11 @@ internal sealed class NewProcessConnectionInfo : RunspaceConnectionInfo
/// </summary>
public bool RunAs32 { get; set; }

/// <summary>
/// Gets or sets an initial working directory for the powershell background process.
/// </summary>
public string WorkingDirectory { get; set; }

/// <summary>
/// Powershell version to execute the job in.
/// </summary>
Expand Down Expand Up @@ -1590,6 +1595,7 @@ public NewProcessConnectionInfo Copy()
NewProcessConnectionInfo result = new NewProcessConnectionInfo(_credential);
result.AuthenticationMechanism = this.AuthenticationMechanism;
result.InitializationScript = this.InitializationScript;
result.WorkingDirectory = this.WorkingDirectory;
result.RunAs32 = this.RunAs32;
result.PSVersion = this.PSVersion;
result.Process = Process;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1003,7 +1003,8 @@ internal override void CreateAsync()
_processInstance = _connectionInfo.Process ?? new PowerShellProcessInstance(_connectionInfo.PSVersion,
_connectionInfo.Credential,
_connectionInfo.InitializationScript,
_connectionInfo.RunAs32);
_connectionInfo.RunAs32,
_connectionInfo.WorkingDirectory);
if (_connectionInfo.Process != null)
{
_processCreated = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ protected void ProcessingThreadStart(object state)
catch (Exception e)
{
PSEtwLog.LogOperationalError(
PSEventId.TransportError, PSOpcode.Open, PSTask.None,
PSEventId.TransportError,
PSOpcode.Open,
PSTask.None,
PSKeyword.UseAlwaysOperational,
Guid.Empty.ToString(),
Guid.Empty.ToString(),
Expand All @@ -96,7 +98,9 @@ protected void ProcessingThreadStart(object state)
e.StackTrace);

PSEtwLog.LogAnalyticError(
PSEventId.TransportError_Analytic, PSOpcode.Open, PSTask.None,
PSEventId.TransportError_Analytic,
PSOpcode.Open,
PSTask.None,
PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic,
Guid.Empty.ToString(),
Guid.Empty.ToString(),
Expand Down Expand Up @@ -158,7 +162,9 @@ protected void OnDataPacketReceived(byte[] rawData, string stream, Guid psGuid)

protected void OnDataAckPacketReceived(Guid psGuid)
{
throw new PSRemotingTransportException(PSRemotingErrorId.IPCUnknownElementReceived, RemotingErrorIdStrings.IPCUnknownElementReceived,
throw new PSRemotingTransportException(
PSRemotingErrorId.IPCUnknownElementReceived,
RemotingErrorIdStrings.IPCUnknownElementReceived,
OutOfProcessUtils.PS_OUT_OF_PROC_DATA_ACK_TAG);
}

Expand Down Expand Up @@ -301,33 +307,39 @@ protected void OnCloseAckPacketReceived(Guid psGuid)

#region Methods

protected OutOfProcessServerSessionTransportManager CreateSessionTransportManager(string configurationName, PSRemotingCryptoHelperServer cryptoHelper)
protected OutOfProcessServerSessionTransportManager CreateSessionTransportManager(string configurationName, PSRemotingCryptoHelperServer cryptoHelper, string workingDirectory)
{
PSSenderInfo senderInfo;
#if !UNIX
WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent();
PSPrincipal userPrincipal = new PSPrincipal(new PSIdentity(string.Empty, true, currentIdentity.Name, null),
PSPrincipal userPrincipal = new PSPrincipal(
new PSIdentity(string.Empty, true, currentIdentity.Name, null),
currentIdentity);
senderInfo = new PSSenderInfo(userPrincipal, "http://localhost");
#else
PSPrincipal userPrincipal = new PSPrincipal(new PSIdentity(string.Empty, true, string.Empty, null),
PSPrincipal userPrincipal = new PSPrincipal(
new PSIdentity(string.Empty, true, string.Empty, null),
null);
senderInfo = new PSSenderInfo(userPrincipal, "http://localhost");
#endif

OutOfProcessServerSessionTransportManager tm = new OutOfProcessServerSessionTransportManager(originalStdOut, originalStdErr, cryptoHelper);

ServerRemoteSession srvrRemoteSession = ServerRemoteSession.CreateServerRemoteSession(senderInfo,
_initialCommand, tm, configurationName);
ServerRemoteSession.CreateServerRemoteSession(
senderInfo,
_initialCommand,
tm,
configurationName,
workingDirectory);

return tm;
}

protected void Start(string initialCommand, PSRemotingCryptoHelperServer cryptoHelper, string configurationName = null)
protected void Start(string initialCommand, PSRemotingCryptoHelperServer cryptoHelper, string workingDirectory = null, string configurationName = null)
{
_initialCommand = initialCommand;

sessionTM = CreateSessionTransportManager(configurationName, cryptoHelper);
sessionTM = CreateSessionTransportManager(configurationName, cryptoHelper, workingDirectory);

try
{
Expand All @@ -338,7 +350,7 @@ protected void Start(string initialCommand, PSRemotingCryptoHelperServer cryptoH
{
if (sessionTM == null)
{
sessionTM = CreateSessionTransportManager(configurationName, cryptoHelper);
sessionTM = CreateSessionTransportManager(configurationName, cryptoHelper, workingDirectory);
}
}

Expand All @@ -352,8 +364,10 @@ protected void Start(string initialCommand, PSRemotingCryptoHelperServer cryptoH
sessionTM = null;
}

throw new PSRemotingTransportException(PSRemotingErrorId.IPCUnknownElementReceived,
RemotingErrorIdStrings.IPCUnknownElementReceived, string.Empty);
throw new PSRemotingTransportException(
PSRemotingErrorId.IPCUnknownElementReceived,
RemotingErrorIdStrings.IPCUnknownElementReceived,
string.Empty);
}

// process data in a thread pool thread..this way Runspace, Command
Expand All @@ -366,7 +380,8 @@ protected void Start(string initialCommand, PSRemotingCryptoHelperServer cryptoH
#else
ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessingThreadStart), data);
#endif
} while (true);
}
while (true);
}
catch (Exception e)
{
Expand Down Expand Up @@ -468,8 +483,11 @@ private OutOfProcessMediator() : base(true)
#region Static Methods

/// <summary>
/// Starts the out-of-process powershell server instance.
/// </summary>
internal static void Run(string initialCommand)
/// <param name="initialCommand">Specifies the initialization script.</param>
/// <param name="workingDirectory">Specifies the initial working directory. The working directory is set before the initial command.</param>
internal static void Run(string initialCommand, string workingDirectory)
{
lock (SyncObject)
{
Expand All @@ -486,7 +504,7 @@ internal static void Run(string initialCommand)
// Setup unhandled exception to log events
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(AppDomainUnhandledException);
#endif
s_singletonInstance.Start(initialCommand, new PSRemotingCryptoHelperServer());
s_singletonInstance.Start(initialCommand, new PSRemotingCryptoHelperServer(), workingDirectory);
}

#endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Management.Automation.Host;
using System.Management.Automation.Internal;
Expand Down Expand Up @@ -86,6 +87,9 @@ private Dictionary<Guid, ServerPowerShellDriver> _associatedShells
// Results in a configured remote runspace pushed onto driver host.
private string _configurationName;

// Optional initial location of the PowerShell session
private readonly string _initialLocation;

/// <summary>
/// Event that get raised when the RunspacePool is closed.
/// </summary>
Expand Down Expand Up @@ -120,6 +124,7 @@ private Dictionary<Guid, ServerPowerShellDriver> _associatedShells
/// <param name="serverCapability">Server capability reported to the client during negotiation (not the actual capability).</param>
/// <param name="psClientVersion">Client PowerShell version.</param>
/// <param name="configurationName">Optional endpoint configuration name to create a pushed configured runspace.</param>
/// <param name="initialLocation">Optional initial location of the powershell.</param>
internal ServerRunspacePoolDriver(
Guid clientRunspacePoolId,
int minRunspaces,
Expand All @@ -134,19 +139,21 @@ internal ServerRunspacePoolDriver(
bool isAdministrator,
RemoteSessionCapability serverCapability,
Version psClientVersion,
string configurationName)
string configurationName,
string initialLocation)
{
Dbg.Assert(configData != null, "ConfigurationData cannot be null");

_serverCapability = serverCapability;
_clientPSVersion = psClientVersion;

_configurationName = configurationName;
_initialLocation = initialLocation;

// Create a new server host and associate for host call
// integration
_remoteHost = new ServerDriverRemoteHost(clientRunspacePoolId,
Guid.Empty, hostInfo, transportManager, null);
_remoteHost = new ServerDriverRemoteHost(
clientRunspacePoolId, Guid.Empty, hostInfo, transportManager, null);

_configData = configData;
_applicationPrivateData = applicationPrivateData;
Expand Down Expand Up @@ -615,6 +622,13 @@ private void HandleRunspaceCreated(object sender, RunspaceCreatedEventArgs args)
// to ignore all but critical errors.
}

if (!string.IsNullOrWhiteSpace(_initialLocation))
{
var setLocationCommand = new Command("Set-Location");
setLocationCommand.Parameters.Add(new CommandParameter("LiteralPath", _initialLocation));
InvokeScript(setLocationCommand, args);
}

// Run startup scripts
InvokeStartupScripts(args);

Expand Down
Loading

0 comments on commit f69f30b

Please sign in to comment.