Skip to content

Delay launch/attach until after config done #2250

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,238 @@ namespace Microsoft.PowerShell.EditorServices.Services
{
internal class BreakpointService
{
private const string _getPSBreakpointLegacy = @"
[CmdletBinding()]
param (
[Parameter()]
[string]
$Script,

[Parameter()]
[int]
$RunspaceId = [Runspace]::DefaultRunspace.Id
)

$runspace = if ($PSBoundParameters.ContainsKey('RunspaceId')) {
Get-Runspace -Id $RunspaceId
$null = $PSBoundParameters.Remove('RunspaceId')
}
else {
[Runspace]::DefaultRunspace
}

$debugger = $runspace.Debugger
$getBreakpointsMeth = $debugger.GetType().GetMethod(
'GetBreakpoints',
[System.Reflection.BindingFlags]'NonPublic, Public, Instance',
$null,
[type[]]@(),
$null)

$runspaceIdProp = [System.Management.Automation.PSNoteProperty]::new(
'RunspaceId',
$runspaceId)

@(
if (-not $getBreakpointsMeth) {
if ($RunspaceId -ne [Runspace]::DefaultRunspace.Id) {
throw 'Failed to find GetBreakpoints method on Debugger.'
}

Microsoft.PowerShell.Utility\Get-PSBreakpoint @PSBoundParameters
}
else {
$getBreakpointsMeth.Invoke($debugger, @()) | Where-Object {
if ($Script) {
$_.Script -eq $Script
}
else {
$true
}
}
}
) | ForEach-Object {
$_.PSObject.Properties.Add($runspaceIdProp)
$_
}
";

private const string _removePSBreakpointLegacy = @"
[CmdletBinding(DefaultParameterSetName = 'Breakpoint')]
param (
[Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Breakpoint')]
[System.Management.Automation.Breakpoint[]]
$Breakpoint,

[Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Id')]
[int[]]
$Id,

[Parameter(ParameterSetName = 'Id')]
[int]
$RunspaceId = [Runspace]::DefaultRunspace.Id
)

begin {
$removeBreakpointMeth = [Runspace]::DefaultRunspace.Debugger.GetType().GetMethod(
'RemoveBreakpoint',
[System.Reflection.BindingFlags]'NonPublic, Public, Instance',
$null,
[type[]]@([System.Management.Automation.Breakpoint]),
$null)
$getBreakpointMeth = [Runspace]::DefaultRunspace.Debugger.GetType().GetMethod(
'GetBreakpoint',
[System.Reflection.BindingFlags]'NonPublic, Public, Instance',
$null,
[type[]]@([int]),
$null)

$breakpointCollection = [System.Collections.Generic.List[System.Management.Automation.Breakpoint]]::new()
}

process {
if ($PSCmdlet.ParameterSetName -eq 'Id') {
$runspace = Get-Runspace -Id $RunspaceId
$runspaceProp = [System.Management.Automation.PSNoteProperty]::new(
'Runspace',
$Runspace)

$breakpoints = if ($getBreakpointMeth) {
foreach ($breakpointId in $Id) {
$getBreakpointMeth.Invoke($runspace.Debugger, @($breakpointId))
}
}
elseif ($runspace -eq [Runspace]::DefaultRunspace) {
Microsoft.PowerShell.Utility\Get-PSBreakpoint -Id $Id
}
else {
throw 'Failed to find GetBreakpoint method on Debugger.'
}

$breakpoints | ForEach-Object {
$_.PSObject.Properties.Add($runspaceProp)
$breakpointCollection.Add($_)
}
}
else {
foreach ($b in $Breakpoint) {
# RunspaceId may be set by _getPSBreakpointLegacy when
# targeting a breakpoint in a specific runspace.
$runspace = if ($b.PSObject.Properties.Match('RunspaceId')) {
Get-Runspace -Id $b.RunspaceId
}
else {
[Runspace]::DefaultRunspace
}

$b.PSObject.Properties.Add(
[System.Management.Automation.PSNoteProperty]::new('Runspace', $runspace))
$breakpointCollection.Add($b)
}
}
}

end {
foreach ($b in $breakpointCollection) {
if ($removeBreakpointMeth) {
$removeBreakpointMeth.Invoke($b.Runspace.Debugger, @($b))
}
elseif ($b.Runspace -eq [Runspace]::DefaultRunspace) {
# If we don't have the method, we can only remove breakpoints
# from the default runspace using Remove-PSBreakpoint.
$b | Microsoft.PowerShell.Utility\Remove-PSBreakpoint
}
else {
throw 'Failed to find RemoveBreakpoint method on Debugger.'
}
}
}
";

/// <summary>
/// Code used on WinPS 5.1 to set breakpoints without Script path validation.
/// It uses reflection because the APIs were not public until 7.0 but just in
/// case something changes it has a fallback to Set-PSBreakpoint.
/// </summary>
private const string _setPSBreakpointLegacy = @"
[CmdletBinding(DefaultParameterSetName = 'Line')]
param (
[Parameter()]
[ScriptBlock]
$Action,

[Parameter(ParameterSetName = 'Command')]
[Parameter(ParameterSetName = 'Line', Mandatory = $true)]
[string]
$Script,

[Parameter(ParameterSetName = 'Line')]
[int]
$Line,

[Parameter(ParameterSetName = 'Line')]
[int]
$Column,

[Parameter(ParameterSetName = 'Command', Mandatory = $true)]
[string]
$Command,

[Parameter()]
[int]
$RunspaceId
)

if ($PSCmdlet.ParameterSetName -eq 'Command') {
$cmdCtor = [System.Management.Automation.CommandBreakpoint].GetConstructor(
[System.Reflection.BindingFlags]'NonPublic, Public, Instance',
$null,
[type[]]@([string], [System.Management.Automation.WildcardPattern], [string], [ScriptBlock]),
$null)

if (-not $cmdCtor) {
if ($PSBoundParameters.ContainsKey('RunspaceId')) {
throw 'Failed to find constructor for CommandBreakpoint.'
}
Microsoft.PowerShell.Utility\Set-PSBreakpoint @PSBoundParameters
return
}

$pattern = [System.Management.Automation.WildcardPattern]::Get(
$Command,
[System.Management.Automation.WildcardOptions]'Compiled, IgnoreCase')
$b = $cmdCtor.Invoke(@($Script, $pattern, $Command, $Action))
}
else {
$lineCtor = [System.Management.Automation.LineBreakpoint].GetConstructor(
[System.Reflection.BindingFlags]'NonPublic, Public, Instance',
$null,
[type[]]@([string], [int], [int], [ScriptBlock]),
$null)

if (-not $lineCtor) {
if ($PSBoundParameters.ContainsKey('RunspaceId')) {
throw 'Failed to find constructor for LineBreakpoint.'
}
Microsoft.PowerShell.Utility\Set-PSBreakpoint @PSBoundParameters
return
}

$b = $lineCtor.Invoke(@($Script, $Line, $Column, $Action))
}

$runspace = if ($PSBoundParameters.ContainsKey('RunspaceId')) {
Get-Runspace -Id $RunspaceId
}
else {
[Runspace]::DefaultRunspace
}

$runspace.Debugger.SetBreakpoints([System.Management.Automation.Breakpoint[]]@($b))

$b
";

private readonly ILogger<BreakpointService> _logger;
private readonly IInternalPowerShellExecutionService _executionService;
private readonly PsesInternalHost _editorServicesHost;
Expand Down Expand Up @@ -51,10 +283,14 @@ public async Task<IReadOnlyList<Breakpoint>> GetBreakpointsAsync()
}

// Legacy behavior
PSCommand psCommand = new PSCommand().AddCommand(@"Microsoft.PowerShell.Utility\Get-PSBreakpoint");
PSCommand psCommand = new PSCommand().AddScript(_getPSBreakpointLegacy, useLocalScope: true);
if (_debugStateService.RunspaceId is not null)
{
psCommand.AddParameter("RunspaceId", _debugStateService.RunspaceId.Value);
}
return await _executionService
.ExecutePSCommandAsync<Breakpoint>(psCommand, CancellationToken.None)
.ConfigureAwait(false);
.ExecutePSCommandAsync<Breakpoint>(psCommand, CancellationToken.None)
.ConfigureAwait(false);
}

public async Task<IReadOnlyList<BreakpointDetails>> SetBreakpointsAsync(string escapedScriptPath, IReadOnlyList<BreakpointDetails> breakpoints)
Expand Down Expand Up @@ -114,8 +350,10 @@ public async Task<IReadOnlyList<BreakpointDetails>> SetBreakpointsAsync(string e
psCommand.AddStatement();
}

// Don't use Set-PSBreakpoint as that will try and validate the Script
// path which may or may not exist.
psCommand
.AddCommand(@"Microsoft.PowerShell.Utility\Set-PSBreakpoint")
.AddScript(_setPSBreakpointLegacy, useLocalScope: true)
.AddParameter("Script", escapedScriptPath)
.AddParameter("Line", breakpoint.LineNumber);

Expand All @@ -132,6 +370,11 @@ public async Task<IReadOnlyList<BreakpointDetails>> SetBreakpointsAsync(string e
{
psCommand.AddParameter("Action", actionScriptBlock);
}

if (_debugStateService.RunspaceId is not null)
{
psCommand.AddParameter("RunspaceId", _debugStateService.RunspaceId.Value);
}
}

// If no PSCommand was created then there are no breakpoints to set.
Expand Down Expand Up @@ -184,7 +427,7 @@ public async Task<IReadOnlyList<CommandBreakpointDetails>> SetCommandBreakpoints
}

psCommand
.AddCommand(@"Microsoft.PowerShell.Utility\Set-PSBreakpoint")
.AddScript(_setPSBreakpointLegacy, useLocalScope: true)
.AddParameter("Command", breakpoint.Name);

// Check if this is a "conditional" line breakpoint.
Expand Down Expand Up @@ -247,14 +490,17 @@ public async Task RemoveAllBreakpointsAsync(string scriptPath = null)
}

