Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Native invocation using ArgumentList #14692

Merged
merged 22 commits into from Apr 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f9cd8c5
Support using ArgumentList with native app execution
JamesWTruher Jan 29, 2021
8dc0d52
add test code for changed behavior
JamesWTruher Jan 29, 2021
7e74f9e
change logic for dealing with nulls, but allow empty spaces '' as arg…
JamesWTruher Feb 2, 2021
581b91a
update test to handle both new and old behaviors
JamesWTruher Feb 2, 2021
ef03db7
Add passing native args as argumentlist as an experimental feature.
JamesWTruher Mar 8, 2021
c676e1d
Update test to use experimental feature and new variable
JamesWTruher Mar 9, 2021
7485621
Address a number of code factor issues.
JamesWTruher Mar 9, 2021
4d2b442
Additional CodeFactor fixes
JamesWTruher Mar 9, 2021
fadf451
Additional CodeFactor fixes
JamesWTruher Mar 10, 2021
93b4c4b
Refactor argument list creation to use common method.
JamesWTruher Mar 10, 2021
15f2851
Update help function to use new style of arg passing for native apps
JamesWTruher Mar 11, 2021
37ada29
Remove escaped quotes from test as they are no longer needed
JamesWTruher Mar 11, 2021
310c731
Fix test by removing (now) extraneous extra quotes
JamesWTruher Mar 11, 2021
8ebabbe
fix codefactor issue
JamesWTruher Mar 14, 2021
0331d3d
Suppress warning about using global variables
JamesWTruher Mar 14, 2021
4ae012d
Address some PR feedback
JamesWTruher Mar 16, 2021
ab67f99
Address some concerns in PR review
JamesWTruher Mar 18, 2021
ee1f5f5
Add a windbg example for native argument passing
JamesWTruher Mar 22, 2021
7ee22e2
Fix typo in test.
JamesWTruher Mar 23, 2021
4eae549
Change tests to use EnabledExperimentalFeatures rather than invoking …
JamesWTruher Mar 24, 2021
6564558
Update src/System.Management.Automation/engine/NativeCommandParameter…
JamesWTruher Mar 31, 2021
8245690
Update src/System.Management.Automation/engine/NativeCommandParameter…
JamesWTruher Mar 31, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/System.Management.Automation/engine/CommandBase.cs
Expand Up @@ -272,6 +272,20 @@ internal void InternalDispose(bool isDisposing)

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.
Expand Down
Expand Up @@ -22,6 +22,7 @@ public class ExperimentalFeature

internal const string EngineSource = "PSEngine";
internal const string PSAnsiProgressFeatureName = "PSAnsiProgress";
internal const string PSNativeCommandArgumentPassingFeatureName = "PSNativeCommandArgumentPassing";

#endregion

Expand Down Expand Up @@ -136,6 +137,9 @@ static ExperimentalFeature()
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);

Expand Down
56 changes: 47 additions & 9 deletions src/System.Management.Automation/engine/InitialSessionState.cs
Expand Up @@ -1308,6 +1308,32 @@ private static void MakeDisallowedEntriesPrivate<T>(InitialSessionStateEntryColl
}
}

#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>
Expand Down Expand Up @@ -1413,7 +1439,7 @@ private static InitialSessionState CreateRestrictedForRemoteServer()
}

// Add built-in variables.
JamesWTruher marked this conversation as resolved.
Show resolved Hide resolved
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))
Expand Down Expand Up @@ -1477,7 +1503,7 @@ public static InitialSessionState Create()
// 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;
Expand All @@ -1500,7 +1526,7 @@ public static InitialSessionState CreateDefault()

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);
Expand Down Expand Up @@ -1567,7 +1593,7 @@ public static InitialSessionState CreateDefault2()
{
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);
Expand Down Expand Up @@ -1608,7 +1634,7 @@ public InitialSessionState Clone()
{
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());
Expand Down Expand Up @@ -4272,7 +4298,13 @@ .FORWARDHELPCATEGORY Cmdlet
}
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.
Expand Down Expand Up @@ -4312,10 +4344,16 @@ .FORWARDHELPCATEGORY Cmdlet
$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%
JamesWTruher marked this conversation as resolved.
Show resolved Hide resolved
}
}
else {
$help | Out-String -Stream -Width ($consoleWidth - 1) | & $pagerCommand
Expand Down
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System.Collections;
using System.Collections.Generic;
JamesWTruher marked this conversation as resolved.
Show resolved Hide resolved
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
Expand Down Expand Up @@ -82,7 +83,7 @@ internal void BindParameters(Collection<CommandParameterInternal> parameters)
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)
{
Expand Down Expand Up @@ -130,7 +131,7 @@ internal void BindParameters(Collection<CommandParameterInternal> parameters)
stringConstantType = StringConstantType.DoubleQuoted;
}

AppendOneNativeArgument(Context, argValue, arrayLiteralAst, sawVerbatimArgumentMarker, stringConstantType);
AppendOneNativeArgument(Context, parameter, argValue, arrayLiteralAst, sawVerbatimArgumentMarker, stringConstantType);
}
}
}
Expand All @@ -151,6 +152,65 @@ internal string Arguments

private readonly StringBuilder _arguments = new StringBuilder();

internal string[] ArgumentList
SteveL-MSFT marked this conversation as resolved.
Show resolved Hide resolved
{
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
Expand All @@ -161,24 +221,27 @@ internal string Arguments
/// 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
{
Expand All @@ -187,7 +250,8 @@ private void AppendOneNativeArgument(ExecutionContext context, object obj, Array
break;
}

arg = PSObject.ToStringParser(context, ParserOps.Current(null, list));
currentObj = ParserOps.Current(null, list);
JamesWTruher marked this conversation as resolved.
Show resolved Hide resolved
arg = PSObject.ToStringParser(context, currentObj);

currentElement += 1;
if (currentElement != 0)
Expand All @@ -198,12 +262,16 @@ private void AppendOneNativeArgument(ExecutionContext context, object obj, Array

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
{
Expand All @@ -227,10 +295,12 @@ private void AppendOneNativeArgument(ExecutionContext context, object obj, Array
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
Expand All @@ -244,10 +314,28 @@ private void AppendOneNativeArgument(ExecutionContext context, object obj, Array
}
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);
}
Expand All @@ -257,8 +345,9 @@ private void AppendOneNativeArgument(ExecutionContext context, object obj, Array
/// 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;

Expand Down Expand Up @@ -311,10 +400,12 @@ private void PossiblyGlobArg(string arg, StringConstantType stringConstantType)
_arguments.Append('"');
_arguments.Append(expandedPath);
_arguments.Append('"');
AddToArgumentList(parameter, expandedPath);
}
else
{
_arguments.Append(expandedPath);
AddToArgumentList(parameter, expandedPath);
}

argExpanded = true;
Expand All @@ -331,12 +422,14 @@ private void PossiblyGlobArg(string arg, StringConstantType stringConstantType)
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;
}
}
Expand All @@ -351,6 +444,7 @@ private void PossiblyGlobArg(string arg, StringConstantType stringConstantType)
if (!argExpanded)
{
_arguments.Append(arg);
AddToArgumentList(parameter, arg);
}
}

Expand Down