From a6f77d36c039f4c4a16439781a24b7c295519a9d Mon Sep 17 00:00:00 2001 From: James Truher Date: Sat, 9 Mar 2019 19:17:17 -0800 Subject: [PATCH 01/52] add sensible defaults code --- Engine/DefaultWriter.cs | 68 ++++++++++++++++++++++++++++++++++++++++ Engine/ScriptAnalyzer.cs | 19 +++++++++++ 2 files changed, 87 insertions(+) create mode 100644 Engine/DefaultWriter.cs diff --git a/Engine/DefaultWriter.cs b/Engine/DefaultWriter.cs new file mode 100644 index 000000000..f15976850 --- /dev/null +++ b/Engine/DefaultWriter.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Management.Automation; +using System.Management.Automation.Runspaces; + +namespace Microsoft.Windows.PowerShell.ScriptAnalyzer +{ + public class SensibleDefaults + { + public StreamStorage Streams; + public Runspace Runspace; + private System.Management.Automation.PowerShell ps; + public SensibleDefaults() + { + Streams = new StreamStorage(); + ps = System.Management.Automation.PowerShell.Create(); + Runspace = ps.Runspace; + } + public void Reset() + { + if ( ps != null ) + { + if ( Runspace != null ) + { + Runspace.Dispose(); + } + ps.Dispose(); + } + if ( Streams != null ) + { + Streams.Dispose(); + } + } + } + + public class StreamStorage : IOutputWriter + { + public StreamStorage() + { + TerminatingErrors = new List(); + Errors = new List(); + Debug = new List(); + Verbose = new List(); + Warning = new List(); + } + public void Dispose() + { + } + public List TerminatingErrors; + public List Errors; + public ListDebug; + public ListVerbose; + public ListWarning; + public void WriteError(ErrorRecord r) { Errors.Add(r); } + public void ThrowTerminatingError(ErrorRecord r) { TerminatingErrors.Add(r); } + public void WriteDebug(string m) { Debug.Add(m); } + public void WriteVerbose(string m) { Verbose.Add(m); } + public void WriteWarning(string m) { Warning.Add(m); } + public void Clear() + { + TerminatingErrors.Clear(); + Errors.Clear(); + Debug.Clear(); + Verbose.Clear(); + Warning.Clear(); + } + } +} diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 18fc06bf8..c455b28b4 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -138,6 +138,25 @@ internal void Initialize( suppressedOnly); } + /// + /// default initialization. We build everything + /// + public void Initialize() + { + SensibleDefaults sd = new SensibleDefaults(); + this.myStreams = sd.Streams; + this.Initialize( sd.Runspace, myStreams, null, null, null, null, true, false, null ); + } + + + private StreamStorage myStreams; + /// + /// get the streams from our default + /// + public StreamStorage MyStreams { + get { return myStreams; } + } + /// /// Initialize : Initializes default rules, loggers and helper. /// From 499a62a3555415689fcd53f98250f1a5ff54b9f7 Mon Sep 17 00:00:00 2001 From: James Truher Date: Tue, 25 Jun 2019 10:25:41 -0700 Subject: [PATCH 02/52] Add hosted analyzer class --- Engine/HostedAnalyzer.cs | 45 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 Engine/HostedAnalyzer.cs diff --git a/Engine/HostedAnalyzer.cs b/Engine/HostedAnalyzer.cs new file mode 100644 index 000000000..552c5eab1 --- /dev/null +++ b/Engine/HostedAnalyzer.cs @@ -0,0 +1,45 @@ +using System; +using SMA = System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Collections.Generic; +using Microsoft.Windows.PowerShell.ScriptAnalyzer; +using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; +using System.IO; + +namespace Microsoft.Windows.PowerShell.ScriptAnalyzer +{ + public class HostedAnalyzer + { + private SMA.PowerShell ps; + private IOutputWriter writer; + private ScriptAnalyzer analyzer; + /// + /// Create an instance of the analyzer + /// + public HostedAnalyzer() + { + ps = SMA.PowerShell.Create(); + writer = new StreamStorage(); + ScriptAnalyzer.Instance.Initialize(ps.Runspace, writer); + analyzer = ScriptAnalyzer.Instance; + } + + /// Analyze a script in the form of a string + public IEnumerable Analyze(string ScriptDefinition) + { + var diagnosticList = analyzer.AnalyzeScriptDefinition(ScriptDefinition); + return diagnosticList; + } + + /// Analyze a script in the form of a file + public IEnumerable Analyze(FileInfo File) + { + return default(IEnumerable); + } + } + + public class AnalyzerResult + { + + } +} \ No newline at end of file From 1b7b9b17dba9c0a514a18d99d05bc15c512fe85d Mon Sep 17 00:00:00 2001 From: James Truher Date: Thu, 27 Jun 2019 14:02:05 -0700 Subject: [PATCH 03/52] more hosting updates --- Engine/Engine.csproj | 1 + Engine/HostedAnalyzer.cs | 308 +++++++++++++++++++++++++++++++++++++-- Engine/Settings.cs | 4 +- 3 files changed, 299 insertions(+), 14 deletions(-) diff --git a/Engine/Engine.csproj b/Engine/Engine.csproj index d2afcc430..8e478153a 100644 --- a/Engine/Engine.csproj +++ b/Engine/Engine.csproj @@ -6,6 +6,7 @@ Microsoft.Windows.PowerShell.ScriptAnalyzer Engine Microsoft.Windows.PowerShell.ScriptAnalyzer + true diff --git a/Engine/HostedAnalyzer.cs b/Engine/HostedAnalyzer.cs index 552c5eab1..83596a75d 100644 --- a/Engine/HostedAnalyzer.cs +++ b/Engine/HostedAnalyzer.cs @@ -2,44 +2,328 @@ using SMA = System.Management.Automation; using System.Management.Automation.Runspaces; using System.Collections.Generic; +using System.Collections; using Microsoft.Windows.PowerShell.ScriptAnalyzer; using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; +using System.Management.Automation.Language; using System.IO; -namespace Microsoft.Windows.PowerShell.ScriptAnalyzer +namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Hosting { + /// + /// This is a helper class which provides a consistent, easy to use set of interfaces for Script Analyzer + /// This class will enable you to: + /// - Analyze a script or file with any configuration which is supported by Invoke-ScriptAnalyzer + /// - Reformat a script as is done via Invoke-Formatter + /// public class HostedAnalyzer { private SMA.PowerShell ps; - private IOutputWriter writer; + internal hostedWriter writer; private ScriptAnalyzer analyzer; /// - /// Create an instance of the analyzer + /// Create an instance of the hosted analyzer /// public HostedAnalyzer() { - ps = SMA.PowerShell.Create(); - writer = new StreamStorage(); - ScriptAnalyzer.Instance.Initialize(ps.Runspace, writer); + InitialSessionState iss = InitialSessionState.CreateDefault2(); + ps = SMA.PowerShell.Create(iss); + writer = new hostedWriter(); + ScriptAnalyzer.Instance.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); analyzer = ScriptAnalyzer.Instance; } - /// Analyze a script in the form of a string - public IEnumerable Analyze(string ScriptDefinition) + /// Reset the the analyzer and associated state + public void Reset() { - var diagnosticList = analyzer.AnalyzeScriptDefinition(ScriptDefinition); - return diagnosticList; + Helper.Instance.Initialize(); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + } + + /// + /// Analyze a script in the form of a string + /// The script as a string + /// The FixedScriptResult which encapsulates the fixed script + /// + public FixedScriptResult Fix(string ScriptDefinition) + { + writer.ClearWriter(); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); + bool fixesApplied; + return new FixedScriptResult() { + OriginalScript = ScriptDefinition, + FixedScript = analyzer.Fix(ScriptDefinition, out fixesApplied), + Analysis = new AnalyzerResult(AnalysisType.Script, analyzer.AnalyzeScriptDefinition(ScriptDefinition), this) + }; + } + + /// + /// Analyze a script in the form of an AST + /// A scriptblockast which represents the script to analyze + /// The tokens in the ast + /// The name of the file which held the script, if there was one + /// + public AnalyzerResult Analyze(ScriptBlockAst scriptast, Token[] tokens, string filename = null) + { + writer.ClearWriter(); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + var result = analyzer.AnalyzeSyntaxTree(scriptast, tokens, filename); + return new AnalyzerResult(AnalysisType.Ast, result, this); + } + + /// Analyze a script in the form of a string with additional Settings + public AnalyzerResult Analyze(string ScriptDefinition, Hashtable settings) + { + writer.ClearWriter(); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + // var set = CreateSettings(settings); + // analyzer.UpdateSettings(set); + var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); + return new AnalyzerResult(AnalysisType.Script, result, this); + } + + /// Analyze a script in the form of a string, based on default + public AnalyzerResult Analyze(string ScriptDefinition) + { + writer.ClearWriter(); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); + return new AnalyzerResult(AnalysisType.Script, result, this); + } + + /// Analyze a script based on passed settings + public AnalyzerResult Analyze(string ScriptDefinition, AnalyzerConfiguration Settings) + { + writer.ClearWriter(); + analyzer.Initialize(ps.Runspace, writer, Settings.RulePath, Settings.IncludeRuleNames, Settings.ExcludeRuleNames, Settings.Severity, Settings.IncludeDefaultRules, Settings.SuppressedOnly); + var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); + return new AnalyzerResult(AnalysisType.Script, result, this); + } + + /// Analyze a file based on passed settings + public AnalyzerResult Analyze(FileInfo File, AnalyzerConfiguration Settings) + { + writer.ClearWriter(); + analyzer.Initialize(ps.Runspace, writer, Settings.RulePath, Settings.IncludeRuleNames, Settings.ExcludeRuleNames, Settings.Severity, Settings.IncludeDefaultRules, Settings.SuppressedOnly); + var result = analyzer.AnalyzePath(File.FullName, (x, y) => { return true; }, false); + return new AnalyzerResult(AnalysisType.File, result, this); } /// Analyze a script in the form of a file - public IEnumerable Analyze(FileInfo File) + public AnalyzerResult Analyze(FileInfo File) + { + writer.ClearWriter(); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + var result = analyzer.AnalyzePath(File.FullName, (x, y) => { return true; }, false); + return new AnalyzerResult(AnalysisType.File, result, this); + } + + /// + /// Create a standard settings object for Script Analyzer + /// This is the object used by analyzer internally + /// It is more functional than the AnalyzerSettings object because + /// it contains the Rule Arguments which are not passable to the Initialize method + /// + public Settings CreateSettings(string SettingsName) + { + return Settings.Create(SettingsName, "", writer, ps.Runspace.SessionStateProxy.Path.GetResolvedProviderPathFromPSPath); + } + + /// + /// Format a script according to the formatting rules + /// PSPlaceCloseBrace + /// PSPlaceOpenBrace + /// PSUseConsistentWhitespace + /// PSUseConsistentIndentation + /// PSAlignAssignmentStatement + /// PSUseCorrectCasing + /// and the union of the actual settings which are passed to it. + /// + public string Format(string scriptDefinition, Settings settings) { - return default(IEnumerable); + Helper.Instance = new Helper(ps.Runspace.SessionStateProxy.InvokeCommand, writer); + Helper.Instance.Initialize(); + + string[] ruleOrder = new string[] + { + "PSPlaceCloseBrace", + "PSPlaceOpenBrace", + "PSUseConsistentWhitespace", + "PSUseConsistentIndentation", + "PSAlignAssignmentStatement", + "PSUseCorrectCasing" + }; + + var text = new EditableText(scriptDefinition); + Range range = null; + foreach (var rule in ruleOrder) + { + if (!settings.RuleArguments.ContainsKey(rule)) + { + continue; + } + + Settings currentSettings = new Settings(new Hashtable(){ + {"IncludeRules", new string[] { rule }}, + {"Rules", new Hashtable() { { rule, new Hashtable(settings.RuleArguments[rule]) } } } + }); + analyzer.UpdateSettings(currentSettings); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + + Range updatedRange; + bool fixesWereApplied; + text = analyzer.Fix(text, range, out updatedRange, out fixesWereApplied); + range = updatedRange; + } + + return text.ToString(); + } + + /// + /// An encapsulation of the arguments passed to Analyzer.Initialize + /// they roughly equate to some of the parameters on the Invoke-ScriptAnalyzer + /// cmdlet, but encapsulated to improve the experience. + /// + public class AnalyzerConfiguration + { + public string[] RulePath; + public string[] IncludeRuleNames; + public string[] ExcludeRuleNames; + public string[] Severity; + public bool IncludeDefaultRules = true; + public bool SuppressedOnly = false; + } + + /// + /// Analyzer usually requires a cmdlet to manage the output to the user. + /// This class is provided to collect the non-diagnostic record output + /// when invoking the methods in the HostedAnalyzer. + /// + internal class hostedWriter : IOutputWriter + { + /// The terminating errors emitted during the invocation of the analyzer + public IList TerminatingErrors; + /// The non-terminating errors emitted during the invocation of the analyzer + public IList Errors; + /// The verbose messages emitted during the invocation of the analyzer + public IList Verbose; + /// The debug messages emitted during the invocation of the analyzer + public IList Debug; + /// The warning messages emitted during the invocation of the analyzer + public IList Warning; + /// Add a terminating error the ccollection + public void ThrowTerminatingError(SMA.ErrorRecord er) { TerminatingErrors.Add(er); } + /// Add a non-terminating error the ccollection + public void WriteError(SMA.ErrorRecord er) { Errors.Add(er); } + /// Add a verbose message to the verbose collection + public void WriteVerbose(string m) { Verbose.Add(m); } + /// Add a debug message to the debug collection + public void WriteDebug(string m) { Debug.Add(m); } + /// Add a warning message to the warning collection + public void WriteWarning(string m) { Warning.Add(m); } + + /// + /// Clear the writer collections to avoid getting output from + /// multiple invocations + /// + public void ClearWriter() + { + TerminatingErrors.Clear(); + Errors.Clear(); + Verbose.Clear(); + Debug.Clear(); + Warning.Clear(); + } + + /// + /// Initialize all the output colections + /// + public hostedWriter() + { + TerminatingErrors = new List(); + Errors = new List(); + Verbose = new List(); + Debug = new List(); + Warning = new List(); + } } } + + /// + /// The encapsulated rules of fixing a script + /// + public class FixedScriptResult + { + /// The original script that was fixed + public string OriginalScript; + /// The script which has all the fixes + public string FixedScript; + /// + /// The analysis results. + /// This includes all the output streams as well as the diagnostic records + /// + public AnalyzerResult Analysis; + } + + /// + /// Type of entity that was passed to the analyzer + /// + public enum AnalysisType { + /// An Ast was passed to the analyzer + Ast, + /// An FileInfo was passed to the analyzer + File, + /// An script (as a string) was passed to the analyzer + Script + } + /// + /// The encapsulated results of the analyzer + /// public class AnalyzerResult { + /// The type of entity which was analyzed + public AnalysisType Type; + /// The diagnostic records found during analysis + public List Result; + /// The terminating errors which occurred during analysis + public List TerminatingErrors; + /// The non-terminating errors which occurred during analysis + public List Errors; + /// The verbose messages delivered during analysis + public List Verbose; + /// The warning messages delivered during analysis + public List Warning; + /// The debug messages delivered during analysis + public List Debug; + + /// + /// initialize storage + /// + private AnalyzerResult() + { + Type = AnalysisType.Script; + Result = new List(); + TerminatingErrors = new List(); + Errors = new List(); + Verbose = new List(); + Warning = new List(); + Debug = new List(); + } + /// + /// Create results from an invocation of the analyzer + /// + public AnalyzerResult(AnalysisType type, IEnumerablerecords, HostedAnalyzer ha) : this() + { + Type = type; + Result.AddRange(records); + TerminatingErrors.AddRange(ha.writer.TerminatingErrors); + Errors.AddRange(ha.writer.Errors); + Verbose.AddRange(ha.writer.Verbose); + Warning.AddRange(ha.writer.Warning); + Debug.AddRange(ha.writer.Debug); + } } } \ No newline at end of file diff --git a/Engine/Settings.cs b/Engine/Settings.cs index 506733cc2..a3b5712e6 100644 --- a/Engine/Settings.cs +++ b/Engine/Settings.cs @@ -31,8 +31,8 @@ public class Settings private List customRulePath; private Dictionary> ruleArguments; - public bool RecurseCustomRulePath => recurseCustomRulePath; - public bool IncludeDefaultRules => includeDefaultRules; + public bool RecurseCustomRulePath { get; set;} = false; + public bool IncludeDefaultRules { get; set; } = false; public string FilePath => filePath; public IEnumerable IncludeRules => includeRules; public IEnumerable ExcludeRules => excludeRules; From 9e5cd8787dcd887dc7bf1b874a277f8296e224d3 Mon Sep 17 00:00:00 2001 From: James Truher Date: Mon, 8 Jul 2019 11:15:43 -0700 Subject: [PATCH 04/52] Formatter seems to be working Had to make the initialization of the analyzer optional as it was clobbering the settings that had been created in the formatter --- Engine/Formatter.cs | 53 ++++++++++++ Engine/HostedAnalyzer.cs | 177 ++++++++++++++++++++++++++------------- Engine/ScriptAnalyzer.cs | 15 ++-- 3 files changed, 182 insertions(+), 63 deletions(-) diff --git a/Engine/Formatter.cs b/Engine/Formatter.cs index d2b7e0776..7383f9069 100644 --- a/Engine/Formatter.cs +++ b/Engine/Formatter.cs @@ -12,6 +12,59 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer /// public class Formatter { + /// + /// Format a powershell script. + /// + /// A string representing a powershell script. + /// Settings to be used for formatting + /// The range in which formatting should take place. + /// The runspace entrance into the powershell engine. + /// The writer for operation message. + /// + public static string Format( + string scriptDefinition, + Settings settings, + Range range, + System.Management.Automation.Runspaces.Runspace runspace, + IOutputWriter writer) + { + // todo implement notnull attribute for such a check + ValidateNotNull(scriptDefinition, "scriptDefinition"); + ValidateNotNull(settings, "settings"); + + Helper.Instance = new Helper(runspace.SessionStateProxy.InvokeCommand, writer); + Helper.Instance.Initialize(); + + var ruleOrder = new string[] + { + "PSPlaceCloseBrace", + "PSPlaceOpenBrace", + "PSUseConsistentWhitespace", + "PSUseConsistentIndentation", + "PSAlignAssignmentStatement", + "PSUseCorrectCasing" + }; + + var text = new EditableText(scriptDefinition); + foreach (var rule in ruleOrder) + { + if (!settings.RuleArguments.ContainsKey(rule)) + { + continue; + } + + var currentSettings = GetCurrentSettings(settings, rule); + ScriptAnalyzer.Instance.UpdateSettings(currentSettings); + ScriptAnalyzer.Instance.Initialize(runspace, writer, null, null, null, null, true, false, null, false); + + Range updatedRange; + bool fixesWereApplied; + text = ScriptAnalyzer.Instance.Fix(text, range, out updatedRange, out fixesWereApplied); + range = updatedRange; + } + + return text.ToString(); + } /// /// Format a powershell script. /// diff --git a/Engine/HostedAnalyzer.cs b/Engine/HostedAnalyzer.cs index 83596a75d..14238427b 100644 --- a/Engine/HostedAnalyzer.cs +++ b/Engine/HostedAnalyzer.cs @@ -7,6 +7,7 @@ using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; using System.Management.Automation.Language; using System.IO; +using System.Linq; namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Hosting { @@ -36,28 +37,14 @@ public HostedAnalyzer() /// Reset the the analyzer and associated state public void Reset() { + analyzer.CleanUp(); + Helper.Instance = new Helper( + ps.Runspace.SessionStateProxy.InvokeCommand, + writer); Helper.Instance.Initialize(); analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); } - /// - /// Analyze a script in the form of a string - /// The script as a string - /// The FixedScriptResult which encapsulates the fixed script - /// - public FixedScriptResult Fix(string ScriptDefinition) - { - writer.ClearWriter(); - analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); - var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); - bool fixesApplied; - return new FixedScriptResult() { - OriginalScript = ScriptDefinition, - FixedScript = analyzer.Fix(ScriptDefinition, out fixesApplied), - Analysis = new AnalyzerResult(AnalysisType.Script, analyzer.AnalyzeScriptDefinition(ScriptDefinition), this) - }; - } - /// /// Analyze a script in the form of an AST /// A scriptblockast which represents the script to analyze @@ -72,7 +59,30 @@ public AnalyzerResult Analyze(ScriptBlockAst scriptast, Token[] tokens, string f return new AnalyzerResult(AnalysisType.Ast, result, this); } - /// Analyze a script in the form of a string with additional Settings + /// + /// Analyze a script in the form of a string with additional Settings + /// The script as a string + /// A hastable which includes the settings + /// + public AnalyzerResult Analyze(string ScriptDefinition, Settings settings) + { + writer.ClearWriter(); + analyzer.Initialize(ps.Runspace, writer, settings.CustomRulePath == null ? null : settings.CustomRulePath.ToArray(), + settings.IncludeRules == null ? null : settings.IncludeRules.ToArray(), + settings.ExcludeRules == null ? null : settings.ExcludeRules.ToArray(), + settings.Severities == null ? null : settings.Severities.ToArray(), + settings.IncludeDefaultRules, + false, + null); + var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); + return new AnalyzerResult(AnalysisType.Script, result, this); + } + + /// + /// Analyze a script in the form of a string with additional Settings + /// The script as a string + /// A hastable which includes the settings + /// public AnalyzerResult Analyze(string ScriptDefinition, Hashtable settings) { writer.ClearWriter(); @@ -130,6 +140,29 @@ public Settings CreateSettings(string SettingsName) return Settings.Create(SettingsName, "", writer, ps.Runspace.SessionStateProxy.Path.GetResolvedProviderPathFromPSPath); } + /// + /// Create a default settings object + /// + public Settings CreateSettings() + { + Settings s = Settings.Create(null, + Directory.GetParent(Directory.GetParent(typeof(ScriptAnalyzer).Assembly.Location).FullName).FullName, + writer, ps.Runspace.SessionStateProxy.Path.GetResolvedProviderPathFromPSPath); + s.IncludeDefaultRules = true; + return s; + } + + /// + /// Create a standard settings object for Script Analyzer + /// This is the object used by analyzer internally + /// It is more functional than the AnalyzerSettings object because + /// it contains the Rule Arguments which are not passable to the Initialize method + /// + public Settings CreateSettings(Hashtable settings) + { + return Settings.Create(settings, "", writer, ps.Runspace.SessionStateProxy.Path.GetResolvedProviderPathFromPSPath); + } + /// /// Format a script according to the formatting rules /// PSPlaceCloseBrace @@ -142,42 +175,9 @@ public Settings CreateSettings(string SettingsName) /// public string Format(string scriptDefinition, Settings settings) { - Helper.Instance = new Helper(ps.Runspace.SessionStateProxy.InvokeCommand, writer); - Helper.Instance.Initialize(); - - string[] ruleOrder = new string[] - { - "PSPlaceCloseBrace", - "PSPlaceOpenBrace", - "PSUseConsistentWhitespace", - "PSUseConsistentIndentation", - "PSAlignAssignmentStatement", - "PSUseCorrectCasing" - }; - - var text = new EditableText(scriptDefinition); - Range range = null; - foreach (var rule in ruleOrder) - { - if (!settings.RuleArguments.ContainsKey(rule)) - { - continue; - } - - Settings currentSettings = new Settings(new Hashtable(){ - {"IncludeRules", new string[] { rule }}, - {"Rules", new Hashtable() { { rule, new Hashtable(settings.RuleArguments[rule]) } } } - }); - analyzer.UpdateSettings(currentSettings); - analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); - - Range updatedRange; - bool fixesWereApplied; - text = analyzer.Fix(text, range, out updatedRange, out fixesWereApplied); - range = updatedRange; - } - - return text.ToString(); + string s = Formatter.Format(scriptDefinition, settings, null, ps.Runspace, writer); + analyzer.CleanUp(); + return s; } /// @@ -253,12 +253,12 @@ public hostedWriter() /// /// The encapsulated rules of fixing a script /// - public class FixedScriptResult + public class FormattedScriptResult { /// The original script that was fixed public string OriginalScript; /// The script which has all the fixes - public string FixedScript; + public string FormattedScript; /// /// The analysis results. /// This includes all the output streams as well as the diagnostic records @@ -326,4 +326,67 @@ public AnalyzerResult(AnalysisType type, IEnumerablerecords, H Debug.AddRange(ha.writer.Debug); } } + + /// A public settings object + public class PSSASettings + { + /// thing + public bool RecurseCustomRulePath { get; set;} = false; + /// thing + public bool IncludeDefaultRules { get; set; } = false; + /// thing + public string FilePath { get; set; } + /// thing + public List Severities { get; set; } + /// thing + public List CustomRulePath { get; set; } + /// The rules which encapsulate an analyzer setting + public ListRules; + + /// Convert to hashtable so the analyzer method can use it + public Hashtable ConvertToHashtable() + { + Hashtable ht = new Hashtable(); + return ht; + } + } + + /// Whether the rule should be included or excluded + public enum RuleStatus { + /// Include the rule + Include, + /// Exclude the rule + Exclude + } + + /// The encapsulation of a rule + public class PSSARule + { + /// the name for a rule + public string Name; + /// Is the rule included or excluded + public RuleStatus RuleAction; + /// the settings for a rule + public DictionaryRuleSettings; + + /// Create a new rule, the default status is to include it + public PSSARule(string name, RuleStatus status = RuleStatus.Include) { + Name = name; + RuleAction = status; + RuleSettings = new Dictionary(); + } + + /// + /// Create a new rule, the default status is to include it + /// + /// + /// + /// + public PSSARule(string name, RuleStatus status, DictionaryruleSettings) { + Name = name; + RuleAction = status; + RuleSettings = ruleSettings; + } + + } } \ No newline at end of file diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index c455b28b4..ef583e347 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -169,18 +169,21 @@ public void Initialize( string[] severity = null, bool includeDefaultRules = false, bool suppressedOnly = false, - string profile = null) + string profile = null, + bool InitializeHelper = true) { if (runspace == null) { throw new ArgumentNullException("runspace"); } - //initialize helper - Helper.Instance = new Helper( - runspace.SessionStateProxy.InvokeCommand, - outputWriter); - Helper.Instance.Initialize(); + // initialize helper if requested + if ( InitializeHelper ) { + Helper.Instance = new Helper( + runspace.SessionStateProxy.InvokeCommand, + outputWriter); + Helper.Instance.Initialize(); + } this.Initialize( From 8c0b30b3f64d7c25a7b855849037e6082ab9f3db Mon Sep 17 00:00:00 2001 From: James Truher Date: Fri, 19 Jul 2019 12:19:36 -0700 Subject: [PATCH 05/52] change calls to analyzer to be more like the cmdlet --- Engine/HostedAnalyzer.cs | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/Engine/HostedAnalyzer.cs b/Engine/HostedAnalyzer.cs index 14238427b..b54a4e6d1 100644 --- a/Engine/HostedAnalyzer.cs +++ b/Engine/HostedAnalyzer.cs @@ -56,6 +56,7 @@ public AnalyzerResult Analyze(ScriptBlockAst scriptast, Token[] tokens, string f writer.ClearWriter(); analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); var result = analyzer.AnalyzeSyntaxTree(scriptast, tokens, filename); + analyzer.CleanUp(); return new AnalyzerResult(AnalysisType.Ast, result, this); } @@ -67,14 +68,11 @@ public AnalyzerResult Analyze(ScriptBlockAst scriptast, Token[] tokens, string f public AnalyzerResult Analyze(string ScriptDefinition, Settings settings) { writer.ClearWriter(); - analyzer.Initialize(ps.Runspace, writer, settings.CustomRulePath == null ? null : settings.CustomRulePath.ToArray(), - settings.IncludeRules == null ? null : settings.IncludeRules.ToArray(), - settings.ExcludeRules == null ? null : settings.ExcludeRules.ToArray(), - settings.Severities == null ? null : settings.Severities.ToArray(), - settings.IncludeDefaultRules, - false, - null); + analyzer.UpdateSettings(settings); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null, false); + // analyzer.Initialize(ps.Runspace, writer, null, null, null, null, false, false, null, false); var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); + analyzer.CleanUp(); return new AnalyzerResult(AnalysisType.Script, result, this); } @@ -90,6 +88,7 @@ public AnalyzerResult Analyze(string ScriptDefinition, Hashtable settings) // var set = CreateSettings(settings); // analyzer.UpdateSettings(set); var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); + analyzer.CleanUp(); return new AnalyzerResult(AnalysisType.Script, result, this); } @@ -108,6 +107,7 @@ public AnalyzerResult Analyze(string ScriptDefinition, AnalyzerConfiguration Set writer.ClearWriter(); analyzer.Initialize(ps.Runspace, writer, Settings.RulePath, Settings.IncludeRuleNames, Settings.ExcludeRuleNames, Settings.Severity, Settings.IncludeDefaultRules, Settings.SuppressedOnly); var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); + analyzer.CleanUp(); return new AnalyzerResult(AnalysisType.Script, result, this); } @@ -117,6 +117,7 @@ public AnalyzerResult Analyze(FileInfo File, AnalyzerConfiguration Settings) writer.ClearWriter(); analyzer.Initialize(ps.Runspace, writer, Settings.RulePath, Settings.IncludeRuleNames, Settings.ExcludeRuleNames, Settings.Severity, Settings.IncludeDefaultRules, Settings.SuppressedOnly); var result = analyzer.AnalyzePath(File.FullName, (x, y) => { return true; }, false); + analyzer.CleanUp(); return new AnalyzerResult(AnalysisType.File, result, this); } @@ -126,6 +127,7 @@ public AnalyzerResult Analyze(FileInfo File) writer.ClearWriter(); analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); var result = analyzer.AnalyzePath(File.FullName, (x, y) => { return true; }, false); + analyzer.CleanUp(); return new AnalyzerResult(AnalysisType.File, result, this); } @@ -175,8 +177,16 @@ public Settings CreateSettings(Hashtable settings) /// public string Format(string scriptDefinition, Settings settings) { - string s = Formatter.Format(scriptDefinition, settings, null, ps.Runspace, writer); - analyzer.CleanUp(); + if ( settings == null ) { + throw new ArgumentException("settings may not be null"); + } + string s; + try { + s = Formatter.Format(scriptDefinition, settings, null, ps.Runspace, writer); + } + finally { + analyzer.CleanUp(); + } return s; } From e5e31af184346b636accf6027ed2c134a8a1e267 Mon Sep 17 00:00:00 2001 From: James Truher Date: Thu, 5 Sep 2019 15:48:38 -0700 Subject: [PATCH 06/52] checkpoint Hosting seems to work, starting to work on nuget packaging --- Engine/HostedAnalyzer.cs | 74 +++++++++++++++++++++++++---------- Engine/Settings.cs | 61 +++++++++++++++++++++++++++++ Rules/PSScriptAnalyzer.nuspec | 28 +++++++++++++ Rules/Rules.csproj | 1 + build.ps1 | 10 ++++- build.psm1 | 5 +++ 6 files changed, 157 insertions(+), 22 deletions(-) create mode 100644 Rules/PSScriptAnalyzer.nuspec diff --git a/Engine/HostedAnalyzer.cs b/Engine/HostedAnalyzer.cs index b54a4e6d1..9da27f3d2 100644 --- a/Engine/HostedAnalyzer.cs +++ b/Engine/HostedAnalyzer.cs @@ -38,9 +38,7 @@ public HostedAnalyzer() public void Reset() { analyzer.CleanUp(); - Helper.Instance = new Helper( - ps.Runspace.SessionStateProxy.InvokeCommand, - writer); + Helper.Instance = new Helper(ps.Runspace.SessionStateProxy.InvokeCommand, writer); Helper.Instance.Initialize(); analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); } @@ -101,6 +99,7 @@ public AnalyzerResult Analyze(string ScriptDefinition) return new AnalyzerResult(AnalysisType.Script, result, this); } + /* /// Analyze a script based on passed settings public AnalyzerResult Analyze(string ScriptDefinition, AnalyzerConfiguration Settings) { @@ -120,6 +119,7 @@ public AnalyzerResult Analyze(FileInfo File, AnalyzerConfiguration Settings) analyzer.CleanUp(); return new AnalyzerResult(AnalysisType.File, result, this); } + */ /// Analyze a script in the form of a file public AnalyzerResult Analyze(FileInfo File) @@ -131,6 +131,13 @@ public AnalyzerResult Analyze(FileInfo File) return new AnalyzerResult(AnalysisType.File, result, this); } + /// Fix a script + public string Fix(string scriptDefinition) + { + bool fixesApplied; + return analyzer.Fix(scriptDefinition, out fixesApplied); + } + /// /// Create a standard settings object for Script Analyzer /// This is the object used by analyzer internally @@ -142,13 +149,22 @@ public Settings CreateSettings(string SettingsName) return Settings.Create(SettingsName, "", writer, ps.Runspace.SessionStateProxy.Path.GetResolvedProviderPathFromPSPath); } + /// + /// Create a standard settings object for Script Analyzer from an existing .psd1 file + /// + public Settings CreateSettingsFromFile(string settingsFile) + { + return new Settings(settingsFile); + } + /// /// Create a default settings object /// public Settings CreateSettings() { - Settings s = Settings.Create(null, - Directory.GetParent(Directory.GetParent(typeof(ScriptAnalyzer).Assembly.Location).FullName).FullName, + + Settings s = Settings.Create(new Hashtable(), + "", writer, ps.Runspace.SessionStateProxy.Path.GetResolvedProviderPathFromPSPath); s.IncludeDefaultRules = true; return s; @@ -186,10 +202,12 @@ public string Format(string scriptDefinition, Settings settings) } finally { analyzer.CleanUp(); + Reset(); } return s; } +/* /// /// An encapsulation of the arguments passed to Analyzer.Initialize /// they roughly equate to some of the parameters on the Invoke-ScriptAnalyzer @@ -204,6 +222,7 @@ public class AnalyzerConfiguration public bool IncludeDefaultRules = true; public bool SuppressedOnly = false; } +*/ /// /// Analyzer usually requires a cmdlet to manage the output to the user. @@ -258,22 +277,30 @@ public hostedWriter() Warning = new List(); } } - } - - /// - /// The encapsulated rules of fixing a script - /// - public class FormattedScriptResult - { - /// The original script that was fixed - public string OriginalScript; - /// The script which has all the fixes - public string FormattedScript; - /// - /// The analysis results. - /// This includes all the output streams as well as the diagnostic records - /// - public AnalyzerResult Analysis; + + /// Get the available builtin rules + /// A collection of strings which contain the wildcard pattern for the rule + public List GetBuiltinRules(string[] ruleNames = null) + { + List builtinRules = new List(); + IEnumerable rules = ScriptAnalyzer.Instance.GetRule(null, ruleNames); + foreach ( IRule rule in rules ) + { + builtinRules.Add( + new RuleInfo( + name: rule.GetName(), + commonName: rule.GetCommonName(), + description: rule.GetDescription(), + sourceType: rule.GetSourceType(), + sourceName: rule.GetSourceName(), + severity: rule.GetSeverity(), + implementingType: rule.GetType() + ) + ); + } + return builtinRules; + } + } /// @@ -337,6 +364,7 @@ public AnalyzerResult(AnalysisType type, IEnumerablerecords, H } } +/* /// A public settings object public class PSSASettings { @@ -360,6 +388,7 @@ public Hashtable ConvertToHashtable() return ht; } } +*/ /// Whether the rule should be included or excluded public enum RuleStatus { @@ -369,6 +398,7 @@ public enum RuleStatus { Exclude } +/* /// The encapsulation of a rule public class PSSARule { @@ -399,4 +429,6 @@ public PSSARule(string name, RuleStatus status, DictionaryruleSe } } +*/ + } \ No newline at end of file diff --git a/Engine/Settings.cs b/Engine/Settings.cs index a3b5712e6..c4e75ab5a 100644 --- a/Engine/Settings.cs +++ b/Engine/Settings.cs @@ -106,6 +106,67 @@ public Settings(object settings) : this(settings, null) { } + /// + /// Add a configurable rule to the rule arguments + /// + public void AddRuleArgument(string name, Dictionarysetting) + { + Dictionary currentSetting; + if (RuleArguments.TryGetValue(name, out currentSetting)) + { + throw new ArgumentException(name + " already in settings"); + } + RuleArguments.Add(name, setting); + } + + /// + /// Add a configuration element to a rule + /// + public void AddRuleArgument(string rule, string setting, object value) + { + // the rule does not exist, that's a problem, first create it and then add the key/value pair + Dictionary settingDictionary; + if ( ! RuleArguments.TryGetValue(rule, out settingDictionary)) + { + Dictionary newSetting = new Dictionary(); + newSetting.Add(setting, value); + RuleArguments.Add(rule, newSetting); + return; + } + + // the setting exists, just change the value + object o; + if ( settingDictionary.TryGetValue(setting, out o)) + { + SetRuleArgument(rule, setting, value); + return; + } + + settingDictionary.Add(setting, value); + return; + } + + /// + /// Allow for changing setting of an existing value + /// + public void SetRuleArgument(string rule, string setting, object value) + { + Dictionary settingDictionary; + if ( ! RuleArguments.TryGetValue(rule, out settingDictionary)) + { + throw new KeyNotFoundException(rule); + } + + object o; + if ( ! settingDictionary.TryGetValue(setting, out o)) + { + throw new KeyNotFoundException(setting); + } + + settingDictionary[setting] = value; + return; + } + /// /// Retrieves the Settings directory from the Module directory structure /// diff --git a/Rules/PSScriptAnalyzer.nuspec b/Rules/PSScriptAnalyzer.nuspec new file mode 100644 index 000000000..18108fb3d --- /dev/null +++ b/Rules/PSScriptAnalyzer.nuspec @@ -0,0 +1,28 @@ + + + + PSScriptAnalyzer.Library + 1.18.2 + PSScriptAnalyzer.Library + Microsoft + Microsoft,PowerShellTeam + https://github.com/PowerShell/PSScriptAnalyzer + https://github.com/PowerShell/PowerShell/blob/master/assets/Powershell_64.png + https://github.com/PowerShell/PowerShell/blob/master/LICENSE.txt + false + Contains hostable PowerShell Script Analyzer + © Microsoft Corporation. All rights reserved. + PowerShell, reference, net452, netstandard2, netstandard2.0 + + + False + + + + + + + + + + diff --git a/Rules/Rules.csproj b/Rules/Rules.csproj index 80d17b516..1fa4bf282 100644 --- a/Rules/Rules.csproj +++ b/Rules/Rules.csproj @@ -6,6 +6,7 @@ Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules Rules Microsoft.Windows.PowerShell.ScriptAnalyzer + ./PSScriptAnalyzer.nuspec diff --git a/build.ps1 b/build.ps1 index 14e8a03ee..8f306c13d 100644 --- a/build.ps1 +++ b/build.ps1 @@ -34,7 +34,12 @@ param( [switch] $InProcess, [Parameter(ParameterSetName='Bootstrap')] - [switch] $Bootstrap + [switch] $Bootstrap, + + [Parameter(ParameterSetName='BuildOne')] + [Parameter(ParameterSetName='BuildAll')] + [switch] $Pack + ) BEGIN { if ($PSVersion -gt 6) { @@ -79,4 +84,7 @@ END { throw "Unexpected parameter set '$setName'" } } + if ( $Pack ) { + Export-NuPkg + } } diff --git a/build.psm1 b/build.psm1 index 0b0d2517c..202b22330 100644 --- a/build.psm1 +++ b/build.psm1 @@ -696,3 +696,8 @@ function Copy-CrossCompatibilityModule } } } + +function Export-NuPkg +{ + dotnet pack +} From 2de9d8f4499c0d858793f918bf9c6d0ac859927f Mon Sep 17 00:00:00 2001 From: James Truher Date: Tue, 10 Sep 2019 12:17:10 -0700 Subject: [PATCH 07/52] Since RunspacePools are disposable, implement IDisposable for HostedAnalyzer, CommandCache, and Helper --- Engine/CommandInfoCache.cs | 6 ++++++ Engine/Helper.cs | 7 ++++++- Engine/HostedAnalyzer.cs | 7 ++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Engine/CommandInfoCache.cs b/Engine/CommandInfoCache.cs index 61e78d86b..16be828c0 100644 --- a/Engine/CommandInfoCache.cs +++ b/Engine/CommandInfoCache.cs @@ -86,6 +86,12 @@ public CommandInfo GetCommandInfoLegacy(string commandOrAliasName, CommandTypes? : GetCommandInfo(commandName, commandTypes: commandTypes); } + /// no + public void Dispose() + { + _runspacePool.Dispose(); + } + /// /// Get a CommandInfo object of the given command name /// diff --git a/Engine/Helper.cs b/Engine/Helper.cs index 71be95b4b..1093fe020 100644 --- a/Engine/Helper.cs +++ b/Engine/Helper.cs @@ -19,7 +19,7 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer /// /// This Helper class contains utility/helper functions for classes in ScriptAnalyzer. /// - public class Helper + public class Helper : IDisposable { #region Private members @@ -67,6 +67,11 @@ internal set #endregion + public void Dispose() + { + _runSpacePool.Dispose(); + } + #region Properties /// diff --git a/Engine/HostedAnalyzer.cs b/Engine/HostedAnalyzer.cs index 9da27f3d2..629d4a5d2 100644 --- a/Engine/HostedAnalyzer.cs +++ b/Engine/HostedAnalyzer.cs @@ -17,7 +17,7 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Hosting /// - Analyze a script or file with any configuration which is supported by Invoke-ScriptAnalyzer /// - Reformat a script as is done via Invoke-Formatter /// - public class HostedAnalyzer + public class HostedAnalyzer : IDisposable { private SMA.PowerShell ps; internal hostedWriter writer; @@ -301,6 +301,11 @@ public List GetBuiltinRules(string[] ruleNames = null) return builtinRules; } + /// no + public void Dispose() + { + Helper.Instance.Dispose(); + } } /// From 1034af89cda68e057ea72bd7f973b5cbe9fd2cc4 Mon Sep 17 00:00:00 2001 From: Jim Truher Date: Tue, 10 Sep 2019 14:30:56 -0700 Subject: [PATCH 08/52] add another supported library --- Rules/PSScriptAnalyzer.nuspec | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Rules/PSScriptAnalyzer.nuspec b/Rules/PSScriptAnalyzer.nuspec index 18108fb3d..80b71df05 100644 --- a/Rules/PSScriptAnalyzer.nuspec +++ b/Rules/PSScriptAnalyzer.nuspec @@ -13,16 +13,22 @@ Contains hostable PowerShell Script Analyzer © Microsoft Corporation. All rights reserved. PowerShell, reference, net452, netstandard2, netstandard2.0 + + + + + From 795b68cd2646253904af944c810bfaffe25d3676 Mon Sep 17 00:00:00 2001 From: James Truher Date: Thu, 12 Sep 2019 14:30:01 -0700 Subject: [PATCH 09/52] Remove IDisposable in Helper as we no longer create a runspace pool --- Engine/Helper.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Engine/Helper.cs b/Engine/Helper.cs index 1093fe020..71be95b4b 100644 --- a/Engine/Helper.cs +++ b/Engine/Helper.cs @@ -19,7 +19,7 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer /// /// This Helper class contains utility/helper functions for classes in ScriptAnalyzer. /// - public class Helper : IDisposable + public class Helper { #region Private members @@ -67,11 +67,6 @@ internal set #endregion - public void Dispose() - { - _runSpacePool.Dispose(); - } - #region Properties /// From a71ac90100a1ef4e9812659c1da1c98160a20ed5 Mon Sep 17 00:00:00 2001 From: James Truher Date: Fri, 13 Sep 2019 10:39:02 -0700 Subject: [PATCH 10/52] make helper disposable to remove all vestiges of runspaces --- Engine/CommandInfoCache.cs | 6 ------ Engine/Helper.cs | 24 +++++++++++++++++++++++- Engine/HostedAnalyzer.cs | 6 ++++-- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/Engine/CommandInfoCache.cs b/Engine/CommandInfoCache.cs index 16be828c0..61e78d86b 100644 --- a/Engine/CommandInfoCache.cs +++ b/Engine/CommandInfoCache.cs @@ -86,12 +86,6 @@ public CommandInfo GetCommandInfoLegacy(string commandOrAliasName, CommandTypes? : GetCommandInfo(commandName, commandTypes: commandTypes); } - /// no - public void Dispose() - { - _runspacePool.Dispose(); - } - /// /// Get a CommandInfo object of the given command name /// diff --git a/Engine/Helper.cs b/Engine/Helper.cs index 71be95b4b..43585682d 100644 --- a/Engine/Helper.cs +++ b/Engine/Helper.cs @@ -19,7 +19,7 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer /// /// This Helper class contains utility/helper functions for classes in ScriptAnalyzer. /// - public class Helper + public class Helper : IDisposable { #region Private members @@ -67,6 +67,28 @@ internal set #endregion + private bool disposed = false; + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if ( disposed ) + { + return; + } + + if ( disposing ) + { + CommandInfoCache.Dispose(); + } + + disposed = true; + } + #region Properties /// diff --git a/Engine/HostedAnalyzer.cs b/Engine/HostedAnalyzer.cs index 629d4a5d2..ce20baae5 100644 --- a/Engine/HostedAnalyzer.cs +++ b/Engine/HostedAnalyzer.cs @@ -301,10 +301,12 @@ public List GetBuiltinRules(string[] ruleNames = null) return builtinRules; } - /// no + /// Dispose the PowerShell instance public void Dispose() { Helper.Instance.Dispose(); + ps.Runspace.Dispose(); + ps.Dispose(); } } @@ -436,4 +438,4 @@ public PSSARule(string name, RuleStatus status, DictionaryruleSe } */ -} \ No newline at end of file +} From 65aae1566c3bb518c8e13bf20aa3bcd379905bc7 Mon Sep 17 00:00:00 2001 From: James Truher Date: Sat, 9 Mar 2019 19:17:17 -0800 Subject: [PATCH 11/52] add sensible defaults code --- Engine/DefaultWriter.cs | 68 ++++++++++++++++++++++++++++++++++++++++ Engine/ScriptAnalyzer.cs | 19 +++++++++++ 2 files changed, 87 insertions(+) create mode 100644 Engine/DefaultWriter.cs diff --git a/Engine/DefaultWriter.cs b/Engine/DefaultWriter.cs new file mode 100644 index 000000000..f15976850 --- /dev/null +++ b/Engine/DefaultWriter.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Management.Automation; +using System.Management.Automation.Runspaces; + +namespace Microsoft.Windows.PowerShell.ScriptAnalyzer +{ + public class SensibleDefaults + { + public StreamStorage Streams; + public Runspace Runspace; + private System.Management.Automation.PowerShell ps; + public SensibleDefaults() + { + Streams = new StreamStorage(); + ps = System.Management.Automation.PowerShell.Create(); + Runspace = ps.Runspace; + } + public void Reset() + { + if ( ps != null ) + { + if ( Runspace != null ) + { + Runspace.Dispose(); + } + ps.Dispose(); + } + if ( Streams != null ) + { + Streams.Dispose(); + } + } + } + + public class StreamStorage : IOutputWriter + { + public StreamStorage() + { + TerminatingErrors = new List(); + Errors = new List(); + Debug = new List(); + Verbose = new List(); + Warning = new List(); + } + public void Dispose() + { + } + public List TerminatingErrors; + public List Errors; + public ListDebug; + public ListVerbose; + public ListWarning; + public void WriteError(ErrorRecord r) { Errors.Add(r); } + public void ThrowTerminatingError(ErrorRecord r) { TerminatingErrors.Add(r); } + public void WriteDebug(string m) { Debug.Add(m); } + public void WriteVerbose(string m) { Verbose.Add(m); } + public void WriteWarning(string m) { Warning.Add(m); } + public void Clear() + { + TerminatingErrors.Clear(); + Errors.Clear(); + Debug.Clear(); + Verbose.Clear(); + Warning.Clear(); + } + } +} diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index 18fc06bf8..c455b28b4 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -138,6 +138,25 @@ internal void Initialize( suppressedOnly); } + /// + /// default initialization. We build everything + /// + public void Initialize() + { + SensibleDefaults sd = new SensibleDefaults(); + this.myStreams = sd.Streams; + this.Initialize( sd.Runspace, myStreams, null, null, null, null, true, false, null ); + } + + + private StreamStorage myStreams; + /// + /// get the streams from our default + /// + public StreamStorage MyStreams { + get { return myStreams; } + } + /// /// Initialize : Initializes default rules, loggers and helper. /// From 567f5304d8d294ce8337b53767d6b0ff757ba3a9 Mon Sep 17 00:00:00 2001 From: James Truher Date: Tue, 25 Jun 2019 10:25:41 -0700 Subject: [PATCH 12/52] Add hosted analyzer class --- Engine/HostedAnalyzer.cs | 45 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 Engine/HostedAnalyzer.cs diff --git a/Engine/HostedAnalyzer.cs b/Engine/HostedAnalyzer.cs new file mode 100644 index 000000000..552c5eab1 --- /dev/null +++ b/Engine/HostedAnalyzer.cs @@ -0,0 +1,45 @@ +using System; +using SMA = System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Collections.Generic; +using Microsoft.Windows.PowerShell.ScriptAnalyzer; +using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; +using System.IO; + +namespace Microsoft.Windows.PowerShell.ScriptAnalyzer +{ + public class HostedAnalyzer + { + private SMA.PowerShell ps; + private IOutputWriter writer; + private ScriptAnalyzer analyzer; + /// + /// Create an instance of the analyzer + /// + public HostedAnalyzer() + { + ps = SMA.PowerShell.Create(); + writer = new StreamStorage(); + ScriptAnalyzer.Instance.Initialize(ps.Runspace, writer); + analyzer = ScriptAnalyzer.Instance; + } + + /// Analyze a script in the form of a string + public IEnumerable Analyze(string ScriptDefinition) + { + var diagnosticList = analyzer.AnalyzeScriptDefinition(ScriptDefinition); + return diagnosticList; + } + + /// Analyze a script in the form of a file + public IEnumerable Analyze(FileInfo File) + { + return default(IEnumerable); + } + } + + public class AnalyzerResult + { + + } +} \ No newline at end of file From 8d7d2a6e666b973c632d6ce73798193a5e6d0693 Mon Sep 17 00:00:00 2001 From: James Truher Date: Thu, 27 Jun 2019 14:02:05 -0700 Subject: [PATCH 13/52] more hosting updates --- Engine/Engine.csproj | 1 + Engine/HostedAnalyzer.cs | 308 +++++++++++++++++++++++++++++++++++++-- Engine/Settings.cs | 4 +- 3 files changed, 299 insertions(+), 14 deletions(-) diff --git a/Engine/Engine.csproj b/Engine/Engine.csproj index d2afcc430..8e478153a 100644 --- a/Engine/Engine.csproj +++ b/Engine/Engine.csproj @@ -6,6 +6,7 @@ Microsoft.Windows.PowerShell.ScriptAnalyzer Engine Microsoft.Windows.PowerShell.ScriptAnalyzer + true diff --git a/Engine/HostedAnalyzer.cs b/Engine/HostedAnalyzer.cs index 552c5eab1..83596a75d 100644 --- a/Engine/HostedAnalyzer.cs +++ b/Engine/HostedAnalyzer.cs @@ -2,44 +2,328 @@ using SMA = System.Management.Automation; using System.Management.Automation.Runspaces; using System.Collections.Generic; +using System.Collections; using Microsoft.Windows.PowerShell.ScriptAnalyzer; using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; +using System.Management.Automation.Language; using System.IO; -namespace Microsoft.Windows.PowerShell.ScriptAnalyzer +namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Hosting { + /// + /// This is a helper class which provides a consistent, easy to use set of interfaces for Script Analyzer + /// This class will enable you to: + /// - Analyze a script or file with any configuration which is supported by Invoke-ScriptAnalyzer + /// - Reformat a script as is done via Invoke-Formatter + /// public class HostedAnalyzer { private SMA.PowerShell ps; - private IOutputWriter writer; + internal hostedWriter writer; private ScriptAnalyzer analyzer; /// - /// Create an instance of the analyzer + /// Create an instance of the hosted analyzer /// public HostedAnalyzer() { - ps = SMA.PowerShell.Create(); - writer = new StreamStorage(); - ScriptAnalyzer.Instance.Initialize(ps.Runspace, writer); + InitialSessionState iss = InitialSessionState.CreateDefault2(); + ps = SMA.PowerShell.Create(iss); + writer = new hostedWriter(); + ScriptAnalyzer.Instance.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); analyzer = ScriptAnalyzer.Instance; } - /// Analyze a script in the form of a string - public IEnumerable Analyze(string ScriptDefinition) + /// Reset the the analyzer and associated state + public void Reset() { - var diagnosticList = analyzer.AnalyzeScriptDefinition(ScriptDefinition); - return diagnosticList; + Helper.Instance.Initialize(); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + } + + /// + /// Analyze a script in the form of a string + /// The script as a string + /// The FixedScriptResult which encapsulates the fixed script + /// + public FixedScriptResult Fix(string ScriptDefinition) + { + writer.ClearWriter(); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); + bool fixesApplied; + return new FixedScriptResult() { + OriginalScript = ScriptDefinition, + FixedScript = analyzer.Fix(ScriptDefinition, out fixesApplied), + Analysis = new AnalyzerResult(AnalysisType.Script, analyzer.AnalyzeScriptDefinition(ScriptDefinition), this) + }; + } + + /// + /// Analyze a script in the form of an AST + /// A scriptblockast which represents the script to analyze + /// The tokens in the ast + /// The name of the file which held the script, if there was one + /// + public AnalyzerResult Analyze(ScriptBlockAst scriptast, Token[] tokens, string filename = null) + { + writer.ClearWriter(); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + var result = analyzer.AnalyzeSyntaxTree(scriptast, tokens, filename); + return new AnalyzerResult(AnalysisType.Ast, result, this); + } + + /// Analyze a script in the form of a string with additional Settings + public AnalyzerResult Analyze(string ScriptDefinition, Hashtable settings) + { + writer.ClearWriter(); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + // var set = CreateSettings(settings); + // analyzer.UpdateSettings(set); + var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); + return new AnalyzerResult(AnalysisType.Script, result, this); + } + + /// Analyze a script in the form of a string, based on default + public AnalyzerResult Analyze(string ScriptDefinition) + { + writer.ClearWriter(); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); + return new AnalyzerResult(AnalysisType.Script, result, this); + } + + /// Analyze a script based on passed settings + public AnalyzerResult Analyze(string ScriptDefinition, AnalyzerConfiguration Settings) + { + writer.ClearWriter(); + analyzer.Initialize(ps.Runspace, writer, Settings.RulePath, Settings.IncludeRuleNames, Settings.ExcludeRuleNames, Settings.Severity, Settings.IncludeDefaultRules, Settings.SuppressedOnly); + var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); + return new AnalyzerResult(AnalysisType.Script, result, this); + } + + /// Analyze a file based on passed settings + public AnalyzerResult Analyze(FileInfo File, AnalyzerConfiguration Settings) + { + writer.ClearWriter(); + analyzer.Initialize(ps.Runspace, writer, Settings.RulePath, Settings.IncludeRuleNames, Settings.ExcludeRuleNames, Settings.Severity, Settings.IncludeDefaultRules, Settings.SuppressedOnly); + var result = analyzer.AnalyzePath(File.FullName, (x, y) => { return true; }, false); + return new AnalyzerResult(AnalysisType.File, result, this); } /// Analyze a script in the form of a file - public IEnumerable Analyze(FileInfo File) + public AnalyzerResult Analyze(FileInfo File) + { + writer.ClearWriter(); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + var result = analyzer.AnalyzePath(File.FullName, (x, y) => { return true; }, false); + return new AnalyzerResult(AnalysisType.File, result, this); + } + + /// + /// Create a standard settings object for Script Analyzer + /// This is the object used by analyzer internally + /// It is more functional than the AnalyzerSettings object because + /// it contains the Rule Arguments which are not passable to the Initialize method + /// + public Settings CreateSettings(string SettingsName) + { + return Settings.Create(SettingsName, "", writer, ps.Runspace.SessionStateProxy.Path.GetResolvedProviderPathFromPSPath); + } + + /// + /// Format a script according to the formatting rules + /// PSPlaceCloseBrace + /// PSPlaceOpenBrace + /// PSUseConsistentWhitespace + /// PSUseConsistentIndentation + /// PSAlignAssignmentStatement + /// PSUseCorrectCasing + /// and the union of the actual settings which are passed to it. + /// + public string Format(string scriptDefinition, Settings settings) { - return default(IEnumerable); + Helper.Instance = new Helper(ps.Runspace.SessionStateProxy.InvokeCommand, writer); + Helper.Instance.Initialize(); + + string[] ruleOrder = new string[] + { + "PSPlaceCloseBrace", + "PSPlaceOpenBrace", + "PSUseConsistentWhitespace", + "PSUseConsistentIndentation", + "PSAlignAssignmentStatement", + "PSUseCorrectCasing" + }; + + var text = new EditableText(scriptDefinition); + Range range = null; + foreach (var rule in ruleOrder) + { + if (!settings.RuleArguments.ContainsKey(rule)) + { + continue; + } + + Settings currentSettings = new Settings(new Hashtable(){ + {"IncludeRules", new string[] { rule }}, + {"Rules", new Hashtable() { { rule, new Hashtable(settings.RuleArguments[rule]) } } } + }); + analyzer.UpdateSettings(currentSettings); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + + Range updatedRange; + bool fixesWereApplied; + text = analyzer.Fix(text, range, out updatedRange, out fixesWereApplied); + range = updatedRange; + } + + return text.ToString(); + } + + /// + /// An encapsulation of the arguments passed to Analyzer.Initialize + /// they roughly equate to some of the parameters on the Invoke-ScriptAnalyzer + /// cmdlet, but encapsulated to improve the experience. + /// + public class AnalyzerConfiguration + { + public string[] RulePath; + public string[] IncludeRuleNames; + public string[] ExcludeRuleNames; + public string[] Severity; + public bool IncludeDefaultRules = true; + public bool SuppressedOnly = false; + } + + /// + /// Analyzer usually requires a cmdlet to manage the output to the user. + /// This class is provided to collect the non-diagnostic record output + /// when invoking the methods in the HostedAnalyzer. + /// + internal class hostedWriter : IOutputWriter + { + /// The terminating errors emitted during the invocation of the analyzer + public IList TerminatingErrors; + /// The non-terminating errors emitted during the invocation of the analyzer + public IList Errors; + /// The verbose messages emitted during the invocation of the analyzer + public IList Verbose; + /// The debug messages emitted during the invocation of the analyzer + public IList Debug; + /// The warning messages emitted during the invocation of the analyzer + public IList Warning; + /// Add a terminating error the ccollection + public void ThrowTerminatingError(SMA.ErrorRecord er) { TerminatingErrors.Add(er); } + /// Add a non-terminating error the ccollection + public void WriteError(SMA.ErrorRecord er) { Errors.Add(er); } + /// Add a verbose message to the verbose collection + public void WriteVerbose(string m) { Verbose.Add(m); } + /// Add a debug message to the debug collection + public void WriteDebug(string m) { Debug.Add(m); } + /// Add a warning message to the warning collection + public void WriteWarning(string m) { Warning.Add(m); } + + /// + /// Clear the writer collections to avoid getting output from + /// multiple invocations + /// + public void ClearWriter() + { + TerminatingErrors.Clear(); + Errors.Clear(); + Verbose.Clear(); + Debug.Clear(); + Warning.Clear(); + } + + /// + /// Initialize all the output colections + /// + public hostedWriter() + { + TerminatingErrors = new List(); + Errors = new List(); + Verbose = new List(); + Debug = new List(); + Warning = new List(); + } } } + + /// + /// The encapsulated rules of fixing a script + /// + public class FixedScriptResult + { + /// The original script that was fixed + public string OriginalScript; + /// The script which has all the fixes + public string FixedScript; + /// + /// The analysis results. + /// This includes all the output streams as well as the diagnostic records + /// + public AnalyzerResult Analysis; + } + + /// + /// Type of entity that was passed to the analyzer + /// + public enum AnalysisType { + /// An Ast was passed to the analyzer + Ast, + /// An FileInfo was passed to the analyzer + File, + /// An script (as a string) was passed to the analyzer + Script + } + /// + /// The encapsulated results of the analyzer + /// public class AnalyzerResult { + /// The type of entity which was analyzed + public AnalysisType Type; + /// The diagnostic records found during analysis + public List Result; + /// The terminating errors which occurred during analysis + public List TerminatingErrors; + /// The non-terminating errors which occurred during analysis + public List Errors; + /// The verbose messages delivered during analysis + public List Verbose; + /// The warning messages delivered during analysis + public List Warning; + /// The debug messages delivered during analysis + public List Debug; + + /// + /// initialize storage + /// + private AnalyzerResult() + { + Type = AnalysisType.Script; + Result = new List(); + TerminatingErrors = new List(); + Errors = new List(); + Verbose = new List(); + Warning = new List(); + Debug = new List(); + } + /// + /// Create results from an invocation of the analyzer + /// + public AnalyzerResult(AnalysisType type, IEnumerablerecords, HostedAnalyzer ha) : this() + { + Type = type; + Result.AddRange(records); + TerminatingErrors.AddRange(ha.writer.TerminatingErrors); + Errors.AddRange(ha.writer.Errors); + Verbose.AddRange(ha.writer.Verbose); + Warning.AddRange(ha.writer.Warning); + Debug.AddRange(ha.writer.Debug); + } } } \ No newline at end of file diff --git a/Engine/Settings.cs b/Engine/Settings.cs index 506733cc2..a3b5712e6 100644 --- a/Engine/Settings.cs +++ b/Engine/Settings.cs @@ -31,8 +31,8 @@ public class Settings private List customRulePath; private Dictionary> ruleArguments; - public bool RecurseCustomRulePath => recurseCustomRulePath; - public bool IncludeDefaultRules => includeDefaultRules; + public bool RecurseCustomRulePath { get; set;} = false; + public bool IncludeDefaultRules { get; set; } = false; public string FilePath => filePath; public IEnumerable IncludeRules => includeRules; public IEnumerable ExcludeRules => excludeRules; From 73fec1baca51df7167c48ce38b99fa3fce952c13 Mon Sep 17 00:00:00 2001 From: James Truher Date: Mon, 8 Jul 2019 11:15:43 -0700 Subject: [PATCH 14/52] Formatter seems to be working Had to make the initialization of the analyzer optional as it was clobbering the settings that had been created in the formatter --- Engine/Formatter.cs | 53 ++++++++++++ Engine/HostedAnalyzer.cs | 177 ++++++++++++++++++++++++++------------- Engine/ScriptAnalyzer.cs | 15 ++-- 3 files changed, 182 insertions(+), 63 deletions(-) diff --git a/Engine/Formatter.cs b/Engine/Formatter.cs index d2b7e0776..7383f9069 100644 --- a/Engine/Formatter.cs +++ b/Engine/Formatter.cs @@ -12,6 +12,59 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer /// public class Formatter { + /// + /// Format a powershell script. + /// + /// A string representing a powershell script. + /// Settings to be used for formatting + /// The range in which formatting should take place. + /// The runspace entrance into the powershell engine. + /// The writer for operation message. + /// + public static string Format( + string scriptDefinition, + Settings settings, + Range range, + System.Management.Automation.Runspaces.Runspace runspace, + IOutputWriter writer) + { + // todo implement notnull attribute for such a check + ValidateNotNull(scriptDefinition, "scriptDefinition"); + ValidateNotNull(settings, "settings"); + + Helper.Instance = new Helper(runspace.SessionStateProxy.InvokeCommand, writer); + Helper.Instance.Initialize(); + + var ruleOrder = new string[] + { + "PSPlaceCloseBrace", + "PSPlaceOpenBrace", + "PSUseConsistentWhitespace", + "PSUseConsistentIndentation", + "PSAlignAssignmentStatement", + "PSUseCorrectCasing" + }; + + var text = new EditableText(scriptDefinition); + foreach (var rule in ruleOrder) + { + if (!settings.RuleArguments.ContainsKey(rule)) + { + continue; + } + + var currentSettings = GetCurrentSettings(settings, rule); + ScriptAnalyzer.Instance.UpdateSettings(currentSettings); + ScriptAnalyzer.Instance.Initialize(runspace, writer, null, null, null, null, true, false, null, false); + + Range updatedRange; + bool fixesWereApplied; + text = ScriptAnalyzer.Instance.Fix(text, range, out updatedRange, out fixesWereApplied); + range = updatedRange; + } + + return text.ToString(); + } /// /// Format a powershell script. /// diff --git a/Engine/HostedAnalyzer.cs b/Engine/HostedAnalyzer.cs index 83596a75d..14238427b 100644 --- a/Engine/HostedAnalyzer.cs +++ b/Engine/HostedAnalyzer.cs @@ -7,6 +7,7 @@ using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; using System.Management.Automation.Language; using System.IO; +using System.Linq; namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Hosting { @@ -36,28 +37,14 @@ public HostedAnalyzer() /// Reset the the analyzer and associated state public void Reset() { + analyzer.CleanUp(); + Helper.Instance = new Helper( + ps.Runspace.SessionStateProxy.InvokeCommand, + writer); Helper.Instance.Initialize(); analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); } - /// - /// Analyze a script in the form of a string - /// The script as a string - /// The FixedScriptResult which encapsulates the fixed script - /// - public FixedScriptResult Fix(string ScriptDefinition) - { - writer.ClearWriter(); - analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); - var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); - bool fixesApplied; - return new FixedScriptResult() { - OriginalScript = ScriptDefinition, - FixedScript = analyzer.Fix(ScriptDefinition, out fixesApplied), - Analysis = new AnalyzerResult(AnalysisType.Script, analyzer.AnalyzeScriptDefinition(ScriptDefinition), this) - }; - } - /// /// Analyze a script in the form of an AST /// A scriptblockast which represents the script to analyze @@ -72,7 +59,30 @@ public AnalyzerResult Analyze(ScriptBlockAst scriptast, Token[] tokens, string f return new AnalyzerResult(AnalysisType.Ast, result, this); } - /// Analyze a script in the form of a string with additional Settings + /// + /// Analyze a script in the form of a string with additional Settings + /// The script as a string + /// A hastable which includes the settings + /// + public AnalyzerResult Analyze(string ScriptDefinition, Settings settings) + { + writer.ClearWriter(); + analyzer.Initialize(ps.Runspace, writer, settings.CustomRulePath == null ? null : settings.CustomRulePath.ToArray(), + settings.IncludeRules == null ? null : settings.IncludeRules.ToArray(), + settings.ExcludeRules == null ? null : settings.ExcludeRules.ToArray(), + settings.Severities == null ? null : settings.Severities.ToArray(), + settings.IncludeDefaultRules, + false, + null); + var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); + return new AnalyzerResult(AnalysisType.Script, result, this); + } + + /// + /// Analyze a script in the form of a string with additional Settings + /// The script as a string + /// A hastable which includes the settings + /// public AnalyzerResult Analyze(string ScriptDefinition, Hashtable settings) { writer.ClearWriter(); @@ -130,6 +140,29 @@ public Settings CreateSettings(string SettingsName) return Settings.Create(SettingsName, "", writer, ps.Runspace.SessionStateProxy.Path.GetResolvedProviderPathFromPSPath); } + /// + /// Create a default settings object + /// + public Settings CreateSettings() + { + Settings s = Settings.Create(null, + Directory.GetParent(Directory.GetParent(typeof(ScriptAnalyzer).Assembly.Location).FullName).FullName, + writer, ps.Runspace.SessionStateProxy.Path.GetResolvedProviderPathFromPSPath); + s.IncludeDefaultRules = true; + return s; + } + + /// + /// Create a standard settings object for Script Analyzer + /// This is the object used by analyzer internally + /// It is more functional than the AnalyzerSettings object because + /// it contains the Rule Arguments which are not passable to the Initialize method + /// + public Settings CreateSettings(Hashtable settings) + { + return Settings.Create(settings, "", writer, ps.Runspace.SessionStateProxy.Path.GetResolvedProviderPathFromPSPath); + } + /// /// Format a script according to the formatting rules /// PSPlaceCloseBrace @@ -142,42 +175,9 @@ public Settings CreateSettings(string SettingsName) /// public string Format(string scriptDefinition, Settings settings) { - Helper.Instance = new Helper(ps.Runspace.SessionStateProxy.InvokeCommand, writer); - Helper.Instance.Initialize(); - - string[] ruleOrder = new string[] - { - "PSPlaceCloseBrace", - "PSPlaceOpenBrace", - "PSUseConsistentWhitespace", - "PSUseConsistentIndentation", - "PSAlignAssignmentStatement", - "PSUseCorrectCasing" - }; - - var text = new EditableText(scriptDefinition); - Range range = null; - foreach (var rule in ruleOrder) - { - if (!settings.RuleArguments.ContainsKey(rule)) - { - continue; - } - - Settings currentSettings = new Settings(new Hashtable(){ - {"IncludeRules", new string[] { rule }}, - {"Rules", new Hashtable() { { rule, new Hashtable(settings.RuleArguments[rule]) } } } - }); - analyzer.UpdateSettings(currentSettings); - analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); - - Range updatedRange; - bool fixesWereApplied; - text = analyzer.Fix(text, range, out updatedRange, out fixesWereApplied); - range = updatedRange; - } - - return text.ToString(); + string s = Formatter.Format(scriptDefinition, settings, null, ps.Runspace, writer); + analyzer.CleanUp(); + return s; } /// @@ -253,12 +253,12 @@ public hostedWriter() /// /// The encapsulated rules of fixing a script /// - public class FixedScriptResult + public class FormattedScriptResult { /// The original script that was fixed public string OriginalScript; /// The script which has all the fixes - public string FixedScript; + public string FormattedScript; /// /// The analysis results. /// This includes all the output streams as well as the diagnostic records @@ -326,4 +326,67 @@ public AnalyzerResult(AnalysisType type, IEnumerablerecords, H Debug.AddRange(ha.writer.Debug); } } + + /// A public settings object + public class PSSASettings + { + /// thing + public bool RecurseCustomRulePath { get; set;} = false; + /// thing + public bool IncludeDefaultRules { get; set; } = false; + /// thing + public string FilePath { get; set; } + /// thing + public List Severities { get; set; } + /// thing + public List CustomRulePath { get; set; } + /// The rules which encapsulate an analyzer setting + public ListRules; + + /// Convert to hashtable so the analyzer method can use it + public Hashtable ConvertToHashtable() + { + Hashtable ht = new Hashtable(); + return ht; + } + } + + /// Whether the rule should be included or excluded + public enum RuleStatus { + /// Include the rule + Include, + /// Exclude the rule + Exclude + } + + /// The encapsulation of a rule + public class PSSARule + { + /// the name for a rule + public string Name; + /// Is the rule included or excluded + public RuleStatus RuleAction; + /// the settings for a rule + public DictionaryRuleSettings; + + /// Create a new rule, the default status is to include it + public PSSARule(string name, RuleStatus status = RuleStatus.Include) { + Name = name; + RuleAction = status; + RuleSettings = new Dictionary(); + } + + /// + /// Create a new rule, the default status is to include it + /// + /// + /// + /// + public PSSARule(string name, RuleStatus status, DictionaryruleSettings) { + Name = name; + RuleAction = status; + RuleSettings = ruleSettings; + } + + } } \ No newline at end of file diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index c455b28b4..ef583e347 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -169,18 +169,21 @@ public void Initialize( string[] severity = null, bool includeDefaultRules = false, bool suppressedOnly = false, - string profile = null) + string profile = null, + bool InitializeHelper = true) { if (runspace == null) { throw new ArgumentNullException("runspace"); } - //initialize helper - Helper.Instance = new Helper( - runspace.SessionStateProxy.InvokeCommand, - outputWriter); - Helper.Instance.Initialize(); + // initialize helper if requested + if ( InitializeHelper ) { + Helper.Instance = new Helper( + runspace.SessionStateProxy.InvokeCommand, + outputWriter); + Helper.Instance.Initialize(); + } this.Initialize( From 874628a34e39a081d38292a621fad789c072d857 Mon Sep 17 00:00:00 2001 From: James Truher Date: Fri, 19 Jul 2019 12:19:36 -0700 Subject: [PATCH 15/52] change calls to analyzer to be more like the cmdlet --- Engine/HostedAnalyzer.cs | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/Engine/HostedAnalyzer.cs b/Engine/HostedAnalyzer.cs index 14238427b..b54a4e6d1 100644 --- a/Engine/HostedAnalyzer.cs +++ b/Engine/HostedAnalyzer.cs @@ -56,6 +56,7 @@ public AnalyzerResult Analyze(ScriptBlockAst scriptast, Token[] tokens, string f writer.ClearWriter(); analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); var result = analyzer.AnalyzeSyntaxTree(scriptast, tokens, filename); + analyzer.CleanUp(); return new AnalyzerResult(AnalysisType.Ast, result, this); } @@ -67,14 +68,11 @@ public AnalyzerResult Analyze(ScriptBlockAst scriptast, Token[] tokens, string f public AnalyzerResult Analyze(string ScriptDefinition, Settings settings) { writer.ClearWriter(); - analyzer.Initialize(ps.Runspace, writer, settings.CustomRulePath == null ? null : settings.CustomRulePath.ToArray(), - settings.IncludeRules == null ? null : settings.IncludeRules.ToArray(), - settings.ExcludeRules == null ? null : settings.ExcludeRules.ToArray(), - settings.Severities == null ? null : settings.Severities.ToArray(), - settings.IncludeDefaultRules, - false, - null); + analyzer.UpdateSettings(settings); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null, false); + // analyzer.Initialize(ps.Runspace, writer, null, null, null, null, false, false, null, false); var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); + analyzer.CleanUp(); return new AnalyzerResult(AnalysisType.Script, result, this); } @@ -90,6 +88,7 @@ public AnalyzerResult Analyze(string ScriptDefinition, Hashtable settings) // var set = CreateSettings(settings); // analyzer.UpdateSettings(set); var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); + analyzer.CleanUp(); return new AnalyzerResult(AnalysisType.Script, result, this); } @@ -108,6 +107,7 @@ public AnalyzerResult Analyze(string ScriptDefinition, AnalyzerConfiguration Set writer.ClearWriter(); analyzer.Initialize(ps.Runspace, writer, Settings.RulePath, Settings.IncludeRuleNames, Settings.ExcludeRuleNames, Settings.Severity, Settings.IncludeDefaultRules, Settings.SuppressedOnly); var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); + analyzer.CleanUp(); return new AnalyzerResult(AnalysisType.Script, result, this); } @@ -117,6 +117,7 @@ public AnalyzerResult Analyze(FileInfo File, AnalyzerConfiguration Settings) writer.ClearWriter(); analyzer.Initialize(ps.Runspace, writer, Settings.RulePath, Settings.IncludeRuleNames, Settings.ExcludeRuleNames, Settings.Severity, Settings.IncludeDefaultRules, Settings.SuppressedOnly); var result = analyzer.AnalyzePath(File.FullName, (x, y) => { return true; }, false); + analyzer.CleanUp(); return new AnalyzerResult(AnalysisType.File, result, this); } @@ -126,6 +127,7 @@ public AnalyzerResult Analyze(FileInfo File) writer.ClearWriter(); analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); var result = analyzer.AnalyzePath(File.FullName, (x, y) => { return true; }, false); + analyzer.CleanUp(); return new AnalyzerResult(AnalysisType.File, result, this); } @@ -175,8 +177,16 @@ public Settings CreateSettings(Hashtable settings) /// public string Format(string scriptDefinition, Settings settings) { - string s = Formatter.Format(scriptDefinition, settings, null, ps.Runspace, writer); - analyzer.CleanUp(); + if ( settings == null ) { + throw new ArgumentException("settings may not be null"); + } + string s; + try { + s = Formatter.Format(scriptDefinition, settings, null, ps.Runspace, writer); + } + finally { + analyzer.CleanUp(); + } return s; } From 67dc945a7af07ee201b2f2874263c8734d19f982 Mon Sep 17 00:00:00 2001 From: James Truher Date: Thu, 5 Sep 2019 15:48:38 -0700 Subject: [PATCH 16/52] checkpoint Hosting seems to work, starting to work on nuget packaging --- Engine/HostedAnalyzer.cs | 74 +++++++++++++++++++++++++---------- Engine/Settings.cs | 61 +++++++++++++++++++++++++++++ Rules/PSScriptAnalyzer.nuspec | 28 +++++++++++++ Rules/Rules.csproj | 1 + build.ps1 | 10 ++++- build.psm1 | 5 +++ 6 files changed, 157 insertions(+), 22 deletions(-) create mode 100644 Rules/PSScriptAnalyzer.nuspec diff --git a/Engine/HostedAnalyzer.cs b/Engine/HostedAnalyzer.cs index b54a4e6d1..9da27f3d2 100644 --- a/Engine/HostedAnalyzer.cs +++ b/Engine/HostedAnalyzer.cs @@ -38,9 +38,7 @@ public HostedAnalyzer() public void Reset() { analyzer.CleanUp(); - Helper.Instance = new Helper( - ps.Runspace.SessionStateProxy.InvokeCommand, - writer); + Helper.Instance = new Helper(ps.Runspace.SessionStateProxy.InvokeCommand, writer); Helper.Instance.Initialize(); analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); } @@ -101,6 +99,7 @@ public AnalyzerResult Analyze(string ScriptDefinition) return new AnalyzerResult(AnalysisType.Script, result, this); } + /* /// Analyze a script based on passed settings public AnalyzerResult Analyze(string ScriptDefinition, AnalyzerConfiguration Settings) { @@ -120,6 +119,7 @@ public AnalyzerResult Analyze(FileInfo File, AnalyzerConfiguration Settings) analyzer.CleanUp(); return new AnalyzerResult(AnalysisType.File, result, this); } + */ /// Analyze a script in the form of a file public AnalyzerResult Analyze(FileInfo File) @@ -131,6 +131,13 @@ public AnalyzerResult Analyze(FileInfo File) return new AnalyzerResult(AnalysisType.File, result, this); } + /// Fix a script + public string Fix(string scriptDefinition) + { + bool fixesApplied; + return analyzer.Fix(scriptDefinition, out fixesApplied); + } + /// /// Create a standard settings object for Script Analyzer /// This is the object used by analyzer internally @@ -142,13 +149,22 @@ public Settings CreateSettings(string SettingsName) return Settings.Create(SettingsName, "", writer, ps.Runspace.SessionStateProxy.Path.GetResolvedProviderPathFromPSPath); } + /// + /// Create a standard settings object for Script Analyzer from an existing .psd1 file + /// + public Settings CreateSettingsFromFile(string settingsFile) + { + return new Settings(settingsFile); + } + /// /// Create a default settings object /// public Settings CreateSettings() { - Settings s = Settings.Create(null, - Directory.GetParent(Directory.GetParent(typeof(ScriptAnalyzer).Assembly.Location).FullName).FullName, + + Settings s = Settings.Create(new Hashtable(), + "", writer, ps.Runspace.SessionStateProxy.Path.GetResolvedProviderPathFromPSPath); s.IncludeDefaultRules = true; return s; @@ -186,10 +202,12 @@ public string Format(string scriptDefinition, Settings settings) } finally { analyzer.CleanUp(); + Reset(); } return s; } +/* /// /// An encapsulation of the arguments passed to Analyzer.Initialize /// they roughly equate to some of the parameters on the Invoke-ScriptAnalyzer @@ -204,6 +222,7 @@ public class AnalyzerConfiguration public bool IncludeDefaultRules = true; public bool SuppressedOnly = false; } +*/ /// /// Analyzer usually requires a cmdlet to manage the output to the user. @@ -258,22 +277,30 @@ public hostedWriter() Warning = new List(); } } - } - - /// - /// The encapsulated rules of fixing a script - /// - public class FormattedScriptResult - { - /// The original script that was fixed - public string OriginalScript; - /// The script which has all the fixes - public string FormattedScript; - /// - /// The analysis results. - /// This includes all the output streams as well as the diagnostic records - /// - public AnalyzerResult Analysis; + + /// Get the available builtin rules + /// A collection of strings which contain the wildcard pattern for the rule + public List GetBuiltinRules(string[] ruleNames = null) + { + List builtinRules = new List(); + IEnumerable rules = ScriptAnalyzer.Instance.GetRule(null, ruleNames); + foreach ( IRule rule in rules ) + { + builtinRules.Add( + new RuleInfo( + name: rule.GetName(), + commonName: rule.GetCommonName(), + description: rule.GetDescription(), + sourceType: rule.GetSourceType(), + sourceName: rule.GetSourceName(), + severity: rule.GetSeverity(), + implementingType: rule.GetType() + ) + ); + } + return builtinRules; + } + } /// @@ -337,6 +364,7 @@ public AnalyzerResult(AnalysisType type, IEnumerablerecords, H } } +/* /// A public settings object public class PSSASettings { @@ -360,6 +388,7 @@ public Hashtable ConvertToHashtable() return ht; } } +*/ /// Whether the rule should be included or excluded public enum RuleStatus { @@ -369,6 +398,7 @@ public enum RuleStatus { Exclude } +/* /// The encapsulation of a rule public class PSSARule { @@ -399,4 +429,6 @@ public PSSARule(string name, RuleStatus status, DictionaryruleSe } } +*/ + } \ No newline at end of file diff --git a/Engine/Settings.cs b/Engine/Settings.cs index a3b5712e6..c4e75ab5a 100644 --- a/Engine/Settings.cs +++ b/Engine/Settings.cs @@ -106,6 +106,67 @@ public Settings(object settings) : this(settings, null) { } + /// + /// Add a configurable rule to the rule arguments + /// + public void AddRuleArgument(string name, Dictionarysetting) + { + Dictionary currentSetting; + if (RuleArguments.TryGetValue(name, out currentSetting)) + { + throw new ArgumentException(name + " already in settings"); + } + RuleArguments.Add(name, setting); + } + + /// + /// Add a configuration element to a rule + /// + public void AddRuleArgument(string rule, string setting, object value) + { + // the rule does not exist, that's a problem, first create it and then add the key/value pair + Dictionary settingDictionary; + if ( ! RuleArguments.TryGetValue(rule, out settingDictionary)) + { + Dictionary newSetting = new Dictionary(); + newSetting.Add(setting, value); + RuleArguments.Add(rule, newSetting); + return; + } + + // the setting exists, just change the value + object o; + if ( settingDictionary.TryGetValue(setting, out o)) + { + SetRuleArgument(rule, setting, value); + return; + } + + settingDictionary.Add(setting, value); + return; + } + + /// + /// Allow for changing setting of an existing value + /// + public void SetRuleArgument(string rule, string setting, object value) + { + Dictionary settingDictionary; + if ( ! RuleArguments.TryGetValue(rule, out settingDictionary)) + { + throw new KeyNotFoundException(rule); + } + + object o; + if ( ! settingDictionary.TryGetValue(setting, out o)) + { + throw new KeyNotFoundException(setting); + } + + settingDictionary[setting] = value; + return; + } + /// /// Retrieves the Settings directory from the Module directory structure /// diff --git a/Rules/PSScriptAnalyzer.nuspec b/Rules/PSScriptAnalyzer.nuspec new file mode 100644 index 000000000..18108fb3d --- /dev/null +++ b/Rules/PSScriptAnalyzer.nuspec @@ -0,0 +1,28 @@ + + + + PSScriptAnalyzer.Library + 1.18.2 + PSScriptAnalyzer.Library + Microsoft + Microsoft,PowerShellTeam + https://github.com/PowerShell/PSScriptAnalyzer + https://github.com/PowerShell/PowerShell/blob/master/assets/Powershell_64.png + https://github.com/PowerShell/PowerShell/blob/master/LICENSE.txt + false + Contains hostable PowerShell Script Analyzer + © Microsoft Corporation. All rights reserved. + PowerShell, reference, net452, netstandard2, netstandard2.0 + + + False + + + + + + + + + + diff --git a/Rules/Rules.csproj b/Rules/Rules.csproj index 80d17b516..1fa4bf282 100644 --- a/Rules/Rules.csproj +++ b/Rules/Rules.csproj @@ -6,6 +6,7 @@ Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules Rules Microsoft.Windows.PowerShell.ScriptAnalyzer + ./PSScriptAnalyzer.nuspec diff --git a/build.ps1 b/build.ps1 index 14e8a03ee..8f306c13d 100644 --- a/build.ps1 +++ b/build.ps1 @@ -34,7 +34,12 @@ param( [switch] $InProcess, [Parameter(ParameterSetName='Bootstrap')] - [switch] $Bootstrap + [switch] $Bootstrap, + + [Parameter(ParameterSetName='BuildOne')] + [Parameter(ParameterSetName='BuildAll')] + [switch] $Pack + ) BEGIN { if ($PSVersion -gt 6) { @@ -79,4 +84,7 @@ END { throw "Unexpected parameter set '$setName'" } } + if ( $Pack ) { + Export-NuPkg + } } diff --git a/build.psm1 b/build.psm1 index 0b0d2517c..202b22330 100644 --- a/build.psm1 +++ b/build.psm1 @@ -696,3 +696,8 @@ function Copy-CrossCompatibilityModule } } } + +function Export-NuPkg +{ + dotnet pack +} From dd5b919f12e9cba34dc6e27745b727893d49d36b Mon Sep 17 00:00:00 2001 From: James Truher Date: Tue, 10 Sep 2019 12:17:10 -0700 Subject: [PATCH 17/52] Since RunspacePools are disposable, implement IDisposable for HostedAnalyzer, CommandCache, and Helper --- Engine/CommandInfoCache.cs | 6 ++++++ Engine/Helper.cs | 7 ++++++- Engine/HostedAnalyzer.cs | 7 ++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Engine/CommandInfoCache.cs b/Engine/CommandInfoCache.cs index 61e78d86b..16be828c0 100644 --- a/Engine/CommandInfoCache.cs +++ b/Engine/CommandInfoCache.cs @@ -86,6 +86,12 @@ public CommandInfo GetCommandInfoLegacy(string commandOrAliasName, CommandTypes? : GetCommandInfo(commandName, commandTypes: commandTypes); } + /// no + public void Dispose() + { + _runspacePool.Dispose(); + } + /// /// Get a CommandInfo object of the given command name /// diff --git a/Engine/Helper.cs b/Engine/Helper.cs index 71be95b4b..1093fe020 100644 --- a/Engine/Helper.cs +++ b/Engine/Helper.cs @@ -19,7 +19,7 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer /// /// This Helper class contains utility/helper functions for classes in ScriptAnalyzer. /// - public class Helper + public class Helper : IDisposable { #region Private members @@ -67,6 +67,11 @@ internal set #endregion + public void Dispose() + { + _runSpacePool.Dispose(); + } + #region Properties /// diff --git a/Engine/HostedAnalyzer.cs b/Engine/HostedAnalyzer.cs index 9da27f3d2..629d4a5d2 100644 --- a/Engine/HostedAnalyzer.cs +++ b/Engine/HostedAnalyzer.cs @@ -17,7 +17,7 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Hosting /// - Analyze a script or file with any configuration which is supported by Invoke-ScriptAnalyzer /// - Reformat a script as is done via Invoke-Formatter /// - public class HostedAnalyzer + public class HostedAnalyzer : IDisposable { private SMA.PowerShell ps; internal hostedWriter writer; @@ -301,6 +301,11 @@ public List GetBuiltinRules(string[] ruleNames = null) return builtinRules; } + /// no + public void Dispose() + { + Helper.Instance.Dispose(); + } } /// From 01f7957b9f8334dae10eef6c2f45d28faf396eee Mon Sep 17 00:00:00 2001 From: Jim Truher Date: Tue, 10 Sep 2019 14:30:56 -0700 Subject: [PATCH 18/52] add another supported library --- Rules/PSScriptAnalyzer.nuspec | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Rules/PSScriptAnalyzer.nuspec b/Rules/PSScriptAnalyzer.nuspec index 18108fb3d..80b71df05 100644 --- a/Rules/PSScriptAnalyzer.nuspec +++ b/Rules/PSScriptAnalyzer.nuspec @@ -13,16 +13,22 @@ Contains hostable PowerShell Script Analyzer © Microsoft Corporation. All rights reserved. PowerShell, reference, net452, netstandard2, netstandard2.0 + + + + + From ed3436b3d92b93650b907d06e452635689c4f0fb Mon Sep 17 00:00:00 2001 From: James Truher Date: Thu, 12 Sep 2019 14:30:01 -0700 Subject: [PATCH 19/52] Remove IDisposable in Helper as we no longer create a runspace pool --- Engine/Helper.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Engine/Helper.cs b/Engine/Helper.cs index 1093fe020..71be95b4b 100644 --- a/Engine/Helper.cs +++ b/Engine/Helper.cs @@ -19,7 +19,7 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer /// /// This Helper class contains utility/helper functions for classes in ScriptAnalyzer. /// - public class Helper : IDisposable + public class Helper { #region Private members @@ -67,11 +67,6 @@ internal set #endregion - public void Dispose() - { - _runSpacePool.Dispose(); - } - #region Properties /// From db5c2f7b968ae333c2d026020c5213b241d535f5 Mon Sep 17 00:00:00 2001 From: James Truher Date: Fri, 13 Sep 2019 10:39:02 -0700 Subject: [PATCH 20/52] make helper disposable to remove all vestiges of runspaces --- Engine/CommandInfoCache.cs | 6 ------ Engine/Helper.cs | 24 +++++++++++++++++++++++- Engine/HostedAnalyzer.cs | 6 ++++-- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/Engine/CommandInfoCache.cs b/Engine/CommandInfoCache.cs index 16be828c0..61e78d86b 100644 --- a/Engine/CommandInfoCache.cs +++ b/Engine/CommandInfoCache.cs @@ -86,12 +86,6 @@ public CommandInfo GetCommandInfoLegacy(string commandOrAliasName, CommandTypes? : GetCommandInfo(commandName, commandTypes: commandTypes); } - /// no - public void Dispose() - { - _runspacePool.Dispose(); - } - /// /// Get a CommandInfo object of the given command name /// diff --git a/Engine/Helper.cs b/Engine/Helper.cs index 71be95b4b..43585682d 100644 --- a/Engine/Helper.cs +++ b/Engine/Helper.cs @@ -19,7 +19,7 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer /// /// This Helper class contains utility/helper functions for classes in ScriptAnalyzer. /// - public class Helper + public class Helper : IDisposable { #region Private members @@ -67,6 +67,28 @@ internal set #endregion + private bool disposed = false; + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if ( disposed ) + { + return; + } + + if ( disposing ) + { + CommandInfoCache.Dispose(); + } + + disposed = true; + } + #region Properties /// diff --git a/Engine/HostedAnalyzer.cs b/Engine/HostedAnalyzer.cs index 629d4a5d2..ce20baae5 100644 --- a/Engine/HostedAnalyzer.cs +++ b/Engine/HostedAnalyzer.cs @@ -301,10 +301,12 @@ public List GetBuiltinRules(string[] ruleNames = null) return builtinRules; } - /// no + /// Dispose the PowerShell instance public void Dispose() { Helper.Instance.Dispose(); + ps.Runspace.Dispose(); + ps.Dispose(); } } @@ -436,4 +438,4 @@ public PSSARule(string name, RuleStatus status, DictionaryruleSe } */ -} \ No newline at end of file +} From a3d58a875eb2c6ad5d92f2abb723958613f99a03 Mon Sep 17 00:00:00 2001 From: James Truher Date: Mon, 23 Sep 2019 16:01:13 -0700 Subject: [PATCH 21/52] Add locking to hosted analyzer methods which change analyzer state --- Engine/HostedAnalyzer.cs | 260 +++++++++++++++++---------------------- 1 file changed, 113 insertions(+), 147 deletions(-) diff --git a/Engine/HostedAnalyzer.cs b/Engine/HostedAnalyzer.cs index ce20baae5..236dd183b 100644 --- a/Engine/HostedAnalyzer.cs +++ b/Engine/HostedAnalyzer.cs @@ -6,6 +6,7 @@ using Microsoft.Windows.PowerShell.ScriptAnalyzer; using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; using System.Management.Automation.Language; +using System.Threading; using System.IO; using System.Linq; @@ -22,6 +23,11 @@ public class HostedAnalyzer : IDisposable private SMA.PowerShell ps; internal hostedWriter writer; private ScriptAnalyzer analyzer; + private bool disposed = false; + + object hostedAnalyzerLock = new object(); + bool lockWasTaken = false; + /// /// Create an instance of the hosted analyzer /// @@ -37,10 +43,13 @@ public HostedAnalyzer() /// Reset the the analyzer and associated state public void Reset() { - analyzer.CleanUp(); - Helper.Instance = new Helper(ps.Runspace.SessionStateProxy.InvokeCommand, writer); - Helper.Instance.Initialize(); - analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + + lock (hostedAnalyzerLock) { + analyzer.CleanUp(); + Helper.Instance = new Helper(ps.Runspace.SessionStateProxy.InvokeCommand, writer); + Helper.Instance.Initialize(); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + } } /// @@ -51,11 +60,19 @@ public void Reset() /// public AnalyzerResult Analyze(ScriptBlockAst scriptast, Token[] tokens, string filename = null) { - writer.ClearWriter(); - analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); - var result = analyzer.AnalyzeSyntaxTree(scriptast, tokens, filename); - analyzer.CleanUp(); - return new AnalyzerResult(AnalysisType.Ast, result, this); + try + { + Monitor.Enter(hostedAnalyzerLock, ref lockWasTaken); + writer.ClearWriter(); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + var result = analyzer.AnalyzeSyntaxTree(scriptast, tokens, filename); + return new AnalyzerResult(AnalysisType.Ast, result, this); + } + finally + { + analyzer.CleanUp(); + Monitor.Exit(hostedAnalyzerLock); + } } /// @@ -65,13 +82,18 @@ public AnalyzerResult Analyze(ScriptBlockAst scriptast, Token[] tokens, string f /// public AnalyzerResult Analyze(string ScriptDefinition, Settings settings) { - writer.ClearWriter(); - analyzer.UpdateSettings(settings); - analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null, false); - // analyzer.Initialize(ps.Runspace, writer, null, null, null, null, false, false, null, false); - var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); - analyzer.CleanUp(); - return new AnalyzerResult(AnalysisType.Script, result, this); + try { + Monitor.Enter(hostedAnalyzerLock, ref lockWasTaken); + writer.ClearWriter(); + analyzer.UpdateSettings(settings); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null, false); + var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); + return new AnalyzerResult(AnalysisType.Script, result, this); + } + finally { + analyzer.CleanUp(); + Monitor.Exit(hostedAnalyzerLock); + } } /// @@ -81,61 +103,72 @@ public AnalyzerResult Analyze(string ScriptDefinition, Settings settings) /// public AnalyzerResult Analyze(string ScriptDefinition, Hashtable settings) { - writer.ClearWriter(); - analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); - // var set = CreateSettings(settings); - // analyzer.UpdateSettings(set); - var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); - analyzer.CleanUp(); - return new AnalyzerResult(AnalysisType.Script, result, this); + try + { + Monitor.Enter(hostedAnalyzerLock, ref lockWasTaken); + writer.ClearWriter(); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + analyzer.UpdateSettings(CreateSettings(settings)); + var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); + return new AnalyzerResult(AnalysisType.Script, result, this); + } + finally + { + analyzer.CleanUp(); + Monitor.Exit(hostedAnalyzerLock); + } } /// Analyze a script in the form of a string, based on default public AnalyzerResult Analyze(string ScriptDefinition) { - writer.ClearWriter(); - analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); - var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); - return new AnalyzerResult(AnalysisType.Script, result, this); - } - - /* - /// Analyze a script based on passed settings - public AnalyzerResult Analyze(string ScriptDefinition, AnalyzerConfiguration Settings) - { - writer.ClearWriter(); - analyzer.Initialize(ps.Runspace, writer, Settings.RulePath, Settings.IncludeRuleNames, Settings.ExcludeRuleNames, Settings.Severity, Settings.IncludeDefaultRules, Settings.SuppressedOnly); - var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); - analyzer.CleanUp(); - return new AnalyzerResult(AnalysisType.Script, result, this); - } - - /// Analyze a file based on passed settings - public AnalyzerResult Analyze(FileInfo File, AnalyzerConfiguration Settings) - { - writer.ClearWriter(); - analyzer.Initialize(ps.Runspace, writer, Settings.RulePath, Settings.IncludeRuleNames, Settings.ExcludeRuleNames, Settings.Severity, Settings.IncludeDefaultRules, Settings.SuppressedOnly); - var result = analyzer.AnalyzePath(File.FullName, (x, y) => { return true; }, false); - analyzer.CleanUp(); - return new AnalyzerResult(AnalysisType.File, result, this); + try + { + Monitor.Enter(hostedAnalyzerLock, ref lockWasTaken); + writer.ClearWriter(); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); + return new AnalyzerResult(AnalysisType.Script, result, this); + } + finally + { + analyzer.CleanUp(); + Monitor.Exit(hostedAnalyzerLock); + } } - */ /// Analyze a script in the form of a file public AnalyzerResult Analyze(FileInfo File) { - writer.ClearWriter(); - analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); - var result = analyzer.AnalyzePath(File.FullName, (x, y) => { return true; }, false); - analyzer.CleanUp(); - return new AnalyzerResult(AnalysisType.File, result, this); + try { + Monitor.Enter(hostedAnalyzerLock, ref lockWasTaken); + writer.ClearWriter(); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + var result = analyzer.AnalyzePath(File.FullName, (x, y) => { return true; }, false); + return new AnalyzerResult(AnalysisType.File, result, this); + } + finally + { + analyzer.CleanUp(); + Monitor.Exit(hostedAnalyzerLock); + } } /// Fix a script public string Fix(string scriptDefinition) { - bool fixesApplied; - return analyzer.Fix(scriptDefinition, out fixesApplied); + try + { + Monitor.Enter(hostedAnalyzerLock, ref lockWasTaken); + bool fixesApplied; + return analyzer.Fix(scriptDefinition, out fixesApplied); + } + finally + { + analyzer.CleanUp(); + Reset(); + Monitor.Exit(hostedAnalyzerLock); + } } /// @@ -162,10 +195,7 @@ public Settings CreateSettingsFromFile(string settingsFile) /// public Settings CreateSettings() { - - Settings s = Settings.Create(new Hashtable(), - "", - writer, ps.Runspace.SessionStateProxy.Path.GetResolvedProviderPathFromPSPath); + Settings s = Settings.Create(new Hashtable(), "", writer, ps.Runspace.SessionStateProxy.Path.GetResolvedProviderPathFromPSPath); s.IncludeDefaultRules = true; return s; } @@ -198,32 +228,19 @@ public string Format(string scriptDefinition, Settings settings) } string s; try { + Monitor.Enter(hostedAnalyzerLock, ref lockWasTaken); s = Formatter.Format(scriptDefinition, settings, null, ps.Runspace, writer); } finally { analyzer.CleanUp(); + // Reset is required because formatting leaves a number of settings behind which + // should be cleared. Reset(); + Monitor.Exit(hostedAnalyzerLock); } return s; } -/* - /// - /// An encapsulation of the arguments passed to Analyzer.Initialize - /// they roughly equate to some of the parameters on the Invoke-ScriptAnalyzer - /// cmdlet, but encapsulated to improve the experience. - /// - public class AnalyzerConfiguration - { - public string[] RulePath; - public string[] IncludeRuleNames; - public string[] ExcludeRuleNames; - public string[] Severity; - public bool IncludeDefaultRules = true; - public bool SuppressedOnly = false; - } -*/ - /// /// Analyzer usually requires a cmdlet to manage the output to the user. /// This class is provided to collect the non-diagnostic record output @@ -304,9 +321,25 @@ public List GetBuiltinRules(string[] ruleNames = null) /// Dispose the PowerShell instance public void Dispose() { - Helper.Instance.Dispose(); - ps.Runspace.Dispose(); - ps.Dispose(); + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if ( disposed ) + { + return; + } + + if ( disposing ) + { + Helper.Instance.Dispose(); + ps.Runspace.Dispose(); + ps.Dispose(); + } + + disposed = true; } } @@ -371,71 +404,4 @@ public AnalyzerResult(AnalysisType type, IEnumerablerecords, H } } -/* - /// A public settings object - public class PSSASettings - { - /// thing - public bool RecurseCustomRulePath { get; set;} = false; - /// thing - public bool IncludeDefaultRules { get; set; } = false; - /// thing - public string FilePath { get; set; } - /// thing - public List Severities { get; set; } - /// thing - public List CustomRulePath { get; set; } - /// The rules which encapsulate an analyzer setting - public ListRules; - - /// Convert to hashtable so the analyzer method can use it - public Hashtable ConvertToHashtable() - { - Hashtable ht = new Hashtable(); - return ht; - } - } -*/ - - /// Whether the rule should be included or excluded - public enum RuleStatus { - /// Include the rule - Include, - /// Exclude the rule - Exclude - } - -/* - /// The encapsulation of a rule - public class PSSARule - { - /// the name for a rule - public string Name; - /// Is the rule included or excluded - public RuleStatus RuleAction; - /// the settings for a rule - public DictionaryRuleSettings; - - /// Create a new rule, the default status is to include it - public PSSARule(string name, RuleStatus status = RuleStatus.Include) { - Name = name; - RuleAction = status; - RuleSettings = new Dictionary(); - } - - /// - /// Create a new rule, the default status is to include it - /// - /// - /// - /// - public PSSARule(string name, RuleStatus status, DictionaryruleSettings) { - Name = name; - RuleAction = status; - RuleSettings = ruleSettings; - } - - } -*/ - } From b38e35d8ac8430bdc8af6b48028a3dad0466fd89 Mon Sep 17 00:00:00 2001 From: James Truher Date: Mon, 7 Oct 2019 12:22:58 -0700 Subject: [PATCH 22/52] Add simple locking to hosted analyzer apis There really isn't a good way to do this as the underlying apis aren't really thread safe, but we can do _something_ to the hosted analyzer If someone is using both the hosted analyzer and the module there may be some unexpected behaviors but doing something is probably better than doing nothing. --- Engine/HostedAnalyzer.cs | 162 +++++++++++++++++++++------------------ 1 file changed, 86 insertions(+), 76 deletions(-) diff --git a/Engine/HostedAnalyzer.cs b/Engine/HostedAnalyzer.cs index 236dd183b..8204e2a11 100644 --- a/Engine/HostedAnalyzer.cs +++ b/Engine/HostedAnalyzer.cs @@ -60,18 +60,19 @@ public void Reset() /// public AnalyzerResult Analyze(ScriptBlockAst scriptast, Token[] tokens, string filename = null) { - try + lock(hostedAnalyzerLock) { - Monitor.Enter(hostedAnalyzerLock, ref lockWasTaken); - writer.ClearWriter(); - analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); - var result = analyzer.AnalyzeSyntaxTree(scriptast, tokens, filename); - return new AnalyzerResult(AnalysisType.Ast, result, this); - } - finally - { - analyzer.CleanUp(); - Monitor.Exit(hostedAnalyzerLock); + try + { + writer.ClearWriter(); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + var result = analyzer.AnalyzeSyntaxTree(scriptast, tokens, filename); + return new AnalyzerResult(AnalysisType.Ast, result, this); + } + finally + { + analyzer.CleanUp(); + } } } @@ -82,17 +83,20 @@ public AnalyzerResult Analyze(ScriptBlockAst scriptast, Token[] tokens, string f /// public AnalyzerResult Analyze(string ScriptDefinition, Settings settings) { - try { - Monitor.Enter(hostedAnalyzerLock, ref lockWasTaken); - writer.ClearWriter(); - analyzer.UpdateSettings(settings); - analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null, false); - var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); - return new AnalyzerResult(AnalysisType.Script, result, this); - } - finally { - analyzer.CleanUp(); - Monitor.Exit(hostedAnalyzerLock); + lock(hostedAnalyzerLock) + { + try + { + writer.ClearWriter(); + analyzer.UpdateSettings(settings); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null, false); + var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); + return new AnalyzerResult(AnalysisType.Script, result, this); + } + finally + { + analyzer.CleanUp(); + } } } @@ -103,71 +107,75 @@ public AnalyzerResult Analyze(string ScriptDefinition, Settings settings) /// public AnalyzerResult Analyze(string ScriptDefinition, Hashtable settings) { - try - { - Monitor.Enter(hostedAnalyzerLock, ref lockWasTaken); - writer.ClearWriter(); - analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); - analyzer.UpdateSettings(CreateSettings(settings)); - var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); - return new AnalyzerResult(AnalysisType.Script, result, this); - } - finally + lock(hostedAnalyzerLock) { - analyzer.CleanUp(); - Monitor.Exit(hostedAnalyzerLock); + try + { + writer.ClearWriter(); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + analyzer.UpdateSettings(CreateSettings(settings)); + var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); + return new AnalyzerResult(AnalysisType.Script, result, this); + } + finally + { + analyzer.CleanUp(); + } } } /// Analyze a script in the form of a string, based on default public AnalyzerResult Analyze(string ScriptDefinition) { - try - { - Monitor.Enter(hostedAnalyzerLock, ref lockWasTaken); - writer.ClearWriter(); - analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); - var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); - return new AnalyzerResult(AnalysisType.Script, result, this); - } - finally + lock(hostedAnalyzerLock) { - analyzer.CleanUp(); - Monitor.Exit(hostedAnalyzerLock); + try + { + writer.ClearWriter(); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + var result = analyzer.AnalyzeScriptDefinition(ScriptDefinition); + return new AnalyzerResult(AnalysisType.Script, result, this); + } + finally + { + analyzer.CleanUp(); + } } } /// Analyze a script in the form of a file public AnalyzerResult Analyze(FileInfo File) { - try { - Monitor.Enter(hostedAnalyzerLock, ref lockWasTaken); - writer.ClearWriter(); - analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); - var result = analyzer.AnalyzePath(File.FullName, (x, y) => { return true; }, false); - return new AnalyzerResult(AnalysisType.File, result, this); - } - finally + lock(hostedAnalyzerLock) { - analyzer.CleanUp(); - Monitor.Exit(hostedAnalyzerLock); + try { + writer.ClearWriter(); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + var result = analyzer.AnalyzePath(File.FullName, (x, y) => { return true; }, false); + return new AnalyzerResult(AnalysisType.File, result, this); + } + finally + { + analyzer.CleanUp(); + } } } /// Fix a script public string Fix(string scriptDefinition) { - try + lock(hostedAnalyzerLock) { - Monitor.Enter(hostedAnalyzerLock, ref lockWasTaken); - bool fixesApplied; - return analyzer.Fix(scriptDefinition, out fixesApplied); - } - finally - { - analyzer.CleanUp(); - Reset(); - Monitor.Exit(hostedAnalyzerLock); + try + { + Reset(); + bool fixesApplied; + return analyzer.Fix(scriptDefinition, out fixesApplied); + } + finally + { + Reset(); + } } } @@ -227,16 +235,17 @@ public string Format(string scriptDefinition, Settings settings) throw new ArgumentException("settings may not be null"); } string s; - try { - Monitor.Enter(hostedAnalyzerLock, ref lockWasTaken); - s = Formatter.Format(scriptDefinition, settings, null, ps.Runspace, writer); - } - finally { - analyzer.CleanUp(); - // Reset is required because formatting leaves a number of settings behind which - // should be cleared. - Reset(); - Monitor.Exit(hostedAnalyzerLock); + lock(hostedAnalyzerLock) + { + try { + s = Formatter.Format(scriptDefinition, settings, null, ps.Runspace, writer); + } + finally { + analyzer.CleanUp(); + // Reset is required because formatting leaves a number of settings behind which + // should be cleared. + Reset(); + } } return s; } @@ -318,13 +327,14 @@ public List GetBuiltinRules(string[] ruleNames = null) return builtinRules; } - /// Dispose the PowerShell instance + /// Dispose the Hosted Analyzer resources public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } + /// Dispose the Helper, runspace, and Powershell instance protected virtual void Dispose(bool disposing) { if ( disposed ) From b2e76ee39c208a87b10a024c0cf58829610dc8af Mon Sep 17 00:00:00 2001 From: James Truher Date: Tue, 8 Oct 2019 16:36:57 -0700 Subject: [PATCH 23/52] Change hostedanalyzer fields to properties --- Engine/HostedAnalyzer.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Engine/HostedAnalyzer.cs b/Engine/HostedAnalyzer.cs index 8204e2a11..189c655f3 100644 --- a/Engine/HostedAnalyzer.cs +++ b/Engine/HostedAnalyzer.cs @@ -371,19 +371,19 @@ public enum AnalysisType { public class AnalyzerResult { /// The type of entity which was analyzed - public AnalysisType Type; + public AnalysisType Type { get; set; } /// The diagnostic records found during analysis - public List Result; + public List Result { get; set; } /// The terminating errors which occurred during analysis - public List TerminatingErrors; + public List TerminatingErrors { get; set; } /// The non-terminating errors which occurred during analysis - public List Errors; + public List Errors { get; set; } /// The verbose messages delivered during analysis - public List Verbose; + public List Verbose { get; set; } /// The warning messages delivered during analysis - public List Warning; + public List Warning { get; set; } /// The debug messages delivered during analysis - public List Debug; + public List Debug { get; set; } /// /// initialize storage From ba88ff8d19edbb712f9466754949523784c62a00 Mon Sep 17 00:00:00 2001 From: James Truher Date: Tue, 15 Oct 2019 10:52:08 -0700 Subject: [PATCH 24/52] Remove dispose of Helper.Instance. Disposing the helper seems to cause downstream problems. Add async calls to the analyzer --- Engine/HostedAnalyzer.cs | 158 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 155 insertions(+), 3 deletions(-) diff --git a/Engine/HostedAnalyzer.cs b/Engine/HostedAnalyzer.cs index 189c655f3..c06d21f10 100644 --- a/Engine/HostedAnalyzer.cs +++ b/Engine/HostedAnalyzer.cs @@ -7,6 +7,7 @@ using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; using System.Management.Automation.Language; using System.Threading; +using System.Threading.Tasks; using System.IO; using System.Linq; @@ -54,10 +55,49 @@ public void Reset() /// /// Analyze a script in the form of an AST + /// /// A scriptblockast which represents the script to analyze /// The tokens in the ast + /// A settings object which defines which rules to run /// The name of the file which held the script, if there was one + /// An AnalyzerResult which encapsulates the analysis of the ast + public AnalyzerResult Analyze(ScriptBlockAst scriptast, Token[] tokens, Settings settings, string filename = null) + { + lock(hostedAnalyzerLock) + { + try + { + writer.ClearWriter(); + analyzer.UpdateSettings(settings); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + var result = analyzer.AnalyzeSyntaxTree(scriptast, tokens, filename); + return new AnalyzerResult(AnalysisType.Ast, result, this); + } + finally + { + analyzer.CleanUp(); + } + } + } + + /// + /// Analyze a script in the form of an AST /// + /// A scriptblockast which represents the script to analyze + /// The tokens in the ast + /// A settings object which defines which rules to run + /// The name of the file which held the script, if there was one + /// An AnalyzerResult which encapsulates the analysis of the ast + public Task AnalyzeAsync(ScriptBlockAst scriptast, Token[] tokens, Settings settings, string filename = null) => + Task.Run(() => Analyze(scriptast, tokens, settings, filename)); + + /// + /// Analyze a script in the form of an AST + /// + /// A scriptblockast which represents the script to analyze + /// The tokens in the ast + /// The name of the file which held the script, if there was one + /// An AnalyzerResult which encapsulates the analysis of the ast public AnalyzerResult Analyze(ScriptBlockAst scriptast, Token[] tokens, string filename = null) { lock(hostedAnalyzerLock) @@ -76,11 +116,23 @@ public AnalyzerResult Analyze(ScriptBlockAst scriptast, Token[] tokens, string f } } + /// + /// Analyze a script in the form of an AST + /// + /// A scriptblockast which represents the script to analyze + /// The tokens in the ast + /// The name of the file which held the script, if there was one + /// An AnalyzerResult which encapsulates the analysis of the ast + public Task AnalyzeAsync(ScriptBlockAst scriptast, Token[] tokens, string filename = null) => + Task.Run(() => Analyze(scriptast, tokens, filename)); + + /// /// Analyze a script in the form of a string with additional Settings + /// /// The script as a string /// A hastable which includes the settings - /// + /// An AnalyzerResult which encapsulates the analysis of the script definition public AnalyzerResult Analyze(string ScriptDefinition, Settings settings) { lock(hostedAnalyzerLock) @@ -102,9 +154,19 @@ public AnalyzerResult Analyze(string ScriptDefinition, Settings settings) /// /// Analyze a script in the form of a string with additional Settings + /// /// The script as a string /// A hastable which includes the settings + /// An AnalyzerResult which encapsulates the analysis of the script definition + public Task AnalyzeAsync(string ScriptDefinition, Settings settings) => + Task.Run(() => Analyze(ScriptDefinition, settings)); + + /// + /// Analyze a script in the form of a string with additional Settings /// + /// The script as a string + /// A hastable which includes the settings + /// An AnalyzerResult which encapsulates the analysis of the script definition public AnalyzerResult Analyze(string ScriptDefinition, Hashtable settings) { lock(hostedAnalyzerLock) @@ -124,7 +186,20 @@ public AnalyzerResult Analyze(string ScriptDefinition, Hashtable settings) } } - /// Analyze a script in the form of a string, based on default + /// + /// Analyze a script in the form of a string with additional Settings + /// + /// The script as a string + /// A hastable which includes the settings + /// An AnalyzerResult which encapsulates the analysis of the script definition + public Task AnalyzeAsync(string ScriptDefinition, Hashtable settings) => + Task.Run(() => Analyze(ScriptDefinition, settings)); + + /// + /// Analyze a script asynchronously in the form of a string, based on default settings + /// + /// The script (as a string) to analyze + /// An AnalyzerResult public AnalyzerResult Analyze(string ScriptDefinition) { lock(hostedAnalyzerLock) @@ -143,7 +218,49 @@ public AnalyzerResult Analyze(string ScriptDefinition) } } + /// + /// Analyze a script asynchronously in the form of a string, based on default + /// + /// The script (as a string) to analyze + /// A Task which encapsulates an AnalyzerResult + public Task AnalyzeAsync(string ScriptDefinition) => + Task.Run(() => Analyze(ScriptDefinition)); + + + /// Analyze a script in the form of a file + /// The file as a FileInfo object to analyze + /// A settings object which defines which rules to run + /// An AnalyzerResult + public AnalyzerResult Analyze(FileInfo File, Settings settings) + { + lock(hostedAnalyzerLock) + { + try { + writer.ClearWriter(); + analyzer.UpdateSettings(settings); + analyzer.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + var result = analyzer.AnalyzePath(File.FullName, (x, y) => { return true; }, false); + return new AnalyzerResult(AnalysisType.File, result, this); + } + finally + { + analyzer.CleanUp(); + } + } + } + + /// + /// Analyze a script asynchronously in the form of a string, based on default + /// + /// The script (as a string) to analyze + /// The file that contains the script + /// A Task which encapsulates an AnalyzerResult + public Task AnalyzeAsync(FileInfo File, Settings settings) => + Task.Run(() => Analyze(File, settings)); + /// Analyze a script in the form of a file + /// The file as a FileInfo object to analyze + /// An AnalyzerResult public AnalyzerResult Analyze(FileInfo File) { lock(hostedAnalyzerLock) @@ -161,7 +278,15 @@ public AnalyzerResult Analyze(FileInfo File) } } + /// Analyze a script in the form of a file + /// The file as a FileInfo object to analyze + /// An AnalyzerResult + public Task AnalyzeAsync(FileInfo File) => + Task.Run(() => Analyze(File)); + /// Fix a script + /// The script to fix + /// The fixed script as a string public string Fix(string scriptDefinition) { lock(hostedAnalyzerLock) @@ -179,6 +304,13 @@ public string Fix(string scriptDefinition) } } + /// Fix a script + /// The script to fix + /// The fixed script as a string + public Task FixAsync(string scriptDefinition) => Task.Run(() => Fix(scriptDefinition)); + + + /// /// Create a standard settings object for Script Analyzer /// This is the object used by analyzer internally @@ -229,6 +361,9 @@ public Settings CreateSettings(Hashtable settings) /// PSUseCorrectCasing /// and the union of the actual settings which are passed to it. /// + /// The script to format + /// The settings to use when formatting + /// The formatted script public string Format(string scriptDefinition, Settings settings) { if ( settings == null ) { @@ -250,6 +385,22 @@ public string Format(string scriptDefinition, Settings settings) return s; } + /// + /// Format a script according to the formatting rules + /// PSPlaceCloseBrace + /// PSPlaceOpenBrace + /// PSUseConsistentWhitespace + /// PSUseConsistentIndentation + /// PSAlignAssignmentStatement + /// PSUseCorrectCasing + /// and the union of the actual settings which are passed to it. + /// + /// The script to format + /// The settings to use when formatting + /// The formatted script + public Task FormatAsync(string scriptDefinition, Settings settings) => + Task.Run(() => Format(scriptDefinition, settings)); + /// /// Analyzer usually requires a cmdlet to manage the output to the user. /// This class is provided to collect the non-diagnostic record output @@ -344,7 +495,8 @@ protected virtual void Dispose(bool disposing) if ( disposing ) { - Helper.Instance.Dispose(); + // Don't dispose the helper instance + // Helper.Instance.Dispose(); ps.Runspace.Dispose(); ps.Dispose(); } From 7610d49aeeaaf4d36594198a7845a559149fa25f Mon Sep 17 00:00:00 2001 From: James Truher Date: Mon, 21 Oct 2019 13:25:56 -0700 Subject: [PATCH 25/52] Add an overload for Fix which takes a settings object Add a simple tostring method to the HostedAnalyzer --- Engine/HostedAnalyzer.cs | 54 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/Engine/HostedAnalyzer.cs b/Engine/HostedAnalyzer.cs index c06d21f10..dd4dcb302 100644 --- a/Engine/HostedAnalyzer.cs +++ b/Engine/HostedAnalyzer.cs @@ -38,6 +38,7 @@ public HostedAnalyzer() ps = SMA.PowerShell.Create(iss); writer = new hostedWriter(); ScriptAnalyzer.Instance.Initialize(ps.Runspace, writer, null, null, null, null, true, false, null); + // This is the static singleton analyzer = ScriptAnalyzer.Instance; } @@ -252,8 +253,8 @@ public AnalyzerResult Analyze(FileInfo File, Settings settings) /// /// Analyze a script asynchronously in the form of a string, based on default /// - /// The script (as a string) to analyze /// The file that contains the script + /// The settings to use when analyzing the script /// A Task which encapsulates an AnalyzerResult public Task AnalyzeAsync(FileInfo File, Settings settings) => Task.Run(() => Analyze(File, settings)); @@ -309,6 +310,51 @@ public string Fix(string scriptDefinition) /// The fixed script as a string public Task FixAsync(string scriptDefinition) => Task.Run(() => Fix(scriptDefinition)); + /// Fix a script + /// The script to fix + /// The settings to use when fixing + /// The fixed script as a string + public string Fix(string scriptDefinition, Settings settings) + { + lock(hostedAnalyzerLock) + { + try + { + Reset(); + analyzer.UpdateSettings(settings); + // First we run the normal rules + string fixedScript = Fix(scriptDefinition); + // now analyze the script according to the provided settings + var analysisResult = Analyze(fixedScript, settings); + + // Replicate the way analyzer fixes a script + // it seems that we can't just call the fix api as the settings + // don't persist in the helper singleton + var corrections = analysisResult.Result + .Select(r => r.SuggestedCorrections) + .Where(sc => sc != null && sc.Any()) + .Select(sc => sc.First()) + .ToList(); + EditableText t = new EditableText(fixedScript); + var orderedList = corrections.OrderByDescending(r => r.StartLineNumber).ThenByDescending(r => r.StartColumnNumber); + + foreach ( CorrectionExtent ce in orderedList ) { + t = t.ApplyEdit(ce); + } + return t.ToString(); + } + finally + { + Reset(); + } + } + } + + /// Fix a script + /// The script to fix + /// The fixed script as a string + /// The settings to use when fixing + public Task FixAsync(string scriptDefinition, Settings settings) => Task.Run(() => Fix(scriptDefinition, settings)); /// @@ -503,6 +549,12 @@ protected virtual void Dispose(bool disposing) disposed = true; } + + /// A simple ToString + public override string ToString() + { + return this.ps.Runspace.InstanceId?.ToString(); + } } /// From e43d72bf0fb6e3ad9c490e5e00b99e9356baac7e Mon Sep 17 00:00:00 2001 From: James Truher Date: Mon, 21 Oct 2019 13:42:41 -0700 Subject: [PATCH 26/52] Adding tests for HostedAnalyzer --- Tests/Engine/HostedAnalyzer.Tests.ps1 | 443 ++++++++++++++++++++++++++ 1 file changed, 443 insertions(+) create mode 100644 Tests/Engine/HostedAnalyzer.Tests.ps1 diff --git a/Tests/Engine/HostedAnalyzer.Tests.ps1 b/Tests/Engine/HostedAnalyzer.Tests.ps1 new file mode 100644 index 000000000..822d864b0 --- /dev/null +++ b/Tests/Engine/HostedAnalyzer.Tests.ps1 @@ -0,0 +1,443 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +Describe "Hosted Analyzer Tests" { + BeforeAll { + $HostedAnalyzer = [Microsoft.Windows.PowerShell.ScriptAnalyzer.Hosting.HostedAnalyzer]::new() + $RunspaceCount = (Get-Runspace).Count + } + AfterAll { + $HostedAnalyzer.Dispose() + } + + Context "Analyze Method Tests" { + $defaultAnalyzerTests = + @{ Script = 'gci'; ExpectedResultCount = 1; ExpectedRuleViolation = @('PSAvoidUsingCmdletAliases') }, + @{ Script = 'gc'; ExpectedResultCount = 1; ExpectedRuleViolation = @('PSAvoidUsingCmdletAliases') }, + @{ Script = 'get-command;gc'; ExpectedResultCount = 1; ExpectedRuleViolation = @('PSAvoidUsingCmdletAliases') }, + @{ Script = '$a ='; ExpectedResultCount = 2; ExpectedRuleViolation = @('ExpectedValueExpression','PSUseDeclaredVarsMoreThanAssignments') }, + @{ Script = 'gc;$a ='; ExpectedResultCount = 3; ExpectedRuleViolation = @('PSAvoidUsingCmdletAliases','ExpectedValueExpression','PSUseDeclaredVarsMoreThanAssignments') }, + @{ Script = 'write-host no'; ExpectedResultCount = 1; ExpectedRuleViolation = @('PSAvoidUsingWriteHost') } + + Context "Tests without Settings" { + + It "Should correctly identify errors in '