// Legacy behavior
PSCommand psCommand = new PSCommand().AddCommand(@"Microsoft.PowerShell.Utility\Get-PSBreakpoint");

PSCommand psCommand = new PSCommand().AddScript(_getPSBreakpointLegacy, useLocalScope: true);
if (_debugStateService.RunspaceId is not null)
{
psCommand.AddParameter("RunspaceId", _debugStateService.RunspaceId.Value);
}
if (!string.IsNullOrEmpty(scriptPath))
{
psCommand.AddParameter("Script", scriptPath);
}

psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Remove-PSBreakpoint");
psCommand.AddScript(_removePSBreakpointLegacy, useLocalScope: true);
await _executionService.ExecutePSCommandAsync<object>(psCommand, CancellationToken.None).ConfigureAwait(false);
}
catch (Exception e)
Expand Down Expand Up @@ -290,8 +536,12 @@ public async Task RemoveBreakpointsAsync(IEnumerable<Breakpoint> breakpoints)
if (breakpointIds.Any())
{
PSCommand psCommand = new PSCommand()
.AddCommand(@"Microsoft.PowerShell.Utility\Remove-PSBreakpoint")
.AddScript(_removePSBreakpointLegacy, useLocalScope: true)
.AddParameter("Id", breakpoints.Select(b => b.Id).ToArray());
if (_debugStateService.RunspaceId is not null)
{
psCommand.AddParameter("RunspaceId", _debugStateService.RunspaceId.Value);
}
await _executionService.ExecutePSCommandAsync<object>(psCommand, CancellationToken.None).ConfigureAwait(false);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,6 @@ private void OnRunspaceChanged(object sender, RunspaceChangedEventArgs e)
{
switch (e.ChangeAction)
{
case RunspaceChangeAction.Enter:
if (_debugStateService.WaitingForAttach
&& e.NewRunspace.RunspaceOrigin == RunspaceOrigin.DebuggedRunspace)
{
// Sends the InitializedEvent so that the debugger will continue
// sending configuration requests
_debugStateService.WaitingForAttach = false;
_debugStateService.ServerStarted.TrySetResult(true);
}
return;

case RunspaceChangeAction.Exit:
if (_debugContext.IsStopped)
{
Expand Down
Loading