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 }); }
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 }); }
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 }; }
/// <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); }
/// <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; }