/// <summary>
        /// Checks if Plaster is installed on the user's machine.
        /// </summary>
        /// <returns>A Task that can be awaited until the check is complete.  The result will be true if Plaster is installed.</returns>
        public async Task <bool> ImportPlasterIfInstalledAsync()
        {
            if (!isPlasterInstalled.HasValue)
            {
                PSCommand psCommand = new();

                psCommand
                .AddCommand("Get-Module")
                .AddParameter("ListAvailable")
                .AddParameter("Name", "Plaster");

                psCommand
                .AddCommand("Sort-Object")
                .AddParameter("Descending")
                .AddParameter("Property", "Version");

                psCommand
                .AddCommand("Select-Object")
                .AddParameter("First", 1);

                _logger.LogTrace("Checking if Plaster is installed...");

                PSObject moduleObject = (await _executionService.ExecutePSCommandAsync <PSObject>(psCommand, CancellationToken.None).ConfigureAwait(false))[0];

                isPlasterInstalled = moduleObject != null;
                string installedQualifier =
                    isPlasterInstalled.Value
                        ? string.Empty : "not ";

                _logger.LogTrace($"Plaster is {installedQualifier}installed!");

                // Attempt to load plaster
                if (isPlasterInstalled.Value && !isPlasterLoaded)
                {
                    _logger.LogTrace("Loading Plaster...");

                    psCommand = new PSCommand();
                    psCommand
                    .AddCommand("Import-Module")
                    .AddParameter("ModuleInfo", (PSModuleInfo)moduleObject.ImmediateBaseObject)
                    .AddParameter("PassThru");

                    IReadOnlyList <PSModuleInfo> importResult = await _executionService.ExecutePSCommandAsync <PSModuleInfo>(psCommand, CancellationToken.None).ConfigureAwait(false);

                    isPlasterLoaded = importResult.Count > 0;
                    string loadedQualifier =
                        isPlasterInstalled.Value
                            ? "was" : "could not be";

                    _logger.LogTrace($"Plaster {loadedQualifier} loaded successfully!");
                }
            }

            return(isPlasterInstalled.Value);
        }
Пример #2
0
        public async Task <DisconnectResponse> Handle(DisconnectArguments request, CancellationToken cancellationToken)
        {
            // TODO: We need to sort out the proper order of operations here.
            //       Currently we just tear things down in some order without really checking what the debugger is doing.
            //       We should instead ensure that the debugger is in some valid state, lock it and then tear things down

            _debugEventHandlerService.UnregisterEventHandlers();

            if (!_debugStateService.ExecutionCompleted)
            {
                _debugStateService.ExecutionCompleted = true;
                _debugService.Abort();

                if (_debugStateService.IsInteractiveDebugSession && _debugStateService.IsAttachSession)
                {
                    // Pop the sessions
                    if (_runspaceContext.CurrentRunspace.RunspaceOrigin == RunspaceOrigin.EnteredProcess)
                    {
                        try
                        {
                            await _executionService.ExecutePSCommandAsync(
                                new PSCommand().AddCommand("Exit-PSHostProcess"),
                                CancellationToken.None).ConfigureAwait(false);

                            if (_debugStateService.IsRemoteAttach &&
                                _runspaceContext.CurrentRunspace.RunspaceOrigin == RunspaceOrigin.EnteredProcess)
                            {
                                await _executionService.ExecutePSCommandAsync(
                                    new PSCommand().AddCommand("Exit-PSSession"),
                                    CancellationToken.None).ConfigureAwait(false);
                            }
                        }
                        catch (Exception e)
                        {
                            _logger.LogException("Caught exception while popping attached process after debugging", e);
                        }
                    }
                }
                _debugService.IsClientAttached = false;
            }

            _logger.LogInformation("Debug adapter is shutting down...");

#pragma warning disable CS4014
            // Trigger the clean up of the debugger. No need to wait for it nor cancel it.
            Task.Run(_psesDebugServer.OnSessionEnded, CancellationToken.None);
#pragma warning restore CS4014

            return(new DisconnectResponse());
        }
