Add the -ConfigurationFile command line parameter to pwsh to support local session configuration (#17447)

This commit is contained in:
Paul Higinbotham 2022-06-06 12:12:53 -07:00 committed by GitHub
parent 97e8e0c43a
commit a8d55851e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 571 additions and 127 deletions

View File

@ -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;

View File

@ -73,6 +73,9 @@ namespace Microsoft.PowerShell
/// <param name="helpText">
/// Help text for minishell. This is displayed on 'minishell -?'.
/// </param>
/// <param name="issProvidedExternally">
/// True when an external caller provides an InitialSessionState object, which can conflict with '-ConfigurationFile' argument.
/// </param>
/// <returns>
/// 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.
/// </returns>
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
/// <returns>
/// The process exit code to be returned by Main.
/// </returns>
private uint DoRunspaceLoop(string initialCommand, bool skipProfiles, Collection<CommandParameter> initialCommandArgs, bool staMode, string configurationName)
private uint DoRunspaceLoop(
string initialCommand,
bool skipProfiles,
Collection<CommandParameter> 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
/// <summary>
/// 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.
/// </summary>
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.
/// </summary>
private void DoCreateRunspace(string initialCommand, bool skipProfiles, bool staMode, string configurationName, Collection<CommandParameter> initialCommandArgs)
/// <param name="args">Runspace creation event arguments.</param>
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<ModuleSpecification> 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<CommandParameter> 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<CommandParameter> 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<CommandParameter> InitialCommandArgs { get; set; }
}
} // namespace

View File

@ -21,7 +21,12 @@ namespace Microsoft.PowerShell
/// <returns>An integer value which should be used as exit code for the process.</returns>
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);
}
/// <summary>Entry point in to ConsoleShell. Used to create a custom Powershell console application.</summary>
@ -31,6 +36,31 @@ namespace Microsoft.PowerShell
/// <param name="args">Commandline parameters specified by user.</param>
/// <returns>An integer value which should be used as exit code for the process.</returns>
public static int Start(InitialSessionState initialSessionState, string? bannerText, string? helpText, string[] args)
{
return StartImpl(
initialSessionState,
bannerText,
helpText,
args,
issProvided: true);
}
/// <summary>
/// Implementation of entry point to ConsoleShell.
/// Used to create a custom Powershell console application.
/// </summary>
/// <param name="initialSessionState">InitialSessionState to be used by the ConsoleHost.</param>
/// <param name="bannerText">Banner text to be displayed by ConsoleHost.</param>
/// <param name="helpText">Help text for the shell.</param>
/// <param name="args">Commandline parameters specified by user.</param>
/// <param name="issProvided">True when the InitialSessionState object is provided by caller.</param>
/// <returns>An integer value which should be used as exit code for the process.</returns>
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);
}
}
}

View File

@ -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)
{

View File

@ -187,7 +187,10 @@ Valid formats are:
<value>Cannot process the command because -STA and -MTA are both specified. Specify either -STA or -MTA.</value>
</data>
<data name="MissingConfigurationNameArgument" xml:space="preserve">
<value>Cannot process the command because -Configuration requires an argument that is a remote endpoint configuration name. Specify this argument and try again.</value>
<value>Cannot process the command because -ConfigurationName requires an argument that is a remote endpoint configuration name. Specify this argument and try again.</value>
</data>
<data name="MissingConfigurationFileArgument" xml:space="preserve">
<value>Cannot process the command because -ConfigurationFile requires an argument that is a session configuration (.pssc) file path. Specify this argument and try again.</value>
</data>
<data name="MissingCustomPipeNameArgument" xml:space="preserve">
<value>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.</value>

View File

@ -135,6 +135,9 @@
<data name="ShellCannotBeStarted" xml:space="preserve">
<value>The shell cannot be started. A failure occurred during initialization:</value>
</data>
<data name="ShellCannotBeStartedWithConfigConflict" xml:space="preserve">
<value>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.</value>
</data>
<data name="UnhandledExceptionShutdownMessage" xml:space="preserve">
<value>An error has occurred that was not properly handled. Additional information is shown below. The PowerShell process will exit.</value>
</data>

View File

@ -288,6 +288,14 @@ All parameters are case-insensitive.</value>
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

View File

@ -1312,7 +1312,7 @@ namespace System.Management.Automation.Runspaces
/// Creates an initial session state from a PSSC configuration file.
/// </summary>
/// <param name="path">The path to the PSSC session configuration file.</param>
/// <returns></returns>
/// <returns>InitialSessionState object.</returns>
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().
/// </param>
/// <returns></returns>
public static InitialSessionState CreateFromSessionConfigurationFile(string path, Func<string, bool> roleVerifier)
/// <returns>InitialSessionState object.</returns>
public static InitialSessionState CreateFromSessionConfigurationFile(
string path,
Func<string, bool> roleVerifier)
{
Remoting.DISCPowerShellConfiguration discConfiguration = new Remoting.DISCPowerShellConfiguration(path, roleVerifier);
return CreateFromSessionConfigurationFile(path, roleVerifier, validateFile: false);
}
/// <summary>
/// Creates an initial session state from a PSSC configuration file.
/// </summary>
/// <param name="path">The path to the PSSC session configuration file.</param>
/// <param name="roleVerifier">
/// 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().
/// </param>
/// <param name="validateFile">Validates the file contents for supported SessionState options.</param>
/// <returns>InitialSessionState object.</returns>
public static InitialSessionState CreateFromSessionConfigurationFile(
string path,
Func<string, bool> 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) },

