mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Add a Windows mode for native commands that allows some commands to use legacy argument passing (#15408)
This commit is contained in:
@@ -281,8 +281,14 @@ namespace System.Management.Automation
|
||||
/// <summary>Use legacy argument parsing via ProcessStartInfo.Arguments.</summary>
|
||||
Legacy = 0,
|
||||
|
||||
/// <summary>Use new style argument parsing via ProcessStartInfo.ArgumentList.</summary>
|
||||
Standard = 1
|
||||
/// <summary>Use new style argument passing via ProcessStartInfo.ArgumentList.</summary>
|
||||
Standard = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Use specific to Windows passing style which is Legacy for selected files on Windows, but
|
||||
/// Standard for everything else. This is the default behavior for Windows.
|
||||
/// </summary>
|
||||
Windows = 2
|
||||
}
|
||||
#endregion NativeArgumentPassingStyle
|
||||
|
||||
|
||||
@@ -1320,13 +1320,18 @@ namespace System.Management.Automation.Runspaces
|
||||
|
||||
// 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.
|
||||
// we need to do this here. Also, since the defaults are different based on platform we need a
|
||||
// bit more logic.
|
||||
if (ExperimentalFeature.IsEnabled("PSNativeCommandArgumentPassing"))
|
||||
{
|
||||
NativeArgumentPassingStyle style = NativeArgumentPassingStyle.Standard;
|
||||
if (Platform.IsWindows) {
|
||||
style = NativeArgumentPassingStyle.Windows;
|
||||
}
|
||||
Variables.Add(
|
||||
new SessionStateVariableEntry(
|
||||
SpecialVariables.NativeArgumentPassing,
|
||||
NativeArgumentPassingStyle.Standard,
|
||||
style,
|
||||
RunspaceInit.NativeCommandArgumentPassingDescription,
|
||||
ScopedItemOptions.None,
|
||||
new ArgumentTypeConverterAttribute(typeof(NativeArgumentPassingStyle))));
|
||||
|
||||
@@ -20,6 +20,8 @@ namespace System.Management.Automation
|
||||
/// </summary>
|
||||
internal class NativeCommandParameterBinder : ParameterBinderBase
|
||||
{
|
||||
private readonly VariablePath s_nativeArgumentPassingVarPath = new VariablePath(SpecialVariables.NativeArgumentPassing);
|
||||
|
||||
#region ctor
|
||||
|
||||
/// <summary>
|
||||
@@ -187,7 +189,7 @@ namespace System.Management.Automation
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether to use an ArgumentList or string for arguments when invoking a native executable.
|
||||
/// </summary>
|
||||
internal bool UseArgumentList
|
||||
internal NativeArgumentPassingStyle ArgumentPassingStyle
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -197,17 +199,17 @@ namespace System.Management.Automation
|
||||
{
|
||||
// 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;
|
||||
Context.GetVariableValue(s_nativeArgumentPassingVarPath, NativeArgumentPassingStyle.Standard));
|
||||
return preference;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// The value is not convertable send back true
|
||||
return true;
|
||||
// The value is not convertable send back Legacy
|
||||
return NativeArgumentPassingStyle.Legacy;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return NativeArgumentPassingStyle.Legacy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,7 +316,7 @@ namespace System.Management.Automation
|
||||
}
|
||||
else
|
||||
{
|
||||
if (argArrayAst != null && UseArgumentList)
|
||||
if (argArrayAst != null && ArgumentPassingStyle == NativeArgumentPassingStyle.Standard)
|
||||
{
|
||||
// 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))
|
||||
@@ -331,7 +333,7 @@ namespace System.Management.Automation
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (UseArgumentList && currentObj != null)
|
||||
else if (ArgumentPassingStyle == NativeArgumentPassingStyle.Standard && currentObj != null)
|
||||
{
|
||||
// add empty strings to arglist, but not nulls
|
||||
AddToArgumentList(parameter, arg);
|
||||
|
||||
@@ -50,13 +50,13 @@ namespace System.Management.Automation
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether to use the new API for StartInfo.
|
||||
/// Gets the value indicating what type of native argument binding to use.
|
||||
/// </summary>
|
||||
internal bool UseArgumentList
|
||||
internal NativeArgumentPassingStyle ArgumentPassingStyle
|
||||
{
|
||||
get
|
||||
{
|
||||
return ((NativeCommandParameterBinder)DefaultParameterBinder).UseArgumentList;
|
||||
return ((NativeCommandParameterBinder)DefaultParameterBinder).ArgumentPassingStyle;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -135,6 +135,26 @@ namespace System.Management.Automation
|
||||
/// </summary>
|
||||
internal class NativeCommandProcessor : CommandProcessorBase
|
||||
{
|
||||
// This is the list of files which will trigger Legacy behavior if
|
||||
// PSNativeCommandArgumentPassing is set to "Windows".
|
||||
private static readonly IReadOnlySet<string> s_legacyFileExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
".js",
|
||||
".wsf",
|
||||
".cmd",
|
||||
".bat",
|
||||
".vbs",
|
||||
};
|
||||
|
||||
// The following native commands have non-standard behavior with regard to argument passing,
|
||||
// so we use Legacy argument parsing for them when PSNativeCommandArgumentPassing is set to Windows.
|
||||
private static readonly IReadOnlySet<string> s_legacyCommands = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"cmd",
|
||||
"cscript",
|
||||
"wscript",
|
||||
};
|
||||
|
||||
#region ctor/native command properties
|
||||
|
||||
/// <summary>
|
||||
@@ -474,6 +494,7 @@ namespace System.Management.Automation
|
||||
// on Windows desktops, see if there is a file association for this command. If so then we'll use that.
|
||||
string executable = FindExecutable(startInfo.FileName);
|
||||
bool notDone = true;
|
||||
// check to see what mode we should be in for argument passing
|
||||
if (!string.IsNullOrEmpty(executable))
|
||||
{
|
||||
isWindowsApplication = IsWindowsApplication(executable);
|
||||
@@ -485,7 +506,16 @@ namespace System.Management.Automation
|
||||
|
||||
string oldArguments = startInfo.Arguments;
|
||||
string oldFileName = startInfo.FileName;
|
||||
startInfo.Arguments = "\"" + startInfo.FileName + "\" " + startInfo.Arguments;
|
||||
// Check to see whether this executable should be using Legacy mode argument parsing
|
||||
bool useSpecialArgumentPassing = UseSpecialArgumentPassing(oldFileName);
|
||||
if (useSpecialArgumentPassing)
|
||||
{
|
||||
startInfo.Arguments = "\"" + oldFileName + "\" " + startInfo.Arguments;
|
||||
}
|
||||
else
|
||||
{
|
||||
startInfo.ArgumentList.Insert(0, oldFileName);
|
||||
}
|
||||
startInfo.FileName = executable;
|
||||
try
|
||||
{
|
||||
@@ -495,7 +525,14 @@ namespace System.Management.Automation
|
||||
catch (Win32Exception)
|
||||
{
|
||||
// Restore the old filename and arguments to try shell execute last...
|
||||
startInfo.Arguments = oldArguments;
|
||||
if (useSpecialArgumentPassing)
|
||||
{
|
||||
startInfo.Arguments = oldArguments;
|
||||
}
|
||||
else
|
||||
{
|
||||
startInfo.ArgumentList.RemoveAt(0);
|
||||
}
|
||||
startInfo.FileName = oldFileName;
|
||||
}
|
||||
}
|
||||
@@ -1108,25 +1145,69 @@ namespace System.Management.Automation
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the start info for process.
|
||||
/// Get whether we should treat this executable with special handling and use the legacy passing style.
|
||||
/// </summary>
|
||||
/// <param name="redirectOutput"></param>
|
||||
/// <param name="redirectError"></param>
|
||||
/// <param name="redirectInput"></param>
|
||||
/// <param name="soloCommand"></param>
|
||||
/// <returns></returns>
|
||||
private ProcessStartInfo GetProcessStartInfo(bool redirectOutput, bool redirectError, bool redirectInput, bool soloCommand)
|
||||
{
|
||||
ProcessStartInfo startInfo = new ProcessStartInfo();
|
||||
startInfo.FileName = this.Path;
|
||||
/// <param name="filePath"></param>
|
||||
private bool UseSpecialArgumentPassing(string filePath) =>
|
||||
NativeParameterBinderController.ArgumentPassingStyle switch
|
||||
{
|
||||
NativeArgumentPassingStyle.Legacy => true,
|
||||
NativeArgumentPassingStyle.Windows => ShouldUseLegacyPassingStyle(filePath),
|
||||
_ => false
|
||||
};
|
||||
|
||||
if (IsExecutable(this.Path))
|
||||
/// <summary>
|
||||
/// Gets the ProcessStartInfo for process.
|
||||
/// </summary>
|
||||
/// <param name="redirectOutput">A boolean that indicates that, when true, output from the process is redirected to a stream, and otherwise is sent to stdout.</param>
|
||||
/// <param name="redirectError">A boolean that indicates that, when true, error output from the process is redirected to a stream, and otherwise is sent to stderr.</param>
|
||||
/// <param name="redirectInput">A boolean that indicates that, when true, input to the process is taken from a stream, and otherwise is taken from stdin.</param>
|
||||
/// <param name="soloCommand">A boolean that indicates, when true, that the command to be executed is not part of a pipeline, and otherwise indicates that is is.</param>
|
||||
/// <returns>A ProcessStartInfo object which is the base of the native invocation.</returns>
|
||||
private ProcessStartInfo GetProcessStartInfo(
|
||||
bool redirectOutput,
|
||||
bool redirectError,
|
||||
bool redirectInput,
|
||||
bool soloCommand)
|
||||
{
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = this.Path
|
||||
};
|
||||
|
||||
if (!IsExecutable(this.Path))
|
||||
{
|
||||
if (Platform.IsNanoServer || Platform.IsIoT)
|
||||
{
|
||||
// Shell doesn't exist on headless SKUs, so documents cannot be associated with an application.
|
||||
// Therefore, we cannot run document in this case.
|
||||
throw InterpreterError.NewInterpreterException(
|
||||
this.Path,
|
||||
typeof(RuntimeException),
|
||||
this.Command.InvocationExtent,
|
||||
"CantActivateDocumentInPowerShellCore",
|
||||
ParserStrings.CantActivateDocumentInPowerShellCore,
|
||||
this.Path);
|
||||
}
|
||||
|
||||
// We only want to ShellExecute something that is standalone...
|
||||
if (!soloCommand)
|
||||
{
|
||||
throw InterpreterError.NewInterpreterException(
|
||||
this.Path,
|
||||
typeof(RuntimeException),
|
||||
this.Command.InvocationExtent,
|
||||
"CantActivateDocumentInPipeline",
|
||||
ParserStrings.CantActivateDocumentInPipeline,
|
||||
this.Path);
|
||||
}
|
||||
|
||||
startInfo.UseShellExecute = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
startInfo.UseShellExecute = false;
|
||||
if (redirectInput)
|
||||
{
|
||||
startInfo.RedirectStandardInput = true;
|
||||
}
|
||||
startInfo.RedirectStandardInput = redirectInput;
|
||||
|
||||
if (redirectOutput)
|
||||
{
|
||||
@@ -1140,32 +1221,13 @@ namespace System.Management.Automation
|
||||
startInfo.StandardErrorEncoding = Console.OutputEncoding;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Platform.IsNanoServer || Platform.IsIoT)
|
||||
{
|
||||
// Shell doesn't exist on headless SKUs, so documents cannot be associated with an application.
|
||||
// Therefore, we cannot run document in this case.
|
||||
throw InterpreterError.NewInterpreterException(this.Path, typeof(RuntimeException),
|
||||
this.Command.InvocationExtent, "CantActivateDocumentInPowerShellCore", ParserStrings.CantActivateDocumentInPowerShellCore, this.Path);
|
||||
}
|
||||
|
||||
// We only want to ShellExecute something that is standalone...
|
||||
if (!soloCommand)
|
||||
{
|
||||
throw InterpreterError.NewInterpreterException(this.Path, typeof(RuntimeException),
|
||||
this.Command.InvocationExtent, "CantActivateDocumentInPipeline", ParserStrings.CantActivateDocumentInPipeline, this.Path);
|
||||
}
|
||||
|
||||
startInfo.UseShellExecute = true;
|
||||
}
|
||||
|
||||
// For minishell value of -outoutFormat parameter depends on value of redirectOutput.
|
||||
// So we delay the parameter binding. Do parameter binding for minishell now.
|
||||
if (_isMiniShell)
|
||||
{
|
||||
MinishellParameterBinderController mpc = (MinishellParameterBinderController)NativeParameterBinderController;
|
||||
mpc.BindParameters(arguments, redirectOutput, this.Command.Context.EngineHostInterface.Name);
|
||||
mpc.BindParameters(arguments, startInfo.RedirectStandardOutput, this.Command.Context.EngineHostInterface.Name);
|
||||
startInfo.CreateNoWindow = mpc.NonInteractive;
|
||||
}
|
||||
|
||||
@@ -1174,7 +1236,8 @@ namespace System.Management.Automation
|
||||
// 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)
|
||||
// We need to check if we're using legacy argument passing or it's a special case.
|
||||
if (UseSpecialArgumentPassing(startInfo.FileName))
|
||||
{
|
||||
using (ParameterBinderBase.bindingTracer.TraceScope("BIND argument [{0}]", NativeParameterBinderController.Arguments))
|
||||
{
|
||||
@@ -1203,9 +1266,27 @@ namespace System.Management.Automation
|
||||
context.EngineSessionState.GetNamespaceCurrentLocation(
|
||||
context.ProviderNames.FileSystem).ProviderPath;
|
||||
startInfo.WorkingDirectory = WildcardPattern.Unescape(rawPath);
|
||||
|
||||
return startInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if we have a special file which will change the way native argument passing
|
||||
/// is done on Windows. We use legacy behavior for cmd.exe, .bat, .cmd files.
|
||||
/// </summary>
|
||||
/// <param name="filePath">The file to use when checking how to pass arguments.</param>
|
||||
/// <returns>A boolean indicating what passing style should be used.</returns>
|
||||
private static bool ShouldUseLegacyPassingStyle(string filePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return s_legacyFileExtensions.Contains(IO.Path.GetExtension(filePath))
|
||||
|| s_legacyCommands.Contains(IO.Path.GetFileNameWithoutExtension(filePath));
|
||||
}
|
||||
|
||||
private static bool IsDownstreamOutDefault(Pipe downstreamPipe)
|
||||
{
|
||||
Diagnostics.Assert(downstreamPipe != null, "Caller makes sure the passed-in parameter is not null.");
|
||||
|
||||
@@ -240,7 +240,7 @@ Describe "ConsoleHost unit tests" -tags "Feature" {
|
||||
$LASTEXITCODE | Should -Be 64
|
||||
}
|
||||
|
||||
It "Empty space command should succeed" {
|
||||
It "Empty space command should succeed on non-Windows" -skip:$IsWindows {
|
||||
& $powershell -noprofile -c '' | Should -BeNullOrEmpty
|
||||
$LASTEXITCODE | Should -Be 0
|
||||
}
|
||||
|
||||
+160
-2
@@ -2,7 +2,165 @@
|
||||
# Licensed under the MIT License.
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidGlobalVars", "")]
|
||||
param()
|
||||
Describe "Will error correctly if an attempt to set variable to improper value" {
|
||||
|
||||
Describe "Behavior is specific for each platform" -tags "CI" {
|
||||
BeforeAll {
|
||||
$skipTests = $EnabledExperimentalFeatures -notcontains 'PSNativeCommandArgumentPassing'
|
||||
}
|
||||
It "PSNativeCommandArgumentPassing is set to 'Windows' on Windows systems" -skip:(-not $IsWindows) {
|
||||
$PSNativeCommandArgumentPassing | Should -Be "Windows"
|
||||
}
|
||||
It "PSNativeCommandArgumentPassing is set to 'Standard' on non-Windows systems" -skip:($IsWindows) {
|
||||
$PSNativeCommandArgumentPassing | Should -Be "Standard"
|
||||
}
|
||||
It "Has proper behavior on Windows" -skip:(-not $IsWindows) {
|
||||
"@echo off`nSET V1=1" > "$TESTDRIVE\script 1.cmd"
|
||||
"@echo off`nSET V2=a`necho %V1%" > "$TESTDRIVE\script 2.cmd"
|
||||
"@echo off`necho %V1%`necho %V2%" > "$TESTDRIVE\script 3.cmd"
|
||||
$result = cmd /c """${TESTDRIVE}\script 1.cmd"" && ""${TESTDRIVE}\script 2.cmd"" && ""${TESTDRIVE}\script 3.cmd"""
|
||||
$result.Count | Should -Be 3
|
||||
$result[0] | Should -Be 1
|
||||
$result[1] | Should -Be 1
|
||||
$result[2] | Should Be "a"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Describe "tests for multiple languages and extensions" -tags "CI" {
|
||||
AfterAll {
|
||||
if (-not $IsWindows -or
|
||||
$EnabledExperimentalFeatures -notcontains 'PSNativeCommandArgumentPassing') {
|
||||
return
|
||||
}
|
||||
$PSNativeCommandArgumentPassing = $passingStyle
|
||||
}
|
||||
BeforeAll {
|
||||
$testCases = @(
|
||||
@{
|
||||
Command = "cscript.exe"
|
||||
Filename = "test.wsf"
|
||||
ExpectedResults = @(
|
||||
"Argument 0 is: <ab cd>"
|
||||
"Argument 1 is: <ab cd>"
|
||||
"Argument 2 is: <ab cd>"
|
||||
"Argument 3 is: <a'b c'd>"
|
||||
)
|
||||
Script = @'
|
||||
<?xml version="1.0" ?>
|
||||
<job id="test">
|
||||
<script language="VBScript">
|
||||
<![CDATA[
|
||||
for i = 0 to wScript.arguments.count-1
|
||||
wscript.echo "Argument " & i & " is: <" & wScript.arguments(i) & ">"
|
||||
next
|
||||
]]>
|
||||
</script>
|
||||
</job>
|
||||
'@
|
||||
}
|
||||
@{
|
||||
Command = "cscript.exe"
|
||||
Filename = "test.vbs"
|
||||
ExpectedResults = @(
|
||||
"Argument 0 is: <ab cd>"
|
||||
"Argument 1 is: <ab cd>"
|
||||
"Argument 2 is: <ab cd>"
|
||||
"Argument 3 is: <a'b c'd>"
|
||||
)
|
||||
Script = @'
|
||||
for i = 0 to wScript.arguments.count - 1
|
||||
wscript.echo "Argument " & i & " is: <" & (wScript.arguments(i)) & ">"
|
||||
next
|
||||
'@
|
||||
}
|
||||
@{
|
||||
Command = "cscript"
|
||||
Filename = "test.js"
|
||||
ExpectedResults = @(
|
||||
"Argument 0 is: <ab cd>"
|
||||
"Argument 1 is: <ab cd>"
|
||||
"Argument 2 is: <ab cd>"
|
||||
"Argument 3 is: <a'b c'd>"
|
||||
)
|
||||
Script = @'
|
||||
for(i = 0; i < WScript.Arguments.Count(); i++) {
|
||||
WScript.echo("Argument " + i + " is: <" + WScript.Arguments(i) + ">");
|
||||
}
|
||||
'@
|
||||
}
|
||||
@{
|
||||
Command = ""
|
||||
Filename = "test.bat"
|
||||
ExpectedResults = @(
|
||||
"Argument 1 is: <a""b c""d>"
|
||||
"Argument 2 is: <a""b c""d>"
|
||||
"Argument 3 is: <""ab cd"">"
|
||||
"Argument 4 is: <""a'b c'd"">"
|
||||
)
|
||||
Script = @'
|
||||
@echo off
|
||||
echo Argument 1 is: ^<%1^>
|
||||
echo Argument 2 is: ^<%2^>
|
||||
echo Argument 3 is: ^<%3^>
|
||||
echo Argument 4 is: ^<%4^>
|
||||
'@
|
||||
}
|
||||
@{
|
||||
Command = ""
|
||||
Filename = "test.cmd"
|
||||
ExpectedResults = @(
|
||||
"Argument 1 is: <a""b c""d>"
|
||||
"Argument 2 is: <a""b c""d>"
|
||||
"Argument 3 is: <""ab cd"">"
|
||||
"Argument 4 is: <""a'b c'd"">"
|
||||
)
|
||||
Script = @'
|
||||
@echo off
|
||||
echo Argument 1 is: ^<%1^>
|
||||
echo Argument 2 is: ^<%2^>
|
||||
echo Argument 3 is: ^<%3^>
|
||||
echo Argument 4 is: ^<%4^>
|
||||
'@
|
||||
}
|
||||
)
|
||||
|
||||
# determine whether we should skip the tests we just defined
|
||||
# doing it in this order ensures that the test output will show each skipped test
|
||||
$skipTests = -not $IsWindows -or $EnabledExperimentalFeatures -notcontains 'PSNativeCommandArgumentPassing'
|
||||
if ($skipTests) {
|
||||
return
|
||||
}
|
||||
|
||||
# save the passing style
|
||||
$passingStyle = $PSNativeCommandArgumentPassing
|
||||
# explicitely set the passing style to Windows
|
||||
$PSNativeCommandArgumentPassing = "Windows"
|
||||
}
|
||||
|
||||
It "Invoking '<Filename>' is compatible with PowerShell 5" -TestCases $testCases -Skip:$($skipTests) {
|
||||
param ( $Command, $Arguments, $Filename, $Script, $ExpectedResults )
|
||||
cscript //h:cscript //nologo //s
|
||||
$a = 'a"b c"d'
|
||||
$scriptPath = Join-Path $TESTDRIVE $Filename
|
||||
$Script | out-file -encoding ASCII $scriptPath
|
||||
if ($Command) {
|
||||
$results = & $Command $scriptPath $a 'a"b c"d' a"b c"d "a'b c'd" 2> "${TESTDRIVE}/error.txt"
|
||||
}
|
||||
else {
|
||||
$results = & $scriptPath $a 'a"b c"d' a"b c"d "a'b c'd" 2> "${TESTDRIVE}/error.txt"
|
||||
}
|
||||
$errorContent = Get-Content "${TESTDRIVE}/error.txt" -ErrorAction Ignore
|
||||
$errorContent | Should -BeNullOrEmpty
|
||||
$results.Count | Should -Be 4
|
||||
$results[0] | Should -Be $ExpectedResults[0]
|
||||
$results[1] | Should -Be $ExpectedResults[1]
|
||||
$results[2] | Should -Be $ExpectedResults[2]
|
||||
$results[3] | Should -Be $ExpectedResults[3]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Describe "Will error correctly if an attempt to set variable to improper value" -tags "CI" {
|
||||
It "will error when setting variable incorrectly" {
|
||||
if ($EnabledExperimentalFeatures -contains 'PSNativeCommandArgumentPassing') {
|
||||
{ $global:PSNativeCommandArgumentPassing = "zzz" } | Should -Throw -ExceptionType System.Management.Automation.ArgumentTransformationMetadataException
|
||||
@@ -107,7 +265,7 @@ foreach ( $argumentListValue in "Standard","Legacy" ) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Describe 'PSPath to native commands' {
|
||||
Describe 'PSPath to native commands' -tags "CI" {
|
||||
BeforeAll {
|
||||
$featureEnabled = $EnabledExperimentalFeatures.Contains('PSNativePSPathResolution')
|
||||
$originalDefaultParameterValues = $PSDefaultParameterValues.Clone()
|
||||
|
||||
Reference in New Issue
Block a user