10
10
using System . Management . Automation ;
11
11
using System . Management . Automation . Language ;
12
12
using System . Reflection ;
13
+ using System . Text ;
13
14
using System . Threading ;
14
15
using Microsoft . Extensions . Logging ;
15
16
@@ -102,39 +103,29 @@ static BreakpointApiUtils()
102
103
103
104
#region Public Static Methods
104
105
105
- public static IEnumerable < Breakpoint > SetBreakpoints ( Debugger debugger , IEnumerable < BreakpointDetailsBase > breakpoints )
106
+ public static Breakpoint SetBreakpoint ( Debugger debugger , BreakpointDetailsBase breakpoint )
106
107
{
107
- var psBreakpoints = new List < Breakpoint > ( breakpoints . Count ( ) ) ;
108
-
109
- foreach ( BreakpointDetailsBase breakpoint in breakpoints )
108
+ ScriptBlock actionScriptBlock = null ;
109
+ string logMessage = breakpoint is BreakpointDetails bd ? bd . LogMessage : null ;
110
+ // Check if this is a "conditional" line breakpoint.
111
+ if ( ! string . IsNullOrWhiteSpace ( breakpoint . Condition ) ||
112
+ ! string . IsNullOrWhiteSpace ( breakpoint . HitCondition ) ||
113
+ ! string . IsNullOrWhiteSpace ( logMessage ) )
110
114
{
111
- ScriptBlock actionScriptBlock = null ;
112
- // Check if this is a "conditional" line breakpoint.
113
- if ( ! string . IsNullOrWhiteSpace ( breakpoint . Condition ) ||
114
- ! string . IsNullOrWhiteSpace ( breakpoint . HitCondition ) )
115
- {
116
- actionScriptBlock = GetBreakpointActionScriptBlock ( breakpoint ) ;
117
- }
118
-
119
- Breakpoint psBreakpoint ;
120
- switch ( breakpoint )
121
- {
122
- case BreakpointDetails lineBreakpoint :
123
- psBreakpoint = SetLineBreakpointDelegate ( debugger , lineBreakpoint . Source , lineBreakpoint . LineNumber , lineBreakpoint . ColumnNumber ?? 0 , actionScriptBlock ) ;
124
- break ;
115
+ actionScriptBlock = GetBreakpointActionScriptBlock ( breakpoint . Condition , breakpoint . HitCondition , logMessage ) ;
116
+ }
125
117
126
- case CommandBreakpointDetails commandBreakpoint :
127
- psBreakpoint = SetCommandBreakpointDelegate ( debugger , commandBreakpoint . Name , null , null ) ;
128
- break ;
118
+ switch ( breakpoint )
119
+ {
120
+ case BreakpointDetails lineBreakpoint :
121
+ return SetLineBreakpointDelegate ( debugger , lineBreakpoint . Source , lineBreakpoint . LineNumber , lineBreakpoint . ColumnNumber ?? 0 , actionScriptBlock ) ;
129
122
130
- default :
131
- throw new NotImplementedException ( "Other breakpoints not supported yet" ) ;
132
- }
123
+ case CommandBreakpointDetails commandBreakpoint :
124
+ return SetCommandBreakpointDelegate ( debugger , commandBreakpoint . Name , null , null ) ;
133
125
134
- psBreakpoints . Add ( psBreakpoint ) ;
126
+ default :
127
+ throw new NotImplementedException ( "Other breakpoints not supported yet" ) ;
135
128
}
136
-
137
- return psBreakpoints ;
138
129
}
139
130
140
131
public static List < Breakpoint > GetBreakpoints ( Debugger debugger )
@@ -147,6 +138,65 @@ public static bool RemoveBreakpoint(Debugger debugger, Breakpoint breakpoint)
147
138
return RemoveBreakpointDelegate ( debugger , breakpoint ) ;
148
139
}
149
140
141
+ public static ScriptBlock GetBreakpointActionScriptBlock ( string condition , string hitCondition , string logMessage )
142
+ {
143
+ StringBuilder builder = new StringBuilder (
144
+ string . IsNullOrEmpty ( logMessage )
145
+ ? "break"
146
+ : $ "Microsoft.PowerShell.Utility\\ Write-Host '{ logMessage } '") ;
147
+
148
+ // If HitCondition specified, parse and verify it.
149
+ if ( ! ( string . IsNullOrWhiteSpace ( hitCondition ) ) )
150
+ {
151
+ if ( ! int . TryParse ( hitCondition , out int parsedHitCount ) )
152
+ {
153
+ throw new InvalidOperationException ( "Hit Count was not a valid integer." ) ;
154
+ }
155
+
156
+ if ( string . IsNullOrWhiteSpace ( condition ) )
157
+ {
158
+ // In the HitCount only case, this is simple as we can just use the HitCount
159
+ // property on the breakpoint object which is represented by $_.
160
+ builder . Insert ( 0 , $ "if ($_.HitCount -eq { parsedHitCount } ) {{ ")
161
+ . Append ( " }}" ) ;
162
+ }
163
+
164
+ Interlocked . Increment ( ref breakpointHitCounter ) ;
165
+
166
+ string globalHitCountVarName =
167
+ $ "$global:{ s_psesGlobalVariableNamePrefix } BreakHitCounter_{ breakpointHitCounter } ";
168
+
169
+ builder . Insert ( 0 , $ "if (++{ globalHitCountVarName } -eq { parsedHitCount } ) {{ ")
170
+ . Append ( " }}" ) ;
171
+ }
172
+
173
+ if ( ! string . IsNullOrWhiteSpace ( condition ) )
174
+ {
175
+ ScriptBlock parsed = ScriptBlock . Create ( condition ) ;
176
+
177
+ // Check for simple, common errors that ScriptBlock parsing will not catch
178
+ // e.g. $i == 3 and $i > 3
179
+ if ( ! ValidateBreakpointConditionAst ( parsed . Ast , out string message ) )
180
+ {
181
+ throw new InvalidOperationException ( message ) ;
182
+ }
183
+
184
+ // Check for "advanced" condition syntax i.e. if the user has specified
185
+ // a "break" or "continue" statement anywhere in their scriptblock,
186
+ // pass their scriptblock through to the Action parameter as-is.
187
+ if ( parsed . Ast . Find ( ast =>
188
+ ( ast is BreakStatementAst || ast is ContinueStatementAst ) , true ) != null )
189
+ {
190
+ return parsed ;
191
+ }
192
+
193
+ builder . Insert ( 0 , $ "if ({ condition } ) {{ ")
194
+ . Append ( " }}" ) ;
195
+ }
196
+
197
+ return ScriptBlock . Create ( builder . ToString ( ) ) ;
198
+ }
199
+
150
200
/// <summary>
151
201
/// Inspects the condition, putting in the appropriate scriptblock template
152
202
/// "if (expression) { break }". If errors are found in the condition, the
0 commit comments