Пример #3
0
        public async Task <List <PSCommandMessage> > Handle(GetCommandParams request, CancellationToken cancellationToken)
        {
            PSCommand psCommand = new();

            // Executes the following:
            // Get-Command -CommandType Function,Cmdlet,ExternalScript | Sort-Object -Property Name
            psCommand
            .AddCommand("Microsoft.PowerShell.Core\\Get-Command")
            .AddParameter("CommandType", new[] { "Function", "Cmdlet", "ExternalScript" })
            .AddCommand("Microsoft.PowerShell.Utility\\Sort-Object")
            .AddParameter("Property", "Name");

            IEnumerable <CommandInfo> result = await _executionService.ExecutePSCommandAsync <CommandInfo>(psCommand, cancellationToken).ConfigureAwait(false);

            List <PSCommandMessage> commandList = new();

            if (result != null)
            {
                foreach (CommandInfo command in result)
                {
                    // Some info objects have a quicker way to get the DefaultParameterSet. These
                    // are also the most likely to show up so win-win.
                    string defaultParameterSet = null;
                    switch (command)
                    {
                    case CmdletInfo info:
                        defaultParameterSet = info.DefaultParameterSet;
                        break;

                    case FunctionInfo info:
                        defaultParameterSet = info.DefaultParameterSet;
                        break;
                    }

                    if (defaultParameterSet == null)
                    {
                        // Try to get the default ParameterSet if it isn't streamlined (ExternalScriptInfo for example)
                        foreach (CommandParameterSetInfo parameterSetInfo in command.ParameterSets)
                        {
                            if (parameterSetInfo.IsDefault)
                            {
                                defaultParameterSet = parameterSetInfo.Name;
                                break;
                            }
                        }
                    }

                    commandList.Add(new PSCommandMessage
                    {
                        Name                = command.Name,
                        ModuleName          = command.ModuleName,
                        Parameters          = command.Parameters,
                        ParameterSets       = command.ParameterSets,
                        DefaultParameterSet = defaultParameterSet
                    });
                }
            }

            return(commandList);
        }
        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
            });
        }
