diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs
index aefcb58cfd..8d31f35774 100644
--- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs
+++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs
@@ -227,6 +227,7 @@ namespace Microsoft.PowerShell
Version = 0x00800000, // -Version | -v
WindowStyle = 0x01000000, // -WindowStyle | -w
WorkingDirectory = 0x02000000, // -WorkingDirectory | -wd
+ ConfigurationFile = 0x04000000, // -ConfigurationFile
// Enum values for specified ExecutionPolicy
EPUnrestricted = 0x0000000100000000, // ExecutionPolicy unrestricted
EPRemoteSigned = 0x0000000200000000, // ExecutionPolicy remote signed
@@ -370,6 +371,15 @@ namespace Microsoft.PowerShell
}
}
+ internal string? ConfigurationFile
+ {
+ get
+ {
+ AssertArgumentsParsed();
+ return _configurationFile;
+ }
+ }
+
internal string? ConfigurationName
{
get
@@ -915,6 +925,19 @@ namespace Microsoft.PowerShell
_noInteractive = false;
ParametersUsed |= ParameterBitmap.Interactive;
}
+ else if (MatchSwitch(switchKey, "configurationfile", "configurationfile"))
+ {
+ ++i;
+ if (i >= args.Length)
+ {
+ SetCommandLineError(
+ CommandLineParameterParserStrings.MissingConfigurationFileArgument);
+ break;
+ }
+
+ _configurationFile = args[i];
+ ParametersUsed |= ParameterBitmap.ConfigurationFile;
+ }
else if (MatchSwitch(switchKey, "configurationname", "config"))
{
++i;
@@ -1474,6 +1497,7 @@ namespace Microsoft.PowerShell
private bool _namedPipeServerMode;
private bool _sshServerMode;
private bool _showVersion;
+ private string? _configurationFile;
private string? _configurationName;
private string? _error;
private bool _showHelp;
diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs
index 6a78b4c864..9dbfdf8c80 100644
--- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs
+++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs
@@ -73,6 +73,9 @@ namespace Microsoft.PowerShell
///
/// Help text for minishell. This is displayed on 'minishell -?'.
///
+ ///
+ /// True when an external caller provides an InitialSessionState object, which can conflict with '-ConfigurationFile' argument.
+ ///
///
/// The exit code for the shell.
///
@@ -99,7 +102,10 @@ namespace Microsoft.PowerShell
/// Anyone checking the exit code of the shell or monitor can mask off the high word to determine the exit code passed
/// by the script that the shell last executed.
///
- internal static int Start(string bannerText, string helpText)
+ internal static int Start(
+ string bannerText,
+ string helpText,
+ bool issProvidedExternally)
{
#if DEBUG
if (Environment.GetEnvironmentVariable("POWERSHELL_DEBUG_STARTUP") != null)
@@ -111,6 +117,12 @@ namespace Microsoft.PowerShell
}
#endif
+ // Check for external InitialSessionState configuration conflict with '-ConfigurationFile' argument.
+ if (issProvidedExternally && !string.IsNullOrEmpty(s_cpp.ConfigurationFile))
+ {
+ throw new ConsoleHostStartupException(ConsoleHostStrings.ShellCannotBeStartedWithConfigConflict);
+ }
+
// put PSHOME in front of PATH so that calling `powershell` within `powershell` always starts the same running version
string path = Environment.GetEnvironmentVariable("PATH");
string pshome = Utils.DefaultPowerShellAppBase + Path.PathSeparator;
@@ -188,7 +200,6 @@ namespace Microsoft.PowerShell
#if !UNIX
TaskbarJumpList.CreateRunAsAdministratorJumpList();
#endif
-
// First check for and handle PowerShell running in a server mode.
if (s_cpp.ServerMode)
{
@@ -197,7 +208,9 @@ namespace Microsoft.PowerShell
StdIOProcessMediator.Run(
initialCommand: s_cpp.InitialCommand,
workingDirectory: s_cpp.WorkingDirectory,
- configurationName: null);
+ configurationName: null,
+ configurationFile: s_cpp.ConfigurationFile,
+ combineErrOutStream: false);
exitCode = 0;
}
else if (s_cpp.SSHServerMode)
@@ -207,7 +220,9 @@ namespace Microsoft.PowerShell
StdIOProcessMediator.Run(
initialCommand: s_cpp.InitialCommand,
workingDirectory: null,
- configurationName: null);
+ configurationName: null,
+ configurationFile: s_cpp.ConfigurationFile,
+ combineErrOutStream: true);
exitCode = 0;
}
else if (s_cpp.NamedPipeServerMode)
@@ -301,8 +316,8 @@ namespace Microsoft.PowerShell
PowerShellConfig.Instance.SetSystemConfigFilePath(s_cpp.SettingsFile);
}
- // Check registry setting for a Group Policy ConfigurationName entry and
- // use it to override anything set by the user.
+ // Check registry setting for a Group Policy ConfigurationName entry,
+ // and use it to override anything set by the user on the command line.
// It depends on setting file so 'SetSystemConfigFilePath()' should be called before.
s_cpp.ConfigurationName = CommandLineParameterParser.GetConfigurationNameFromGroupPolicy();
}
@@ -1489,7 +1504,7 @@ namespace Microsoft.PowerShell
// NTRAID#Windows Out Of Band Releases-915506-2005/09/09
// Removed HandleUnexpectedExceptions infrastructure
- exitCode = DoRunspaceLoop(cpp.InitialCommand, cpp.SkipProfiles, cpp.Args, cpp.StaMode, cpp.ConfigurationName);
+ exitCode = DoRunspaceLoop(cpp.InitialCommand, cpp.SkipProfiles, cpp.Args, cpp.StaMode, cpp.ConfigurationName, cpp.ConfigurationFile);
}
while (false);
@@ -1502,13 +1517,19 @@ namespace Microsoft.PowerShell
///
/// The process exit code to be returned by Main.
///
- private uint DoRunspaceLoop(string initialCommand, bool skipProfiles, Collection initialCommandArgs, bool staMode, string configurationName)
+ private uint DoRunspaceLoop(
+ string initialCommand,
+ bool skipProfiles,
+ Collection initialCommandArgs,
+ bool staMode,
+ string configurationName,
+ string configurationFilePath)
{
ExitCode = ExitCodeSuccess;
while (!ShouldEndSession)
{
- RunspaceCreationEventArgs args = new RunspaceCreationEventArgs(initialCommand, skipProfiles, staMode, configurationName, initialCommandArgs);
+ RunspaceCreationEventArgs args = new RunspaceCreationEventArgs(initialCommand, skipProfiles, staMode, configurationName, configurationFilePath, initialCommandArgs);
CreateRunspace(args);
if (ExitCode == ExitCodeInitFailure) { break; }
@@ -1584,14 +1605,12 @@ namespace Microsoft.PowerShell
return e;
}
- private void CreateRunspace(object runspaceCreationArgs)
+ private void CreateRunspace(RunspaceCreationEventArgs runspaceCreationArgs)
{
- RunspaceCreationEventArgs args = null;
try
{
- args = runspaceCreationArgs as RunspaceCreationEventArgs;
- Dbg.Assert(args != null, "Event Arguments to CreateRunspace should not be null");
- DoCreateRunspace(args.InitialCommand, args.SkipProfiles, args.StaMode, args.ConfigurationName, args.InitialCommandArgs);
+ Dbg.Assert(runspaceCreationArgs != null, "Arguments to CreateRunspace should not be null.");
+ DoCreateRunspace(runspaceCreationArgs);
}
catch (ConsoleHostStartupException startupException)
{
@@ -1603,7 +1622,7 @@ namespace Microsoft.PowerShell
///
/// Check if a screen reviewer utility is running.
/// When a screen reader is running, we don't auto-load the PSReadLine module at startup,
- /// since PSReadLine is not accessibility-firendly enough as of today.
+ /// since PSReadLine is not accessibility-friendly enough as of today.
///
private bool IsScreenReaderActive()
{
@@ -1646,12 +1665,33 @@ namespace Microsoft.PowerShell
/// Opens and Initializes the Host's sole Runspace. Processes the startup scripts and runs any command passed on the
/// command line.
///
- private void DoCreateRunspace(string initialCommand, bool skipProfiles, bool staMode, string configurationName, Collection initialCommandArgs)
+ /// Runspace creation event arguments.
+ private void DoCreateRunspace(RunspaceCreationEventArgs args)
{
- Dbg.Assert(_runspaceRef == null, "runspace should be null");
+ Dbg.Assert(_runspaceRef == null, "_runspaceRef field should be null");
Dbg.Assert(DefaultInitialSessionState != null, "DefaultInitialSessionState should not be null");
s_runspaceInitTracer.WriteLine("Calling RunspaceFactory.CreateRunspace");
+ // Use session configuration file if provided.
+ bool customConfigurationProvided = false;
+ if (!string.IsNullOrEmpty(args.ConfigurationFilePath))
+ {
+ try
+ {
+ // Replace DefaultInitialSessionState with the initial state configuration defined by the file.
+ DefaultInitialSessionState = InitialSessionState.CreateFromSessionConfigurationFile(
+ path: args.ConfigurationFilePath,
+ roleVerifier: null,
+ validateFile: true);
+ }
+ catch (Exception ex)
+ {
+ throw new ConsoleHostStartupException(ConsoleHostStrings.ShellCannotBeStarted, ex);
+ }
+
+ customConfigurationProvided = true;
+ }
+
try
{
Runspace consoleRunspace = null;
@@ -1667,7 +1707,7 @@ namespace Microsoft.PowerShell
// powershell -command "Update-Module PSReadline"
// This should work just fine as long as no other instances of PowerShell are running.
ReadOnlyCollection defaultImportModulesList = null;
- if (LoadPSReadline())
+ if (!customConfigurationProvided && LoadPSReadline())
{
if (IsScreenReaderActive())
{
@@ -1682,7 +1722,7 @@ namespace Microsoft.PowerShell
consoleRunspace = RunspaceFactory.CreateRunspace(this, DefaultInitialSessionState);
try
{
- OpenConsoleRunspace(consoleRunspace, staMode);
+ OpenConsoleRunspace(consoleRunspace, args.StaMode);
}
catch (Exception)
{
@@ -1702,7 +1742,7 @@ namespace Microsoft.PowerShell
}
consoleRunspace = RunspaceFactory.CreateRunspace(this, DefaultInitialSessionState);
- OpenConsoleRunspace(consoleRunspace, staMode);
+ OpenConsoleRunspace(consoleRunspace, args.StaMode);
}
Runspace.PrimaryRunspace = consoleRunspace;
@@ -1734,7 +1774,7 @@ namespace Microsoft.PowerShell
_readyForInputTimeInMS = (DateTime.Now - Process.GetCurrentProcess().StartTime).TotalMilliseconds;
#endif
- DoRunspaceInitialization(skipProfiles, initialCommand, configurationName, initialCommandArgs);
+ DoRunspaceInitialization(args);
}
private static void OpenConsoleRunspace(Runspace runspace, bool staMode)
@@ -1751,7 +1791,7 @@ namespace Microsoft.PowerShell
runspace.Open();
}
- private void DoRunspaceInitialization(bool skipProfiles, string initialCommand, string configurationName, Collection initialCommandArgs)
+ private void DoRunspaceInitialization(RunspaceCreationEventArgs args)
{
if (_runspaceRef.Runspace.Debugger != null)
{
@@ -1786,13 +1826,13 @@ namespace Microsoft.PowerShell
}
}
- if (!string.IsNullOrEmpty(configurationName))
+ if (!string.IsNullOrEmpty(args.ConfigurationName))
{
// If an endpoint configuration is specified then create a loop-back remote runspace targeting
// the endpoint and push onto runspace ref stack. Ignore profile and configuration scripts.
try
{
- RemoteRunspace remoteRunspace = HostUtilities.CreateConfiguredRunspace(configurationName, this);
+ RemoteRunspace remoteRunspace = HostUtilities.CreateConfiguredRunspace(args.ConfigurationName, this);
remoteRunspace.ShouldCloseOnPop = true;
PushRunspace(remoteRunspace);
@@ -1827,7 +1867,7 @@ namespace Microsoft.PowerShell
currentUserProfile,
currentUserHostSpecificProfile));
- if (!skipProfiles)
+ if (!args.SkipProfiles)
{
// Run the profiles.
// Profiles are run in the following order:
@@ -1887,11 +1927,11 @@ namespace Microsoft.PowerShell
tempPipeline.Commands.Add(c);
- if (initialCommandArgs != null)
+ if (args.InitialCommandArgs != null)
{
// add the args passed to the command.
- foreach (CommandParameter p in initialCommandArgs)
+ foreach (CommandParameter p in args.InitialCommandArgs)
{
c.Parameters.Add(p);
}
@@ -1948,19 +1988,19 @@ namespace Microsoft.PowerShell
ReportException(e1, exec);
}
}
- else if (!string.IsNullOrEmpty(initialCommand))
+ else if (!string.IsNullOrEmpty(args.InitialCommand))
{
// Run the command passed on the command line
s_tracer.WriteLine("running initial command");
- Pipeline tempPipeline = exec.CreatePipeline(initialCommand, true);
+ Pipeline tempPipeline = exec.CreatePipeline(args.InitialCommand, true);
- if (initialCommandArgs != null)
+ if (args.InitialCommandArgs != null)
{
// add the args passed to the command.
- foreach (CommandParameter p in initialCommandArgs)
+ foreach (CommandParameter p in args.InitialCommandArgs)
{
tempPipeline.Commands[0].Parameters.Add(p);
}
@@ -1976,7 +2016,7 @@ namespace Microsoft.PowerShell
ParseError[] errors;
// Detect if they're using input. If so, read from it.
- Ast parsedInput = Parser.ParseInput(initialCommand, out tokens, out errors);
+ Ast parsedInput = Parser.ParseInput(args.InitialCommand, out tokens, out errors);
if (AstSearcher.IsUsingDollarInput(parsedInput))
{
executionOptions |= Executor.ExecutionOptions.ReadInputObjects;
@@ -3021,12 +3061,14 @@ namespace Microsoft.PowerShell
bool skipProfiles,
bool staMode,
string configurationName,
+ string configurationFilePath,
Collection initialCommandArgs)
{
InitialCommand = initialCommand;
SkipProfiles = skipProfiles;
StaMode = staMode;
ConfigurationName = configurationName;
+ ConfigurationFilePath = configurationFilePath;
InitialCommandArgs = initialCommandArgs;
}
@@ -3038,6 +3080,8 @@ namespace Microsoft.PowerShell
internal string ConfigurationName { get; set; }
+ internal string ConfigurationFilePath { get; set; }
+
internal Collection InitialCommandArgs { get; set; }
}
} // namespace
diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleShell.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleShell.cs
index 11dd276a52..44f2931162 100644
--- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleShell.cs
+++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleShell.cs
@@ -21,7 +21,12 @@ namespace Microsoft.PowerShell
/// An integer value which should be used as exit code for the process.
public static int Start(string? bannerText, string? helpText, string[] args)
{
- return Start(InitialSessionState.CreateDefault2(), bannerText, helpText, args);
+ return StartImpl(
+ initialSessionState: InitialSessionState.CreateDefault2(),
+ bannerText,
+ helpText,
+ args,
+ issProvided: false);
}
/// Entry point in to ConsoleShell. Used to create a custom Powershell console application.
@@ -31,6 +36,31 @@ namespace Microsoft.PowerShell
/// Commandline parameters specified by user.
/// An integer value which should be used as exit code for the process.
public static int Start(InitialSessionState initialSessionState, string? bannerText, string? helpText, string[] args)
+ {
+ return StartImpl(
+ initialSessionState,
+ bannerText,
+ helpText,
+ args,
+ issProvided: true);
+ }
+
+ ///
+ /// Implementation of entry point to ConsoleShell.
+ /// Used to create a custom Powershell console application.
+ ///
+ /// InitialSessionState to be used by the ConsoleHost.
+ /// Banner text to be displayed by ConsoleHost.
+ /// Help text for the shell.
+ /// Commandline parameters specified by user.
+ /// True when the InitialSessionState object is provided by caller.
+ /// An integer value which should be used as exit code for the process.
+ private static int StartImpl(
+ InitialSessionState initialSessionState,
+ string? bannerText,
+ string? helpText,
+ string[] args,
+ bool issProvided)
{
if (initialSessionState == null)
{
@@ -45,7 +75,7 @@ namespace Microsoft.PowerShell
ConsoleHost.ParseCommandLine(args);
ConsoleHost.DefaultInitialSessionState = initialSessionState;
- return ConsoleHost.Start(bannerText, helpText);
+ return ConsoleHost.Start(bannerText, helpText, issProvided);
}
}
}
diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs
index 6dbc21ab24..18df48d360 100644
--- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs
+++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs
@@ -96,7 +96,10 @@ namespace Microsoft.PowerShell
ConsoleHost.DefaultInitialSessionState = InitialSessionState.CreateDefault2();
- exitCode = ConsoleHost.Start(banner, ManagedEntranceStrings.UsageHelp);
+ exitCode = ConsoleHost.Start(
+ bannerText: banner,
+ helpText: ManagedEntranceStrings.UsageHelp,
+ issProvidedExternally: false);
}
catch (HostException e)
{
diff --git a/src/Microsoft.PowerShell.ConsoleHost/resources/CommandLineParameterParserStrings.resx b/src/Microsoft.PowerShell.ConsoleHost/resources/CommandLineParameterParserStrings.resx
index ab893b91a0..e346331b41 100644
--- a/src/Microsoft.PowerShell.ConsoleHost/resources/CommandLineParameterParserStrings.resx
+++ b/src/Microsoft.PowerShell.ConsoleHost/resources/CommandLineParameterParserStrings.resx
@@ -187,7 +187,10 @@ Valid formats are:
Cannot process the command because -STA and -MTA are both specified. Specify either -STA or -MTA.
- Cannot process the command because -Configuration requires an argument that is a remote endpoint configuration name. Specify this argument and try again.
+ Cannot process the command because -ConfigurationName requires an argument that is a remote endpoint configuration name. Specify this argument and try again.
+
+
+ Cannot process the command because -ConfigurationFile requires an argument that is a session configuration (.pssc) file path. Specify this argument and try again.
Cannot process the command because -CustomPipeName requires an argument that is a name of the pipe you want to use. Specify this argument and try again.
diff --git a/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostStrings.resx b/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostStrings.resx
index 7296bb30fe..49c22783c1 100644
--- a/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostStrings.resx
+++ b/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostStrings.resx
@@ -135,6 +135,9 @@
The shell cannot be started. A failure occurred during initialization:
+
+ The shell cannot be started. An InitialSessionState object has been provided along with a -ConfigurationFile argument. Both configuration directives cannot be used at the same time.
+
An error has occurred that was not properly handled. Additional information is shown below. The PowerShell process will exit.
diff --git a/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx b/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx
index 481a4216cf..24a6e78ad6 100644
--- a/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx
+++ b/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx
@@ -288,6 +288,14 @@ All parameters are case-insensitive.
Example: "pwsh -ConfigurationName AdminRoles"
+-ConfigurationFile
+
+ Specifies a session configuration (.pssc) file path. The configuration
+ contained in the configuration file will be applied to the PowerShell
+ session.
+
+ Example: "pwsh -ConfigurationFile "C:\ProgramData\PowerShell\MyConfig.pssc"
+
-CustomPipeName
Specifies the name to use for an additional IPC server (named pipe) used
diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs
index bfae1b3e6b..cb4126ed1f 100644
--- a/src/System.Management.Automation/engine/InitialSessionState.cs
+++ b/src/System.Management.Automation/engine/InitialSessionState.cs
@@ -1312,7 +1312,7 @@ namespace System.Management.Automation.Runspaces
/// Creates an initial session state from a PSSC configuration file.
///
/// The path to the PSSC session configuration file.
- ///
+ /// InitialSessionState object.
public static InitialSessionState CreateFromSessionConfigurationFile(string path)
{
return CreateFromSessionConfigurationFile(path, null);
@@ -1327,10 +1327,48 @@ namespace System.Management.Automation.Runspaces
/// target session. If you have a WindowsPrincipal for a user, for example, create a Function that
/// checks windowsPrincipal.IsInRole().
///
- ///
- public static InitialSessionState CreateFromSessionConfigurationFile(string path, Func roleVerifier)
+ /// InitialSessionState object.
+ public static InitialSessionState CreateFromSessionConfigurationFile(
+ string path,
+ Func roleVerifier)
{
- Remoting.DISCPowerShellConfiguration discConfiguration = new Remoting.DISCPowerShellConfiguration(path, roleVerifier);
+ return CreateFromSessionConfigurationFile(path, roleVerifier, validateFile: false);
+ }
+
+ ///
+ /// Creates an initial session state from a PSSC configuration file.
+ ///
+ /// The path to the PSSC session configuration file.
+ ///
+ /// The verifier that PowerShell should call to determine if groups in the Role entry apply to the
+ /// target session. If you have a WindowsPrincipal for a user, for example, create a Function that
+ /// checks windowsPrincipal.IsInRole().
+ ///
+ /// Validates the file contents for supported SessionState options.
+ /// InitialSessionState object.
+ public static InitialSessionState CreateFromSessionConfigurationFile(
+ string path,
+ Func roleVerifier,
+ bool validateFile)
+ {
+ if (path is null)
+ {
+ throw new PSArgumentNullException(nameof(path));
+ }
+
+ if (!File.Exists(path))
+ {
+ throw new PSInvalidOperationException(
+ StringUtil.Format(ConsoleInfoErrorStrings.ConfigurationFileDoesNotExist, path));
+ }
+
+ if (!path.EndsWith(".pssc", StringComparison.OrdinalIgnoreCase))
+ {
+ throw new PSInvalidOperationException(
+ StringUtil.Format(ConsoleInfoErrorStrings.NotConfigurationFile, path));
+ }
+
+ Remoting.DISCPowerShellConfiguration discConfiguration = new Remoting.DISCPowerShellConfiguration(path, roleVerifier, validateFile);
return discConfiguration.GetInitialSessionState(null);
}
@@ -5241,7 +5279,6 @@ end {
{ "Enable-PSSessionConfiguration", new SessionStateCmdletEntry("Enable-PSSessionConfiguration", typeof(EnablePSSessionConfigurationCommand), helpFile) },
{ "Get-PSSessionCapability", new SessionStateCmdletEntry("Get-PSSessionCapability", typeof(GetPSSessionCapabilityCommand), helpFile) },
{ "Get-PSSessionConfiguration", new SessionStateCmdletEntry("Get-PSSessionConfiguration", typeof(GetPSSessionConfigurationCommand), helpFile) },
- { "New-PSSessionConfigurationFile", new SessionStateCmdletEntry("New-PSSessionConfigurationFile", typeof(NewPSSessionConfigurationFileCommand), helpFile) },
{ "Receive-PSSession", new SessionStateCmdletEntry("Receive-PSSession", typeof(ReceivePSSessionCommand), helpFile) },
{ "Register-PSSessionConfiguration", new SessionStateCmdletEntry("Register-PSSessionConfiguration", typeof(RegisterPSSessionConfigurationCommand), helpFile) },
{ "Unregister-PSSessionConfiguration", new SessionStateCmdletEntry("Unregister-PSSessionConfiguration", typeof(UnregisterPSSessionConfigurationCommand), helpFile) },
@@ -5273,6 +5310,7 @@ end {
{ "New-ModuleManifest", new SessionStateCmdletEntry("New-ModuleManifest", typeof(NewModuleManifestCommand), helpFile) },
{ "New-PSRoleCapabilityFile", new SessionStateCmdletEntry("New-PSRoleCapabilityFile", typeof(NewPSRoleCapabilityFileCommand), helpFile) },
{ "New-PSSession", new SessionStateCmdletEntry("New-PSSession", typeof(NewPSSessionCommand), helpFile) },
+ { "New-PSSessionConfigurationFile", new SessionStateCmdletEntry("New-PSSessionConfigurationFile", typeof(NewPSSessionConfigurationFileCommand), helpFile) },
{ "New-PSSessionOption", new SessionStateCmdletEntry("New-PSSessionOption", typeof(NewPSSessionOptionCommand), helpFile) },
{ "New-PSTransportOption", new SessionStateCmdletEntry("New-PSTransportOption", typeof(NewPSTransportOptionCommand), helpFile) },
{ "Out-Default", new SessionStateCmdletEntry("Out-Default", typeof(OutDefaultCommand), helpFile) },
diff --git a/src/System.Management.Automation/engine/remoting/commands/NewPSSessionConfigurationFile.cs b/src/System.Management.Automation/engine/remoting/commands/NewPSSessionConfigurationFile.cs
index 658876c1d8..e5aa6bef79 100644
--- a/src/System.Management.Automation/engine/remoting/commands/NewPSSessionConfigurationFile.cs
+++ b/src/System.Management.Automation/engine/remoting/commands/NewPSSessionConfigurationFile.cs
@@ -15,7 +15,6 @@ using System.Text;
namespace Microsoft.PowerShell.Commands
{
-#if !UNIX
///
/// New-PSSessionConfigurationFile command implementation
///
@@ -1126,7 +1125,6 @@ namespace Microsoft.PowerShell.Commands
#endregion
}
-#endif
///
/// New-PSRoleCapabilityFile command implementation
diff --git a/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs b/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs
index d428e521fe..744e8ff8e1 100644
--- a/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs
+++ b/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs
@@ -22,6 +22,8 @@ using Dbg = System.Management.Automation.Diagnostics;
namespace System.Management.Automation.Remoting
{
+ #region WSMan endpoint configuration
+
///
/// This struct is used to represent contents from configuration xml. The
/// XML is passed to plugins by WSMan API.
@@ -56,6 +58,8 @@ namespace System.Management.Automation.Remoting
#endregion
+ #region Fields
+
internal string StartupScript;
// this field is used only by an Out-Of-Process (IPC) server process
internal string InitializationScriptForOutOfProcessRunspace;
@@ -71,6 +75,10 @@ namespace System.Management.Automation.Remoting
internal PSSessionConfigurationData SessionConfigurationData;
internal string ConfigFilePath;
+ #endregion
+
+ #region Methods
+
///
/// Using optionName and optionValue updates the current object.
///
@@ -324,6 +332,8 @@ namespace System.Management.Automation.Remoting
throw PSTraceSource.NewArgumentException("typeToLoad", RemotingErrorIdStrings.UnableToLoadType,
EndPointConfigurationTypeName, ConfigurationDataFromXML.INITPARAMETERSTOKEN);
}
+
+ #endregion
}
///
@@ -450,7 +460,8 @@ namespace System.Management.Automation.Remoting
...
*/
- internal static ConfigurationDataFromXML LoadEndPointConfiguration(string shellId,
+ internal static ConfigurationDataFromXML LoadEndPointConfiguration(
+ string shellId,
string initializationParameters)
{
ConfigurationDataFromXML configData = null;
@@ -798,6 +809,8 @@ namespace System.Management.Automation.Remoting
///
internal sealed class DefaultRemotePowerShellConfiguration : PSSessionConfiguration
{
+ #region Method overrides
+
///
///
///
@@ -852,9 +865,15 @@ namespace System.Management.Automation.Remoting
return sessionState;
}
+
+ #endregion
}
- #region Declarative Initial Session Configuration
+ #endregion
+
+ #region Declarative InitialSession Configuration
+
+ #region Supporting types
///
/// Specifies type of initial session state to use. Valid values are Empty and Default.
@@ -898,6 +917,10 @@ namespace System.Management.Automation.Remoting
}
}
+ #endregion
+
+ #region ConfigFileConstants
+
///
/// Configuration file constants.
///
@@ -1355,6 +1378,8 @@ namespace System.Management.Automation.Remoting
}
}
+ #endregion
+
#region DISC Utilities
///
@@ -1681,6 +1706,8 @@ namespace System.Management.Automation.Remoting
#endregion
+ #region DISCPowerShellConfiguration
+
///
/// Creates an initial session state based on the configuration language for PSSC files.
///
@@ -1706,7 +1733,11 @@ namespace System.Management.Automation.Remoting
/// target session. If you have a WindowsPrincipal for a user, for example, create a Function that
/// checks windowsPrincipal.IsInRole().
///
- internal DISCPowerShellConfiguration(string configFile, Func roleVerifier)
+ /// Validate file for supported configuration options.
+ internal DISCPowerShellConfiguration(
+ string configFile,
+ Func roleVerifier,
+ bool validateFile = false)
{
_configFile = configFile;
if (roleVerifier == null)
@@ -1726,6 +1757,12 @@ namespace System.Management.Automation.Remoting
configFile, out scriptName);
_configHash = DISCUtils.LoadConfigFile(Runspace.DefaultRunspace.ExecutionContext, script);
+
+ if (validateFile)
+ {
+ DISCFileValidation.ValidateContents(_configHash);
+ }
+
MergeRoleRulesIntoConfigHash(roleVerifier);
MergeRoleCapabilitiesIntoConfigHash();
@@ -2890,4 +2927,110 @@ namespace System.Management.Automation.Remoting
}
}
#endregion
+
+ #region DISCFileValidation
+
+ internal static class DISCFileValidation
+ {
+ // Set of supported configuration options for a PowerShell InitialSessionState.
+#if UNIX
+ private static readonly HashSet SupportedConfigOptions = new HashSet(StringComparer.OrdinalIgnoreCase)
+ {
+ "AliasDefinitions",
+ "AssembliesToLoad",
+ "Author",
+ "CompanyName",
+ "Copyright",
+ "Description",
+ "EnvironmentVariables",
+ "FormatsToProcess",
+ "FunctionDefinitions",
+ "GUID",
+ "LanguageMode",
+ "ModulesToImport",
+ "MountUserDrive",
+ "SchemaVersion",
+ "ScriptsToProcess",
+ "SessionType",
+ "TranscriptDirectory",
+ "TypesToProcess",
+ "UserDriveMaximumSize",
+ "VisibleAliases",
+ "VisibleCmdlets",
+ "VariableDefinitions",
+ "VisibleExternalCommands",
+ "VisibleFunctions",
+ "VisibleProviders"
+ };
+#else
+ private static readonly HashSet SupportedConfigOptions = new HashSet(StringComparer.OrdinalIgnoreCase)
+ {
+ "AliasDefinitions",
+ "AssembliesToLoad",
+ "Author",
+ "CompanyName",
+ "Copyright",
+ "Description",
+ "EnvironmentVariables",
+ "ExecutionPolicy",
+ "FormatsToProcess",
+ "FunctionDefinitions",
+ "GUID",
+ "LanguageMode",
+ "ModulesToImport",
+ "MountUserDrive",
+ "SchemaVersion",
+ "ScriptsToProcess",
+ "SessionType",
+ "TranscriptDirectory",
+ "TypesToProcess",
+ "UserDriveMaximumSize",
+ "VisibleAliases",
+ "VisibleCmdlets",
+ "VariableDefinitions",
+ "VisibleExternalCommands",
+ "VisibleFunctions",
+ "VisibleProviders"
+ };
+#endif
+
+ // These are configuration options for WSMan (WinRM) endpoint configurations, that
+ // appearand in .pssc files, but are not part of PowerShell InitialSessionState.
+ private static readonly HashSet UnsupportedConfigOptions = new HashSet(StringComparer.OrdinalIgnoreCase)
+ {
+ "GroupManagedServiceAccount",
+ "PowerShellVersion",
+ "RequiredGroups",
+ "RoleDefinitions",
+ "RunAsVirtualAccount",
+ "RunAsVirtualAccountGroups"
+ };
+
+ internal static void ValidateContents(Hashtable configHash)
+ {
+ foreach (var key in configHash.Keys)
+ {
+ if (key is not string keyName)
+ {
+ throw new PSInvalidOperationException(RemotingErrorIdStrings.DISCInvalidConfigKeyType);
+ }
+
+ if (UnsupportedConfigOptions.Contains(keyName))
+ {
+ throw new PSInvalidOperationException(
+ StringUtil.Format(RemotingErrorIdStrings.DISCUnsupportedConfigName, keyName));
+ }
+
+ if (!SupportedConfigOptions.Contains(keyName))
+ {
+ throw new PSInvalidOperationException(
+ StringUtil.Format(RemotingErrorIdStrings.DISCUnknownConfigName, keyName));
+ }
+ }
+ }
+ }
+
+ #endregion
+
+ #endregion
}
diff --git a/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs b/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs
index 2883bfd68f..5636835e19 100644
--- a/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs
+++ b/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs
@@ -1980,10 +1980,10 @@ namespace System.Management.Automation.Remoting.Client
break;
}
- if (data.StartsWith(System.Management.Automation.Remoting.Server.NamedPipeErrorTextWriter.ErrorPrefix, StringComparison.OrdinalIgnoreCase))
+ if (data.StartsWith(System.Management.Automation.Remoting.Server.FormattedErrorTextWriter.ErrorPrefix, StringComparison.OrdinalIgnoreCase))
{
// Error message from the server.
- string errorData = data.Substring(System.Management.Automation.Remoting.Server.NamedPipeErrorTextWriter.ErrorPrefix.Length);
+ string errorData = data.Substring(System.Management.Automation.Remoting.Server.FormattedErrorTextWriter.ErrorPrefix.Length);
HandleErrorDataReceived(errorData);
}
else
@@ -2592,6 +2592,12 @@ namespace System.Management.Automation.Remoting.Server
_stdOutWriter = outWriter;
_stdErrWriter = errWriter;
_cmdTransportManagers = new Dictionary();
+
+ this.WSManTransportErrorOccured += (object sender, TransportErrorOccuredEventArgs e) =>
+ {
+ string msg = e.Exception.TransportMessage ?? e.Exception.InnerException?.Message ?? string.Empty;
+ _stdErrWriter.WriteLine(StringUtil.Format(RemotingErrorIdStrings.RemoteTransportError, msg));
+ };
}
#endregion
diff --git a/src/System.Management.Automation/engine/remoting/fanin/WSManPlugin.cs b/src/System.Management.Automation/engine/remoting/fanin/WSManPlugin.cs
index 9382beb9b3..79d583e063 100644
--- a/src/System.Management.Automation/engine/remoting/fanin/WSManPlugin.cs
+++ b/src/System.Management.Automation/engine/remoting/fanin/WSManPlugin.cs
@@ -306,10 +306,16 @@ namespace System.Management.Automation.Remoting
PSOpcode.Connect, PSTask.None,
PSKeyword.ManagedPlugin | PSKeyword.UseAlwaysAnalytic,
requestDetails.ToString(), senderInfo.UserInfo.Identity.Name, requestDetails.resourceUri);
- ServerRemoteSession remoteShellSession = ServerRemoteSession.CreateServerRemoteSession(senderInfo,
- requestDetails.resourceUri,
- extraInfo,
- serverTransportMgr);
+
+ ServerRemoteSession remoteShellSession = ServerRemoteSession.CreateServerRemoteSession(
+ senderInfo: senderInfo,
+ configurationProviderId: requestDetails.resourceUri,
+ initializationParameters: extraInfo,
+ transportManager: serverTransportMgr,
+ initialCommand: null, // Not used by WinRM endpoint.
+ configurationName: null, // Not used by WinRM endpoint, which has its own configuration.
+ configurationFile: null, // Same.
+ initialLocation: null); // Same.
if (remoteShellSession == null)
{
diff --git a/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs b/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs
index 510dbeea19..6fd6ae59cd 100644
--- a/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs
+++ b/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs
@@ -300,6 +300,7 @@ namespace System.Management.Automation.Remoting.Server
protected OutOfProcessServerSessionTransportManager CreateSessionTransportManager(
string configurationName,
+ string configurationFile,
PSRemotingCryptoHelperServer cryptoHelper,
string workingDirectory)
{
@@ -317,14 +318,20 @@ namespace System.Management.Automation.Remoting.Server
senderInfo = new PSSenderInfo(userPrincipal, "http://localhost");
#endif
- OutOfProcessServerSessionTransportManager tm = new OutOfProcessServerSessionTransportManager(originalStdOut, originalStdErr, cryptoHelper);
+ var tm = new OutOfProcessServerSessionTransportManager(
+ originalStdOut,
+ originalStdErr,
+ cryptoHelper);
ServerRemoteSession.CreateServerRemoteSession(
- senderInfo,
- _initialCommand,
- tm,
- configurationName,
- workingDirectory);
+ senderInfo: senderInfo,
+ configurationProviderId: "Microsoft.PowerShell",
+ initializationParameters: string.Empty,
+ transportManager: tm,
+ initialCommand: _initialCommand,
+ configurationName: configurationName,
+ configurationFile: configurationFile,
+ initialLocation: workingDirectory);
return tm;
}
@@ -333,11 +340,16 @@ namespace System.Management.Automation.Remoting.Server
string initialCommand,
PSRemotingCryptoHelperServer cryptoHelper,
string workingDirectory,
- string configurationName)
+ string configurationName,
+ string configurationFile)
{
_initialCommand = initialCommand;
- sessionTM = CreateSessionTransportManager(configurationName, cryptoHelper, workingDirectory);
+ sessionTM = CreateSessionTransportManager(
+ configurationName: configurationName,
+ configurationFile: configurationFile,
+ cryptoHelper: cryptoHelper,
+ workingDirectory: workingDirectory);
try
{
@@ -348,7 +360,11 @@ namespace System.Management.Automation.Remoting.Server
{
if (sessionTM == null)
{
- sessionTM = CreateSessionTransportManager(configurationName, cryptoHelper, workingDirectory);
+ sessionTM = CreateSessionTransportManager(
+ configurationName: configurationName,
+ configurationFile: configurationFile,
+ cryptoHelper: cryptoHelper,
+ workingDirectory: workingDirectory);
}
}
@@ -432,7 +448,8 @@ namespace System.Management.Automation.Remoting.Server
/// It will replace StdIn,StdOut and StdErr stream with TextWriter.Null. This is
/// to make sure these streams are totally used by our Mediator.
///
- private StdIOProcessMediator() : base(true)
+ /// Redirects remoting errors to the Out stream.
+ private StdIOProcessMediator(bool combineErrOutStream) : base(exitProcessOnError: true)
{
// Create input stream reader from Console standard input stream.
// We don't use the provided Console.In TextReader because it can have
@@ -441,16 +458,23 @@ namespace System.Management.Automation.Remoting.Server
// stream encoding. This way the stream encoding is determined by the
// stream BOM as needed.
originalStdIn = new StreamReader(Console.OpenStandardInput(), true);
- Console.SetIn(TextReader.Null);
- // replacing StdOut with Null so that no other app messes with the
- // original stream
+ // Remoting errors can optionally be written to stdErr or stdOut with
+ // special formatting.
originalStdOut = new OutOfProcessTextWriter(Console.Out);
- Console.SetOut(TextWriter.Null);
+ if (combineErrOutStream)
+ {
+ originalStdErr = new FormattedErrorTextWriter(Console.Out);
+ }
+ else
+ {
+ originalStdErr = new OutOfProcessTextWriter(Console.Error);
+ }
- // replacing StdErr with Null so that no other app messes with the
- // original stream
- originalStdErr = new OutOfProcessTextWriter(Console.Error);
+ // Replacing StdIn, StdOut, StdErr with Null so that no other app messes with the
+ // original streams.
+ Console.SetIn(TextReader.Null);
+ Console.SetOut(TextWriter.Null);
Console.SetError(TextWriter.Null);
}
@@ -464,10 +488,14 @@ namespace System.Management.Automation.Remoting.Server
/// Specifies the initialization script.
/// Specifies the initial working directory. The working directory is set before the initial command.
/// Specifies an optional configuration name that configures the endpoint session.
+ /// Specifies an optional path to a configuration (.pssc) file for the session.
+ /// Specifies the option to write remoting errors to stdOut stream, with special formatting.
internal static void Run(
string initialCommand,
string workingDirectory,
- string configurationName)
+ string configurationName,
+ string configurationFile,
+ bool combineErrOutStream)
{
lock (SyncObject)
{
@@ -477,14 +505,15 @@ namespace System.Management.Automation.Remoting.Server
return;
}
- s_singletonInstance = new StdIOProcessMediator();
+ s_singletonInstance = new StdIOProcessMediator(combineErrOutStream);
}
s_singletonInstance.Start(
initialCommand: initialCommand,
cryptoHelper: new PSRemotingCryptoHelperServer(),
workingDirectory: workingDirectory,
- configurationName: configurationName);
+ configurationName: configurationName,
+ configurationFile: configurationFile);
}
#endregion
@@ -526,7 +555,7 @@ namespace System.Management.Automation.Remoting.Server
// Create transport reader/writers from named pipe.
originalStdIn = namedPipeServer.TextReader;
originalStdOut = new OutOfProcessTextWriter(namedPipeServer.TextWriter);
- originalStdErr = new NamedPipeErrorTextWriter(namedPipeServer.TextWriter);
+ originalStdErr = new FormattedErrorTextWriter(namedPipeServer.TextWriter);
#if !UNIX
// Flow impersonation as needed.
@@ -557,17 +586,18 @@ namespace System.Management.Automation.Remoting.Server
initialCommand: initialCommand,
cryptoHelper: new PSRemotingCryptoHelperServer(),
workingDirectory: null,
- configurationName: namedPipeServer.ConfigurationName);
+ configurationName: namedPipeServer.ConfigurationName,
+ configurationFile: null);
}
#endregion
}
- internal sealed class NamedPipeErrorTextWriter : OutOfProcessTextWriter
+ internal sealed class FormattedErrorTextWriter : OutOfProcessTextWriter
{
#region Constructors
- internal NamedPipeErrorTextWriter(
+ internal FormattedErrorTextWriter(
TextWriter textWriter) : base(textWriter)
{ }
@@ -575,6 +605,8 @@ namespace System.Management.Automation.Remoting.Server
#region Base class overrides
+ // Write error data to stream with 'ErrorPrefix' prefix that will
+ // be interpreted by the client.
public override void WriteLine(string data)
{
string dataToWrite = (data != null) ? ErrorPrefix + data : null;
@@ -632,7 +664,8 @@ namespace System.Management.Automation.Remoting.Server
initialCommand: initialCommand,
cryptoHelper: new PSRemotingCryptoHelperServer(),
workingDirectory: null,
- configurationName: configurationName);
+ configurationName: configurationName,
+ configurationFile: null);
}
#endregion
diff --git a/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs b/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs
index fe31e0fad7..8bfc137f35 100644
--- a/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs
+++ b/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs
@@ -89,6 +89,10 @@ namespace System.Management.Automation.Remoting
// Creates a pushed remote runspace session created with this configuration name.
private string _configurationName;
+ // Specifies an optional .pssc configuration file path for out-of-proc session use.
+ // The .pssc file is used to configure the runspace for the endpoint session.
+ private string _configurationFile;
+
// Specifies an initial location of the powershell session.
private string _initialLocation;
@@ -173,7 +177,9 @@ namespace System.Management.Automation.Remoting
/// xml.
///
///
+ /// Optional initial command used for OutOfProc sessions.
/// Optional configuration endpoint name for OutOfProc sessions.
+ /// Optional configuration file (.pssc) path for OutOfProc sessions.
/// Optional configuration initial location of the powershell session.
///
///
@@ -192,8 +198,10 @@ namespace System.Management.Automation.Remoting
string configurationProviderId,
string initializationParameters,
AbstractServerSessionTransportManager transportManager,
- string configurationName = null,
- string initialLocation = null)
+ string initialCommand,
+ string configurationName,
+ string configurationFile,
+ string initialLocation)
{
Dbg.Assert(
(senderInfo != null) && (senderInfo.UserInfo != null),
@@ -215,7 +223,9 @@ namespace System.Management.Automation.Remoting
initializationParameters,
transportManager)
{
+ _initScriptForOutOfProcRS = initialCommand,
_configurationName = configurationName,
+ _configurationFile = configurationFile,
_initialLocation = initialLocation
};
@@ -226,33 +236,6 @@ namespace System.Management.Automation.Remoting
return result;
}
- ///
- /// Used by OutOfProcessServerMediator to create a remote session.
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- internal static ServerRemoteSession CreateServerRemoteSession(
- PSSenderInfo senderInfo,
- string initializationScriptForOutOfProcessRunspace,
- AbstractServerSessionTransportManager transportManager,
- string configurationName,
- string initialLocation)
- {
- ServerRemoteSession result = CreateServerRemoteSession(
- senderInfo,
- "Microsoft.PowerShell",
- string.Empty,
- transportManager,
- configurationName: configurationName,
- initialLocation: initialLocation);
- result._initScriptForOutOfProcRS = initializationScriptForOutOfProcessRunspace;
- return result;
- }
-
#endregion
#region Overrides
@@ -754,8 +737,7 @@ namespace System.Management.Automation.Remoting
// Get Initial Session State from custom session config suppliers
// like Exchange.
ConfigurationDataFromXML configurationData =
- PSSessionConfiguration.LoadEndPointConfiguration(_configProviderId,
- _initParameters);
+ PSSessionConfiguration.LoadEndPointConfiguration(_configProviderId, _initParameters);
// used by Out-Of-Proc (IPC) runspace.
configurationData.InitializationScriptForOutOfProcessRunspace = _initScriptForOutOfProcRS;
// start with data from configuration XML and then override with data
@@ -763,8 +745,6 @@ namespace System.Management.Automation.Remoting
_maxRecvdObjectSize = configurationData.MaxReceivedObjectSizeMB;
_maxRecvdDataSizeCommand = configurationData.MaxReceivedCommandSizeMB;
- DISCPowerShellConfiguration discProvider = null;
-
if (string.IsNullOrEmpty(configurationData.ConfigFilePath))
{
_sessionConfigProvider = configurationData.CreateEndPointConfigurationInstance();
@@ -772,11 +752,8 @@ namespace System.Management.Automation.Remoting
else
{
System.Security.Principal.WindowsPrincipal windowsPrincipal = new System.Security.Principal.WindowsPrincipal(_senderInfo.UserInfo.WindowsIdentity);
-
Func validator = (role) => windowsPrincipal.IsInRole(role);
-
- discProvider = new DISCPowerShellConfiguration(configurationData.ConfigFilePath, validator);
- _sessionConfigProvider = discProvider;
+ _sessionConfigProvider = new DISCPowerShellConfiguration(configurationData.ConfigFilePath, validator);
}
// exchange of ApplicationArguments and ApplicationPrivateData is be done as early as possible
@@ -787,6 +764,7 @@ namespace System.Management.Automation.Remoting
if (configurationData.SessionConfigurationData != null)
{
+ // Use the provided WinRM endpoint runspace configuration information.
try
{
rsSessionStateToUse =
@@ -797,8 +775,21 @@ namespace System.Management.Automation.Remoting
rsSessionStateToUse = _sessionConfigProvider.GetInitialSessionState(_senderInfo);
}
}
+ else if (!string.IsNullOrEmpty(_configurationFile))
+ {
+ // Use the optional _configurationFile parameter to create the endpoint runspace configuration.
+ // This parameter is only used by Out-Of-Proc transports (not WinRM transports).
+ var discConfiguration = new Remoting.DISCPowerShellConfiguration(
+ configFile: _configurationFile,
+ roleVerifier: null,
+ validateFile: true);
+ rsSessionStateToUse = discConfiguration.GetInitialSessionState(_senderInfo);
+ }
else
{
+ // Create a runspace configuration based on the provided PSSessionConfiguration provider.
+ // This can be either a 'default' configuration, or third party configuration PSSessionConfiguration provider object.
+ // So far, only Exchange provides a custom PSSessionConfiguration provider implementation.
rsSessionStateToUse = _sessionConfigProvider.GetInitialSessionState(_senderInfo);
}
diff --git a/src/System.Management.Automation/resources/ConsoleInfoErrorStrings.resx b/src/System.Management.Automation/resources/ConsoleInfoErrorStrings.resx
index 201dde0e4a..ba0809e901 100644
--- a/src/System.Management.Automation/resources/ConsoleInfoErrorStrings.resx
+++ b/src/System.Management.Automation/resources/ConsoleInfoErrorStrings.resx
@@ -234,4 +234,10 @@
The Save operation failed. Cannot remove the file {0}.
+
+ The provided configuration file '{0}' does not exist.
+
+
+ The provided configuration file '{0}' must have a .pssc file extension.
+
diff --git a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx
index 429557a795..57d2a22a0a 100644
--- a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx
+++ b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx
@@ -1705,4 +1705,13 @@ SSH client process terminated before connection could be established.
The Runspace argument to Create must be a non-null RemoteRunspace object.
+
+ The session configuration hash table contains an invalid key type. Keys should be string types.
+
+
+ The session configuration file contains an unsupported configuration option: {0}. This is a remoting endpoint configuration option, that does not apply to PowerShell session state.
+
+
+ The session configuration file contains an unknown configuration option: {0}.
+
diff --git a/test/SSHRemoting/SSHRemoting.Basic.Tests.ps1 b/test/SSHRemoting/SSHRemoting.Basic.Tests.ps1
index 29e7096d49..1aa87ba3d6 100644
--- a/test/SSHRemoting/SSHRemoting.Basic.Tests.ps1
+++ b/test/SSHRemoting/SSHRemoting.Basic.Tests.ps1
@@ -230,6 +230,25 @@ Describe "SSHRemoting Basic Tests" -tags CI {
VerifySession $script:sessions[1]
Write-Verbose -Verbose "It Complete"
}
+
+ It "Verifies the 'pwshconfig' configured endpoint." {
+ Write-Verbose -Verbose "It Starting: Verifies the 'pwshconfig' configured endpoint."
+ $script:session = TryNewPSSession -HostName localhost -Subsystem 'pwshconfig'
+ $script:session | Should -Not -BeNullOrEmpty
+ # Configured session should be in ConstrainedLanguage mode.
+ $sessionLangMode = Invoke-Command -Session $script:session -ScriptBlock { "$($ExecutionContext.SessionState.LanguageMode)" }
+ $sessionLangMode | Should -BeExactly "ConstrainedLanguage"
+ Write-Verbose -Verbose "It Complete"
+ }
+
+ <#
+ It "Verifes that 'pwshbroken' throws expected error for missing config file." {
+ Write-Verbose -Verbose "It Starting: Verifes that 'pwshbroken' throws expected error for missing config file."
+ { $script:session = TryNewPSSession -HostName localhost -Subsystem 'pwshbroken' } | Should -Throw
+ $script:session = $null
+ Write-Verbose -Verbose "It Complete"
+ }
+ #>
}
function TryCreateRunspace
@@ -270,7 +289,7 @@ Describe "SSHRemoting Basic Tests" -tags CI {
}
}
- if ($null -eq $rs)
+ if (($null -eq $rs) -or !($rs -is [runspace]))
{
$message = "Runspace open unable to connect to SSH remoting endpoint after two attempts. Error: $($connectionError.Message)"
throw [System.Management.Automation.PSInvalidOperationException]::new($message)
@@ -319,7 +338,7 @@ Describe "SSHRemoting Basic Tests" -tags CI {
AfterEach {
Write-Verbose -Verbose "Starting Runspace close AfterEach"
- if ($script:rs -ne $null) { $script:rs.Dispose() }
+ if (($script:rs -ne $null) -and ($script:rs -is [runspace])) { $script:rs.Dispose() }
Write-Verbose -Verbose "AfterEach complete"
}
diff --git a/test/hosting/test_HostingBasic.cs b/test/hosting/test_HostingBasic.cs
index 6cff7978e4..e29b14e38c 100644
--- a/test/hosting/test_HostingBasic.cs
+++ b/test/hosting/test_HostingBasic.cs
@@ -183,6 +183,19 @@ namespace PowerShell.Hosting.SDK.Tests
Assert.Equal(42, ret);
}
+ /* Test disabled because CommandLineParser is static and can only be intialized once (above in TestConsoleShellScenario)
+ ///
+ /// ConsoleShell cannot start with both InitialSessionState and -ConfigurationFile argument configurations specified.
+ ///
+ [Fact]
+ public static void TestConsoleShellConfigConflictError()
+ {
+ var iss = System.Management.Automation.Runspaces.InitialSessionState.CreateDefault2();
+ int ret = ConsoleShell.Start(iss, "BannerText", string.Empty, new string[] { @"-ConfigurationFile ""noneSuch""" });
+ Assert.Equal(70, ret); // ExitCodeInitFailure.
+ }
+ */
+
[Fact]
public static void TestBuiltInModules()
{
diff --git a/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 b/test/powershell/engine/Basic/DefaultCommands.Tests.ps1
index e970d5a06a..aaa6cb432b 100644
--- a/test/powershell/engine/Basic/DefaultCommands.Tests.ps1
+++ b/test/powershell/engine/Basic/DefaultCommands.Tests.ps1
@@ -375,7 +375,7 @@ Describe "Verify approved aliases list" -Tags "CI" {
"Cmdlet", "New-PSDrive", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "Low"
"Cmdlet", "New-PSRoleCapabilityFile", "", $( $CoreWindows -or $CoreUnix), "", "", "None"
"Cmdlet", "New-PSSession", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None"
-"Cmdlet", "New-PSSessionConfigurationFile", "", $($FullCLR -or $CoreWindows ), "", "", "None"
+"Cmdlet", "New-PSSessionConfigurationFile", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None"
"Cmdlet", "New-PSSessionOption", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None"
"Cmdlet", "New-PSTransportOption", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None"
"Cmdlet", "New-Service", "", $($FullCLR -or $CoreWindows ), "", "", "Medium"
diff --git a/test/tools/Modules/HelpersRemoting/HelpersRemoting.psm1 b/test/tools/Modules/HelpersRemoting/HelpersRemoting.psm1
index b554436426..5c88a4c053 100644
--- a/test/tools/Modules/HelpersRemoting/HelpersRemoting.psm1
+++ b/test/tools/Modules/HelpersRemoting/HelpersRemoting.psm1
@@ -556,7 +556,28 @@ function Install-SSHRemotingOnLinux
Write-Verbose -Verbose "Running Enable-SSHRemoting ..."
Write-Verbose -Verbose "PSScriptRoot: $PSScriptRoot"
$modulePath = "${PSScriptRoot}\..\Microsoft.PowerShell.RemotingTools\Microsoft.PowerShell.RemotingTools.psd1"
- $cmdLine = "Import-Module ${modulePath}; Enable-SSHRemoting -SSHDConfigFilePath /etc/ssh/sshd_config -PowerShellFilePath $PowerShellPath -Force"
+ $sshdFilePath = '/etc/ssh/sshd_config'
+
+ # First create a default 'powershell' named endpoint.
+ $cmdLine = "Import-Module ${modulePath}; Enable-SSHRemoting -SSHDConfigFilePath $sshdFilePath -PowerShellFilePath $PowerShellPath -Force"
+ Write-Verbose -Verbose "CmdLine: $cmdLine"
+ sudo pwsh -c $cmdLine
+
+ # Next create a 'pwshconfig' named configured endpoint.
+ # Configuration file:
+ $configFilePath = Join-Path -Path "$env:HOME" -ChildPath 'PSTestConfig.pssc'
+ '@{
+ GUID = "4d667b90-25f8-47d5-9c90-619b27954748"
+ Author = "Microsoft"
+ Description = "Test local PowerShell session configuration"
+ LanguageMode = "ConstrainedLanguage"
+ }' | Out-File -FilePath $configFilePath
+ $cmdLine = "Import-Module ${modulePath}; Enable-SSHRemoting -SSHDConfigFilePath $sshdFilePath -PowerShellFilePath $PowerShellPath -ConfigFilePath $configFilePath -SubsystemName 'pwshconfig' -Force"
+ Write-Verbose -Verbose "CmdLine: $cmdLine"
+ sudo pwsh -c $cmdLine
+
+ # Finally create a 'pwshbroken' named configured endpoint.
+ $cmdLine = "Import-Module ${modulePath}; Enable-SSHRemoting -SSHDConfigFilePath $sshdFilePath -PowerShellFilePath $PowerShellPath -ConfigFilePath '$HOME/NoSuch.pssc' -SubsystemName 'pwshbroken' -Force"
Write-Verbose -Verbose "CmdLine: $cmdLine"
sudo pwsh -c $cmdLine
diff --git a/test/tools/Modules/Microsoft.PowerShell.RemotingTools/Microsoft.PowerShell.RemotingTools.psm1 b/test/tools/Modules/Microsoft.PowerShell.RemotingTools/Microsoft.PowerShell.RemotingTools.psm1
index 737d0e2930..41a57772fd 100644
--- a/test/tools/Modules/Microsoft.PowerShell.RemotingTools/Microsoft.PowerShell.RemotingTools.psm1
+++ b/test/tools/Modules/Microsoft.PowerShell.RemotingTools/Microsoft.PowerShell.RemotingTools.psm1
@@ -85,14 +85,17 @@ class SSHRemotingConfig
[PlatformInfo] $platformInfo
[SSHSubSystemEntry[]] $psSubSystemEntries = @()
[string] $configFilePath
+ [string] $subsystemName
$configComponents = @()
SSHRemotingConfig(
[PlatformInfo] $platInfo,
- [string] $configFilePath)
+ [string] $configFilePath,
+ [string] $subsystemName)
{
$this.platformInfo = $platInfo
$this.configFilePath = $configFilePath
+ $this.subsystemName = $subsystemName
$this.ParseSSHRemotingConfig()
}
@@ -121,7 +124,7 @@ class SSHRemotingConfig
$components = $this.SplitConfigLine($line)
$this.configComponents += @{ Line = $line; Components = $components }
- if (($components[0] -eq "Subsystem") -and ($components[1] -eq "powershell"))
+ if (($components[0] -eq "Subsystem") -and ($components[1] -eq $this.subsystemName))
{
$entry = [SSHSubSystemEntry]::New()
$entry.subSystemLine = $line
@@ -143,7 +146,9 @@ function UpdateConfiguration
{
param (
[SSHRemotingConfig] $config,
- [string] $PowerShellPath
+ [string] $PowerShellPath,
+ [string] $SubsystemName,
+ [string] $ConfigFilePath
)
#
@@ -152,15 +157,19 @@ function UpdateConfiguration
# Subsystem
[System.Collections.Generic.List[string]] $newContents = [System.Collections.Generic.List[string]]::new()
- $psSubSystemEntry = "Subsystem powershell {0} {1} {2} {3}" -f $powerShellPath, "-SSHS", "-NoProfile", "-NoLogo"
- $subSystemAdded = $false
+ $psSubSystemEntry = "Subsystem {0} {1} -SSHS -NoProfile -NoLogo" -f $SubsystemName, $powerShellPath
+ if (![string]::IsNullOrEmpty($ConfigFilePath))
+ {
+ $psSubSystemEntry += " -ConfigurationFile {0}" -f $ConfigFilePath
+ }
+ $subSystemAdded = $false
foreach ($lineItem in $config.configComponents)
{
$line = $lineItem.Line
$components = $lineItem.Components
- if ($components[0] -eq "SubSystem")
+ if ($components[0] -eq "Subsystem")
{
if (! $subSystemAdded)
{
@@ -169,7 +178,7 @@ function UpdateConfiguration
$subSystemAdded = $true
}
- if ($components[1] -eq "powershell")
+ if ($components[1] -eq $SubsystemName)
{
# Remove all existing powershell subsystem entries
continue
@@ -324,6 +333,10 @@ function Enable-SSHRemoting
[string] $PowerShellFilePath,
+ [string] $SubsystemName = "powershell",
+
+ [string] $ConfigFilePath,
+
[switch] $Force
)
@@ -475,7 +488,7 @@ function Enable-SSHRemoting
WriteLine "$SSHDConfigFilePath" -AppendLines 1
# Get the SSHD configurtion
- $sshdConfig = [SSHRemotingConfig]::new($platformInfo, $SSHDConfigFilePath)
+ $sshdConfig = [SSHRemotingConfig]::new($platformInfo, $SSHDConfigFilePath, $SubsystemName)
if ($sshdConfig.psSubSystemEntries.Count -gt 0)
{
@@ -499,7 +512,7 @@ function Enable-SSHRemoting
{
WriteLine "Updating configuration file ..." -PrependLines 1 -AppendLines 1
- UpdateConfiguration $sshdConfig $PowerShellToUse
+ UpdateConfiguration $sshdConfig $PowerShellToUse $SubsystemName $ConfigFilePath
WriteLine "The configuration file has been updated:" -PrependLines 1
WriteLine $sshdConfig.configFilePath -AppendLines 1
diff --git a/test/xUnit/csharp/test_CommandLineParser.cs b/test/xUnit/csharp/test_CommandLineParser.cs
index cf2a128121..70621dfdf0 100644
--- a/test/xUnit/csharp/test_CommandLineParser.cs
+++ b/test/xUnit/csharp/test_CommandLineParser.cs
@@ -24,6 +24,7 @@ namespace PSTests.Parallel
Assert.False(cpp.AbortStartup);
Assert.Empty(cpp.Args);
Assert.Null(cpp.ConfigurationName);
+ Assert.Null(cpp.ConfigurationFile);
Assert.Null(cpp.CustomPipeName);
Assert.Null(cpp.ErrorMessage);
Assert.Null(cpp.ExecutionPolicy);
@@ -437,6 +438,38 @@ namespace PSTests.Parallel
Assert.Null(cpp.ErrorMessage);
}
+ [Theory]
+ [InlineData("-configurationfile")]
+ public static void TestParameter_ConfigurationFile_No_Name(params string[] commandLine)
+ {
+ var cpp = new CommandLineParameterParser();
+
+ cpp.Parse(commandLine);
+
+ Assert.True(cpp.AbortStartup);
+ Assert.True(cpp.NoExit);
+ Assert.False(cpp.ShowShortHelp);
+ Assert.False(cpp.ShowBanner);
+ Assert.Equal((uint)ConsoleHost.ExitCodeBadCommandLineParameter, cpp.ExitCode);
+ Assert.Equal(CommandLineParameterParserStrings.MissingConfigurationFileArgument, cpp.ErrorMessage);
+ }
+
+ [Theory]
+ [InlineData("-configurationfile", "qwerty")]
+ public static void TestParameter_ConfigurationFile_With_Name(params string[] commandLine)
+ {
+ var cpp = new CommandLineParameterParser();
+
+ cpp.Parse(commandLine);
+
+ Assert.False(cpp.AbortStartup);
+ Assert.True(cpp.NoExit);
+ Assert.False(cpp.ShowShortHelp);
+ Assert.True(cpp.ShowBanner);
+ Assert.Equal("qwerty", cpp.ConfigurationFile);
+ Assert.Null(cpp.ErrorMessage);
+ }
+
[Theory]
[InlineData("-custompipename")]
[InlineData("-cus")]