@@ -25,9 +25,8 @@ public class AnalysisService : IDisposable
2525 {
2626 #region Private Fields
2727
28- private Runspace analysisRunspace ;
28+ private RunspacePool analysisRunspacePool ;
2929 private PSModuleInfo scriptAnalyzerModuleInfo ;
30- private Object runspaceLock ;
3130 private string [ ] activeRules ;
3231 private string settingsPath ;
3332
@@ -67,10 +66,7 @@ public string[] ActiveRules
6766
6867 set
6968 {
70- lock ( runspaceLock )
71- {
72- activeRules = value ;
73- }
69+ activeRules = value ;
7470 }
7571 }
7672
@@ -86,10 +82,7 @@ public string SettingsPath
8682 }
8783 set
8884 {
89- lock ( runspaceLock )
90- {
91- settingsPath = value ;
92- }
85+ settingsPath = value ;
9386 }
9487 }
9588
@@ -107,11 +100,21 @@ public AnalysisService(IConsoleHost consoleHost, string settingsPath = null)
107100 {
108101 try
109102 {
110- this . runspaceLock = new Object ( ) ;
111103 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 ( ) ;
115118 ActiveRules = IncludedRules . ToArray ( ) ;
116119 InitializePSScriptAnalyzer ( ) ;
117120 }
@@ -134,9 +137,9 @@ public AnalysisService(IConsoleHost consoleHost, string settingsPath = null)
134137 /// </summary>
135138 /// <param name="file">The ScriptFile which will be analyzed for semantic markers.</param>
136139 /// <returns>An array of ScriptFileMarkers containing semantic analysis results.</returns>
137- public ScriptFileMarker [ ] GetSemanticMarkers ( ScriptFile file )
140+ public async Task < ScriptFileMarker [ ] > GetSemanticMarkersAsync ( ScriptFile file )
138141 {
139- return GetSemanticMarkers ( file , activeRules , settingsPath ) ;
142+ return await GetSemanticMarkersAsync ( file , activeRules , settingsPath ) ;
140143 }
141144
142145 /// <summary>
@@ -145,9 +148,9 @@ public ScriptFileMarker[] GetSemanticMarkers(ScriptFile file)
145148 /// <param name="file">The ScriptFile to be analyzed.</param>
146149 /// <param name="settings">ScriptAnalyzer settings</param>
147150 /// <returns></returns>
148- public ScriptFileMarker [ ] GetSemanticMarkers ( ScriptFile file , Hashtable settings )
151+ public async Task < ScriptFileMarker [ ] > GetSemanticMarkersAsync ( ScriptFile file , Hashtable settings )
149152 {
150- return GetSemanticMarkers < Hashtable > ( file , null , settings ) ;
153+ return await GetSemanticMarkersAsync < Hashtable > ( file , null , settings ) ;
151154 }
152155
153156 /// <summary>
@@ -158,17 +161,10 @@ public IEnumerable<string> GetPSScriptAnalyzerRules()
158161 List < string > ruleNames = new List < string > ( ) ;
159162 if ( scriptAnalyzerModuleInfo != null )
160163 {
161- lock ( runspaceLock )
164+ var ruleObjects = InvokePowerShell ( "Get-ScriptAnalyzerRule" , new Dictionary < string , object > ( ) ) ;
165+ foreach ( var rule in ruleObjects )
162166 {
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 ) ;
172168 }
173169 }
174170
@@ -201,19 +197,19 @@ public Hashtable GetPSSASettingsHashtable(IDictionary<string, Hashtable> ruleSet
201197 /// </summary>
202198 public void Dispose ( )
203199 {
204- if ( this . analysisRunspace != null )
200+ if ( this . analysisRunspacePool != null )
205201 {
206- this . analysisRunspace . Close ( ) ;
207- this . analysisRunspace . Dispose ( ) ;
208- this . analysisRunspace = null ;
202+ this . analysisRunspacePool . Close ( ) ;
203+ this . analysisRunspacePool . Dispose ( ) ;
204+ this . analysisRunspacePool = null ;
209205 }
210206 }
211207
212208 #endregion // public methods
213209
214210 #region Private Methods
215211
216- private ScriptFileMarker [ ] GetSemanticMarkers < TSettings > (
212+ private async Task < ScriptFileMarker [ ] > GetSemanticMarkersAsync < TSettings > (
217213 ScriptFile file ,
218214 string [ ] rules ,
219215 TSettings settings ) where TSettings : class
@@ -223,23 +219,8 @@ private ScriptFileMarker[] GetSemanticMarkers<TSettings>(
223219 && ( typeof ( TSettings ) == typeof ( string ) || typeof ( TSettings ) == typeof ( Hashtable ) )
224220 && ( rules != null || settings != null ) )
225221 {
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 ( ) ;
243224 }
244225 else
245226 {
@@ -250,64 +231,53 @@ private ScriptFileMarker[] GetSemanticMarkers<TSettings>(
250231
251232 private void FindPSScriptAnalyzer ( )
252233 {
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 >
256237 {
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." ) ;
281256 }
282257 }
283258
284259 private void ImportPSScriptAnalyzer ( )
285260 {
286261 if ( scriptAnalyzerModuleInfo != null )
287262 {
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 >
291266 {
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" ) ) ;
311281 }
312282 }
313283 }
@@ -331,54 +301,47 @@ private void EnumeratePSScriptAnalyzerRules()
331301 private void InitializePSScriptAnalyzer ( )
332302 {
333303 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.
334308 ImportPSScriptAnalyzer ( ) ;
335- EnumeratePSScriptAnalyzerRules ( ) ;
336- }
337309
338- private IEnumerable < PSObject > GetDiagnosticRecords ( ScriptFile file )
339- {
340- return GetDiagnosticRecords ( file , this . activeRules , this . settingsPath ) ;
310+ EnumeratePSScriptAnalyzerRules ( ) ;
341311 }
342312
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 > (
346314 ScriptFile file ,
347315 string [ ] rules ,
348- TSettings settings ) where TSettings : class
316+ TSettings settings ) where TSettings : class
349317 {
350318 IEnumerable < PSObject > diagnosticRecords = Enumerable . Empty < PSObject > ( ) ;
351319
352320 if ( this . scriptAnalyzerModuleInfo != null
353321 && ( typeof ( TSettings ) == typeof ( string )
354322 || typeof ( TSettings ) == typeof ( Hashtable ) ) )
355323 {
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 )
357328 {
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 ;
381331 }
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+ } ) ;
382345 }
383346
384347 Logger . Write (
@@ -388,6 +351,34 @@ private IEnumerable<PSObject> GetDiagnosticRecords<TSettings>(
388351 return diagnosticRecords ;
389352 }
390353
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+
391382 #endregion //private methods
392383 }
393384}
0 commit comments