@@ -25,9 +25,8 @@ public class AnalysisService : IDisposable
25
25
{
26
26
#region Private Fields
27
27
28
- private Runspace analysisRunspace ;
28
+ private RunspacePool analysisRunspacePool ;
29
29
private PSModuleInfo scriptAnalyzerModuleInfo ;
30
- private Object runspaceLock ;
31
30
private string [ ] activeRules ;
32
31
private string settingsPath ;
33
32
@@ -67,10 +66,7 @@ public string[] ActiveRules
67
66
68
67
set
69
68
{
70
- lock ( runspaceLock )
71
- {
72
- activeRules = value ;
73
- }
69
+ activeRules = value ;
74
70
}
75
71
}
76
72
@@ -86,10 +82,7 @@ public string SettingsPath
86
82
}
87
83
set
88
84
{
89
- lock ( runspaceLock )
90
- {
91
- settingsPath = value ;
92
- }
85
+ settingsPath = value ;
93
86
}
94
87
}
95
88
@@ -107,11 +100,21 @@ public AnalysisService(IConsoleHost consoleHost, string settingsPath = null)
107
100
{
108
101
try
109
102
{
110
- this . runspaceLock = new Object ( ) ;
111
103
this . SettingsPath = settingsPath ;
112
- this . analysisRunspace = RunspaceFactory . CreateRunspace ( InitialSessionState . CreateDefault2 ( ) ) ;
113
- this . analysisRunspace . ThreadOptions = PSThreadOptions . ReuseThread ;
114
- this . analysisRunspace . Open ( ) ;
104
+ var sessionState = InitialSessionState . CreateDefault2 ( ) ;
105
+
106
+ // import PSScriptAnalyzer in all runspaces
107
+ sessionState . ImportPSModule ( new string [ ] { "PSScriptAnalyzer" } ) ;
108
+
109
+ // runspacepool takes care of queuing commands for us so we do not
110
+ // need to worry about executing concurrent commands
111
+ this . analysisRunspacePool = RunspaceFactory . CreateRunspacePool ( sessionState ) ;
112
+
113
+ // having more than one runspace doesn't block code formatting if one
114
+ // runspace is occupied for diagnostics
115
+ this . analysisRunspacePool . SetMaxRunspaces ( 2 ) ;
116
+ this . analysisRunspacePool . ThreadOptions = PSThreadOptions . ReuseThread ;
117
+ this . analysisRunspacePool . Open ( ) ;
115
118
ActiveRules = IncludedRules . ToArray ( ) ;
116
119
InitializePSScriptAnalyzer ( ) ;
117
120
}
@@ -134,9 +137,9 @@ public AnalysisService(IConsoleHost consoleHost, string settingsPath = null)
134
137
/// </summary>
135
138
/// <param name="file">The ScriptFile which will be analyzed for semantic markers.</param>
136
139
/// <returns>An array of ScriptFileMarkers containing semantic analysis results.</returns>
137
- public ScriptFileMarker [ ] GetSemanticMarkers ( ScriptFile file )
140
+ public async Task < ScriptFileMarker [ ] > GetSemanticMarkersAsync ( ScriptFile file )
138
141
{
139
- return GetSemanticMarkers ( file , activeRules , settingsPath ) ;
142
+ return await GetSemanticMarkersAsync ( file , activeRules , settingsPath ) ;
140
143
}
141
144
142
145
/// <summary>
@@ -145,9 +148,9 @@ public ScriptFileMarker[] GetSemanticMarkers(ScriptFile file)
145
148
/// <param name="file">The ScriptFile to be analyzed.</param>
146
149
/// <param name="settings">ScriptAnalyzer settings</param>
147
150
/// <returns></returns>
148
- public ScriptFileMarker [ ] GetSemanticMarkers ( ScriptFile file , Hashtable settings )
151
+ public async Task < ScriptFileMarker [ ] > GetSemanticMarkersAsync ( ScriptFile file , Hashtable settings )
149
152
{
150
- return GetSemanticMarkers < Hashtable > ( file , null , settings ) ;
153
+ return await GetSemanticMarkersAsync < Hashtable > ( file , null , settings ) ;
151
154
}
152
155
153
156
/// <summary>
@@ -158,17 +161,10 @@ public IEnumerable<string> GetPSScriptAnalyzerRules()
158
161
List < string > ruleNames = new List < string > ( ) ;
159
162
if ( scriptAnalyzerModuleInfo != null )
160
163
{
161
- lock ( runspaceLock )
164
+ var ruleObjects = InvokePowerShell ( "Get-ScriptAnalyzerRule" , new Dictionary < string , object > ( ) ) ;
165
+ foreach ( var rule in ruleObjects )
162
166
{
163
- using ( var ps = System . Management . Automation . PowerShell . Create ( ) )
164
- {
165
- ps . Runspace = this . analysisRunspace ;
166
- var ruleObjects = ps . AddCommand ( "Get-ScriptAnalyzerRule" ) . Invoke ( ) ;
167
- foreach ( var rule in ruleObjects )
168
- {
169
- ruleNames . Add ( ( string ) rule . Members [ "RuleName" ] . Value ) ;
170
- }
171
- }
167
+ ruleNames . Add ( ( string ) rule . Members [ "RuleName" ] . Value ) ;
172
168
}
173
169
}
174
170
@@ -201,19 +197,19 @@ public Hashtable GetPSSASettingsHashtable(IDictionary<string, Hashtable> ruleSet
201
197
/// </summary>
202
198
public void Dispose ( )
203
199
{
204
- if ( this . analysisRunspace != null )
200
+ if ( this . analysisRunspacePool != null )
205
201
{
206
- this . analysisRunspace . Close ( ) ;
207
- this . analysisRunspace . Dispose ( ) ;
208
- this . analysisRunspace = null ;
202
+ this . analysisRunspacePool . Close ( ) ;
203
+ this . analysisRunspacePool . Dispose ( ) ;
204
+ this . analysisRunspacePool = null ;
209
205
}
210
206
}
211
207
212
208
#endregion // public methods
213
209
214
210
#region Private Methods
215
211
216
- private ScriptFileMarker [ ] GetSemanticMarkers < TSettings > (
212
+ private async Task < ScriptFileMarker [ ] > GetSemanticMarkersAsync < TSettings > (
217
213
ScriptFile file ,
218
214
string [ ] rules ,
219
215
TSettings settings ) where TSettings : class
@@ -223,23 +219,8 @@ private ScriptFileMarker[] GetSemanticMarkers<TSettings>(
223
219
&& ( typeof ( TSettings ) == typeof ( string ) || typeof ( TSettings ) == typeof ( Hashtable ) )
224
220
&& ( rules != null || settings != null ) )
225
221
{
226
- // TODO: This is a temporary fix until we can change how
227
- // ScriptAnalyzer invokes their async tasks.
228
- // TODO: Make this async
229
- Task < ScriptFileMarker [ ] > analysisTask =
230
- Task . Factory . StartNew < ScriptFileMarker [ ] > (
231
- ( ) =>
232
- {
233
- return
234
- GetDiagnosticRecords ( file , rules , settings )
235
- . Select ( ScriptFileMarker . FromDiagnosticRecord )
236
- . ToArray ( ) ;
237
- } ,
238
- CancellationToken . None ,
239
- TaskCreationOptions . None ,
240
- TaskScheduler . Default ) ;
241
- analysisTask . Wait ( ) ;
242
- return analysisTask . Result ;
222
+ var scriptFileMarkers = await GetDiagnosticRecordsAsync ( file , rules , settings ) ;
223
+ return scriptFileMarkers . Select ( ScriptFileMarker . FromDiagnosticRecord ) . ToArray ( ) ;
243
224
}
244
225
else
245
226
{
@@ -250,64 +231,53 @@ private ScriptFileMarker[] GetSemanticMarkers<TSettings>(
250
231
251
232
private void FindPSScriptAnalyzer ( )
252
233
{
253
- lock ( runspaceLock )
254
- {
255
- using ( var ps = System . Management . Automation . PowerShell . Create ( ) )
234
+ var modules = InvokePowerShell (
235
+ "Get-Module" ,
236
+ new Dictionary < string , object >
256
237
{
257
- ps . Runspace = this . analysisRunspace ;
258
-
259
- var modules = ps . AddCommand ( "Get-Module" )
260
- . AddParameter ( "List" )
261
- . AddParameter ( "Name" , "PSScriptAnalyzer" )
262
- . Invoke ( ) ;
263
-
264
- var psModule = modules == null ? null : modules . FirstOrDefault ( ) ;
265
- if ( psModule != null )
266
- {
267
- scriptAnalyzerModuleInfo = psModule . ImmediateBaseObject as PSModuleInfo ;
268
- Logger . Write (
269
- LogLevel . Normal ,
270
- string . Format (
271
- "PSScriptAnalyzer found at {0}" ,
272
- scriptAnalyzerModuleInfo . Path ) ) ;
273
- }
274
- else
275
- {
276
- Logger . Write (
277
- LogLevel . Normal ,
278
- "PSScriptAnalyzer module was not found." ) ;
279
- }
280
- }
238
+ { "ListAvailable" , true } ,
239
+ { "Name" , "PSScriptAnalyzer" }
240
+ } ) ;
241
+ var psModule = modules . Count ( ) == 0 ? null : modules . FirstOrDefault ( ) ;
242
+ if ( psModule != null )
243
+ {
244
+ scriptAnalyzerModuleInfo = psModule . ImmediateBaseObject as PSModuleInfo ;
245
+ Logger . Write (
246
+ LogLevel . Normal ,
247
+ string . Format (
248
+ "PSScriptAnalyzer found at {0}" ,
249
+ scriptAnalyzerModuleInfo . Path ) ) ;
250
+ }
251
+ else
252
+ {
253
+ Logger . Write (
254
+ LogLevel . Normal ,
255
+ "PSScriptAnalyzer module was not found." ) ;
281
256
}
282
257
}
283
258
284
259
private void ImportPSScriptAnalyzer ( )
285
260
{
286
261
if ( scriptAnalyzerModuleInfo != null )
287
262
{
288
- lock ( runspaceLock )
289
- {
290
- using ( var ps = System . Management . Automation . PowerShell . Create ( ) )
263
+ var module = InvokePowerShell (
264
+ "Import-Module" ,
265
+ new Dictionary < string , object >
291
266
{
292
- ps . Runspace = this . analysisRunspace ;
293
-
294
- var module = ps . AddCommand ( "Import-Module" )
295
- . AddParameter ( "ModuleInfo" , scriptAnalyzerModuleInfo )
296
- . AddParameter ( "PassThru" )
297
- . Invoke ( ) ;
298
-
299
- if ( module == null )
300
- {
301
- this . scriptAnalyzerModuleInfo = null ;
302
- Logger . Write ( LogLevel . Warning ,
303
- String . Format ( "Cannot Import PSScriptAnalyzer: {0}" ) ) ;
304
- }
305
- else
306
- {
307
- Logger . Write ( LogLevel . Normal ,
308
- String . Format ( "Successfully imported PSScriptAnalyzer" ) ) ;
309
- }
310
- }
267
+ { "ModuleInfo" , scriptAnalyzerModuleInfo } ,
268
+ { "PassThru" , true } ,
269
+ } ) ;
270
+
271
+ if ( module . Count ( ) == 0 )
272
+ {
273
+ this . scriptAnalyzerModuleInfo = null ;
274
+ Logger . Write ( LogLevel . Warning ,
275
+ String . Format ( "Cannot Import PSScriptAnalyzer: {0}" ) ) ;
276
+ }
277
+ else
278
+ {
279
+ Logger . Write ( LogLevel . Normal ,
280
+ String . Format ( "Successfully imported PSScriptAnalyzer" ) ) ;
311
281
}
312
282
}
313
283
}
@@ -331,54 +301,47 @@ private void EnumeratePSScriptAnalyzerRules()
331
301
private void InitializePSScriptAnalyzer ( )
332
302
{
333
303
FindPSScriptAnalyzer ( ) ;
304
+
305
+ // this import is redundant if we are importing the
306
+ // module while creating the runspace, but it helps
307
+ // us log the import related messages.
334
308
ImportPSScriptAnalyzer ( ) ;
335
- EnumeratePSScriptAnalyzerRules ( ) ;
336
- }
337
309
338
- private IEnumerable < PSObject > GetDiagnosticRecords ( ScriptFile file )
339
- {
340
- return GetDiagnosticRecords ( file , this . activeRules , this . settingsPath ) ;
310
+ EnumeratePSScriptAnalyzerRules ( ) ;
341
311
}
342
312
343
- // TSettings can either be of type Hashtable or string
344
- // as scriptanalyzer settings parameter takes either a hashtable or string
345
- private IEnumerable < PSObject > GetDiagnosticRecords < TSettings > (
313
+ private async Task < IEnumerable < PSObject > > GetDiagnosticRecordsAsync < TSettings > (
346
314
ScriptFile file ,
347
315
string [ ] rules ,
348
- TSettings settings ) where TSettings : class
316
+ TSettings settings ) where TSettings : class
349
317
{
350
318
IEnumerable < PSObject > diagnosticRecords = Enumerable . Empty < PSObject > ( ) ;
351
319
352
320
if ( this . scriptAnalyzerModuleInfo != null
353
321
&& ( typeof ( TSettings ) == typeof ( string )
354
322
|| typeof ( TSettings ) == typeof ( Hashtable ) ) )
355
323
{
356
- lock ( runspaceLock )
324
+ //Use a settings file if one is provided, otherwise use the default rule list.
325
+ string settingParameter ;
326
+ object settingArgument ;
327
+ if ( settings != null )
357
328
{
358
- using ( var powerShell = System . Management . Automation . PowerShell . Create ( ) )
359
- {
360
- powerShell . Runspace = this . analysisRunspace ;
361
- Logger . Write (
362
- LogLevel . Verbose ,
363
- String . Format ( "Running PSScriptAnalyzer against {0}" , file . FilePath ) ) ;
364
-
365
- powerShell
366
- . AddCommand ( "Invoke-ScriptAnalyzer" )
367
- . AddParameter ( "ScriptDefinition" , file . Contents ) ;
368
-
369
- // Use a settings file if one is provided, otherwise use the default rule list.
370
- if ( settings != null )
371
- {
372
- powerShell . AddParameter ( "Settings" , settings ) ;
373
- }
374
- else
375
- {
376
- powerShell . AddParameter ( "IncludeRule" , rules ) ;
377
- }
378
-
379
- diagnosticRecords = powerShell . Invoke ( ) ;
380
- }
329
+ settingParameter = "Settings" ;
330
+ settingArgument = settings ;
381
331
}
332
+ else
333
+ {
334
+ settingParameter = "IncludeRule" ;
335
+ settingArgument = rules ;
336
+ }
337
+
338
+ diagnosticRecords = await InvokePowerShellAsync (
339
+ "Invoke-ScriptAnalyzer" ,
340
+ new Dictionary < string , object >
341
+ {
342
+ { "ScriptDefinition" , file . Contents } ,
343
+ { settingParameter , settingArgument }
344
+ } ) ;
382
345
}
383
346
384
347
Logger . Write (
@@ -388,6 +351,34 @@ private IEnumerable<PSObject> GetDiagnosticRecords<TSettings>(
388
351
return diagnosticRecords ;
389
352
}
390
353
354
+ private IEnumerable < PSObject > InvokePowerShell ( string command , IDictionary < string , object > paramArgMap )
355
+ {
356
+ var task = InvokePowerShellAsync ( command , paramArgMap ) ;
357
+ task . Wait ( ) ;
358
+ return task . Result ;
359
+ }
360
+
361
+ private async Task < IEnumerable < PSObject > > InvokePowerShellAsync ( string command , IDictionary < string , object > paramArgMap )
362
+ {
363
+ using ( var powerShell = System . Management . Automation . PowerShell . Create ( ) )
364
+ {
365
+ powerShell . RunspacePool = this . analysisRunspacePool ;
366
+ powerShell . AddCommand ( command ) ;
367
+ foreach ( var kvp in paramArgMap )
368
+ {
369
+ powerShell . AddParameter ( kvp . Key , kvp . Value ) ;
370
+ }
371
+
372
+ var result = await Task . Factory . FromAsync ( powerShell . BeginInvoke ( ) , powerShell . EndInvoke ) ;
373
+ if ( result == null )
374
+ {
375
+ return Enumerable . Empty < PSObject > ( ) ;
376
+ }
377
+
378
+ return result ;
379
+ }
380
+ }
381
+
391
382
#endregion //private methods
392
383
}
393
384
}
0 commit comments