protected async Task HandleEvaluateRequestAsync(
            EvaluateRequestArguments evaluateParams,
            RequestContext <EvaluateResponseBody> requestContext)
        {
            string valueString = null;
            int    variableId  = 0;

            bool isFromRepl =
                string.Equals(
                    evaluateParams.Context,
                    "repl",
                    StringComparison.CurrentCultureIgnoreCase);

            if (isFromRepl)
            {
                var notAwaited =
                    _editorSession
                    .PowerShellContext
                    .ExecuteScriptStringAsync(evaluateParams.Expression, false, true)
                    .ConfigureAwait(false);
            }
            else
            {
                VariableDetailsBase result = null;

                // VS Code might send this request after the debugger
                // has been resumed, return an empty result in this case.
                if (_editorSession.PowerShellContext.IsDebuggerStopped)
                {
                    // First check to see if the watch expression refers to a naked variable reference.
                    result =
                        _editorSession.DebugService.GetVariableFromExpression(evaluateParams.Expression, evaluateParams.FrameId);

                    // If the expression is not a naked variable reference, then evaluate the expression.
                    if (result == null)
                    {
                        result =
                            await _editorSession.DebugService.EvaluateExpressionAsync(
                                evaluateParams.Expression,
                                evaluateParams.FrameId,
                                isFromRepl);
                    }
                }

                if (result != null)
                {
                    valueString = result.ValueString;
                    variableId  =
                        result.IsExpandable ?
                        result.Id : 0;
                }
            }

            await requestContext.SendResultAsync(
                new EvaluateResponseBody
            {
                Result             = valueString,
                VariablesReference = variableId
            });
        }
        public async Task <EvaluateResponseBody> Handle(EvaluateRequestArguments request, CancellationToken cancellationToken)
        {
            string valueString = "";
            int    variableId  = 0;

            bool isFromRepl =
                string.Equals(
                    request.Context,
                    "repl",
                    StringComparison.CurrentCultureIgnoreCase);

            if (isFromRepl)
            {
                var notAwaited =
                    _powerShellContextService
                    .ExecuteScriptStringAsync(request.Expression, false, true)
                    .ConfigureAwait(false);
            }
            else
            {
                VariableDetailsBase result = null;

                // VS Code might send this request after the debugger
                // has been resumed, return an empty result in this case.
                if (_powerShellContextService.IsDebuggerStopped)
                {
                    // First check to see if the watch expression refers to a naked variable reference.
                    result =
                        _debugService.GetVariableFromExpression(request.Expression, request.FrameId);

                    // If the expression is not a naked variable reference, then evaluate the expression.
                    if (result == null)
                    {
                        result =
                            await _debugService.EvaluateExpressionAsync(
                                request.Expression,
                                request.FrameId,
                                isFromRepl).ConfigureAwait(false);
                    }
                }

                if (result != null)
                {
                    valueString = result.ValueString;
                    variableId  =
                        result.IsExpandable ?
                        result.Id : 0;
                }
            }

            return(new EvaluateResponseBody
            {
                Result = valueString,
                VariablesReference = variableId
            });
        }
Example #3
0
 public static Variable Create(VariableDetailsBase variable)
 {
     return(new Variable
     {
         Name = variable.Name,
         Value = variable.ValueString ?? string.Empty,
         VariablesReference =
             variable.IsExpandable ?
             variable.Id : 0
     });
 }
 public static Variable Create(VariableDetailsBase variable)
 {
     return new Variable
     {
         Name = variable.Name,
         Value = variable.ValueString ?? string.Empty,
         VariablesReference = 
             variable.IsExpandable ?
                 variable.Id : 0
     };
 }
        public async Task <EvaluateResponseBody> Handle(EvaluateRequestArguments request, CancellationToken cancellationToken)
        {
            string valueString = "";
            int    variableId  = 0;

            bool isFromRepl =
                string.Equals(
                    request.Context,
                    "repl",
                    StringComparison.CurrentCultureIgnoreCase);

            if (isFromRepl)
            {
                await _executionService.ExecutePSCommandAsync(
                    new PSCommand().AddScript(request.Expression),
                    CancellationToken.None,
                    new PowerShellExecutionOptions { WriteOutputToHost = true, ThrowOnError = false, AddToHistory = true }).HandleErrorsAsync(_logger).ConfigureAwait(false);
            }
            else
            {
                VariableDetailsBase result = null;

                // VS Code might send this request after the debugger
                // has been resumed, return an empty result in this case.
                if (_debugContext.IsStopped)
                {
                    // First check to see if the watch expression refers to a naked variable reference.
                    result = _debugService.GetVariableFromExpression(request.Expression);

                    // If the expression is not a naked variable reference, then evaluate the expression.
                    if (result == null)
                    {
                        result =
                            await _debugService.EvaluateExpressionAsync(
                                request.Expression,
                                isFromRepl).ConfigureAwait(false);
                    }
                }

                if (result != null)
                {
                    valueString = result.ValueString;
                    variableId  =
                        result.IsExpandable ?
                        result.Id : 0;
                }
            }

            return(new EvaluateResponseBody
            {
                Result = valueString,
                VariablesReference = variableId
            });
        }
