Native invocation using ArgumentList
(#14692)
Co-authored-by: Ilya <darpa@yandex.ru>
This commit is contained in:
parent
cf6876e716
commit
a520e2c982
|
@ -272,6 +272,20 @@ namespace System.Management.Automation.Internal
|
|||
|
||||
namespace System.Management.Automation
|
||||
{
|
||||
#region NativeArgumentPassingStyle
|
||||
/// <summary>
|
||||
/// Defines the different native command argument parsing options.
|
||||
/// </summary>
|
||||
public enum NativeArgumentPassingStyle
|
||||
{
|
||||
/// <summary>Use legacy argument parsing via ProcessStartInfo.Arguments.</summary>
|
||||
Legacy = 0,
|
||||
|
||||
/// <summary>Use new style argument parsing via ProcessStartInfo.ArgumentList.</summary>
|
||||
Standard = 1
|
||||
}
|
||||
#endregion NativeArgumentPassingStyle
|
||||
|
||||
#region ErrorView
|
||||
/// <summary>
|
||||
/// Defines the potential ErrorView options.
|
||||
|
|
|
@ -22,6 +22,7 @@ namespace System.Management.Automation
|
|||
|
||||
internal const string EngineSource = "PSEngine";
|
||||
internal const string PSAnsiProgressFeatureName = "PSAnsiProgress";
|
||||
internal const string PSNativeCommandArgumentPassingFeatureName = "PSNativeCommandArgumentPassing";
|
||||
|
||||
#endregion
|
||||
|
||||
|
@ -136,6 +137,9 @@ namespace System.Management.Automation
|
|||
new ExperimentalFeature(
|
||||
name: PSAnsiProgressFeatureName,
|
||||
description: "Enable lightweight progress bar that leverages ANSI codes for rendering"),
|
||||
new ExperimentalFeature(
|
||||
name: PSNativeCommandArgumentPassingFeatureName,
|
||||
description: "Use ArgumentList when invoking a native command"),
|
||||
};
|
||||
EngineExperimentalFeatures = new ReadOnlyCollection<ExperimentalFeature>(engineFeatures);
|
||||
|
||||
|
|
|
@ -1308,6 +1308,32 @@ namespace System.Management.Automation.Runspaces
|
|||
}
|
||||
}
|
||||
|
||||
#region VariableHelper
|
||||
/// <summary>
|
||||
/// A helper for adding variables to session state.
|
||||
/// Experimental features can be handled here.
|
||||
/// </summary>
|
||||
/// <param name="variables">The variables to add to session state.</param>
|
||||
private void AddVariables(IEnumerable<SessionStateVariableEntry> variables)
|
||||
{
|
||||
Variables.Add(variables);
|
||||
|
||||
// If the PSNativeCommandArgumentPassing feature is enabled, create the variable which controls the behavior
|
||||
// Since the BuiltInVariables list is static, and this should be done dynamically
|
||||
// we need to do this here.
|
||||
if (ExperimentalFeature.IsEnabled("PSNativeCommandArgumentPassing"))
|
||||
{
|
||||
Variables.Add(
|
||||
new SessionStateVariableEntry(
|
||||
SpecialVariables.NativeArgumentPassing,
|
||||
NativeArgumentPassingStyle.Standard,
|
||||
RunspaceInit.NativeCommandArgumentPassingDescription,
|
||||
ScopedItemOptions.None,
|
||||
new ArgumentTypeConverterAttribute(typeof(NativeArgumentPassingStyle))));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Creates an initial session state from a PSSC configuration file.
|
||||
/// </summary>
|
||||
|
@ -1413,7 +1439,7 @@ namespace System.Management.Automation.Runspaces
|
|||
}
|
||||
|
||||
// Add built-in variables.
|
||||
iss.Variables.Add(BuiltInVariables);
|
||||
iss.AddVariables(BuiltInVariables);
|
||||
|
||||
// wrap some commands in a proxy function to restrict their parameters
|
||||
foreach (KeyValuePair<string, CommandMetadata> proxyFunction in CommandMetadata.GetRestrictedCommands(SessionCapabilities.RemoteServer))
|
||||
|
@ -1477,7 +1503,7 @@ namespace System.Management.Automation.Runspaces
|
|||
// be causing test failures - i suspect due to lack test isolation - brucepay Mar 06/2008
|
||||
#if false
|
||||
// Add the default variables and make them private...
|
||||
iss.Variables.Add(BuiltInVariables);
|
||||
iss.AddVariables(BuiltInVariables);
|
||||
foreach (SessionStateVariableEntry v in iss.Variables)
|
||||
{
|
||||
v.Visibility = SessionStateEntryVisibility.Private;
|
||||
|
@ -1500,7 +1526,7 @@ namespace System.Management.Automation.Runspaces
|
|||
|
||||
InitialSessionState ss = new InitialSessionState();
|
||||
|
||||
ss.Variables.Add(BuiltInVariables);
|
||||
ss.AddVariables(BuiltInVariables);
|
||||
ss.Commands.Add(new SessionStateApplicationEntry("*"));
|
||||
ss.Commands.Add(new SessionStateScriptEntry("*"));
|
||||
ss.Commands.Add(BuiltInFunctions);
|
||||
|
@ -1567,7 +1593,7 @@ namespace System.Management.Automation.Runspaces
|
|||
{
|
||||
InitialSessionState ss = new InitialSessionState();
|
||||
|
||||
ss.Variables.Add(BuiltInVariables);
|
||||
ss.AddVariables(BuiltInVariables);
|
||||
ss.Commands.Add(new SessionStateApplicationEntry("*"));
|
||||
ss.Commands.Add(new SessionStateScriptEntry("*"));
|
||||
ss.Commands.Add(BuiltInFunctions);
|
||||
|
@ -1608,7 +1634,7 @@ namespace System.Management.Automation.Runspaces
|
|||
{
|
||||
InitialSessionState ss = new InitialSessionState();
|
||||
|
||||
ss.Variables.Add(this.Variables.Clone());
|
||||
ss.AddVariables(this.Variables.Clone());
|
||||
ss.EnvironmentVariables.Add(this.EnvironmentVariables.Clone());
|
||||
ss.Commands.Add(this.Commands.Clone());
|
||||
ss.Assemblies.Add(this.Assemblies.Clone());
|
||||
|
@ -4272,7 +4298,13 @@ param(
|
|||
}
|
||||
else {
|
||||
$pagerCommand = 'less'
|
||||
$pagerArgs = '-Ps""Page %db?B of %D:.\. Press h for help or q to quit\.$""'
|
||||
# PSNativeCommandArgumentPassing arguments should be constructed differently.
|
||||
if ($EnabledExperimentalFeatures -contains 'PSNativeCommandArgumentPassing') {
|
||||
$pagerArgs = '-s','-P','Page %db?B of %D:.\. Press h for help or q to quit\.'
|
||||
}
|
||||
else {
|
||||
$pagerArgs = '-Ps""Page %db?B of %D:.\. Press h for help or q to quit\.$""'
|
||||
}
|
||||
}
|
||||
|
||||
# Respect PAGER environment variable which allows user to specify a custom pager.
|
||||
|
@ -4312,10 +4344,16 @@ param(
|
|||
$consoleWidth = [System.Math]::Max([System.Console]::WindowWidth, 20)
|
||||
|
||||
if ($pagerArgs) {
|
||||
# Supply pager arguments to an application without any PowerShell parsing of the arguments.
|
||||
# Start the pager arguments directly if the PSNativeCommandArgumentPassing feature is enabled.
|
||||
# Otherwise, supply pager arguments to an application without any PowerShell parsing of the arguments.
|
||||
# Leave environment variable to help user debug arguments supplied in $env:PAGER.
|
||||
$env:__PSPAGER_ARGS = $pagerArgs
|
||||
$help | Out-String -Stream -Width ($consoleWidth - 1) | & $pagerCommand --% %__PSPAGER_ARGS%
|
||||
if ($EnabledExperimentalFeatures -contains 'PSNativeCommandArgumentPassing') {
|
||||
$help | Out-String -Stream -Width ($consoleWidth - 1) | & $pagerCommand $pagerArgs
|
||||
}
|
||||
else {
|
||||
$env:__PSPAGER_ARGS = $pagerArgs
|
||||
$help | Out-String -Stream -Width ($consoleWidth - 1) | & $pagerCommand --% %__PSPAGER_ARGS%
|
||||
}
|
||||
}
|
||||
else {
|
||||
$help | Out-String -Stream -Width ($consoleWidth - 1) | & $pagerCommand
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
@ -82,7 +83,7 @@ namespace System.Management.Automation
|
|||
if (parameter.ParameterNameSpecified)
|
||||
{
|
||||
Diagnostics.Assert(!parameter.ParameterText.Contains(' '), "Parameters cannot have whitespace");
|
||||
PossiblyGlobArg(parameter.ParameterText, StringConstantType.BareWord);
|
||||
PossiblyGlobArg(parameter.ParameterText, parameter, StringConstantType.BareWord);
|
||||
|
||||
if (parameter.SpaceAfterParameter)
|
||||
{
|
||||
|
@ -130,7 +131,7 @@ namespace System.Management.Automation
|
|||
stringConstantType = StringConstantType.DoubleQuoted;
|
||||
}
|
||||
|
||||
AppendOneNativeArgument(Context, argValue, arrayLiteralAst, sawVerbatimArgumentMarker, stringConstantType);
|
||||
AppendOneNativeArgument(Context, parameter, argValue, arrayLiteralAst, sawVerbatimArgumentMarker, stringConstantType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -151,6 +152,65 @@ namespace System.Management.Automation
|
|||
|
||||
private readonly StringBuilder _arguments = new StringBuilder();
|
||||
|
||||
internal string[] ArgumentList
|
||||
{
|
||||
get
|
||||
{
|
||||
return _argumentList.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an argument to the ArgumentList.
|
||||
/// We may need to construct the argument out of the parameter text and the argument
|
||||
/// in the case that we have a parameter that appears as "-switch:value".
|
||||
/// </summary>
|
||||
/// <param name="parameter">The parameter associated with the operation.</param>
|
||||
/// <param name="argument">The value used with parameter.</param>
|
||||
internal void AddToArgumentList(CommandParameterInternal parameter, string argument)
|
||||
{
|
||||
if (parameter.ParameterNameSpecified && parameter.ParameterText.EndsWith(":"))
|
||||
{
|
||||
if (argument != parameter.ParameterText)
|
||||
{
|
||||
_argumentList.Add(parameter.ParameterText + argument);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_argumentList.Add(argument);
|
||||
}
|
||||
}
|
||||
|
||||
private List<string> _argumentList = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether to use an ArgumentList or string for arguments when invoking a native executable.
|
||||
/// </summary>
|
||||
internal bool UseArgumentList
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ExperimentalFeature.IsEnabled("PSNativeCommandArgumentPassing"))
|
||||
{
|
||||
try
|
||||
{
|
||||
// This will default to the new behavior if it is set to anything other than Legacy
|
||||
var preference = LanguagePrimitives.ConvertTo<NativeArgumentPassingStyle>(
|
||||
Context.GetVariableValue(new VariablePath(SpecialVariables.NativeArgumentPassing), NativeArgumentPassingStyle.Standard));
|
||||
return preference != NativeArgumentPassingStyle.Legacy;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// The value is not convertable send back true
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion internal members
|
||||
|
||||
#region private members
|
||||
|
@ -161,24 +221,27 @@ namespace System.Management.Automation
|
|||
/// each of which will be stringized.
|
||||
/// </summary>
|
||||
/// <param name="context">Execution context instance.</param>
|
||||
/// <param name="parameter">The parameter associated with the operation.</param>
|
||||
/// <param name="obj">The object to append.</param>
|
||||
/// <param name="argArrayAst">If the argument was an array literal, the Ast, otherwise null.</param>
|
||||
/// <param name="sawVerbatimArgumentMarker">True if the argument occurs after --%.</param>
|
||||
/// <param name="stringConstantType">Bare, SingleQuoted, or DoubleQuoted.</param>
|
||||
private void AppendOneNativeArgument(ExecutionContext context, object obj, ArrayLiteralAst argArrayAst, bool sawVerbatimArgumentMarker, StringConstantType stringConstantType)
|
||||
private void AppendOneNativeArgument(ExecutionContext context, CommandParameterInternal parameter, object obj, ArrayLiteralAst argArrayAst, bool sawVerbatimArgumentMarker, StringConstantType stringConstantType)
|
||||
{
|
||||
IEnumerator list = LanguagePrimitives.GetEnumerator(obj);
|
||||
|
||||
Diagnostics.Assert((argArrayAst == null) || obj is object[] && ((object[])obj).Length == argArrayAst.Elements.Count, "array argument and ArrayLiteralAst differ in number of elements");
|
||||
Diagnostics.Assert((argArrayAst == null) || (obj is object[] && ((object[])obj).Length == argArrayAst.Elements.Count), "array argument and ArrayLiteralAst differ in number of elements");
|
||||
|
||||
int currentElement = -1;
|
||||
string separator = string.Empty;
|
||||
do
|
||||
{
|
||||
string arg;
|
||||
object currentObj;
|
||||
if (list == null)
|
||||
{
|
||||
arg = PSObject.ToStringParser(context, obj);
|
||||
currentObj = obj;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -187,7 +250,8 @@ namespace System.Management.Automation
|
|||
break;
|
||||
}
|
||||
|
||||
arg = PSObject.ToStringParser(context, ParserOps.Current(null, list));
|
||||
currentObj = ParserOps.Current(null, list);
|
||||
arg = PSObject.ToStringParser(context, currentObj);
|
||||
|
||||
currentElement += 1;
|
||||
if (currentElement != 0)
|
||||
|
@ -198,12 +262,16 @@ namespace System.Management.Automation
|
|||
|
||||
if (!string.IsNullOrEmpty(arg))
|
||||
{
|
||||
// Only add the separator to the argument string rather than adding a separator to the ArgumentList.
|
||||
_arguments.Append(separator);
|
||||
|
||||
if (sawVerbatimArgumentMarker)
|
||||
{
|
||||
arg = Environment.ExpandEnvironmentVariables(arg);
|
||||
_arguments.Append(arg);
|
||||
|
||||
// we need to split the argument on spaces
|
||||
_argumentList.AddRange(arg.Split(' ', StringSplitOptions.RemoveEmptyEntries));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -227,10 +295,12 @@ namespace System.Management.Automation
|
|||
if (stringConstantType == StringConstantType.DoubleQuoted)
|
||||
{
|
||||
_arguments.Append(ResolvePath(arg, Context));
|
||||
AddToArgumentList(parameter, ResolvePath(arg, Context));
|
||||
}
|
||||
else
|
||||
{
|
||||
_arguments.Append(arg);
|
||||
AddToArgumentList(parameter, arg);
|
||||
}
|
||||
|
||||
// need to escape all trailing backslashes so the native command receives it correctly
|
||||
|
@ -244,10 +314,28 @@ namespace System.Management.Automation
|
|||
}
|
||||
else
|
||||
{
|
||||
PossiblyGlobArg(arg, stringConstantType);
|
||||
if (argArrayAst != null && UseArgumentList)
|
||||
{
|
||||
// We have a literal array, so take the extent, break it on spaces and add them to the argument list.
|
||||
foreach (string element in argArrayAst.Extent.Text.Split(' ', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
PossiblyGlobArg(element, parameter, stringConstantType);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
PossiblyGlobArg(arg, parameter, stringConstantType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (UseArgumentList && currentObj != null)
|
||||
{
|
||||
// add empty strings to arglist, but not nulls
|
||||
AddToArgumentList(parameter, arg);
|
||||
}
|
||||
}
|
||||
while (list != null);
|
||||
}
|
||||
|
@ -257,8 +345,9 @@ namespace System.Management.Automation
|
|||
/// On Unix, do globbing as appropriate, otherwise just append <paramref name="arg"/>.
|
||||
/// </summary>
|
||||
/// <param name="arg">The argument that possibly needs expansion.</param>
|
||||
/// <param name="parameter">The parameter associated with the operation.</param>
|
||||
/// <param name="stringConstantType">Bare, SingleQuoted, or DoubleQuoted.</param>
|
||||
private void PossiblyGlobArg(string arg, StringConstantType stringConstantType)
|
||||
private void PossiblyGlobArg(string arg, CommandParameterInternal parameter, StringConstantType stringConstantType)
|
||||
{
|
||||
var argExpanded = false;
|
||||
|
||||
|
@ -311,10 +400,12 @@ namespace System.Management.Automation
|
|||
_arguments.Append('"');
|
||||
_arguments.Append(expandedPath);
|
||||
_arguments.Append('"');
|
||||
AddToArgumentList(parameter, expandedPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
_arguments.Append(expandedPath);
|
||||
AddToArgumentList(parameter, expandedPath);
|
||||
}
|
||||
|
||||
argExpanded = true;
|
||||
|
@ -331,12 +422,14 @@ namespace System.Management.Automation
|
|||
if (string.Equals(arg, "~"))
|
||||
{
|
||||
_arguments.Append(home);
|
||||
AddToArgumentList(parameter, home);
|
||||
argExpanded = true;
|
||||
}
|
||||
else if (arg.StartsWith("~/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var replacementString = home + arg.Substring(1);
|
||||
_arguments.Append(replacementString);
|
||||
AddToArgumentList(parameter, replacementString);
|
||||
argExpanded = true;
|
||||
}
|
||||
}
|
||||
|
@ -351,6 +444,7 @@ namespace System.Management.Automation
|
|||
if (!argExpanded)
|
||||
{
|
||||
_arguments.Append(arg);
|
||||
AddToArgumentList(parameter, arg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,28 @@ namespace System.Management.Automation
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the command arguments as an array of strings.
|
||||
/// </summary>
|
||||
internal string[] ArgumentList
|
||||
{
|
||||
get
|
||||
{
|
||||
return ((NativeCommandParameterBinder)DefaultParameterBinder).ArgumentList;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether to use the new API for StartInfo.
|
||||
/// </summary>
|
||||
internal bool UseArgumentList
|
||||
{
|
||||
get
|
||||
{
|
||||
return ((NativeCommandParameterBinder)DefaultParameterBinder).UseArgumentList;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Passes the binding directly through to the parameter binder.
|
||||
/// It does no verification against metadata.
|
||||
|
@ -49,8 +71,7 @@ namespace System.Management.Automation
|
|||
/// Ignored.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if the parameter was successfully bound. Any error condition
|
||||
/// produces an exception.
|
||||
/// True if the parameter was successfully bound. Any error condition produces an exception.
|
||||
/// </returns>
|
||||
internal override bool BindParameter(
|
||||
CommandParameterInternal argument,
|
||||
|
|
|
@ -357,7 +357,7 @@ namespace System.Management.Automation
|
|||
|
||||
/// <summary>
|
||||
/// Indicate if we have called 'NotifyBeginApplication()' on the host, so that
|
||||
/// we can call the counterpart 'NotifyEndApplication' as approriate.
|
||||
/// we can call the counterpart 'NotifyEndApplication' as appropriate.
|
||||
/// </summary>
|
||||
private bool _hasNotifiedBeginApplication;
|
||||
|
||||
|
@ -1157,10 +1157,35 @@ namespace System.Management.Automation
|
|||
startInfo.CreateNoWindow = mpc.NonInteractive;
|
||||
}
|
||||
|
||||
startInfo.Arguments = NativeParameterBinderController.Arguments;
|
||||
|
||||
ExecutionContext context = this.Command.Context;
|
||||
|
||||
// We provide the user a way to select the new behavior via a new preference variable
|
||||
using (ParameterBinderBase.bindingTracer.TraceScope("BIND NAMED native application line args [{0}]", this.Path))
|
||||
{
|
||||
if (!NativeParameterBinderController.UseArgumentList)
|
||||
{
|
||||
using (ParameterBinderBase.bindingTracer.TraceScope("BIND argument [{0}]", NativeParameterBinderController.Arguments))
|
||||
{
|
||||
startInfo.Arguments = NativeParameterBinderController.Arguments;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use new API for running native application
|
||||
int position = 0;
|
||||
foreach (string nativeArgument in NativeParameterBinderController.ArgumentList)
|
||||
{
|
||||
if (nativeArgument != null)
|
||||
{
|
||||
using (ParameterBinderBase.bindingTracer.TraceScope("BIND cmd line arg [{0}] to position [{1}]", nativeArgument, position++))
|
||||
{
|
||||
startInfo.ArgumentList.Add(nativeArgument);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start command in the current filesystem directory
|
||||
string rawPath =
|
||||
context.EngineSessionState.GetNamespaceCurrentLocation(
|
||||
|
|
|
@ -257,6 +257,11 @@ namespace System.Management.Automation
|
|||
|
||||
#endregion Preference Variables
|
||||
|
||||
// Native command argument passing style
|
||||
internal const string NativeArgumentPassing = "PSNativeCommandArgumentPassing";
|
||||
|
||||
internal static readonly VariablePath NativeArgumentPassingVarPath = new VariablePath(NativeArgumentPassing);
|
||||
|
||||
internal const string ErrorView = "ErrorView";
|
||||
|
||||
internal static readonly VariablePath ErrorViewVarPath = new VariablePath(ErrorView);
|
||||
|
|
|
@ -189,6 +189,9 @@
|
|||
<data name="WhatIfPreferenceDescription" xml:space="preserve">
|
||||
<value>If true, WhatIf is considered to be enabled for all commands.</value>
|
||||
</data>
|
||||
<data name="NativeCommandArgumentPassingDescription" xml:space="preserve">
|
||||
<value>Dictates how arguments are passed to native executables.</value>
|
||||
</data>
|
||||
<data name="FormatEnumerationLimitDescription" xml:space="preserve">
|
||||
<value>Dictates the limit of enumeration on formatting IEnumerable objects</value>
|
||||
</data>
|
||||
|
|
|
@ -235,11 +235,16 @@ Describe "ConsoleHost unit tests" -tags "Feature" {
|
|||
$observed | Should -BeExactly "h-llo"
|
||||
}
|
||||
|
||||
It "Empty command should fail" {
|
||||
& $powershell -noprofile -c ''
|
||||
It "Missing command should fail" {
|
||||
& $powershell -noprofile -c
|
||||
$LASTEXITCODE | Should -Be 64
|
||||
}
|
||||
|
||||
It "Empty space command should succeed" {
|
||||
& $powershell -noprofile -c '' | Should -BeNullOrEmpty
|
||||
$LASTEXITCODE | Should -Be 0
|
||||
}
|
||||
|
||||
It "Whitespace command should succeed" {
|
||||
& $powershell -noprofile -c ' ' | Should -BeNullOrEmpty
|
||||
$LASTEXITCODE | Should -Be 0
|
||||
|
|
|
@ -90,7 +90,7 @@ Describe "Validate start of console host" -Tag CI {
|
|||
Remove-Item $profileDataFile -Force
|
||||
}
|
||||
|
||||
$loadedAssemblies = & "$PSHOME/pwsh" -noprofile -command '([System.AppDomain]::CurrentDomain.GetAssemblies()).manifestmodule | Where-Object { $_.Name -notlike ""<*>"" } | ForEach-Object { $_.Name }'
|
||||
$loadedAssemblies = & "$PSHOME/pwsh" -noprofile -command '([System.AppDomain]::CurrentDomain.GetAssemblies()).manifestmodule | Where-Object { $_.Name -notlike "<*>" } | ForEach-Object { $_.Name }'
|
||||
}
|
||||
|
||||
It "No new assemblies are loaded" {
|
||||
|
|
|
@ -1,70 +1,112 @@
|
|||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
Describe "Native Command Arguments" -tags "CI" {
|
||||
# When passing arguments to native commands, quoted segments that contain
|
||||
# spaces need to be quoted with '"' characters when they are passed to the
|
||||
# native command (or to bash or sh on Linux).
|
||||
#
|
||||
# This test checks that the proper quoting is occuring by passing arguments
|
||||
# to the testexe native command and looking at how it got the arguments.
|
||||
It "Should handle quoted spaces correctly" {
|
||||
$a = 'a"b c"d'
|
||||
$lines = testexe -echoargs $a 'a"b c"d' a"b c"d
|
||||
$lines.Count | Should -Be 3
|
||||
$lines[0] | Should -BeExactly 'Arg 0 is <ab cd>'
|
||||
$lines[1] | Should -BeExactly 'Arg 1 is <ab cd>'
|
||||
$lines[2] | Should -BeExactly 'Arg 2 is <ab cd>'
|
||||
}
|
||||
|
||||
# In order to pass '"' characters so they are actually part of command line
|
||||
# arguments for native commands, they need to be escaped with a '\' (this
|
||||
# is in addition to the '`' escaping needed inside '"' quoted strings in
|
||||
# PowerShell).
|
||||
#
|
||||
# This functionality was broken in PowerShell 5.0 and 5.1, so this test
|
||||
# will fail on those versions unless the fix is backported to them.
|
||||
#
|
||||
# This test checks that the proper quoting and escaping is occurring by
|
||||
# passing arguments with escaped quotes to the testexe native command and
|
||||
# looking at how it got the arguments.
|
||||
It "Should handle spaces between escaped quotes" {
|
||||
$lines = testexe -echoargs 'a\"b c\"d' "a\`"b c\`"d"
|
||||
$lines.Count | Should -Be 2
|
||||
$lines[0] | Should -BeExactly 'Arg 0 is <a"b c"d>'
|
||||
$lines[1] | Should -BeExactly 'Arg 1 is <a"b c"d>'
|
||||
}
|
||||
|
||||
It "Should correctly quote paths with spaces: <arguments>" -TestCases @(
|
||||
@{arguments = "'.\test 1\' `".\test 2\`"" ; expected = @(".\test 1\",".\test 2\")},
|
||||
@{arguments = "'.\test 1\\\' `".\test 2\\`""; expected = @(".\test 1\\\",".\test 2\\")}
|
||||
) {
|
||||
param($arguments, $expected)
|
||||
$lines = Invoke-Expression "testexe -echoargs $arguments"
|
||||
$lines.Count | Should -Be $expected.Count
|
||||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||||
$lines[$i] | Should -BeExactly "Arg $i is <$($expected[$i])>"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidGlobalVars", "")]
|
||||
param()
|
||||
Describe "Will error correctly if an attempt to set variable to improper value" {
|
||||
It "will error when setting variable incorrectly" {
|
||||
if ($EnabledExperimentalFeatures -contains 'PSNativeCommandArgumentPassing') {
|
||||
{ $global:PSNativeCommandArgumentPassing = "zzz" } | Should -Throw -ExceptionType System.Management.Automation.ArgumentTransformationMetadataException
|
||||
}
|
||||
}
|
||||
|
||||
It "Should handle PowerShell arrays with or without spaces correctly: <arguments>" -TestCases @(
|
||||
@{arguments = "1,2"; expected = @("1,2")}
|
||||
@{arguments = "1,2,3"; expected = @("1,2,3")}
|
||||
@{arguments = "1, 2"; expected = "1,", "2"}
|
||||
@{arguments = "1 ,2"; expected = "1", ",2"}
|
||||
@{arguments = "1 , 2"; expected = "1", ",", "2"}
|
||||
@{arguments = "1, 2,3"; expected = "1,", "2,3"}
|
||||
@{arguments = "1 ,2,3"; expected = "1", ",2,3"}
|
||||
@{arguments = "1 , 2,3"; expected = "1", ",", "2,3"}
|
||||
) {
|
||||
param($arguments, $expected)
|
||||
$lines = @(Invoke-Expression "testexe -echoargs $arguments")
|
||||
$lines.Count | Should -Be $expected.Count
|
||||
for ($i = 0; $i -lt $expected.Count; $i++) {
|
||||
$lines[$i] | Should -BeExactly "Arg $i is <$($expected[$i])>"
|
||||
else {
|
||||
Set-Test -State skipped -Because "Experimental feature 'PSNativeCommandArgumentPassing' is not enabled"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $argumentListValue in "Standard","Legacy" ) {
|
||||
$PSNativeCommandArgumentPassing = $argumentListValue
|
||||
Describe "Native Command Arguments (${PSNativeCommandArgumentPassing})" -tags "CI" {
|
||||
# When passing arguments to native commands, quoted segments that contain
|
||||
# spaces need to be quoted with '"' characters when they are passed to the
|
||||
# native command (or to bash or sh on Linux).
|
||||
#
|
||||
# This test checks that the proper quoting is occuring by passing arguments
|
||||
# to the testexe native command and looking at how it got the arguments.
|
||||
It "Should handle quoted spaces correctly (ArgumentList=${PSNativeCommandArgumentPassing})" {
|
||||
$a = 'a"b c"d'
|
||||
$lines = testexe -echoargs $a 'a"b c"d' a"b c"d "a'b c'd"
|
||||
$lines.Count | Should -Be 4
|
||||
if (($EnabledExperimentalFeatures -contains 'PSNativeCommandArgumentPassing') -and $PSNativeCommandArgumentPassing -ne "Legacy") {
|
||||
$lines[0] | Should -BeExactly 'Arg 0 is <a"b c"d>'
|
||||
$lines[1] | Should -BeExactly 'Arg 1 is <a"b c"d>'
|
||||
}
|
||||
else {
|
||||
$lines[0] | Should -BeExactly 'Arg 0 is <ab cd>'
|
||||
$lines[1] | Should -BeExactly 'Arg 1 is <ab cd>'
|
||||
}
|
||||
$lines[2] | Should -BeExactly 'Arg 2 is <ab cd>'
|
||||
$lines[3] | Should -BeExactly 'Arg 3 is <a''b c''d>'
|
||||
}
|
||||
|
||||
# In order to pass '"' characters so they are actually part of command line
|
||||
# arguments for native commands, they need to be escaped with a '\' (this
|
||||
# is in addition to the '`' escaping needed inside '"' quoted strings in
|
||||
# PowerShell).
|
||||
#
|
||||
# This functionality was broken in PowerShell 5.0 and 5.1, so this test
|
||||
# will fail on those versions unless the fix is backported to them.
|
||||
#
|
||||
# This test checks that the proper quoting and escaping is occurring by
|
||||
# passing arguments with escaped quotes to the testexe native command and
|
||||
# looking at how it got the arguments.
|
||||
It "Should handle spaces between escaped quotes (ArgumentList=${PSNativeCommandArgumentPassing})" {
|
||||
$lines = testexe -echoargs 'a\"b c\"d' "a\`"b c\`"d"
|
||||
$lines.Count | Should -Be 2
|
||||
if (($EnabledExperimentalFeatures -contains 'PSNativeCommandArgumentPassing') -and $PSNativeCommandArgumentPassing -ne "Legacy") {
|
||||
$lines[0] | Should -BeExactly 'Arg 0 is <a\"b c\"d>'
|
||||
$lines[1] | Should -BeExactly 'Arg 1 is <a\"b c\"d>'
|
||||
}
|
||||
else {
|
||||
$lines[0] | Should -BeExactly 'Arg 0 is <a"b c"d>'
|
||||
$lines[1] | Should -BeExactly 'Arg 1 is <a"b c"d>'
|
||||
}
|
||||
}
|
||||
|
||||
It "Should correctly quote paths with spaces (ArgumentList=${PSNativeCommandArgumentPassing}): <arguments>" -TestCases @(
|
||||
@{arguments = "'.\test 1\' `".\test 2\`"" ; expected = @(".\test 1\",".\test 2\")},
|
||||
@{arguments = "'.\test 1\\\' `".\test 2\\`""; expected = @(".\test 1\\\",".\test 2\\")}
|
||||
) {
|
||||
param($arguments, $expected)
|
||||
$lines = Invoke-Expression "testexe -echoargs $arguments"
|
||||
$lines.Count | Should -Be $expected.Count
|
||||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||||
$lines[$i] | Should -BeExactly "Arg $i is <$($expected[$i])>"
|
||||
}
|
||||
}
|
||||
|
||||
It "Should handle arguments that include commas without spaces (windbg example)" {
|
||||
$lines = testexe -echoargs -k com:port=\\devbox\pipe\debug,pipe,resets=0,reconnect
|
||||
$lines.Count | Should -Be 2
|
||||
$lines[0] | Should -BeExactly "Arg 0 is <-k>"
|
||||
$lines[1] | Should -BeExactly "Arg 1 is <com:port=\\devbox\pipe\debug,pipe,resets=0,reconnect>"
|
||||
}
|
||||
|
||||
It "Should handle DOS style arguments" {
|
||||
$lines = testexe -echoargs /arg1 /c:"a string"
|
||||
$lines.Count | Should -Be 2
|
||||
$lines[0] | Should -BeExactly "Arg 0 is </arg1>"
|
||||
$lines[1] | Should -BeExactly "Arg 1 is </c:a string>"
|
||||
}
|
||||
|
||||
It "Should handle PowerShell arrays with or without spaces correctly (ArgumentList=${PSNativeCommandArgumentPassing}): <arguments>" -TestCases @(
|
||||
@{arguments = "1,2"; expected = @("1,2")}
|
||||
@{arguments = "1,2,3"; expected = @("1,2,3")}
|
||||
@{arguments = "1, 2"; expected = "1,", "2"}
|
||||
@{arguments = "1 ,2"; expected = "1", ",2"}
|
||||
@{arguments = "1 , 2"; expected = "1", ",", "2"}
|
||||
@{arguments = "1, 2,3"; expected = "1,", "2,3"}
|
||||
@{arguments = "1 ,2,3"; expected = "1", ",2,3"}
|
||||
@{arguments = "1 , 2,3"; expected = "1", ",", "2,3"}
|
||||
) {
|
||||
param($arguments, $expected)
|
||||
$lines = @(Invoke-Expression "testexe -echoargs $arguments")
|
||||
$lines.Count | Should -Be $expected.Count
|
||||
for ($i = 0; $i -lt $expected.Count; $i++) {
|
||||
$lines[$i] | Should -BeExactly "Arg $i is <$($expected[$i])>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Describe 'PSPath to native commands' {
|
||||
BeforeAll {
|
||||
$featureEnabled = $EnabledExperimentalFeatures.Contains('PSNativePSPathResolution')
|
||||
|
|
|
@ -12,9 +12,9 @@ Describe "Native streams behavior with PowerShell" -Tags 'CI' {
|
|||
$error.Clear()
|
||||
|
||||
$command = [string]::Join('', @(
|
||||
'[Console]::Error.Write(\"foo`n`nbar`n`nbaz\"); ',
|
||||
'[Console]::Error.Write(\"middle\"); ',
|
||||
'[Console]::Error.Write(\"foo`n`nbar`n`nbaz\")'
|
||||
'[Console]::Error.Write("foo`n`nbar`n`nbaz"); ',
|
||||
'[Console]::Error.Write("middle"); ',
|
||||
'[Console]::Error.Write("foo`n`nbar`n`nbaz")'
|
||||
))
|
||||
|
||||
$out = & $powershell -noprofile -command $command 2>&1
|
||||
|
|
Loading…
Reference in a new issue