2723 lines
102 KiB
C#
2723 lines
102 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Dynamic;
|
|
using System.Globalization;
|
|
using System.Linq.Expressions;
|
|
using System.Management.Automation;
|
|
using System.Management.Automation.Internal;
|
|
using System.Management.Automation.Language;
|
|
using System.Management.Automation.PSTasks;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Text;
|
|
using System.Threading;
|
|
|
|
using CommonParamSet = System.Management.Automation.Internal.CommonParameters;
|
|
using Dbg = System.Management.Automation.Diagnostics;
|
|
using NotNullWhen = System.Diagnostics.CodeAnalysis.NotNullWhenAttribute;
|
|
|
|
namespace Microsoft.PowerShell.Commands
|
|
{
|
|
/// <summary>
|
|
/// A thin wrapper over a property-getting Callsite, to allow reuse when possible.
|
|
/// </summary>
|
|
internal struct DynamicPropertyGetter
|
|
{
|
|
private CallSite<Func<CallSite, object, object>> _getValueDynamicSite;
|
|
|
|
// For the wildcard case, lets us know if we can reuse the callsite:
|
|
private string _lastUsedPropertyName;
|
|
|
|
public object GetValue(PSObject inputObject, string propertyName)
|
|
{
|
|
Dbg.Assert(!WildcardPattern.ContainsWildcardCharacters(propertyName), "propertyName should be pre-resolved by caller");
|
|
|
|
// If wildcards are involved, the resolved property name could potentially
|
|
// be different on every object... but probably not, so we'll attempt to
|
|
// reuse the callsite if possible.
|
|
if (!propertyName.Equals(_lastUsedPropertyName, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
_lastUsedPropertyName = propertyName;
|
|
_getValueDynamicSite = CallSite<Func<CallSite, object, object>>.Create(
|
|
PSGetMemberBinder.Get(
|
|
propertyName,
|
|
classScope: (Type)null,
|
|
@static: false));
|
|
}
|
|
|
|
return _getValueDynamicSite.Target.Invoke(_getValueDynamicSite, inputObject);
|
|
}
|
|
}
|
|
|
|
#region Built-in cmdlets that are used by or require direct access to the engine.
|
|
|
|
/// <summary>
|
|
/// Implements a cmdlet that applies a script block
|
|
/// to each element of the pipeline.
|
|
/// </summary>
|
|
[Cmdlet("ForEach", "Object", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Low,
|
|
DefaultParameterSetName = ForEachObjectCommand.ScriptBlockSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096867",
|
|
RemotingCapability = RemotingCapability.None)]
|
|
public sealed class ForEachObjectCommand : PSCmdlet, IDisposable
|
|
{
|
|
#region Private Members
|
|
|
|
private const string ParallelParameterSet = "ParallelParameterSet";
|
|
private const string ScriptBlockSet = "ScriptBlockSet";
|
|
private const string PropertyAndMethodSet = "PropertyAndMethodSet";
|
|
|
|
#endregion
|
|
|
|
#region Common Parameters
|
|
|
|
/// <summary>
|
|
/// This parameter specifies the current pipeline object.
|
|
/// </summary>
|
|
[Parameter(ValueFromPipeline = true, ParameterSetName = ForEachObjectCommand.ScriptBlockSet)]
|
|
[Parameter(ValueFromPipeline = true, ParameterSetName = ForEachObjectCommand.PropertyAndMethodSet)]
|
|
[Parameter(ValueFromPipeline = true, ParameterSetName = ForEachObjectCommand.ParallelParameterSet)]
|
|
public PSObject InputObject
|
|
{
|
|
get { return _inputObject; }
|
|
|
|
set { _inputObject = value; }
|
|
}
|
|
|
|
private PSObject _inputObject = AutomationNull.Value;
|
|
|
|
#endregion
|
|
|
|
#region ScriptBlockSet
|
|
|
|
private readonly List<ScriptBlock> _scripts = new List<ScriptBlock>();
|
|
|
|
/// <summary>
|
|
/// Gets or sets the script block to apply in begin processing.
|
|
/// </summary>
|
|
[Parameter(ParameterSetName = ForEachObjectCommand.ScriptBlockSet)]
|
|
public ScriptBlock Begin
|
|
{
|
|
get
|
|
{
|
|
return null;
|
|
}
|
|
|
|
set
|
|
{
|
|
_scripts.Insert(0, value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the script block to apply.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = ForEachObjectCommand.ScriptBlockSet)]
|
|
[AllowNull]
|
|
[AllowEmptyCollection]
|
|
public ScriptBlock[] Process
|
|
{
|
|
get
|
|
{
|
|
return null;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (value == null)
|
|
{
|
|
_scripts.Add(null);
|
|
}
|
|
else
|
|
{
|
|
_scripts.AddRange(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
private ScriptBlock _endScript;
|
|
private bool _setEndScript;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the script block to apply in complete processing.
|
|
/// </summary>
|
|
[Parameter(ParameterSetName = ForEachObjectCommand.ScriptBlockSet)]
|
|
public ScriptBlock End
|
|
{
|
|
get
|
|
{
|
|
return _endScript;
|
|
}
|
|
|
|
set
|
|
{
|
|
_endScript = value;
|
|
_setEndScript = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the remaining script blocks to apply.
|
|
/// </summary>
|
|
[Parameter(ParameterSetName = ForEachObjectCommand.ScriptBlockSet, ValueFromRemainingArguments = true)]
|
|
[AllowNull]
|
|
[AllowEmptyCollection]
|
|
public ScriptBlock[] RemainingScripts
|
|
{
|
|
get
|
|
{
|
|
return null;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (value == null)
|
|
{
|
|
_scripts.Add(null);
|
|
}
|
|
else
|
|
{
|
|
_scripts.AddRange(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
private int _start, _end;
|
|
|
|
#endregion ScriptBlockSet
|
|
|
|
#region PropertyAndMethodSet
|
|
|
|
/// <summary>
|
|
/// Gets or sets the property or method name.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = ForEachObjectCommand.PropertyAndMethodSet)]
|
|
[ValidateTrustedData]
|
|
[ValidateNotNullOrEmpty]
|
|
public string MemberName
|
|
{
|
|
get
|
|
{
|
|
return _propertyOrMethodName;
|
|
}
|
|
|
|
set
|
|
{
|
|
_propertyOrMethodName = value;
|
|
}
|
|
}
|
|
|
|
private string _propertyOrMethodName;
|
|
private string _targetString;
|
|
private DynamicPropertyGetter _propGetter;
|
|
|
|
/// <summary>
|
|
/// The arguments passed to a method invocation.
|
|
/// </summary>
|
|
[Parameter(ParameterSetName = ForEachObjectCommand.PropertyAndMethodSet, ValueFromRemainingArguments = true)]
|
|
[ValidateTrustedData]
|
|
[Alias("Args")]
|
|
public object[] ArgumentList
|
|
{
|
|
get { return _arguments; }
|
|
|
|
set { _arguments = value; }
|
|
}
|
|
|
|
private object[] _arguments;
|
|
|
|
#endregion PropertyAndMethodSet
|
|
|
|
#region ParallelParameterSet
|
|
|
|
/// <summary>
|
|
/// Gets or sets a script block to run in parallel for each pipeline object.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = ForEachObjectCommand.ParallelParameterSet)]
|
|
public ScriptBlock Parallel { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the maximum number of concurrently running scriptblocks on separate threads.
|
|
/// The default number is 5.
|
|
/// </summary>
|
|
[Parameter(ParameterSetName = ForEachObjectCommand.ParallelParameterSet)]
|
|
[ValidateRange(1, Int32.MaxValue)]
|
|
public int ThrottleLimit { get; set; } = 5;
|
|
|
|
/// <summary>
|
|
/// Gets or sets a timeout time in seconds, after which the parallel running scripts will be stopped
|
|
/// The default value is 0, indicating no timeout.
|
|
/// </summary>
|
|
[Parameter(ParameterSetName = ForEachObjectCommand.ParallelParameterSet)]
|
|
[ValidateRange(0, (Int32.MaxValue / 1000))]
|
|
public int TimeoutSeconds { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a flag that returns a job object immediately for the parallel operation, instead of returning after
|
|
/// all foreach processing is completed.
|
|
/// </summary>
|
|
[Parameter(ParameterSetName = ForEachObjectCommand.ParallelParameterSet)]
|
|
public SwitchParameter AsJob { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a flag so that a new runspace object is created for each loop iteration, instead of reusing objects
|
|
/// from the runspace pool.
|
|
/// By default, runspaces are reused from a runspace pool.
|
|
/// </summary>
|
|
[Parameter(ParameterSetName = ForEachObjectCommand.ParallelParameterSet)]
|
|
public SwitchParameter UseNewRunspace { get; set; }
|
|
|
|
#endregion
|
|
|
|
#region Overrides
|
|
|
|
/// <summary>
|
|
/// Execute the begin scriptblock at the start of processing.
|
|
/// </summary>
|
|
/// <exception cref="ParseException">Could not parse script.</exception>
|
|
/// <exception cref="RuntimeException">See Pipeline.Invoke.</exception>
|
|
/// <exception cref="ParameterBindingException">See Pipeline.Invoke.</exception>
|
|
protected override void BeginProcessing()
|
|
{
|
|
switch (ParameterSetName)
|
|
{
|
|
case ForEachObjectCommand.ScriptBlockSet:
|
|
InitScriptBlockParameterSet();
|
|
break;
|
|
|
|
case ForEachObjectCommand.ParallelParameterSet:
|
|
InitParallelParameterSet();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Execute the processing script blocks on the current pipeline object
|
|
/// which is passed as it's only parameter.
|
|
/// </summary>
|
|
/// <exception cref="ParseException">Could not parse script.</exception>
|
|
/// <exception cref="RuntimeException">See Pipeline.Invoke.</exception>
|
|
/// <exception cref="ParameterBindingException">See Pipeline.Invoke.</exception>
|
|
protected override void ProcessRecord()
|
|
{
|
|
switch (ParameterSetName)
|
|
{
|
|
case ForEachObjectCommand.ScriptBlockSet:
|
|
ProcessScriptBlockParameterSet();
|
|
break;
|
|
|
|
case ForEachObjectCommand.PropertyAndMethodSet:
|
|
ProcessPropertyAndMethodParameterSet();
|
|
break;
|
|
|
|
case ForEachObjectCommand.ParallelParameterSet:
|
|
ProcessParallelParameterSet();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Execute the end scriptblock when the pipeline is complete.
|
|
/// </summary>
|
|
/// <exception cref="ParseException">Could not parse script.</exception>
|
|
/// <exception cref="RuntimeException">See Pipeline.Invoke.</exception>
|
|
/// <exception cref="ParameterBindingException">See Pipeline.Invoke.</exception>
|
|
protected override void EndProcessing()
|
|
{
|
|
switch (ParameterSetName)
|
|
{
|
|
case ForEachObjectCommand.ScriptBlockSet:
|
|
EndBlockParameterSet();
|
|
break;
|
|
|
|
case ForEachObjectCommand.ParallelParameterSet:
|
|
EndParallelParameterSet();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle pipeline stop signal.
|
|
/// </summary>
|
|
protected override void StopProcessing()
|
|
{
|
|
switch (ParameterSetName)
|
|
{
|
|
case ForEachObjectCommand.ParallelParameterSet:
|
|
StopParallelProcessing();
|
|
break;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IDisposable
|
|
|
|
/// <summary>
|
|
/// Dispose cmdlet instance.
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
// Ensure all parallel task objects are disposed
|
|
_taskTimer?.Dispose();
|
|
_taskDataStreamWriter?.Dispose();
|
|
_taskPool?.Dispose();
|
|
_taskCollection?.Dispose();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Methods
|
|
|
|
#region PSTasks
|
|
|
|
private PSTaskPool _taskPool;
|
|
private PSTaskDataStreamWriter _taskDataStreamWriter;
|
|
private Dictionary<string, object> _usingValuesMap;
|
|
private Timer _taskTimer;
|
|
private PSTaskJob _taskJob;
|
|
private PSDataCollection<System.Management.Automation.PSTasks.PSTask> _taskCollection;
|
|
private Exception _taskCollectionException;
|
|
private string _currentLocationPath;
|
|
|
|
// List of Foreach-Object command names and aliases.
|
|
// TODO: Look into using SessionState.Internal.GetAliasTable() to find all user created aliases.
|
|
// But update Alias command logic to maintain reverse table that lists all aliases mapping
|
|
// to a single command definition, for performance.
|
|
private static readonly string[] forEachNames = new string[]
|
|
{
|
|
"ForEach-Object",
|
|
"foreach",
|
|
"%"
|
|
};
|
|
|
|
private void InitParallelParameterSet()
|
|
{
|
|
// The following common parameters are not (yet) supported in this parameter set.
|
|
// ErrorAction, WarningAction, InformationAction, PipelineVariable.
|
|
if (MyInvocation.BoundParameters.ContainsKey(nameof(CommonParamSet.ErrorAction)) ||
|
|
MyInvocation.BoundParameters.ContainsKey(nameof(CommonParamSet.WarningAction)) ||
|
|
MyInvocation.BoundParameters.ContainsKey(nameof(CommonParamSet.InformationAction)) ||
|
|
MyInvocation.BoundParameters.ContainsKey(nameof(CommonParamSet.PipelineVariable)))
|
|
{
|
|
ThrowTerminatingError(
|
|
new ErrorRecord(
|
|
new PSNotSupportedException(InternalCommandStrings.ParallelCommonParametersNotSupported),
|
|
"ParallelCommonParametersNotSupported",
|
|
ErrorCategory.NotImplemented,
|
|
this));
|
|
}
|
|
|
|
// Get the current working directory location, if available.
|
|
try
|
|
{
|
|
_currentLocationPath = SessionState.Internal.CurrentLocation.Path;
|
|
}
|
|
catch (PSInvalidOperationException)
|
|
{
|
|
}
|
|
|
|
var allowUsingExpression = this.Context.SessionState.LanguageMode != PSLanguageMode.NoLanguage;
|
|
_usingValuesMap = ScriptBlockToPowerShellConverter.GetUsingValuesForEachParallel(
|
|
scriptBlock: Parallel,
|
|
isTrustedInput: allowUsingExpression,
|
|
context: this.Context,
|
|
foreachNames: forEachNames);
|
|
|
|
// Validate using values map, which is a map of '$using:' variables referenced in the script.
|
|
// Script block variables are not allowed since their behavior is undefined outside the runspace
|
|
// in which they were created.
|
|
foreach (object item in _usingValuesMap.Values)
|
|
{
|
|
if (item is ScriptBlock)
|
|
{
|
|
ThrowTerminatingError(
|
|
new ErrorRecord(
|
|
new PSArgumentException(InternalCommandStrings.ParallelUsingVariableCannotBeScriptBlock),
|
|
"ParallelUsingVariableCannotBeScriptBlock",
|
|
ErrorCategory.InvalidType,
|
|
this));
|
|
}
|
|
}
|
|
|
|
if (AsJob)
|
|
{
|
|
// Set up for returning a job object.
|
|
if (MyInvocation.BoundParameters.ContainsKey(nameof(TimeoutSeconds)))
|
|
{
|
|
ThrowTerminatingError(
|
|
new ErrorRecord(
|
|
new PSArgumentException(InternalCommandStrings.ParallelCannotUseTimeoutWithJob),
|
|
"ParallelCannotUseTimeoutWithJob",
|
|
ErrorCategory.InvalidOperation,
|
|
this));
|
|
}
|
|
|
|
_taskJob = new PSTaskJob(
|
|
Parallel.ToString(),
|
|
ThrottleLimit,
|
|
UseNewRunspace);
|
|
|
|
return;
|
|
}
|
|
|
|
// Set up for synchronous processing and data streaming.
|
|
_taskCollection = new PSDataCollection<System.Management.Automation.PSTasks.PSTask>();
|
|
_taskDataStreamWriter = new PSTaskDataStreamWriter(this);
|
|
_taskPool = new PSTaskPool(ThrottleLimit, UseNewRunspace);
|
|
_taskPool.PoolComplete += (sender, args) => _taskDataStreamWriter.Close();
|
|
|
|
// Create timeout timer if requested.
|
|
if (TimeoutSeconds != 0)
|
|
{
|
|
_taskTimer = new Timer(
|
|
callback: (_) => { _taskCollection.Complete(); _taskPool.StopAll(); },
|
|
state: null,
|
|
dueTime: TimeoutSeconds * 1000,
|
|
period: Timeout.Infinite);
|
|
}
|
|
|
|
// Task collection handler.
|
|
System.Threading.ThreadPool.QueueUserWorkItem(
|
|
(_) =>
|
|
{
|
|
// As piped input are converted to PSTasks and added to the _taskCollection,
|
|
// transfer the task to the _taskPool on this dedicated thread.
|
|
// The _taskPool will block this thread when it is full, and allow more tasks to
|
|
// be added only when a currently running task completes and makes space in the pool.
|
|
// Continue adding any tasks appearing in _taskCollection until the collection is closed.
|
|
while (true)
|
|
{
|
|
// This handle will unblock the thread when a new task is available or the _taskCollection
|
|
// is closed.
|
|
_taskCollection.WaitHandle.WaitOne();
|
|
|
|
// Task collection open state is volatile.
|
|
// Record current task collection open state here, to be checked after processing.
|
|
bool isOpen = _taskCollection.IsOpen;
|
|
|
|
try
|
|
{
|
|
// Read all tasks in the collection.
|
|
foreach (var task in _taskCollection.ReadAll())
|
|
{
|
|
// This _taskPool method will block if the pool is full and will unblock
|
|
// only after a task completes making more space.
|
|
_taskPool.Add(task);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_taskCollection.Complete();
|
|
_taskCollectionException = ex;
|
|
_taskDataStreamWriter.Close();
|
|
|
|
break;
|
|
}
|
|
|
|
// Loop is exited only when task collection is closed and all task
|
|
// collection tasks are processed.
|
|
if (!isOpen)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// We are done adding tasks and can close the task pool.
|
|
_taskPool.Close();
|
|
});
|
|
}
|
|
|
|
private void ProcessParallelParameterSet()
|
|
{
|
|
// Validate piped InputObject
|
|
if (_inputObject != null &&
|
|
_inputObject.BaseObject is ScriptBlock)
|
|
{
|
|
WriteError(
|
|
new ErrorRecord(
|
|
new PSArgumentException(InternalCommandStrings.ParallelPipedInputObjectCannotBeScriptBlock),
|
|
"ParallelPipedInputObjectCannotBeScriptBlock",
|
|
ErrorCategory.InvalidType,
|
|
this));
|
|
|
|
return;
|
|
}
|
|
|
|
if (AsJob)
|
|
{
|
|
// Add child task job.
|
|
var taskChildJob = new PSTaskChildJob(
|
|
Parallel,
|
|
_usingValuesMap,
|
|
InputObject,
|
|
_currentLocationPath);
|
|
|
|
_taskJob.AddJob(taskChildJob);
|
|
|
|
return;
|
|
}
|
|
|
|
// Write any streaming data
|
|
_taskDataStreamWriter.WriteImmediate();
|
|
|
|
// Add to task collection for processing.
|
|
if (_taskCollection.IsOpen)
|
|
{
|
|
try
|
|
{
|
|
// Create a PSTask based on this piped input and add it to the task collection.
|
|
// A dedicated thread will add it to the PSTask pool in a performant manner.
|
|
_taskCollection.Add(
|
|
new System.Management.Automation.PSTasks.PSTask(
|
|
Parallel,
|
|
_usingValuesMap,
|
|
InputObject,
|
|
_currentLocationPath,
|
|
_taskDataStreamWriter));
|
|
}
|
|
catch (InvalidOperationException)
|
|
{
|
|
// This exception is thrown if the task collection is closed, which should not happen.
|
|
Dbg.Assert(false, "Should not add to a closed PSTask collection");
|
|
}
|
|
}
|
|
}
|
|
|
|
private void EndParallelParameterSet()
|
|
{
|
|
if (AsJob)
|
|
{
|
|
// Start and return parent job object.
|
|
_taskJob.Start();
|
|
JobRepository.Add(_taskJob);
|
|
WriteObject(_taskJob);
|
|
|
|
return;
|
|
}
|
|
|
|
// Close task collection and wait for processing to complete while streaming data.
|
|
_taskDataStreamWriter.WriteImmediate();
|
|
_taskCollection.Complete();
|
|
_taskDataStreamWriter.WaitAndWrite();
|
|
|
|
// Check for an unexpected error from the _taskCollection handler thread and report here.
|
|
var ex = _taskCollectionException;
|
|
if (ex != null)
|
|
{
|
|
var msg = string.Format(CultureInfo.InvariantCulture, InternalCommandStrings.ParallelPipedInputProcessingError, ex);
|
|
WriteError(
|
|
new ErrorRecord(
|
|
exception: new InvalidOperationException(msg),
|
|
errorId: "ParallelPipedInputProcessingError",
|
|
errorCategory: ErrorCategory.InvalidOperation,
|
|
targetObject: this));
|
|
}
|
|
}
|
|
|
|
private void StopParallelProcessing()
|
|
{
|
|
_taskCollection?.Complete();
|
|
_taskPool?.StopAll();
|
|
}
|
|
|
|
#endregion
|
|
|
|
private void EndBlockParameterSet()
|
|
{
|
|
if (_endScript == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var emptyArray = Array.Empty<object>();
|
|
_endScript.InvokeUsingCmdlet(
|
|
contextCmdlet: this,
|
|
useLocalScope: false,
|
|
errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe,
|
|
dollarUnder: AutomationNull.Value,
|
|
input: emptyArray,
|
|
scriptThis: AutomationNull.Value,
|
|
args: emptyArray);
|
|
}
|
|
|
|
private void ProcessPropertyAndMethodParameterSet()
|
|
{
|
|
_targetString = string.Format(CultureInfo.InvariantCulture, InternalCommandStrings.ForEachObjectTarget, GetStringRepresentation(InputObject));
|
|
|
|
if (LanguagePrimitives.IsNull(InputObject))
|
|
{
|
|
if (_arguments != null && _arguments.Length > 0)
|
|
{
|
|
WriteError(GenerateNameParameterError("InputObject", ParserStrings.InvokeMethodOnNull,
|
|
"InvokeMethodOnNull", _inputObject));
|
|
}
|
|
else
|
|
{
|
|
// should process
|
|
string propertyAction = string.Format(CultureInfo.InvariantCulture,
|
|
InternalCommandStrings.ForEachObjectPropertyAction, _propertyOrMethodName);
|
|
|
|
if (ShouldProcess(_targetString, propertyAction))
|
|
{
|
|
if (Context.IsStrictVersion(2))
|
|
{
|
|
WriteError(GenerateNameParameterError("InputObject", InternalCommandStrings.InputObjectIsNull,
|
|
"InputObjectIsNull", _inputObject));
|
|
}
|
|
else
|
|
{
|
|
// we write null out because:
|
|
// PS C:\> $null | ForEach-object {$_.aa} | ForEach-Object {$_ + 3}
|
|
// 3
|
|
// so we also want
|
|
// PS C:\> $null | ForEach-object aa | ForEach-Object {$_ + 3}
|
|
// 3
|
|
// But if we don't write anything to the pipeline when _inputObject is null,
|
|
// the result 3 will not be generated.
|
|
WriteObject(null);
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
ErrorRecord errorRecord = null;
|
|
|
|
// if args exist, this is explicitly a method invocation
|
|
if (_arguments != null && _arguments.Length > 0)
|
|
{
|
|
MethodCallWithArguments();
|
|
}
|
|
// no arg provided
|
|
else
|
|
{
|
|
// if inputObject is of IDictionary, get the value
|
|
if (GetValueFromIDictionaryInput()) { return; }
|
|
|
|
PSMemberInfo member = null;
|
|
if (WildcardPattern.ContainsWildcardCharacters(_propertyOrMethodName))
|
|
{
|
|
// get the matched member(s)
|
|
ReadOnlyPSMemberInfoCollection<PSMemberInfo> members =
|
|
_inputObject.Members.Match(_propertyOrMethodName, PSMemberTypes.All);
|
|
Dbg.Assert(members != null, "The return value of Members.Match should never be null");
|
|
|
|
if (members.Count > 1)
|
|
{
|
|
// write error record: property method ambiguous
|
|
StringBuilder possibleMatches = new StringBuilder();
|
|
foreach (PSMemberInfo item in members)
|
|
{
|
|
possibleMatches.AppendFormat(CultureInfo.InvariantCulture, " {0}", item.Name);
|
|
}
|
|
|
|
WriteError(GenerateNameParameterError("Name", InternalCommandStrings.AmbiguousPropertyOrMethodName,
|
|
"AmbiguousPropertyOrMethodName", _inputObject,
|
|
_propertyOrMethodName, possibleMatches));
|
|
return;
|
|
}
|
|
|
|
if (members.Count == 1)
|
|
{
|
|
member = members[0];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
member = _inputObject.Members[_propertyOrMethodName];
|
|
}
|
|
|
|
// member is a method
|
|
if (member is PSMethodInfo)
|
|
{
|
|
// first we check if the member is a ParameterizedProperty
|
|
PSParameterizedProperty targetParameterizedProperty = member as PSParameterizedProperty;
|
|
if (targetParameterizedProperty != null)
|
|
{
|
|
// should process
|
|
string propertyAction = string.Format(CultureInfo.InvariantCulture,
|
|
InternalCommandStrings.ForEachObjectPropertyAction, targetParameterizedProperty.Name);
|
|
|
|
// ParameterizedProperty always take parameters, so we output the member.Value directly
|
|
if (ShouldProcess(_targetString, propertyAction))
|
|
{
|
|
WriteObject(member.Value);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
PSMethodInfo targetMethod = member as PSMethodInfo;
|
|
Dbg.Assert(targetMethod != null, "targetMethod should not be null here.");
|
|
try
|
|
{
|
|
// should process
|
|
string methodAction = string.Format(CultureInfo.InvariantCulture,
|
|
InternalCommandStrings.ForEachObjectMethodActionWithoutArguments, targetMethod.Name);
|
|
|
|
if (ShouldProcess(_targetString, methodAction))
|
|
{
|
|
if (!BlockMethodInLanguageMode(InputObject))
|
|
{
|
|
object result = targetMethod.Invoke(Array.Empty<object>());
|
|
WriteToPipelineWithUnrolling(result);
|
|
}
|
|
}
|
|
}
|
|
catch (PipelineStoppedException)
|
|
{
|
|
// PipelineStoppedException can be caused by select-object
|
|
throw;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
MethodException mex = ex as MethodException;
|
|
if (mex != null && mex.ErrorRecord != null && mex.ErrorRecord.FullyQualifiedErrorId == "MethodCountCouldNotFindBest")
|
|
{
|
|
WriteObject(targetMethod.Value);
|
|
}
|
|
else
|
|
{
|
|
WriteError(new ErrorRecord(ex, "MethodInvocationError", ErrorCategory.InvalidOperation, _inputObject));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
string resolvedPropertyName = null;
|
|
bool isBlindDynamicAccess = false;
|
|
if (member == null)
|
|
{
|
|
if ((_inputObject.BaseObject is IDynamicMetaObjectProvider) &&
|
|
!WildcardPattern.ContainsWildcardCharacters(_propertyOrMethodName))
|
|
{
|
|
// Let's just try a dynamic property access. Note that if it
|
|
// comes to depending on dynamic access, we are assuming it is a
|
|
// property; we don't have ETS info to tell us up front if it
|
|
// even exists or not, let alone if it is a method or something
|
|
// else.
|
|
//
|
|
// Note that this is "truly blind"--the name did not show up in
|
|
// GetDynamicMemberNames(), else it would show up as a dynamic
|
|
// member.
|
|
|
|
resolvedPropertyName = _propertyOrMethodName;
|
|
isBlindDynamicAccess = true;
|
|
}
|
|
else
|
|
{
|
|
errorRecord = GenerateNameParameterError("Name", InternalCommandStrings.PropertyOrMethodNotFound,
|
|
"PropertyOrMethodNotFound", _inputObject,
|
|
_propertyOrMethodName);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// member is [presumably] a property (note that it could be a
|
|
// dynamic property, if it shows up in GetDynamicMemberNames())
|
|
resolvedPropertyName = member.Name;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(resolvedPropertyName))
|
|
{
|
|
// should process
|
|
string propertyAction = string.Format(CultureInfo.InvariantCulture,
|
|
InternalCommandStrings.ForEachObjectPropertyAction, resolvedPropertyName);
|
|
|
|
if (ShouldProcess(_targetString, propertyAction))
|
|
{
|
|
try
|
|
{
|
|
WriteToPipelineWithUnrolling(_propGetter.GetValue(InputObject, resolvedPropertyName));
|
|
}
|
|
catch (TerminateException) // The debugger is terminating the execution
|
|
{
|
|
throw;
|
|
}
|
|
catch (MethodException)
|
|
{
|
|
throw;
|
|
}
|
|
catch (PipelineStoppedException)
|
|
{
|
|
// PipelineStoppedException can be caused by select-object
|
|
throw;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// For normal property accesses, we do not generate an error
|
|
// here. The problem for truly blind dynamic accesses (the
|
|
// member did not show up in GetDynamicMemberNames) is that
|
|
// we can't tell the difference between "it failed because
|
|
// the property does not exist" (let's call this case 1) and
|
|
// "it failed because accessing it actually threw some
|
|
// exception" (let's call that case 2).
|
|
//
|
|
// PowerShell behavior for normal (non-dynamic) properties
|
|
// is different for these two cases: case 1 gets an error
|
|
// (which is possible because the ETS tells us up front if
|
|
// the property exists or not), and case 2 does not. (For
|
|
// normal properties, this catch block /is/ case 2.)
|
|
//
|
|
// For IDMOPs, we have the chance to attempt a "blind"
|
|
// access, but the cost is that we must have the same
|
|
// response to both cases (because we cannot distinguish
|
|
// between the two). So we have to make a choice: we can
|
|
// either swallow ALL errors (including "The property
|
|
// 'Blarg' does not exist"), or expose them all.
|
|
//
|
|
// Here, for truly blind dynamic access, we choose to
|
|
// preserve the behavior of showing "The property 'Blarg'
|
|
// does not exist" (case 1) errors than to suppress
|
|
// "FooException thrown when accessing Bloop property" (case
|
|
// 2) errors.
|
|
|
|
if (isBlindDynamicAccess)
|
|
{
|
|
errorRecord = new ErrorRecord(ex,
|
|
"DynamicPropertyAccessFailed_" + _propertyOrMethodName,
|
|
ErrorCategory.InvalidOperation,
|
|
InputObject);
|
|
}
|
|
else
|
|
{
|
|
// When the property is not gettable or it throws an exception.
|
|
// e.g. when trying to access an assembly's location property, since dynamic assemblies are not backed up by a file,
|
|
// an exception will be thrown when accessing its location property. In this case, return null.
|
|
WriteObject(null);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (errorRecord != null)
|
|
{
|
|
string propertyAction = string.Format(CultureInfo.InvariantCulture,
|
|
InternalCommandStrings.ForEachObjectPropertyAction, _propertyOrMethodName);
|
|
|
|
if (ShouldProcess(_targetString, propertyAction))
|
|
{
|
|
if (Context.IsStrictVersion(2))
|
|
{
|
|
WriteError(errorRecord);
|
|
}
|
|
else
|
|
{
|
|
// we write null out because:
|
|
// PS C:\> "string" | ForEach-Object {$_.aa} | ForEach-Object {$_ + 3}
|
|
// 3
|
|
// so we also want
|
|
// PS C:\> "string" | ForEach-Object aa | ForEach-Object {$_ + 3}
|
|
// 3
|
|
// But if we don't write anything to the pipeline when no member is found,
|
|
// the result 3 will not be generated.
|
|
WriteObject(null);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ProcessScriptBlockParameterSet()
|
|
{
|
|
for (int i = _start; i < _end; i++)
|
|
{
|
|
// Only execute scripts that aren't null. This isn't treated as an error
|
|
// because it allows you to parameterize a command - for example you might allow
|
|
// for actions before and after the main processing script. They could be null
|
|
// by default and therefore ignored then filled in later...
|
|
if (_scripts[i] != null)
|
|
{
|
|
_scripts[i].InvokeUsingCmdlet(
|
|
contextCmdlet: this,
|
|
useLocalScope: false,
|
|
errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe,
|
|
dollarUnder: InputObject,
|
|
input: new object[] { InputObject },
|
|
scriptThis: AutomationNull.Value,
|
|
args: Array.Empty<object>());
|
|
}
|
|
}
|
|
}
|
|
|
|
private void InitScriptBlockParameterSet()
|
|
{
|
|
// Win8: 176403: ScriptCmdlets sets the global WhatIf and Confirm preferences
|
|
// This effects the new W8 foreach-object cmdlet with -whatif and -confirm
|
|
// implemented. -whatif and -confirm needed only for PropertyAndMethodSet
|
|
// parameter set. So erring out in cases where these are used with ScriptBlockSet.
|
|
// Not using MshCommandRuntime, as those variables will be affected by ScriptCmdlet
|
|
// infrastructure (wherein ScriptCmdlet modifies the global preferences).
|
|
Dictionary<string, object> psBoundParameters = this.MyInvocation.BoundParameters;
|
|
if (psBoundParameters != null)
|
|
{
|
|
SwitchParameter whatIf = false;
|
|
SwitchParameter confirm = false;
|
|
|
|
object argument;
|
|
if (psBoundParameters.TryGetValue("whatif", out argument))
|
|
{
|
|
whatIf = (SwitchParameter)argument;
|
|
}
|
|
|
|
if (psBoundParameters.TryGetValue("confirm", out argument))
|
|
{
|
|
confirm = (SwitchParameter)argument;
|
|
}
|
|
|
|
if (whatIf || confirm)
|
|
{
|
|
string message = InternalCommandStrings.NoShouldProcessForScriptBlockSet;
|
|
|
|
ErrorRecord errorRecord = new ErrorRecord(
|
|
new InvalidOperationException(message),
|
|
"NoShouldProcessForScriptBlockSet",
|
|
ErrorCategory.InvalidOperation,
|
|
null);
|
|
ThrowTerminatingError(errorRecord);
|
|
}
|
|
}
|
|
|
|
// Calculate the start and end indexes for the processRecord script blocks
|
|
_end = _scripts.Count;
|
|
_start = _scripts.Count > 1 ? 1 : 0;
|
|
|
|
// and set the end script if it wasn't explicitly set with a named parameter.
|
|
if (!_setEndScript)
|
|
{
|
|
if (_scripts.Count > 2)
|
|
{
|
|
_end = _scripts.Count - 1;
|
|
_endScript = _scripts[_end];
|
|
}
|
|
}
|
|
|
|
// only process the start script if there is more than one script...
|
|
if (_end < 2)
|
|
return;
|
|
|
|
if (_scripts[0] == null)
|
|
return;
|
|
|
|
var emptyArray = Array.Empty<object>();
|
|
_scripts[0].InvokeUsingCmdlet(
|
|
contextCmdlet: this,
|
|
useLocalScope: false,
|
|
errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe,
|
|
dollarUnder: AutomationNull.Value,
|
|
input: emptyArray,
|
|
scriptThis: AutomationNull.Value,
|
|
args: emptyArray);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Do method invocation with arguments.
|
|
/// </summary>
|
|
private void MethodCallWithArguments()
|
|
{
|
|
// resolve the name
|
|
ReadOnlyPSMemberInfoCollection<PSMemberInfo> methods =
|
|
_inputObject.Members.Match(
|
|
_propertyOrMethodName,
|
|
PSMemberTypes.Methods | PSMemberTypes.ParameterizedProperty);
|
|
|
|
Dbg.Assert(methods != null, "The return value of Members.Match should never be null.");
|
|
if (methods.Count > 1)
|
|
{
|
|
// write error record: method ambiguous
|
|
StringBuilder possibleMatches = new StringBuilder();
|
|
foreach (PSMemberInfo item in methods)
|
|
{
|
|
possibleMatches.AppendFormat(CultureInfo.InvariantCulture, " {0}", item.Name);
|
|
}
|
|
|
|
WriteError(GenerateNameParameterError(
|
|
"Name",
|
|
InternalCommandStrings.AmbiguousMethodName,
|
|
"AmbiguousMethodName",
|
|
_inputObject,
|
|
_propertyOrMethodName,
|
|
possibleMatches));
|
|
}
|
|
else if (methods.Count == 0 || methods[0] is not PSMethodInfo)
|
|
{
|
|
// write error record: method no found
|
|
WriteError(GenerateNameParameterError(
|
|
"Name",
|
|
InternalCommandStrings.MethodNotFound,
|
|
"MethodNotFound",
|
|
_inputObject,
|
|
_propertyOrMethodName));
|
|
}
|
|
else
|
|
{
|
|
PSMethodInfo targetMethod = methods[0] as PSMethodInfo;
|
|
Dbg.Assert(targetMethod != null, "targetMethod should not be null here.");
|
|
|
|
// should process
|
|
StringBuilder arglist = new StringBuilder(GetStringRepresentation(_arguments[0]));
|
|
for (int i = 1; i < _arguments.Length; i++)
|
|
{
|
|
arglist.AppendFormat(CultureInfo.InvariantCulture, ", {0}", GetStringRepresentation(_arguments[i]));
|
|
}
|
|
|
|
string methodAction = string.Format(CultureInfo.InvariantCulture,
|
|
InternalCommandStrings.ForEachObjectMethodActionWithArguments,
|
|
targetMethod.Name, arglist);
|
|
|
|
try
|
|
{
|
|
if (ShouldProcess(_targetString, methodAction))
|
|
{
|
|
if (!BlockMethodInLanguageMode(InputObject))
|
|
{
|
|
object result = targetMethod.Invoke(_arguments);
|
|
WriteToPipelineWithUnrolling(result);
|
|
}
|
|
}
|
|
}
|
|
catch (PipelineStoppedException)
|
|
{
|
|
// PipelineStoppedException can be caused by select-object
|
|
throw;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
WriteError(new ErrorRecord(ex, "MethodInvocationError", ErrorCategory.InvalidOperation, _inputObject));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the string representation of the passed-in object.
|
|
/// </summary>
|
|
/// <param name="obj">Source object.</param>
|
|
/// <returns>String representation of the source object.</returns>
|
|
private static string GetStringRepresentation(object obj)
|
|
{
|
|
string objInString;
|
|
try
|
|
{
|
|
// The "ToString()" method could throw an exception
|
|
objInString = LanguagePrimitives.IsNull(obj) ? "null" : obj.ToString();
|
|
}
|
|
catch (Exception)
|
|
{
|
|
objInString = null;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(objInString))
|
|
{
|
|
var psobj = obj as PSObject;
|
|
objInString = psobj != null ? psobj.BaseObject.GetType().FullName : obj.GetType().FullName;
|
|
}
|
|
|
|
return objInString;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the value by taking _propertyOrMethodName as the key, if the
|
|
/// input object is a IDictionary.
|
|
/// </summary>
|
|
/// <returns>True if success.</returns>
|
|
private bool GetValueFromIDictionaryInput()
|
|
{
|
|
object target = PSObject.Base(_inputObject);
|
|
IDictionary hash = target as IDictionary;
|
|
|
|
try
|
|
{
|
|
if (hash != null && hash.Contains(_propertyOrMethodName))
|
|
{
|
|
string keyAction = string.Format(
|
|
CultureInfo.InvariantCulture,
|
|
InternalCommandStrings.ForEachObjectKeyAction,
|
|
_propertyOrMethodName);
|
|
if (ShouldProcess(_targetString, keyAction))
|
|
{
|
|
object result = hash[_propertyOrMethodName];
|
|
WriteToPipelineWithUnrolling(result);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
catch (InvalidOperationException)
|
|
{
|
|
// Ignore invalid operation exception, it can happen if the dictionary
|
|
// has keys that can't be compared to property.
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unroll the object to be output. If it's of type IEnumerator, unroll and output it
|
|
/// by calling WriteOutIEnumerator. If it's not, unroll and output it by calling WriteObject(obj, true)
|
|
/// </summary>
|
|
/// <param name="obj">Source object.</param>
|
|
private void WriteToPipelineWithUnrolling(object obj)
|
|
{
|
|
IEnumerator objAsEnumerator = LanguagePrimitives.GetEnumerator(obj);
|
|
if (objAsEnumerator != null)
|
|
{
|
|
WriteOutIEnumerator(objAsEnumerator);
|
|
}
|
|
else
|
|
{
|
|
WriteObject(obj, true);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unroll an IEnumerator and output all entries.
|
|
/// </summary>
|
|
/// <param name="list">Source list.</param>
|
|
private void WriteOutIEnumerator(IEnumerator list)
|
|
{
|
|
if (list != null)
|
|
{
|
|
while (ParserOps.MoveNext(this.Context, null, list))
|
|
{
|
|
object val = ParserOps.Current(null, list);
|
|
|
|
if (val != AutomationNull.Value)
|
|
{
|
|
WriteObject(val);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if the language mode is the restrictedLanguageMode before invoking a method.
|
|
/// Write out error message and return true if we are in restrictedLanguageMode.
|
|
/// </summary>
|
|
/// <param name="inputObject">Source object.</param>
|
|
/// <returns>True if we are in restrictedLanguageMode.</returns>
|
|
private bool BlockMethodInLanguageMode(object inputObject)
|
|
{
|
|
// Cannot invoke a method in RestrictedLanguage mode
|
|
if (Context.LanguageMode == PSLanguageMode.RestrictedLanguage)
|
|
{
|
|
PSInvalidOperationException exception =
|
|
new PSInvalidOperationException(InternalCommandStrings.NoMethodInvocationInRestrictedLanguageMode);
|
|
|
|
WriteError(new ErrorRecord(exception, "NoMethodInvocationInRestrictedLanguageMode", ErrorCategory.InvalidOperation, null));
|
|
return true;
|
|
}
|
|
|
|
// Cannot invoke certain methods in ConstrainedLanguage mode
|
|
if (Context.LanguageMode == PSLanguageMode.ConstrainedLanguage)
|
|
{
|
|
object baseObject = PSObject.Base(inputObject);
|
|
|
|
if (!CoreTypes.Contains(baseObject.GetType()))
|
|
{
|
|
PSInvalidOperationException exception =
|
|
new PSInvalidOperationException(ParserStrings.InvokeMethodConstrainedLanguage);
|
|
|
|
WriteError(new ErrorRecord(exception, "MethodInvocationNotSupportedInConstrainedLanguage", ErrorCategory.InvalidOperation, null));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Generate the appropriate error record.
|
|
/// </summary>
|
|
/// <param name="paraName"></param>
|
|
/// <param name="resourceString"></param>
|
|
/// <param name="errorId"></param>
|
|
/// <param name="target"></param>
|
|
/// <param name="args"></param>
|
|
/// <returns></returns>
|
|
internal static ErrorRecord GenerateNameParameterError(string paraName, string resourceString, string errorId, object target, params object[] args)
|
|
{
|
|
string message;
|
|
if (args == null || args.Length == 0)
|
|
{
|
|
// Don't format in case the string contains literal curly braces
|
|
message = resourceString;
|
|
}
|
|
else
|
|
{
|
|
message = StringUtil.Format(resourceString, args);
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(message))
|
|
{
|
|
Dbg.Assert(false, "Could not load text for error record '" + errorId + "'");
|
|
}
|
|
|
|
ErrorRecord errorRecord = new ErrorRecord(
|
|
new PSArgumentException(message, paraName),
|
|
errorId,
|
|
ErrorCategory.InvalidArgument,
|
|
target);
|
|
|
|
return errorRecord;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Implements a cmdlet that applys a script block
|
|
/// to each element of the pipeline. If the result of that
|
|
/// application is true, then the current pipeline object
|
|
/// is passed on, otherwise it is dropped.
|
|
/// </summary>
|
|
[Cmdlet("Where", "Object", DefaultParameterSetName = "EqualSet",
|
|
HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096806", RemotingCapability = RemotingCapability.None)]
|
|
public sealed class WhereObjectCommand : PSCmdlet
|
|
{
|
|
/// <summary>
|
|
/// Gets or sets the current pipeline object.
|
|
/// </summary>
|
|
[Parameter(ValueFromPipeline = true)]
|
|
public PSObject InputObject
|
|
{
|
|
get
|
|
{
|
|
return _inputObject;
|
|
}
|
|
|
|
set
|
|
{
|
|
_inputObject = value;
|
|
}
|
|
}
|
|
|
|
private PSObject _inputObject = AutomationNull.Value;
|
|
|
|
private ScriptBlock _script;
|
|
/// <summary>
|
|
/// Gets or sets the script block to apply.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "ScriptBlockSet")]
|
|
public ScriptBlock FilterScript
|
|
{
|
|
get
|
|
{
|
|
return _script;
|
|
}
|
|
|
|
set
|
|
{
|
|
_script = value;
|
|
}
|
|
}
|
|
|
|
private string _property;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the property to retrieve value.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "EqualSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "CaseSensitiveEqualSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "NotEqualSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "CaseSensitiveNotEqualSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "GreaterThanSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "CaseSensitiveGreaterThanSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "LessThanSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "CaseSensitiveLessThanSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "GreaterOrEqualSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "CaseSensitiveGreaterOrEqualSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "LessOrEqualSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "CaseSensitiveLessOrEqualSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "LikeSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "CaseSensitiveLikeSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "NotLikeSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "CaseSensitiveNotLikeSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "MatchSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "CaseSensitiveMatchSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "NotMatchSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "CaseSensitiveNotMatchSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "ContainsSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "CaseSensitiveContainsSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "NotContainsSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "CaseSensitiveNotContainsSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "InSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "CaseSensitiveInSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "NotInSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "CaseSensitiveNotInSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "IsSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "IsNotSet")]
|
|
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "Not")]
|
|
[ValidateNotNullOrEmpty]
|
|
public string Property
|
|
{
|
|
get
|
|
{
|
|
return _property;
|
|
}
|
|
|
|
set
|
|
{
|
|
_property = value;
|
|
}
|
|
}
|
|
|
|
private object _convertedValue;
|
|
private object _value = true;
|
|
private bool _valueNotSpecified = true;
|
|
|
|
/// <summary>
|
|
/// The value to compare against.
|
|
/// </summary>
|
|
[Parameter(Position = 1, ParameterSetName = "EqualSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "CaseSensitiveEqualSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "NotEqualSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "CaseSensitiveNotEqualSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "GreaterThanSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "CaseSensitiveGreaterThanSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "LessThanSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "CaseSensitiveLessThanSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "GreaterOrEqualSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "CaseSensitiveGreaterOrEqualSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "LessOrEqualSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "CaseSensitiveLessOrEqualSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "LikeSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "CaseSensitiveLikeSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "NotLikeSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "CaseSensitiveNotLikeSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "MatchSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "CaseSensitiveMatchSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "NotMatchSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "CaseSensitiveNotMatchSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "ContainsSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "CaseSensitiveContainsSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "NotContainsSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "CaseSensitiveNotContainsSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "InSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "CaseSensitiveInSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "NotInSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "CaseSensitiveNotInSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "IsSet")]
|
|
[Parameter(Position = 1, ParameterSetName = "IsNotSet")]
|
|
public object Value
|
|
{
|
|
get
|
|
{
|
|
return _value;
|
|
}
|
|
|
|
set
|
|
{
|
|
_value = value;
|
|
_valueNotSpecified = false;
|
|
}
|
|
}
|
|
|
|
#region binary operator parameters
|
|
|
|
private TokenKind _binaryOperator = TokenKind.Ieq;
|
|
|
|
// set to false if the user specified "-EQ" in the command line.
|
|
// remain to be true if "EqualSet" is chosen by default.
|
|
private bool _forceBooleanEvaluation = true;
|
|
|
|
/// <summary>
|
|
/// Gets or sets binary operator -Equal
|
|
/// It's the default parameter set, so -EQ is not mandatory.
|
|
/// </summary>
|
|
[Parameter(ParameterSetName = "EqualSet")]
|
|
[Alias("IEQ")]
|
|
public SwitchParameter EQ
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Ieq;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Ieq;
|
|
_forceBooleanEvaluation = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets case sensitive binary operator -ceq.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveEqualSet")]
|
|
public SwitchParameter CEQ
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Ceq;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Ceq;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets binary operator -NotEqual.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "NotEqualSet")]
|
|
[Alias("INE")]
|
|
public SwitchParameter NE
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Ine;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Ine;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets case sensitive binary operator -cne.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveNotEqualSet")]
|
|
public SwitchParameter CNE
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Cne;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Cne;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets binary operator -GreaterThan.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "GreaterThanSet")]
|
|
[Alias("IGT")]
|
|
public SwitchParameter GT
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Igt;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Igt;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets case sensitive binary operator -cgt.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveGreaterThanSet")]
|
|
public SwitchParameter CGT
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Cgt;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Cgt;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets binary operator -LessThan.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "LessThanSet")]
|
|
[Alias("ILT")]
|
|
public SwitchParameter LT
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Ilt;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Ilt;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets -sets case sensitive binary operator -clt.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveLessThanSet")]
|
|
public SwitchParameter CLT
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Clt;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Clt;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets binary operator -GreaterOrEqual.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "GreaterOrEqualSet")]
|
|
[Alias("IGE")]
|
|
public SwitchParameter GE
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Ige;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Ige;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets case sensitive binary operator -cge.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveGreaterOrEqualSet")]
|
|
public SwitchParameter CGE
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Cge;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Cge;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets binary operator -LessOrEqual.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "LessOrEqualSet")]
|
|
[Alias("ILE")]
|
|
public SwitchParameter LE
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Ile;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Ile;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets case sensitive binary operator -cle.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveLessOrEqualSet")]
|
|
public SwitchParameter CLE
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Cle;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Cle;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///Gets or sets binary operator -Like.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "LikeSet")]
|
|
[Alias("ILike")]
|
|
public SwitchParameter Like
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Ilike;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Ilike;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets case sensitive binary operator -clike.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveLikeSet")]
|
|
public SwitchParameter CLike
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Clike;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Clike;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets binary operator -NotLike.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "NotLikeSet")]
|
|
[Alias("INotLike")]
|
|
public SwitchParameter NotLike
|
|
{
|
|
get
|
|
{
|
|
return false;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Inotlike;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets case sensitive binary operator -cnotlike.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveNotLikeSet")]
|
|
public SwitchParameter CNotLike
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Cnotlike;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Cnotlike;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get or sets binary operator -Match.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "MatchSet")]
|
|
[Alias("IMatch")]
|
|
public SwitchParameter Match
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Imatch;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Imatch;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets case sensitive binary operator -cmatch.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveMatchSet")]
|
|
public SwitchParameter CMatch
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Cmatch;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Cmatch;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets binary operator -NotMatch.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "NotMatchSet")]
|
|
[Alias("INotMatch")]
|
|
public SwitchParameter NotMatch
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Inotmatch;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Inotmatch;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets case sensitive binary operator -cnotmatch.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveNotMatchSet")]
|
|
public SwitchParameter CNotMatch
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Cnotmatch;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Cnotmatch;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets binary operator -Contains.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "ContainsSet")]
|
|
[Alias("IContains")]
|
|
public SwitchParameter Contains
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Icontains;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Icontains;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets case sensitive binary operator -ccontains.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveContainsSet")]
|
|
public SwitchParameter CContains
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Ccontains;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Ccontains;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets binary operator -NotContains.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "NotContainsSet")]
|
|
[Alias("INotContains")]
|
|
public SwitchParameter NotContains
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Inotcontains;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Inotcontains;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets case sensitive binary operator -cnotcontains.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveNotContainsSet")]
|
|
public SwitchParameter CNotContains
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Cnotcontains;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Cnotcontains;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets binary operator -In.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "InSet")]
|
|
[Alias("IIn")]
|
|
public SwitchParameter In
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.In;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.In;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets case sensitive binary operator -cin.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveInSet")]
|
|
public SwitchParameter CIn
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Cin;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Cin;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets binary operator -NotIn.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "NotInSet")]
|
|
[Alias("INotIn")]
|
|
public SwitchParameter NotIn
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Inotin;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Inotin;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets case sensitive binary operator -cnotin.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveNotInSet")]
|
|
public SwitchParameter CNotIn
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Cnotin;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Cnotin;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets binary operator -Is.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "IsSet")]
|
|
public SwitchParameter Is
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Is;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Is;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets binary operator -IsNot.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "IsNotSet")]
|
|
public SwitchParameter IsNot
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.IsNot;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.IsNot;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets binary operator -Not.
|
|
/// </summary>
|
|
[Parameter(Mandatory = true, ParameterSetName = "Not")]
|
|
public SwitchParameter Not
|
|
{
|
|
get
|
|
{
|
|
return _binaryOperator == TokenKind.Not;
|
|
}
|
|
|
|
set
|
|
{
|
|
_binaryOperator = TokenKind.Not;
|
|
}
|
|
}
|
|
|
|
#endregion binary operator parameters
|
|
|
|
private readonly CallSite<Func<CallSite, object, bool>> _toBoolSite =
|
|
CallSite<Func<CallSite, object, bool>>.Create(PSConvertBinder.Get(typeof(bool)));
|
|
|
|
private Func<object, object, object> _operationDelegate;
|
|
|
|
private static Func<object, object, object> GetCallSiteDelegate(ExpressionType expressionType, bool ignoreCase)
|
|
{
|
|
var site = CallSite<Func<CallSite, object, object, object>>.Create(PSBinaryOperationBinder.Get(expressionType, ignoreCase));
|
|
return (x, y) => site.Target.Invoke(site, x, y);
|
|
}
|
|
|
|
private static Func<object, object, object> GetCallSiteDelegateBoolean(ExpressionType expressionType, bool ignoreCase)
|
|
{
|
|
// flip 'lval' and 'rval' in the scenario '... | Where-Object property' so as to make it
|
|
// equivalent to '... | Where-Object {$true -eq property}'. Because we want the property to
|
|
// be compared under the bool context. So that '"string" | Where-Object Length' would behave
|
|
// just like '"string" | Where-Object {$_.Length}'.
|
|
var site = CallSite<Func<CallSite, object, object, object>>.Create(binder: PSBinaryOperationBinder.Get(expressionType, ignoreCase));
|
|
return (x, y) => site.Target.Invoke(site, y, x);
|
|
}
|
|
|
|
private static Tuple<CallSite<Func<CallSite, object, IEnumerator>>, CallSite<Func<CallSite, object, object, object>>> GetContainsCallSites(bool ignoreCase)
|
|
{
|
|
var enumerableSite = CallSite<Func<CallSite, object, IEnumerator>>.Create(PSEnumerableBinder.Get());
|
|
var equalSite =
|
|
CallSite<Func<CallSite, object, object, object>>.Create(PSBinaryOperationBinder.Get(
|
|
ExpressionType.Equal, ignoreCase, scalarCompare: true));
|
|
|
|
return Tuple.Create(enumerableSite, equalSite);
|
|
}
|
|
|
|
private void CheckLanguageMode()
|
|
{
|
|
if (Context.LanguageMode.Equals(PSLanguageMode.RestrictedLanguage))
|
|
{
|
|
string message = string.Format(
|
|
CultureInfo.InvariantCulture,
|
|
InternalCommandStrings.OperationNotAllowedInRestrictedLanguageMode,
|
|
_binaryOperator);
|
|
PSInvalidOperationException exception =
|
|
new PSInvalidOperationException(message);
|
|
ThrowTerminatingError(new ErrorRecord(exception, "OperationNotAllowedInRestrictedLanguageMode", ErrorCategory.InvalidOperation, null));
|
|
}
|
|
}
|
|
|
|
private object GetLikeRHSOperand(object operand)
|
|
{
|
|
if (!(operand is string val))
|
|
{
|
|
return operand;
|
|
}
|
|
|
|
var wildcardOptions = _binaryOperator == TokenKind.Ilike || _binaryOperator == TokenKind.Inotlike
|
|
? WildcardOptions.IgnoreCase
|
|
: WildcardOptions.None;
|
|
return WildcardPattern.Get(val, wildcardOptions);
|
|
}
|
|
|
|
/// <summary/>
|
|
protected override void BeginProcessing()
|
|
{
|
|
if (_script != null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
switch (_binaryOperator)
|
|
{
|
|
case TokenKind.Ieq:
|
|
if (!_forceBooleanEvaluation)
|
|
{
|
|
_operationDelegate = GetCallSiteDelegate(ExpressionType.Equal, ignoreCase: true);
|
|
}
|
|
else
|
|
{
|
|
_operationDelegate = GetCallSiteDelegateBoolean(ExpressionType.Equal, ignoreCase: true);
|
|
}
|
|
|
|
break;
|
|
case TokenKind.Ceq:
|
|
_operationDelegate = GetCallSiteDelegate(ExpressionType.Equal, ignoreCase: false);
|
|
break;
|
|
case TokenKind.Ine:
|
|
_operationDelegate = GetCallSiteDelegate(ExpressionType.NotEqual, ignoreCase: true);
|
|
break;
|
|
case TokenKind.Cne:
|
|
_operationDelegate = GetCallSiteDelegate(ExpressionType.NotEqual, ignoreCase: false);
|
|
break;
|
|
case TokenKind.Igt:
|
|
_operationDelegate = GetCallSiteDelegate(ExpressionType.GreaterThan, ignoreCase: true);
|
|
break;
|
|
case TokenKind.Cgt:
|
|
_operationDelegate = GetCallSiteDelegate(ExpressionType.GreaterThan, ignoreCase: false);
|
|
break;
|
|
case TokenKind.Ilt:
|
|
_operationDelegate = GetCallSiteDelegate(ExpressionType.LessThan, ignoreCase: true);
|
|
break;
|
|
case TokenKind.Clt:
|
|
_operationDelegate = GetCallSiteDelegate(ExpressionType.LessThan, ignoreCase: false);
|
|
break;
|
|
case TokenKind.Ige:
|
|
_operationDelegate = GetCallSiteDelegate(ExpressionType.GreaterThanOrEqual, ignoreCase: true);
|
|
break;
|
|
case TokenKind.Cge:
|
|
_operationDelegate = GetCallSiteDelegate(ExpressionType.GreaterThanOrEqual, ignoreCase: false);
|
|
break;
|
|
case TokenKind.Ile:
|
|
_operationDelegate = GetCallSiteDelegate(ExpressionType.LessThanOrEqual, ignoreCase: true);
|
|
break;
|
|
case TokenKind.Cle:
|
|
_operationDelegate = GetCallSiteDelegate(ExpressionType.LessThanOrEqual, ignoreCase: false);
|
|
break;
|
|
case TokenKind.Ilike:
|
|
_operationDelegate =
|
|
(lval, rval) => ParserOps.LikeOperator(Context, PositionUtilities.EmptyExtent, lval, rval, _binaryOperator);
|
|
break;
|
|
case TokenKind.Clike:
|
|
_operationDelegate =
|
|
(lval, rval) => ParserOps.LikeOperator(Context, PositionUtilities.EmptyExtent, lval, rval, _binaryOperator);
|
|
break;
|
|
case TokenKind.Inotlike:
|
|
_operationDelegate =
|
|
(lval, rval) => ParserOps.LikeOperator(Context, PositionUtilities.EmptyExtent, lval, rval, _binaryOperator);
|
|
break;
|
|
case TokenKind.Cnotlike:
|
|
_operationDelegate =
|
|
(lval, rval) => ParserOps.LikeOperator(Context, PositionUtilities.EmptyExtent, lval, rval, _binaryOperator);
|
|
break;
|
|
case TokenKind.Imatch:
|
|
CheckLanguageMode();
|
|
_operationDelegate =
|
|
(lval, rval) => ParserOps.MatchOperator(Context, PositionUtilities.EmptyExtent, lval, rval, notMatch: false, ignoreCase: true);
|
|
break;
|
|
case TokenKind.Cmatch:
|
|
CheckLanguageMode();
|
|
_operationDelegate =
|
|
(lval, rval) => ParserOps.MatchOperator(Context, PositionUtilities.EmptyExtent, lval, rval, notMatch: false, ignoreCase: false);
|
|
break;
|
|
case TokenKind.Inotmatch:
|
|
CheckLanguageMode();
|
|
_operationDelegate =
|
|
(lval, rval) => ParserOps.MatchOperator(Context, PositionUtilities.EmptyExtent, lval, rval, notMatch: true, ignoreCase: true);
|
|
break;
|
|
case TokenKind.Cnotmatch:
|
|
CheckLanguageMode();
|
|
_operationDelegate =
|
|
(lval, rval) => ParserOps.MatchOperator(Context, PositionUtilities.EmptyExtent, lval, rval, notMatch: true, ignoreCase: false);
|
|
break;
|
|
case TokenKind.Not:
|
|
_operationDelegate = GetCallSiteDelegateBoolean(ExpressionType.NotEqual, ignoreCase: true);
|
|
break;
|
|
|
|
// the second to last parameter in ContainsOperator has flipped semantics compared to others.
|
|
// "true" means "contains" while "false" means "notcontains"
|
|
case TokenKind.Icontains:
|
|
case TokenKind.Inotcontains:
|
|
case TokenKind.In:
|
|
case TokenKind.Inotin:
|
|
{
|
|
var sites = GetContainsCallSites(ignoreCase: true);
|
|
switch (_binaryOperator)
|
|
{
|
|
case TokenKind.Icontains:
|
|
_operationDelegate =
|
|
(lval, rval) => ParserOps.ContainsOperatorCompiled(Context, sites.Item1, sites.Item2, lval, rval);
|
|
break;
|
|
case TokenKind.Inotcontains:
|
|
_operationDelegate =
|
|
(lval, rval) => !ParserOps.ContainsOperatorCompiled(Context, sites.Item1, sites.Item2, lval, rval);
|
|
break;
|
|
case TokenKind.In:
|
|
_operationDelegate =
|
|
(lval, rval) => ParserOps.ContainsOperatorCompiled(Context, sites.Item1, sites.Item2, rval, lval);
|
|
break;
|
|
case TokenKind.Inotin:
|
|
_operationDelegate =
|
|
(lval, rval) => !ParserOps.ContainsOperatorCompiled(Context, sites.Item1, sites.Item2, rval, lval);
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case TokenKind.Ccontains:
|
|
case TokenKind.Cnotcontains:
|
|
case TokenKind.Cin:
|
|
case TokenKind.Cnotin:
|
|
{
|
|
var sites = GetContainsCallSites(ignoreCase: false);
|
|
switch (_binaryOperator)
|
|
{
|
|
case TokenKind.Ccontains:
|
|
_operationDelegate =
|
|
(lval, rval) => ParserOps.ContainsOperatorCompiled(Context, sites.Item1, sites.Item2, lval, rval);
|
|
break;
|
|
case TokenKind.Cnotcontains:
|
|
_operationDelegate =
|
|
(lval, rval) => !ParserOps.ContainsOperatorCompiled(Context, sites.Item1, sites.Item2, lval, rval);
|
|
break;
|
|
case TokenKind.Cin:
|
|
_operationDelegate =
|
|
(lval, rval) => ParserOps.ContainsOperatorCompiled(Context, sites.Item1, sites.Item2, rval, lval);
|
|
break;
|
|
case TokenKind.Cnotin:
|
|
_operationDelegate =
|
|
(lval, rval) => !ParserOps.ContainsOperatorCompiled(Context, sites.Item1, sites.Item2, rval, lval);
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case TokenKind.Is:
|
|
_operationDelegate = (lval, rval) => ParserOps.IsOperator(Context, PositionUtilities.EmptyExtent, lval, rval);
|
|
break;
|
|
case TokenKind.IsNot:
|
|
_operationDelegate = (lval, rval) => ParserOps.IsNotOperator(Context, PositionUtilities.EmptyExtent, lval, rval);
|
|
break;
|
|
}
|
|
|
|
_convertedValue = _value;
|
|
if (!_valueNotSpecified)
|
|
{
|
|
switch (_binaryOperator)
|
|
{
|
|
case TokenKind.Ilike:
|
|
case TokenKind.Clike:
|
|
case TokenKind.Inotlike:
|
|
case TokenKind.Cnotlike:
|
|
_convertedValue = GetLikeRHSOperand(_convertedValue);
|
|
break;
|
|
|
|
case TokenKind.Is:
|
|
case TokenKind.IsNot:
|
|
// users might input [int], [string] as they do when using scripts
|
|
var strValue = _convertedValue as string;
|
|
if (strValue != null)
|
|
{
|
|
var typeLength = strValue.Length;
|
|
if (typeLength > 2 && strValue[0] == '[' && strValue[typeLength - 1] == ']')
|
|
{
|
|
_convertedValue = strValue.Substring(1, typeLength - 2);
|
|
}
|
|
|
|
_convertedValue = LanguagePrimitives.ConvertTo<Type>(_convertedValue);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private DynamicPropertyGetter _propGetter;
|
|
|
|
/// <summary>
|
|
/// Execute the script block passing in the current pipeline object as
|
|
/// it's only parameter.
|
|
/// </summary>
|
|
/// <exception cref="ParseException">Could not parse script.</exception>
|
|
/// <exception cref="RuntimeException">See Pipeline.Invoke.</exception>
|
|
/// <exception cref="ParameterBindingException">See Pipeline.Invoke.</exception>
|
|
protected override void ProcessRecord()
|
|
{
|
|
if (_inputObject == AutomationNull.Value)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_script != null)
|
|
{
|
|
object result = _script.DoInvokeReturnAsIs(
|
|
useLocalScope: false,
|
|
errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe,
|
|
dollarUnder: InputObject,
|
|
input: new object[] { _inputObject },
|
|
scriptThis: AutomationNull.Value,
|
|
args: Array.Empty<object>());
|
|
|
|
if (_toBoolSite.Target.Invoke(_toBoolSite, result))
|
|
{
|
|
WriteObject(InputObject);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Both -Property and -Value need to be specified if the user specifies the binary operation
|
|
if (_valueNotSpecified && ((_binaryOperator != TokenKind.Ieq && _binaryOperator != TokenKind.Not) || !_forceBooleanEvaluation))
|
|
{
|
|
// The binary operation is specified explicitly by the user and the -Value parameter is
|
|
// not specified
|
|
ThrowTerminatingError(
|
|
ForEachObjectCommand.GenerateNameParameterError(
|
|
"Value",
|
|
InternalCommandStrings.ValueNotSpecifiedForWhereObject,
|
|
"ValueNotSpecifiedForWhereObject",
|
|
target: null));
|
|
}
|
|
|
|
// The binary operation needs to be specified if the user specifies both the -Property and -Value
|
|
if (!_valueNotSpecified && (_binaryOperator == TokenKind.Ieq && _forceBooleanEvaluation))
|
|
{
|
|
// The -Property and -Value are specified explicitly by the user but the binary operation is not
|
|
ThrowTerminatingError(
|
|
ForEachObjectCommand.GenerateNameParameterError(
|
|
"Operator",
|
|
InternalCommandStrings.OperatorNotSpecified,
|
|
"OperatorNotSpecified",
|
|
target: null));
|
|
}
|
|
|
|
bool strictModeWithError = false;
|
|
object lvalue = GetValue(ref strictModeWithError);
|
|
if (strictModeWithError)
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
object result = _operationDelegate.Invoke(lvalue, _convertedValue);
|
|
if (_toBoolSite.Target.Invoke(_toBoolSite, result))
|
|
{
|
|
WriteObject(InputObject);
|
|
}
|
|
}
|
|
catch (PipelineStoppedException)
|
|
{
|
|
// PipelineStoppedException can be caused by select-object
|
|
throw;
|
|
}
|
|
catch (ArgumentException ae)
|
|
{
|
|
ErrorRecord errorRecord = new ErrorRecord(
|
|
PSTraceSource.NewArgumentException("BinaryOperator", ParserStrings.BadOperatorArgument, _binaryOperator, ae.Message),
|
|
"BadOperatorArgument",
|
|
ErrorCategory.InvalidArgument,
|
|
_inputObject);
|
|
WriteError(errorRecord);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ErrorRecord errorRecord = new ErrorRecord(
|
|
PSTraceSource.NewInvalidOperationException(ParserStrings.OperatorFailed, _binaryOperator, ex.Message),
|
|
"OperatorFailed",
|
|
ErrorCategory.InvalidOperation,
|
|
_inputObject);
|
|
WriteError(errorRecord);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the value based on the given property name.
|
|
/// </summary>
|
|
/// <returns>The value of the property.</returns>
|
|
private object GetValue(ref bool error)
|
|
{
|
|
if (LanguagePrimitives.IsNull(InputObject))
|
|
{
|
|
if (Context.IsStrictVersion(2))
|
|
{
|
|
WriteError(
|
|
ForEachObjectCommand.GenerateNameParameterError(
|
|
"InputObject",
|
|
InternalCommandStrings.InputObjectIsNull,
|
|
"InputObjectIsNull",
|
|
_inputObject,
|
|
_property));
|
|
error = true;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// If the target is a hash table and it contains the requested key
|
|
// return that, otherwise fall through and see if there is an
|
|
// underlying member corresponding to the key...
|
|
object target = PSObject.Base(_inputObject);
|
|
IDictionary hash = target as IDictionary;
|
|
try
|
|
{
|
|
if (hash != null && hash.Contains(_property))
|
|
{
|
|
return hash[_property];
|
|
}
|
|
}
|
|
catch (InvalidOperationException)
|
|
{
|
|
// Ignore invalid operation exception, it can happen if the dictionary
|
|
// has keys that can't be compared to property.
|
|
}
|
|
|
|
string resolvedPropertyName = null;
|
|
bool isBlindDynamicAccess = false;
|
|
|
|
ReadOnlyPSMemberInfoCollection<PSMemberInfo> members = GetMatchMembers();
|
|
if (members.Count > 1)
|
|
{
|
|
StringBuilder possibleMatches = new StringBuilder();
|
|
foreach (PSMemberInfo item in members)
|
|
{
|
|
possibleMatches.AppendFormat(CultureInfo.InvariantCulture, " {0}", item.Name);
|
|
}
|
|
|
|
WriteError(
|
|
ForEachObjectCommand.GenerateNameParameterError(
|
|
"Property",
|
|
InternalCommandStrings.AmbiguousPropertyOrMethodName,
|
|
"AmbiguousPropertyName",
|
|
_inputObject,
|
|
_property,
|
|
possibleMatches));
|
|
error = true;
|
|
}
|
|
else if (members.Count == 0)
|
|
{
|
|
if ((InputObject.BaseObject is IDynamicMetaObjectProvider) &&
|
|
!WildcardPattern.ContainsWildcardCharacters(_property))
|
|
{
|
|
// Let's just try a dynamic property access. Note that if it comes to
|
|
// depending on dynamic access, we are assuming it is a property; we
|
|
// don't have ETS info to tell us up front if it even exists or not,
|
|
// let alone if it is a method or something else.
|
|
//
|
|
// Note that this is "truly blind"--the name did not show up in
|
|
// GetDynamicMemberNames(), else it would show up as a dynamic member.
|
|
|
|
resolvedPropertyName = _property;
|
|
isBlindDynamicAccess = true;
|
|
}
|
|
else if (Context.IsStrictVersion(2))
|
|
{
|
|
WriteError(ForEachObjectCommand.GenerateNameParameterError(
|
|
"Property",
|
|
InternalCommandStrings.PropertyNotFound,
|
|
"PropertyNotFound",
|
|
_inputObject,
|
|
_property));
|
|
error = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
resolvedPropertyName = members[0].Name;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(resolvedPropertyName))
|
|
{
|
|
try
|
|
{
|
|
return _propGetter.GetValue(_inputObject, resolvedPropertyName);
|
|
}
|
|
catch (TerminateException)
|
|
{
|
|
throw;
|
|
}
|
|
catch (MethodException)
|
|
{
|
|
throw;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// For normal property accesses, we do not generate an error here. The problem
|
|
// for truly blind dynamic accesses (the member did not show up in
|
|
// GetDynamicMemberNames) is that we can't tell the difference between "it
|
|
// failed because the property does not exist" (let's call this case
|
|
// 1) and "it failed because accessing it actually threw some exception" (let's
|
|
// call that case 2).
|
|
//
|
|
// PowerShell behavior for normal (non-dynamic) properties is different for
|
|
// these two cases: case 1 gets an error (if strict mode is on) (which is
|
|
// possible because the ETS tells us up front if the property exists or not),
|
|
// and case 2 does not. (For normal properties, this catch block /is/ case 2.)
|
|
//
|
|
// For IDMOPs, we have the chance to attempt a "blind" access, but the cost is
|
|
// that we must have the same response to both cases (because we cannot
|
|
// distinguish between the two). So we have to make a choice: we can either
|
|
// swallow ALL errors (including "The property 'Blarg' does not exist"), or
|
|
// expose them all.
|
|
//
|
|
// Here, for truly blind dynamic access, we choose to preserve the behavior of
|
|
// showing "The property 'Blarg' does not exist" (case 1) errors than to
|
|
// suppress "FooException thrown when accessing Bloop property" (case
|
|
// 2) errors.
|
|
if (isBlindDynamicAccess && Context.IsStrictVersion(2))
|
|
{
|
|
WriteError(new ErrorRecord(ex,
|
|
"DynamicPropertyAccessFailed_" + _property,
|
|
ErrorCategory.InvalidOperation,
|
|
_inputObject));
|
|
|
|
error = true;
|
|
}
|
|
else
|
|
{
|
|
// When the property is not gettable or it throws an exception
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the matched PSMembers.
|
|
/// </summary>
|
|
/// <returns>Matched PSMembers.</returns>
|
|
private ReadOnlyPSMemberInfoCollection<PSMemberInfo> GetMatchMembers()
|
|
{
|
|
if (!WildcardPattern.ContainsWildcardCharacters(_property))
|
|
{
|
|
PSMemberInfoInternalCollection<PSMemberInfo> results = new PSMemberInfoInternalCollection<PSMemberInfo>();
|
|
PSMemberInfo member = _inputObject.Members[_property];
|
|
if (member != null)
|
|
{
|
|
results.Add(member);
|
|
}
|
|
|
|
return new ReadOnlyPSMemberInfoCollection<PSMemberInfo>(results);
|
|
}
|
|
|
|
ReadOnlyPSMemberInfoCollection<PSMemberInfo> members = _inputObject.Members.Match(_property, PSMemberTypes.All);
|
|
Dbg.Assert(members != null, "The return value of Members.Match should never be null.");
|
|
return members;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Implements a cmdlet that sets the script debugging options.
|
|
/// </summary>
|
|
[Cmdlet(VerbsCommon.Set, "PSDebug", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096959")]
|
|
public sealed class SetPSDebugCommand : PSCmdlet
|
|
{
|
|
/// <summary>
|
|
/// Gets or sets the script tracing level.
|
|
/// </summary>
|
|
[Parameter(ParameterSetName = "on")]
|
|
[ValidateRange(0, 2)]
|
|
public int Trace
|
|
{
|
|
get
|
|
{
|
|
return _trace;
|
|
}
|
|
|
|
set
|
|
{
|
|
_trace = value;
|
|
}
|
|
}
|
|
|
|
private int _trace = -1;
|
|
|
|
/// <summary>
|
|
/// Gets or sets stepping on and off.
|
|
/// </summary>
|
|
[Parameter(ParameterSetName = "on")]
|
|
public SwitchParameter Step
|
|
{
|
|
get
|
|
{
|
|
return (SwitchParameter)_step;
|
|
}
|
|
|
|
set
|
|
{
|
|
_step = value;
|
|
}
|
|
}
|
|
|
|
private bool? _step;
|
|
|
|
/// <summary>
|
|
/// Gets or sets strict mode on and off.
|
|
/// </summary>
|
|
[Parameter(ParameterSetName = "on")]
|
|
public SwitchParameter Strict
|
|
{
|
|
get
|
|
{
|
|
return (SwitchParameter)_strict;
|
|
}
|
|
|
|
set
|
|
{
|
|
_strict = value;
|
|
}
|
|
}
|
|
|
|
private bool? _strict;
|
|
|
|
/// <summary>
|
|
/// Gets or sets all script debugging features off.
|
|
/// </summary>
|
|
[Parameter(ParameterSetName = "off")]
|
|
public SwitchParameter Off
|
|
{
|
|
get
|
|
{
|
|
return _off;
|
|
}
|
|
|
|
set
|
|
{
|
|
_off = value;
|
|
}
|
|
}
|
|
|
|
private bool _off;
|
|
|
|
/// <summary>
|
|
/// Execute the begin scriptblock at the start of processing.
|
|
/// </summary>
|
|
protected override void BeginProcessing()
|
|
{
|
|
// -off gets processed after the others so it takes precedence...
|
|
if (_off)
|
|
{
|
|
Context.Debugger.DisableTracing();
|
|
Context.EngineSessionState.GlobalScope.StrictModeVersion = null;
|
|
}
|
|
else
|
|
{
|
|
if (_trace >= 0 || _step != null)
|
|
{
|
|
Context.Debugger.EnableTracing(_trace, _step);
|
|
}
|
|
|
|
// Version 0 is the same as off
|
|
if (_strict != null)
|
|
{
|
|
Context.EngineSessionState.GlobalScope.StrictModeVersion = new Version((bool)_strict ? 1 : 0, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#region Set-StrictMode
|
|
|
|
/// <summary>
|
|
/// Set-StrictMode causes the interpreter to throw an exception in the following cases:
|
|
/// * Referencing an unassigned variable
|
|
/// * Referencing a non-existent property of an object
|
|
/// * Calling a function as a method (with parentheses and commas)
|
|
/// * Using the variable expansion syntax in a string literal w/o naming a variable, i.e. "${}"
|
|
///
|
|
/// Parameters:
|
|
///
|
|
/// -Version allows the script author to specify which strict mode version to enforce.
|
|
/// -Off turns strict mode off
|
|
///
|
|
/// Note:
|
|
///
|
|
/// Unlike Set-PSDebug -strict, Set-StrictMode is not engine-wide, and only
|
|
/// affects the scope it was defined in.
|
|
/// </summary>
|
|
[Cmdlet(VerbsCommon.Set, "StrictMode", DefaultParameterSetName = "Version", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096804")]
|
|
public class SetStrictModeCommand : PSCmdlet
|
|
{
|
|
/// <summary>
|
|
/// Gets or sets strict mode off.
|
|
/// </summary>
|
|
[Parameter(ParameterSetName = "Off", Mandatory = true)]
|
|
public SwitchParameter Off
|
|
{
|
|
get
|
|
{
|
|
return _off;
|
|
}
|
|
|
|
set
|
|
{
|
|
_off = value;
|
|
}
|
|
}
|
|
|
|
private SwitchParameter _off;
|
|
|
|
/// <summary>
|
|
/// Handle 'latest', which we interpret to be the current version of PowerShell.
|
|
/// </summary>
|
|
private sealed class ArgumentToPSVersionTransformationAttribute : ArgumentToVersionTransformationAttribute
|
|
{
|
|
protected override bool TryConvertFromString(string versionString, [NotNullWhen(true)] out Version version)
|
|
{
|
|
if (string.Equals("latest", versionString, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
version = PSVersionInfo.PSVersion;
|
|
return true;
|
|
}
|
|
|
|
return base.TryConvertFromString(versionString, out version);
|
|
}
|
|
}
|
|
|
|
private sealed class ValidateVersionAttribute : ValidateArgumentsAttribute
|
|
{
|
|
protected override void Validate(object arguments, EngineIntrinsics engineIntrinsics)
|
|
{
|
|
Version version = arguments as Version;
|
|
if (version == null || !PSVersionInfo.IsValidPSVersion(version))
|
|
{
|
|
// No conversion succeeded so throw and exception...
|
|
throw new ValidationMetadataException(
|
|
"InvalidPSVersion",
|
|
null,
|
|
Metadata.ValidateVersionFailure,
|
|
arguments);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets strict mode in the current scope.
|
|
/// </summary>
|
|
[Parameter(ParameterSetName = "Version", Mandatory = true)]
|
|
[ArgumentToPSVersionTransformation]
|
|
[ValidateVersion]
|
|
[Alias("v")]
|
|
public Version Version
|
|
{
|
|
get
|
|
{
|
|
return _version;
|
|
}
|
|
|
|
set
|
|
{
|
|
_version = value;
|
|
}
|
|
}
|
|
|
|
private Version _version;
|
|
|
|
/// <summary>
|
|
/// Set the correct version for strict mode checking in the current scope.
|
|
/// </summary>
|
|
protected override void EndProcessing()
|
|
{
|
|
if (_off.IsPresent)
|
|
{
|
|
_version = new Version(0, 0);
|
|
}
|
|
|
|
Context.EngineSessionState.CurrentScope.StrictModeVersion = _version;
|
|
}
|
|
}
|
|
#endregion Set-StrictMode
|
|
|
|
#endregion Built-in cmdlets that are used by or require direct access to the engine.
|
|
}
|