View File

@ -15,7 +15,6 @@ using System.Text;
namespace Microsoft.PowerShell.Commands
{
#if !UNIX
/// <summary>
/// New-PSSessionConfigurationFile command implementation
///
@ -1126,7 +1125,6 @@ namespace Microsoft.PowerShell.Commands
#endregion
}
#endif
/// <summary>
/// New-PSRoleCapabilityFile command implementation

View File

@ -22,6 +22,8 @@ using Dbg = System.Management.Automation.Diagnostics;
namespace System.Management.Automation.Remoting
{
#region WSMan endpoint configuration
/// <summary>
/// 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
/// <summary>
/// Using optionName and optionValue updates the current object.
/// </summary>
@ -324,6 +332,8 @@ namespace System.Management.Automation.Remoting
throw PSTraceSource.NewArgumentException("typeToLoad", RemotingErrorIdStrings.UnableToLoadType,
EndPointConfigurationTypeName, ConfigurationDataFromXML.INITPARAMETERSTOKEN);
}
#endregion
}
/// <summary>
@ -450,7 +460,8 @@ namespace System.Management.Automation.Remoting
...
</InitializationParameters>
*/
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
/// </summary>
internal sealed class DefaultRemotePowerShellConfiguration : PSSessionConfiguration
{
#region Method overrides
/// <summary>
/// </summary>
/// <param name="senderInfo"></param>
@ -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
/// <summary>
/// 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
/// <summary>
/// Configuration file constants.
/// </summary>
@ -1355,6 +1378,8 @@ namespace System.Management.Automation.Remoting
}
}
#endregion
#region DISC Utilities
/// <summary>
@ -1681,6 +1706,8 @@ namespace System.Management.Automation.Remoting
#endregion
#region DISCPowerShellConfiguration
/// <summary>
/// Creates an initial session state based on the configuration language for PSSC files.
/// </summary>
@ -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().
/// </param>
internal DISCPowerShellConfiguration(string configFile, Func<string, bool> roleVerifier)
/// <param name="validateFile">Validate file for supported configuration options.</param>
internal DISCPowerShellConfiguration(
string configFile,
Func<string, bool> 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<string> SupportedConfigOptions = new HashSet<string>(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<string> SupportedConfigOptions = new HashSet<string>(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<string> UnsupportedConfigOptions = new HashSet<string>(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
}

View File

@ -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<Guid, OutOfProcessServerTransportManager>();
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

View File

@ -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)
{

View File

@ -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.
/// </summary>
private StdIOProcessMediator() : base(true)
/// <param name="combineErrOutStream">Redirects remoting errors to the Out stream.</param>
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
/// <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>
/// <param name="configurationName">Specifies an optional configuration name that configures the endpoint session.</param>
/// <param name="configurationFile">Specifies an optional path to a configuration (.pssc) file for the session.</param>
/// <param name="combineErrOutStream">Specifies the option to write remoting errors to stdOut stream, with special formatting.</param>
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

View File

@ -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.
/// </param>
/// <param name="transportManager"></param>
/// <param name="initialCommand">Optional initial command used for OutOfProc sessions.</param>
/// <param name="configurationName">Optional configuration endpoint name for OutOfProc sessions.</param>
/// <param name="configurationFile">Optional configuration file (.pssc) path for OutOfProc sessions.</param>
/// <param name="initialLocation">Optional configuration initial location of the powershell session.</param>
/// <returns></returns>
/// <exception cref="InvalidOperationException">
@ -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;
}
/// <summary>
/// Used by OutOfProcessServerMediator to create a remote session.
/// </summary>
/// <param name="senderInfo"></param>
/// <param name="initializationScriptForOutOfProcessRunspace"></param>
/// <param name="transportManager"></param>
/// <param name="configurationName"></param>
/// <param name="initialLocation"></param>
/// <returns></returns>
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<string, bool> 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);
}

View File

@ -234,4 +234,10 @@
<data name="ExportConsoleCannotDeleteFile" xml:space="preserve">
<value>The Save operation failed. Cannot remove the file {0}.</value>
</data>
<data name="ConfigurationFileDoesNotExist" xml:space="preserve">
<value>The provided configuration file '{0}' does not exist.</value>
</data>
<data name="NotConfigurationFile" xml:space="preserve">
<value>The provided configuration file '{0}' must have a .pssc file extension.</value>
</data>
</root>

View File

@ -1705,4 +1705,13 @@ SSH client process terminated before connection could be established.</value>
<data name="InvalidPSSessionArgument" xml:space="preserve">
<value>The Runspace argument to Create must be a non-null RemoteRunspace object.</value>
</data>
<data name="DISCInvalidConfigKeyType" xml:space="preserve">
<value>The session configuration hash table contains an invalid key type. Keys should be string types.</value>
</data>
<data name="DISCUnsupportedConfigName" xml:space="preserve">
<value>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.</value>
</data>
<data name="DISCUnknownConfigName" xml:space="preserve">
<value>The session configuration file contains an unknown configuration option: {0}.</value>
</data>
</root>

View File

@ -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"
}

View File

@ -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)
/// <summary>
/// ConsoleShell cannot start with both InitialSessionState and -ConfigurationFile argument configurations specified.
/// </summary>
[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()
{

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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")]