/// <summary> /// Validates and performs any transformations that the new attribute /// implements. /// </summary> /// <param name="item"> /// The new attribute to be added to the collection. /// </param> /// <returns> /// The new variable value. This may change from the original value if the /// new attribute is an ArgumentTransformationAttribute. /// </returns> private object VerifyNewAttribute(Attribute item) { object variableValue = _variable.Value; // Perform transformation before validating ArgumentTransformationAttribute argumentTransformation = item as ArgumentTransformationAttribute; if (argumentTransformation != null) { // Get an EngineIntrinsics instance using the context of the thread. ExecutionContext context = Runspaces.LocalPipeline.GetExecutionContextFromTLS(); EngineIntrinsics engine = null; if (context != null) { engine = context.EngineIntrinsics; } variableValue = argumentTransformation.TransformInternal(engine, variableValue); } if (!PSVariable.IsValidValue(variableValue, item)) { ValidationMetadataException e = new ValidationMetadataException( "ValidateSetFailure", null, Metadata.InvalidMetadataForCurrentValue, _variable.Name, ((_variable.Value != null) ? _variable.Value.ToString() : string.Empty)); throw e; } return(variableValue); }
/// <summary> /// Sets the specified variable by container variableReferenceId and variable name to the /// specified new value. If the variable cannot be set or converted to that value this /// method will throw InvalidPowerShellExpressionException, ArgumentTransformationMetadataException, or /// SessionStateUnauthorizedAccessException. /// </summary> /// <param name="variableContainerReferenceId">The container (Autos, Local, Script, Global) that holds the variable.</param> /// <param name="name">The name of the variable prefixed with $.</param> /// <param name="value">The new string value. This value must not be null. If you want to set the variable to $null /// pass in the string "$null".</param> /// <returns>The string representation of the value the variable was set to.</returns> /// <exception cref="InvalidPowerShellExpressionException"></exception> public async Task <string> SetVariableAsync(int variableContainerReferenceId, string name, string value) { Validate.IsNotNull(nameof(name), name); Validate.IsNotNull(nameof(value), value); _logger.LogTrace($"SetVariableRequest for '{name}' to value string (pre-quote processing): '{value}'"); // An empty or whitespace only value is not a valid expression for SetVariable. if (value.Trim().Length == 0) { throw new InvalidPowerShellExpressionException("Expected an expression."); } // Evaluate the expression to get back a PowerShell object from the expression string. // This may throw, in which case the exception is propagated to the caller PSCommand evaluateExpressionCommand = new PSCommand().AddScript(value); IReadOnlyList <object> expressionResults = await _executionService.ExecutePSCommandAsync <object>(evaluateExpressionCommand, CancellationToken.None).ConfigureAwait(false); if (expressionResults.Count == 0) { throw new InvalidPowerShellExpressionException("Expected an expression result."); } object expressionResult = expressionResults[0]; // If PowerShellContext.ExecuteCommand returns an ErrorRecord as output, the expression failed evaluation. // Ideally we would have a separate means from communicating error records apart from normal output. if (expressionResult is ErrorRecord errorRecord) { throw new InvalidPowerShellExpressionException(errorRecord.ToString()); } // OK, now we have a PS object from the supplied value string (expression) to assign to a variable. // Get the variable referenced by variableContainerReferenceId and variable name. VariableContainerDetails variableContainer = null; await debugInfoHandle.WaitAsync().ConfigureAwait(false); try { variableContainer = (VariableContainerDetails)variables[variableContainerReferenceId]; } finally { debugInfoHandle.Release(); } VariableDetailsBase variable = variableContainer.Children[name]; // Determine scope in which the variable lives so we can pass it to `Get-Variable // -Scope`. The default is scope 0 which is safe because if a user is able to see a // variable in the debugger and so change it through this interface, it's either in the // top-most scope or in one of the following named scopes. The default scope is most // likely in the case of changing from the "auto variables" container. string scope = "0"; // NOTE: This can't use a switch because the IDs aren't constant. if (variableContainerReferenceId == localScopeVariables.Id) { scope = VariableContainerDetails.LocalScopeName; } else if (variableContainerReferenceId == scriptScopeVariables.Id) { scope = VariableContainerDetails.ScriptScopeName; } else if (variableContainerReferenceId == globalScopeVariables.Id) { scope = VariableContainerDetails.GlobalScopeName; } // Now that we have the scope, get the associated PSVariable object for the variable to be set. PSCommand getVariableCommand = new PSCommand() .AddCommand(@"Microsoft.PowerShell.Utility\Get-Variable") .AddParameter("Name", name.TrimStart('$')) .AddParameter("Scope", scope); IReadOnlyList <PSVariable> psVariables = await _executionService.ExecutePSCommandAsync <PSVariable>(getVariableCommand, CancellationToken.None).ConfigureAwait(false); if (psVariables.Count == 0) { throw new Exception("Failed to retrieve PSVariables"); } PSVariable psVariable = psVariables[0]; if (psVariable is null) { throw new Exception($"Failed to retrieve PSVariable object for '{name}' from scope '{scope}'."); } // We have the PSVariable object for the variable the user wants to set and an object to assign to that variable. // The last step is to determine whether the PSVariable is "strongly typed" which may require a conversion. // If it is not strongly typed, we simply assign the object directly to the PSVariable potentially changing its type. // Turns out ArgumentTypeConverterAttribute is not public. So we call the attribute through it's base class - // ArgumentTransformationAttribute. ArgumentTransformationAttribute argTypeConverterAttr = null; foreach (Attribute variableAttribute in psVariable.Attributes) { if (variableAttribute is ArgumentTransformationAttribute argTransformAttr && argTransformAttr.GetType().Name.Equals("ArgumentTypeConverterAttribute")) { argTypeConverterAttr = argTransformAttr; break; } } if (argTypeConverterAttr is not null) { // PSVariable *is* strongly typed, so we have to convert it. _logger.LogTrace($"Setting variable '{name}' using conversion to value: {expressionResult ?? "<null>"}"); // NOTE: We use 'Get-Variable' here instead of 'SessionStateProxy.GetVariable()' // because we already have a pipeline running (the debugger) and the latter cannot // run concurrently (threw 'NoSessionStateProxyWhenPipelineInProgress'). IReadOnlyList <EngineIntrinsics> results = await _executionService.ExecutePSCommandAsync <EngineIntrinsics>( new PSCommand() .AddCommand(@"Microsoft.PowerShell.Utility\Get-Variable") .AddParameter("Name", "ExecutionContext") .AddParameter("ValueOnly"), CancellationToken.None).ConfigureAwait(false); EngineIntrinsics engineIntrinsics = results.Count > 0 ? results[0] : throw new Exception("Couldn't get EngineIntrinsics!"); // TODO: This is almost (but not quite) the same as 'LanguagePrimitives.Convert()', // which does not require the pipeline thread. We should investigate changing it. psVariable.Value = argTypeConverterAttr.Transform(engineIntrinsics, expressionResult); } else { // PSVariable is *not* strongly typed. In this case, whack the old value with the new value. _logger.LogTrace($"Setting variable '{name}' directly to value: {expressionResult ?? "<null>"} - previous type was {psVariable.Value?.GetType().Name ?? "<unknown>"}"); psVariable.Value = expressionResult; } // Use the VariableDetails.ValueString functionality to get the string representation for client debugger. // This makes the returned string consistent with the strings normally displayed for variables in the debugger. VariableDetails tempVariable = new(psVariable); _logger.LogTrace($"Set variable '{name}' to: {tempVariable.ValueString ?? "<null>"}"); return(tempVariable.ValueString); }
/// <summary> /// Processes the Attribute metadata to generate a CompiledCommandAttribute. /// </summary> /// <exception cref="MetadataException"> /// If the attribute is a parameter attribute and another parameter attribute /// has been processed with the same parameter-set name. /// </exception> private void ProcessAttribute( string memberName, Attribute attribute, ref Collection <ValidateArgumentsAttribute> validationAttributes, ref Collection <ArgumentTransformationAttribute> argTransformationAttributes, ref string[] aliases) { if (attribute == null) { return; } CompiledAttributes.Add(attribute); // Now process the attribute based on it's type if (attribute is ParameterAttribute paramAttr) { ProcessParameterAttribute(memberName, paramAttr); return; } ValidateArgumentsAttribute validateAttr = attribute as ValidateArgumentsAttribute; if (validateAttr != null) { if (validationAttributes == null) { validationAttributes = new Collection <ValidateArgumentsAttribute>(); } validationAttributes.Add(validateAttr); if ((attribute is ValidateNotNullAttribute) || (attribute is ValidateNotNullOrEmptyAttribute)) { this.CannotBeNull = true; } return; } AliasAttribute aliasAttr = attribute as AliasAttribute; if (aliasAttr != null) { if (aliases == null) { aliases = aliasAttr.aliasNames; } else { var prevAliasNames = aliases; var newAliasNames = aliasAttr.aliasNames; aliases = new string[prevAliasNames.Length + newAliasNames.Length]; Array.Copy(prevAliasNames, aliases, prevAliasNames.Length); Array.Copy(newAliasNames, 0, aliases, prevAliasNames.Length, newAliasNames.Length); } return; } ArgumentTransformationAttribute argumentAttr = attribute as ArgumentTransformationAttribute; if (argumentAttr != null) { if (argTransformationAttributes == null) { argTransformationAttributes = new Collection <ArgumentTransformationAttribute>(); } argTransformationAttributes.Add(argumentAttr); return; } AllowNullAttribute allowNullAttribute = attribute as AllowNullAttribute; if (allowNullAttribute != null) { this.AllowsNullArgument = true; return; } AllowEmptyStringAttribute allowEmptyStringAttribute = attribute as AllowEmptyStringAttribute; if (allowEmptyStringAttribute != null) { this.AllowsEmptyStringArgument = true; return; } AllowEmptyCollectionAttribute allowEmptyCollectionAttribute = attribute as AllowEmptyCollectionAttribute; if (allowEmptyCollectionAttribute != null) { this.AllowsEmptyCollectionArgument = true; return; } ObsoleteAttribute obsoleteAttr = attribute as ObsoleteAttribute; if (obsoleteAttr != null) { ObsoleteAttribute = obsoleteAttr; return; } PSTypeNameAttribute psTypeNameAttribute = attribute as PSTypeNameAttribute; if (psTypeNameAttribute != null) { this.PSTypeName = psTypeNameAttribute.PSTypeName; } }