Example #6
0
 public static Variable CreateVariable(VariableDetailsBase variable)
 {
     return new Variable
     {
         Name = variable.Name,
         Value = variable.ValueString ?? string.Empty,
         Type = variable.Type,
         EvaluateName = variable.Name,
         VariablesReference =
             variable.IsExpandable ?
                 variable.Id : 0
     };
 }
Example #7
0
        /// <summary>
        /// Evaluates a variable expression in the context of the stopped
        /// debugger.  This method decomposes the variable expression to
        /// walk the cached variable data for the specified stack frame.
        /// </summary>
        /// <param name="variableExpression">The variable expression string to evaluate.</param>
        /// <param name="stackFrameId">The ID of the stack frame in which the expression should be evaluated.</param>
        /// <returns>A VariableDetailsBase object containing the result.</returns>
        public VariableDetailsBase GetVariableFromExpression(string variableExpression, int stackFrameId)
        {
            // NOTE: From a watch we will get passed expressions that are not naked variables references.
            // Probably the right way to do this woudld be to examine the AST of the expr before calling
            // this method to make sure it is a VariableReference.  But for the most part, non-naked variable
            // references are very unlikely to find a matching variable e.g. "$i+5.2" will find no var matching "$i+5".

            // Break up the variable path
            string[] variablePathParts = variableExpression.Split('.');

            VariableDetailsBase resolvedVariable = null;
            IEnumerable <VariableDetailsBase> variableList;

            // Ensure debug info isn't currently being built.
            this.debugInfoHandle.Wait();
            try
            {
                variableList = this.variables;
            }
            finally
            {
                this.debugInfoHandle.Release();
            }

            foreach (var variableName in variablePathParts)
            {
                if (variableList == null)
                {
                    // If there are no children left to search, break out early
                    return(null);
                }

                resolvedVariable =
                    variableList.FirstOrDefault(
                        v =>
                        string.Equals(
                            v.Name,
                            variableName,
                            StringComparison.CurrentCultureIgnoreCase));

                if (resolvedVariable != null &&
                    resolvedVariable.IsExpandable)
                {
                    // Continue by searching in this variable's children
                    variableList = this.GetVariables(resolvedVariable.Id);
                }
            }

            return(resolvedVariable);
        }
        public async Task DebuggerVariableHashtableDisplaysCorrectly()
        {
            await this.debugService.SetLineBreakpointsAsync(
                this.variableScriptFile,
                new[] { BreakpointDetails.Create(this.variableScriptFile.FilePath, 11) }).ConfigureAwait(false);

            // Execute the script and wait for the breakpoint to be hit
            Task executeTask =
                this.powerShellContext.ExecuteScriptStringAsync(
                    this.variableScriptFile.FilePath);

            await this.AssertDebuggerStopped(this.variableScriptFile.FilePath).ConfigureAwait(false);

            StackFrameDetails[] stackFrames = debugService.GetStackFrames();

            VariableDetailsBase[] variables =
                debugService.GetVariables(stackFrames[0].LocalVariables.Id);

            VariableDetailsBase var = variables.FirstOrDefault(v => v.Name == "$assocArrVar");

            Assert.NotNull(var);
            Assert.Equal("[Hashtable: 2]", var.ValueString);
            Assert.True(var.IsExpandable);

            VariableDetailsBase[] childVars = debugService.GetVariables(var.Id);
            Assert.Equal(9, childVars.Length);
            Assert.Equal("[0]", childVars[0].Name);
            Assert.Equal("[1]", childVars[1].Name);

            var childVarStrs = new HashSet <string>(childVars.Select(v => v.ValueString));
            var expectedVars = new[] {
                "[firstChild, \"Child\"]",
                "[secondChild, 42]"
            };

            foreach (string expectedVar in expectedVars)
            {
                Assert.Contains(expectedVar, childVarStrs);
            }

            // Abort script execution early and wait for completion
            this.debugService.Abort();
            await executeTask.ConfigureAwait(false);
        }
        /// <summary>
        /// Gets the list of variables that are children of the scope or variable
        /// that is identified by the given referenced ID.
        /// </summary>
        /// <param name="variableReferenceId"></param>
        /// <returns>An array of VariableDetails instances which describe the requested variables.</returns>
        public VariableDetailsBase[] GetVariables(int variableReferenceId)
        {
            VariableDetailsBase[] childVariables;
            debugInfoHandle.Wait();
            try
            {
                if ((variableReferenceId < 0) || (variableReferenceId >= variables.Count))
                {
                    _logger.LogWarning($"Received request for variableReferenceId {variableReferenceId} that is out of range of valid indices.");
                    return(Array.Empty <VariableDetailsBase>());
                }

                VariableDetailsBase parentVariable = variables[variableReferenceId];
                if (parentVariable.IsExpandable)
                {
                    childVariables = parentVariable.GetChildren(_logger);
                    foreach (VariableDetailsBase child in childVariables)
                    {
                        // Only add child if it hasn't already been added.
                        if (child.Id < 0)
                        {
                            child.Id = nextVariableId++;
                            variables.Add(child);
                        }
                    }
                }
                else
                {
                    childVariables = Array.Empty <VariableDetailsBase>();
                }

                return(childVariables);
            }
            finally
            {
                debugInfoHandle.Release();
            }
        }
        /// <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);
        }