Пример #5
0
        public async Task <Unit> Handle(ShowHelpParams request, CancellationToken cancellationToken)
        {
            const string CheckHelpScript = @"
                [CmdletBinding()]
                param (
                    [String]$CommandName
                )
                try {
                    $command = Microsoft.PowerShell.Core\Get-Command $CommandName -ErrorAction Stop
                } catch [System.Management.Automation.CommandNotFoundException] {
                    $PSCmdlet.ThrowTerminatingError($PSItem)
                }
                try {
                    $helpUri = [Microsoft.PowerShell.Commands.GetHelpCodeMethods]::GetHelpUri($command)

                    $oldSslVersion = [System.Net.ServicePointManager]::SecurityProtocol
                    [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12

                    # HEAD means we don't need the content itself back, just the response header
                    $status = (Microsoft.PowerShell.Utility\Invoke-WebRequest -Method Head -Uri $helpUri -TimeoutSec 5 -ErrorAction Stop).StatusCode
                    if ($status -lt 400) {
                        $null = Microsoft.PowerShell.Core\Get-Help $CommandName -Online
                        return
                    }
                } catch {
                    # Ignore - we want to drop out to Get-Help -Full
                } finally {
                    [System.Net.ServicePointManager]::SecurityProtocol = $oldSslVersion
                }

                return Microsoft.PowerShell.Core\Get-Help $CommandName -Full
                ";

            string helpParams = request.Text;

            if (string.IsNullOrEmpty(helpParams))
            {
                helpParams = "Get-Help";
            }

            PSCommand checkHelpPSCommand = new PSCommand()
                                           .AddScript(CheckHelpScript, useLocalScope: true)
                                           .AddArgument(helpParams);

            // TODO: Rather than print the help in the console, we should send the string back
            //       to VSCode to display in a help pop-up (or similar)
            await _executionService.ExecutePSCommandAsync <PSObject>(checkHelpPSCommand, cancellationToken, new PowerShellExecutionOptions { WriteOutputToHost = true, ThrowOnError = false }).ConfigureAwait(false);

            return(Unit.Value);
        }
        public async Task <RunspaceResponse[]> Handle(GetRunspaceParams request, CancellationToken cancellationToken)
        {
            IEnumerable <PSObject> runspaces = null;

            if (request.ProcessId == null)
            {
                request.ProcessId = "current";
            }

            // If the processId is a valid int, we need to run Get-Runspace within that process
            // otherwise just use the current runspace.
            if (int.TryParse(request.ProcessId, out int pid))
            {
                // Create a remote runspace that we will invoke Get-Runspace in.
                using Runspace rs   = RunspaceFactory.CreateRunspace(new NamedPipeConnectionInfo(pid));
                using PowerShell ps = PowerShell.Create();
                rs.Open();
                ps.Runspace = rs;
                // Returns deserialized Runspaces. For simpler code, we use PSObject and rely on dynamic later.
                runspaces = ps.AddCommand("Microsoft.PowerShell.Utility\\Get-Runspace").Invoke <PSObject>();
            }
            else
            {
                PSCommand psCommand = new PSCommand().AddCommand("Microsoft.PowerShell.Utility\\Get-Runspace");
                // returns (not deserialized) Runspaces. For simpler code, we use PSObject and rely on dynamic later.
                runspaces = await _executionService.ExecutePSCommandAsync <PSObject>(psCommand, cancellationToken).ConfigureAwait(false);
            }

            List <RunspaceResponse> runspaceResponses = new();

            if (runspaces != null)
            {
                foreach (dynamic runspace in runspaces)
                {
                    runspaceResponses.Add(
                        new RunspaceResponse
                    {
                        Id           = runspace.Id,
                        Name         = runspace.Name,
                        Availability = runspace.RunspaceAvailability.ToString()
                    });
                }
            }

            return(runspaceResponses.ToArray());
        }
        public async Task <ExpandAliasResult> Handle(ExpandAliasParams request, CancellationToken cancellationToken)
        {
            const string script = @"
function __Expand-Alias {

    param($targetScript)

    [ref]$errors=$null

    $tokens = [System.Management.Automation.PsParser]::Tokenize($targetScript, $errors).Where({$_.type -eq 'command'}) |
                    Sort-Object Start -Descending

    foreach ($token in  $tokens) {
        $definition=(Get-Command ('`'+$token.Content) -CommandType Alias -ErrorAction SilentlyContinue).Definition

        if($definition) {
            $lhs=$targetScript.Substring(0, $token.Start)
            $rhs=$targetScript.Substring($token.Start + $token.Length)

            $targetScript=$lhs + $definition + $rhs
       }
    }

    $targetScript
}";

            // TODO: Refactor to not rerun the function definition every time.
            PSCommand psCommand = new();

            psCommand
            .AddScript(script)
            .AddStatement()
            .AddCommand("__Expand-Alias")
            .AddArgument(request.Text);
            System.Collections.Generic.IReadOnlyList <string> result = await _executionService.ExecutePSCommandAsync <string>(psCommand, cancellationToken).ConfigureAwait(false);

            return(new ExpandAliasResult
            {
                Text = result[0]
            });
        }
        public async Task <EvaluateResponseBody> Handle(EvaluateRequestArguments request, CancellationToken cancellationToken)
        {
            // This API is mostly used for F8 execution, so it needs to interrupt the command prompt
            // (or other foreground task).
            await _executionService.ExecutePSCommandAsync(
                new PSCommand().AddScript(request.Expression),
                CancellationToken.None,
                new PowerShellExecutionOptions
            {
                WriteInputToHost           = true,
                WriteOutputToHost          = true,
                AddToHistory               = true,
                ThrowOnError               = false,
                InterruptCurrentForeground = true
            }).ConfigureAwait(false);

            // TODO: Should we return a more informative result?
            return(new EvaluateResponseBody
            {
                Result = "",
                VariablesReference = 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>
        /// <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);
        }