/// <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);
        }
Exemplo n.º 3
0
        /// <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;
            }
        }