Example #11
0
        /// <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>
        public async Task <string> SetVariableAsync(int variableContainerReferenceId, string name, string value)
        {
            Validate.IsNotNull(nameof(name), name);
            Validate.IsNotNull(nameof(value), value);

            this.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.
            PSCommand psCommand = new PSCommand();

            psCommand.AddScript(value);
            var errorMessages = new StringBuilder();
            var results       =
                await this.powerShellContext.ExecuteCommandAsync <object>(
                    psCommand,
                    errorMessages,
                    false,
                    false).ConfigureAwait(false);

            // Check if PowerShell's evaluation of the expression resulted in an error.
            object psobject = results.FirstOrDefault();

            if ((psobject == null) && (errorMessages.Length > 0))
            {
                throw new InvalidPowerShellExpressionException(errorMessages.ToString());
            }

            // 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 (psobject 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 this.debugInfoHandle.WaitAsync().ConfigureAwait(false);

            try
            {
                variableContainer = (VariableContainerDetails)this.variables[variableContainerReferenceId];
            }
            finally
            {
                this.debugInfoHandle.Release();
            }

            VariableDetailsBase variable = variableContainer.Children[name];
            // Determine scope in which the variable lives. This is required later for the call to Get-Variable -Scope.
            string scope = null;

            if (variableContainerReferenceId == this.scriptScopeVariables.Id)
            {
                scope = "Script";
            }
            else if (variableContainerReferenceId == this.globalScopeVariables.Id)
            {
                scope = "Global";
            }
            else
            {
                // Determine which stackframe's local scope the variable is in.
                StackFrameDetails[] stackFrames = await this.GetStackFramesAsync().ConfigureAwait(false);

                for (int i = 0; i < stackFrames.Length; i++)
                {
                    var stackFrame = stackFrames[i];
                    if (stackFrame.LocalVariables.ContainsVariable(variable.Id))
                    {
                        scope = i.ToString();
                        break;
                    }
                }
            }

            if (scope == null)
            {
                // Hmm, this would be unexpected.  No scope means do not pass GO, do not collect $200.
                throw new Exception("Could not find the scope for this variable.");
            }

            // Now that we have the scope, get the associated PSVariable object for the variable to be set.
            psCommand.Commands.Clear();
            psCommand = new PSCommand();
            psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Get-Variable");
            psCommand.AddParameter("Name", name.TrimStart('$'));
            psCommand.AddParameter("Scope", scope);

            IEnumerable <PSVariable> result = await this.powerShellContext.ExecuteCommandAsync <PSVariable>(psCommand, sendErrorToHost : false).ConfigureAwait(false);

            PSVariable psVariable = result.FirstOrDefault();

            if (psVariable == 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.
            var argTypeConverterAttr =
                psVariable.Attributes
                .OfType <ArgumentTransformationAttribute>()
                .FirstOrDefault(a => a.GetType().Name.Equals("ArgumentTypeConverterAttribute"));

            if (argTypeConverterAttr != null)
            {
                // PSVariable is strongly typed. Need to apply the conversion/transform to the new value.
                psCommand.Commands.Clear();
                psCommand = new PSCommand();
                psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Get-Variable");
                psCommand.AddParameter("Name", "ExecutionContext");
                psCommand.AddParameter("ValueOnly");

                errorMessages.Clear();

                var getExecContextResults =
                    await this.powerShellContext.ExecuteCommandAsync <object>(
                        psCommand,
                        errorMessages,
                        sendErrorToHost : false).ConfigureAwait(false);

                EngineIntrinsics executionContext = getExecContextResults.OfType <EngineIntrinsics>().FirstOrDefault();

                var msg = $"Setting variable '{name}' using conversion to value: {psobject ?? "<null>"}";
                this.logger.LogTrace(msg);

                psVariable.Value = argTypeConverterAttr.Transform(executionContext, psobject);
            }
            else
            {
                // PSVariable is *not* strongly typed. In this case, whack the old value with the new value.
                var msg = $"Setting variable '{name}' directly to value: {psobject ?? "<null>"} - previous type was {psVariable.Value?.GetType().Name ?? "<unknown>"}";
                this.logger.LogTrace(msg);
                psVariable.Value = psobject;
            }

            // 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.
            var tempVariable = new VariableDetails(psVariable);

            this.logger.LogTrace($"Set variable '{name}' to: {tempVariable.ValueString ?? "<null>"}");
            return(tempVariable.ValueString);
        }
 public VariableInspector(VariableDetailsBase variable)
 {
     Name  = variable.Name;
     Value = variable.Value;
 }