diff --git a/ScriptAnalyzer2/Builder/BuiltinRulesBuilder.cs b/ScriptAnalyzer2/Builder/BuiltinRulesBuilder.cs new file mode 100644 index 000000000..d731a86ac --- /dev/null +++ b/ScriptAnalyzer2/Builder/BuiltinRulesBuilder.cs @@ -0,0 +1,43 @@ +using Microsoft.PowerShell.ScriptAnalyzer.Builtin; +using Microsoft.PowerShell.ScriptAnalyzer.Configuration; +using Microsoft.PowerShell.ScriptAnalyzer.Instantiation; +using System; +using System.Collections.Generic; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Builder +{ + public class BuiltinRulesBuilder + { + private IReadOnlyDictionary _ruleConfiguration; + + private IRuleComponentProvider _ruleComponents; + + public BuiltinRulesBuilder WithRuleConfiguration(IReadOnlyDictionary ruleConfigurationCollection) + { + _ruleConfiguration = ruleConfigurationCollection; + return this; + } + + public BuiltinRulesBuilder WithRuleComponentProvider(IRuleComponentProvider ruleComponentProvider) + { + _ruleComponents = ruleComponentProvider; + return this; + } + + public BuiltinRulesBuilder WithRuleComponentBuilder(Action configureRuleComponents) + { + var ruleComponentProviderBuilder = new RuleComponentProviderBuilder(); + configureRuleComponents(ruleComponentProviderBuilder); + _ruleComponents = ruleComponentProviderBuilder.Build(); + return this; + } + + public TypeRuleProvider Build() + { + return TypeRuleProvider.FromTypes( + _ruleConfiguration ?? Default.RuleConfiguration, + _ruleComponents ?? Default.RuleComponentProvider, + BuiltinRules.DefaultRules); + } + } +} diff --git a/ScriptAnalyzer2/Builder/ConfiguredBuilding.cs b/ScriptAnalyzer2/Builder/ConfiguredBuilding.cs new file mode 100644 index 000000000..4455608db --- /dev/null +++ b/ScriptAnalyzer2/Builder/ConfiguredBuilding.cs @@ -0,0 +1,57 @@ +using Microsoft.PowerShell.ScriptAnalyzer.Configuration; +using Microsoft.PowerShell.ScriptAnalyzer.Execution; +using Microsoft.PowerShell.ScriptAnalyzer.Instantiation; +using Microsoft.PowerShell.ScriptAnalyzer.Utils; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Builder +{ + public static class ConfiguredBuilding + { + public static ScriptAnalyzer CreateScriptAnalyzer(this IScriptAnalyzerConfiguration configuration) + { + var analyzerBuilder = new ScriptAnalyzerBuilder(); + + switch (configuration.BuiltinRules ?? BuiltinRulePreference.Default) + { + case BuiltinRulePreference.Aggressive: + case BuiltinRulePreference.Default: + analyzerBuilder.AddBuiltinRules(); + break; + } + + switch (configuration.RuleExecution ?? RuleExecutionMode.Default) + { + case RuleExecutionMode.Default: + case RuleExecutionMode.Parallel: + analyzerBuilder.WithRuleExecutorFactory(new ParallelLinqRuleExecutorFactory()); + break; + + case RuleExecutionMode.Sequential: + analyzerBuilder.WithRuleExecutorFactory(new SequentialRuleExecutorFactory()); + break; + } + + var componentProvider = new RuleComponentProviderBuilder().Build(); + + if (configuration.RulePaths != null) + { + foreach (string rulePath in configuration.RulePaths) + { + string extension = Path.GetExtension(rulePath); + + if (extension.CaseInsensitiveEquals(".dll")) + { + analyzerBuilder.AddRuleProvider(TypeRuleProvider.FromAssemblyFile(configuration.RuleConfiguration, componentProvider, rulePath)); + break; + } + } + } + + return analyzerBuilder.Build(); + } + } +} diff --git a/ScriptAnalyzer2/Builder/ScriptAnalyzerBuilder.cs b/ScriptAnalyzer2/Builder/ScriptAnalyzerBuilder.cs new file mode 100644 index 000000000..efdb96f58 --- /dev/null +++ b/ScriptAnalyzer2/Builder/ScriptAnalyzerBuilder.cs @@ -0,0 +1,58 @@ +using Microsoft.PowerShell.ScriptAnalyzer.Builtin; +using Microsoft.PowerShell.ScriptAnalyzer.Execution; +using Microsoft.PowerShell.ScriptAnalyzer.Instantiation; +using System; +using System.Collections.Generic; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Builder +{ + public class ScriptAnalyzerBuilder + { + private readonly List _ruleProviders; + + private IRuleExecutorFactory _ruleExecutorFactory; + + public ScriptAnalyzerBuilder() + { + _ruleProviders = new List(); + } + + public ScriptAnalyzerBuilder WithRuleExecutorFactory(IRuleExecutorFactory ruleExecutorFactory) + { + _ruleExecutorFactory = ruleExecutorFactory; + return this; + } + + public ScriptAnalyzerBuilder AddRuleProvider(IRuleProvider ruleProvider) + { + _ruleProviders.Add(ruleProvider); + return this; + } + + public ScriptAnalyzerBuilder AddBuiltinRules() + { + _ruleProviders.Add(TypeRuleProvider.FromTypes( + Default.RuleConfiguration, + Default.RuleComponentProvider, + BuiltinRules.DefaultRules)); + return this; + } + + public ScriptAnalyzerBuilder AddBuiltinRules(Action configureBuiltinRules) + { + var builtinRulesBuilder = new BuiltinRulesBuilder(); + configureBuiltinRules(builtinRulesBuilder); + _ruleProviders.Add(builtinRulesBuilder.Build()); + return this; + } + + public ScriptAnalyzer Build() + { + IRuleProvider ruleProvider = _ruleProviders.Count == 1 + ? _ruleProviders[0] + : new CompositeRuleProvider(_ruleProviders); + + return new ScriptAnalyzer(ruleProvider, _ruleExecutorFactory ?? Default.RuleExecutorFactory); + } + } +} diff --git a/ScriptAnalyzer2/Builtin/BuiltinRuleProvider.cs b/ScriptAnalyzer2/Builtin/BuiltinRuleProvider.cs new file mode 100644 index 000000000..5574f390a --- /dev/null +++ b/ScriptAnalyzer2/Builtin/BuiltinRuleProvider.cs @@ -0,0 +1,61 @@ +using Microsoft.PowerShell.ScriptAnalyzer.Builder; +using Microsoft.PowerShell.ScriptAnalyzer.Builtin.Rules; +using Microsoft.PowerShell.ScriptAnalyzer.Configuration; +using Microsoft.PowerShell.ScriptAnalyzer.Execution; +using Microsoft.PowerShell.ScriptAnalyzer.Runtime; +using Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules; +using System; +using System.Collections.Generic; +using System.Management.Automation.Runspaces; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Builtin +{ + public static class BuiltinRules + { + public static IReadOnlyList DefaultRules { get; } = new[] + { + typeof(AvoidEmptyCatchBlock), + typeof(AvoidGlobalVars), + typeof(AvoidPositionalParameters), + typeof(AvoidUsingWMICmdlet), + typeof(UseDeclaredVarsMoreThanAssignments), + typeof(UseShouldProcessForStateChangingFunctions), + }; + } + + public static class Default + { + private static readonly Lazy s_ruleComponentProviderLazy = new Lazy(BuildRuleComponentProvider); + + public static IReadOnlyDictionary RuleConfiguration { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "PS/AvoidUsingEmptyCatchBlock", null }, + { "PS/AvoidGlobalVars", null }, + { "PS/AvoidUsingPositionalParameters", null }, + { "PS/AvoidUsingWMICmdlet", null }, + { "PS/UseDeclaredVarsMoreThanAssignments", null }, + { "PS/UseShouldProcessForStateChangingFunctions", null }, + }; + + public static IRuleExecutorFactory RuleExecutorFactory { get; } = new ParallelLinqRuleExecutorFactory(); + + public static IRuleComponentProvider RuleComponentProvider => s_ruleComponentProviderLazy.Value; + + private static IRuleComponentProvider BuildRuleComponentProvider() + { + return new RuleComponentProviderBuilder() + .AddSingleton(InstantiatePowerShellCommandDatabase()) + .Build(); + } + + private static IPowerShellCommandDatabase InstantiatePowerShellCommandDatabase() + { + using (Runspace runspace = RunspaceFactory.CreateRunspace()) + { + runspace.Open(); + return SessionStateCommandDatabase.Create(runspace.SessionStateProxy.InvokeCommand); + } + } + } + +} diff --git a/ScriptAnalyzer2/Builtin/Rules/AvoidEmptyCatchBlock.cs b/ScriptAnalyzer2/Builtin/Rules/AvoidEmptyCatchBlock.cs new file mode 100644 index 000000000..fca1cc8f8 --- /dev/null +++ b/ScriptAnalyzer2/Builtin/Rules/AvoidEmptyCatchBlock.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Management.Automation.Language; +using System.Globalization; +using Microsoft.PowerShell.ScriptAnalyzer.Rules; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Builtin.Rules +{ + /// + /// AvoidEmptyCatchBlock: Check if any empty catch block is used. + /// + [IdempotentRule] + [ThreadsafeRule] + [RuleDescription(typeof(Strings), nameof(Strings.AvoidUsingEmptyCatchBlockDescription))] + [Rule("AvoidUsingEmptyCatchBlock")] + public class AvoidEmptyCatchBlock : ScriptRule + { + public AvoidEmptyCatchBlock(RuleInfo ruleInfo) + : base(ruleInfo) + { + } + + /// + /// AnalyzeScript: Analyze the script to check if any empty catch block is used. + /// + public override IEnumerable AnalyzeScript(Ast ast, IReadOnlyList tokens, string fileName) + { + if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage); + + // Finds all CommandAsts. + IEnumerable foundAsts = ast.FindAll(testAst => testAst is CatchClauseAst, true); + + // Iterates all CatchClauseAst and check the statements count. + foreach (Ast foundAst in foundAsts) + { + CatchClauseAst catchAst = (CatchClauseAst)foundAst; + + if (catchAst.Body.Statements.Count == 0) + { + yield return CreateDiagnostic( + string.Format(CultureInfo.CurrentCulture, Strings.AvoidEmptyCatchBlockError), + catchAst); + } + } + } + } +} + + + + diff --git a/ScriptAnalyzer2/Builtin/Rules/AvoidGlobalVars.cs b/ScriptAnalyzer2/Builtin/Rules/AvoidGlobalVars.cs new file mode 100644 index 000000000..0e202d1fe --- /dev/null +++ b/ScriptAnalyzer2/Builtin/Rules/AvoidGlobalVars.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Management.Automation.Language; +using System.Globalization; +using Microsoft.PowerShell.ScriptAnalyzer.Rules; +using Microsoft.PowerShell.ScriptAnalyzer; +using Microsoft.PowerShell.ScriptAnalyzer.Builtin.Rules; + +namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules +{ + /// + /// AvoidGlobalVars: Analyzes the ast to check that global variables are not used. + /// + [IdempotentRule] + [ThreadsafeRule] + [RuleDescription(typeof(Strings), nameof(Strings.AvoidGlobalVarsDescription))] + [Rule("AvoidGlobalVars")] + public class AvoidGlobalVars : ScriptRule + { + public AvoidGlobalVars(RuleInfo ruleInfo) : base(ruleInfo) + { + } + + /// + /// AnalyzeScript: Analyzes the ast to check that global variables are not used. From the ILintScriptRule interface. + /// + /// The script's ast + /// The script's file name + /// A List of diagnostic results of this rule + public override IEnumerable AnalyzeScript(Ast ast, IReadOnlyList tokens, string fileName) + { + IEnumerable varAsts = ast.FindAll(testAst => testAst is VariableExpressionAst, true); + + if (varAsts == null) + { + yield break; + } + + foreach (VariableExpressionAst varAst in varAsts) + { + if (varAst.VariablePath.IsGlobal) + { + yield return CreateDiagnostic( + string.Format(CultureInfo.CurrentCulture, Strings.AvoidGlobalVarsError, varAst.VariablePath.UserPath), + varAst); + } + } + } + } +} + + + + diff --git a/ScriptAnalyzer2/Builtin/Rules/AvoidPositionalParameters.cs b/ScriptAnalyzer2/Builtin/Rules/AvoidPositionalParameters.cs new file mode 100644 index 000000000..e6ca37cff --- /dev/null +++ b/ScriptAnalyzer2/Builtin/Rules/AvoidPositionalParameters.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Management.Automation.Language; +using System.Globalization; +using Microsoft.PowerShell.ScriptAnalyzer.Rules; +using Microsoft.PowerShell.ScriptAnalyzer; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Builtin.Rules +{ + /// + /// AvoidPositionalParameters: Check to make sure that positional parameters are not used. + /// + public class AvoidPositionalParameters : ScriptRule + { + public AvoidPositionalParameters(RuleInfo ruleInfo) : base(ruleInfo) + { + } + + /// + /// AnalyzeScript: Analyze the ast to check that positional parameters are not used. + /// + public override IEnumerable AnalyzeScript(Ast ast, IReadOnlyList tokens, string fileName) + { + // Find all function definitions in the script and add them to the set. + IEnumerable functionDefinitionAsts = ast.FindAll(testAst => testAst is FunctionDefinitionAst, true); + var declaredFunctionNames = new HashSet(); + + foreach (FunctionDefinitionAst functionDefinitionAst in functionDefinitionAsts) + { + if (string.IsNullOrEmpty(functionDefinitionAst.Name)) + { + continue; + } + declaredFunctionNames.Add(functionDefinitionAst.Name); + } + + // Finds all CommandAsts. + IEnumerable foundAsts = ast.FindAll(testAst => testAst is CommandAst, true); + + // Iterates all CommandAsts and check the command name. + foreach (Ast foundAst in foundAsts) + { + CommandAst cmdAst = (CommandAst)foundAst; + // Handles the exception caused by commands like, {& $PLINK $args 2> $TempErrorFile}. + // You can also review the remark section in following document, + // MSDN: CommandAst.GetCommandName Method + if (cmdAst.GetCommandName() == null) continue; + + throw new NotImplementedException(); + + /* + if ((Helper.Instance.IsKnownCmdletFunctionOrExternalScript(cmdAst) || declaredFunctionNames.Contains(cmdAst.GetCommandName())) && + (Helper.Instance.PositionalParameterUsed(cmdAst, true))) + { + PipelineAst parent = cmdAst.Parent as PipelineAst; + + if (parent != null && parent.PipelineElements.Count > 1) + { + // raise if it's the first element in pipeline. otherwise no. + if (parent.PipelineElements[0] == cmdAst) + { + yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingPositionalParametersError, cmdAst.GetCommandName()), + cmdAst.Extent, GetName(), DiagnosticSeverity.Information, fileName, cmdAst.GetCommandName()); + } + } + // not in pipeline so just raise it normally + else + { + yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingPositionalParametersError, cmdAst.GetCommandName()), + cmdAst.Extent, GetName(), DiagnosticSeverity.Information, fileName, cmdAst.GetCommandName()); + } + } + */ + } + + yield break; + } + } +} + diff --git a/ScriptAnalyzer2/Builtin/Rules/AvoidUsingWMICmdlet.cs b/ScriptAnalyzer2/Builtin/Rules/AvoidUsingWMICmdlet.cs new file mode 100644 index 000000000..4f300673e --- /dev/null +++ b/ScriptAnalyzer2/Builtin/Rules/AvoidUsingWMICmdlet.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Management.Automation.Language; +using System.Globalization; +using Microsoft.PowerShell.ScriptAnalyzer.Rules; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Builtin.Rules +{ + /// + /// AvoidUsingWMICmdlet: Avoid Using Get-WMIObject, Remove-WMIObject, Invoke-WmiMethod, Register-WmiEvent, Set-WmiInstance + /// + [ThreadsafeRule] + [IdempotentRule] + [RuleDescription(typeof(Strings), nameof(Strings.AvoidUsingWMICmdletDescription))] + [Rule("AvoidUsingWMICmdlet")] + public class AvoidUsingWMICmdlet : ScriptRule + { + public AvoidUsingWMICmdlet(RuleInfo ruleInfo) + : base(ruleInfo) + { + } + + /// + /// AnalyzeScript: Avoid Using Get-WMIObject, Remove-WMIObject, Invoke-WmiMethod, Register-WmiEvent, Set-WmiInstance + /// + public override IEnumerable AnalyzeScript(Ast ast, IReadOnlyList tokens, string fileName) + { + if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage); + + // Rule is applicable only when PowerShell Version is < 3.0, since CIM cmdlet was introduced in 3.0 + int majorPSVersion = GetPSRequiredVersionMajor(ast); + if (!(3 > majorPSVersion && 0 < majorPSVersion)) + { + // Finds all CommandAsts. + IEnumerable commandAsts = ast.FindAll(testAst => testAst is CommandAst, true); + + // Iterate all CommandAsts and check the command name + foreach (CommandAst cmdAst in commandAsts) + { + if (cmdAst.GetCommandName() != null && + (String.Equals(cmdAst.GetCommandName(), "get-wmiobject", StringComparison.OrdinalIgnoreCase) + || String.Equals(cmdAst.GetCommandName(), "remove-wmiobject", StringComparison.OrdinalIgnoreCase) + || String.Equals(cmdAst.GetCommandName(), "invoke-wmimethod", StringComparison.OrdinalIgnoreCase) + || String.Equals(cmdAst.GetCommandName(), "register-wmievent", StringComparison.OrdinalIgnoreCase) + || String.Equals(cmdAst.GetCommandName(), "set-wmiinstance", StringComparison.OrdinalIgnoreCase)) + ) + { + if (String.IsNullOrWhiteSpace(fileName)) + { + yield return CreateDiagnostic( + String.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingWMICmdletErrorScriptDefinition), + cmdAst.Extent); + } + else + { + yield return CreateDiagnostic( + String.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingWMICmdletError, System.IO.Path.GetFileName(fileName)), + cmdAst.Extent); + } + } + } + } + } + + /// + /// GetPSMajorVersion: Retrieves Major PowerShell Version when supplied using #requires keyword in the script + /// + /// The name of this rule + private int GetPSRequiredVersionMajor(Ast ast) + { + if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage); + + IEnumerable scriptBlockAsts = ast.FindAll(testAst => testAst is ScriptBlockAst, true); + + foreach (ScriptBlockAst scriptBlockAst in scriptBlockAsts) + { + if (null != scriptBlockAst.ScriptRequirements && null != scriptBlockAst.ScriptRequirements.RequiredPSVersion) + { + return scriptBlockAst.ScriptRequirements.RequiredPSVersion.Major; + } + } + + // return a non valid Major version if #requires -Version is not supplied in the Script + return -1; + } + } +} + + + + diff --git a/ScriptAnalyzer2/Builtin/Rules/Strings.Designer.cs b/ScriptAnalyzer2/Builtin/Rules/Strings.Designer.cs new file mode 100644 index 000000000..9f76b0e50 --- /dev/null +++ b/ScriptAnalyzer2/Builtin/Rules/Strings.Designer.cs @@ -0,0 +1,3078 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.PowerShell.ScriptAnalyzer.Builtin.Rules { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Strings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Strings() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.PowerShell.ScriptAnalyzer.Builtin.Rules.Strings", typeof(Strings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Align assignment statement. + /// + internal static string AlignAssignmentStatementCommonName { + get { + return ResourceManager.GetString("AlignAssignmentStatementCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Line up assignment statements such that the assignment operator are aligned.. + /// + internal static string AlignAssignmentStatementDescription { + get { + return ResourceManager.GetString("AlignAssignmentStatementDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Assignment statements are not aligned. + /// + internal static string AlignAssignmentStatementError { + get { + return ResourceManager.GetString("AlignAssignmentStatementError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AlignAssignmentStatement. + /// + internal static string AlignAssignmentStatementName { + get { + return ResourceManager.GetString("AlignAssignmentStatementName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidAssignmentToAutomaticVariable. + /// + internal static string AvoidAssignmentToAutomaticVariableName { + get { + return ResourceManager.GetString("AvoidAssignmentToAutomaticVariableName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use a different variable name. + /// + internal static string AvoidAssignmentToReadOnlyAutomaticVariable { + get { + return ResourceManager.GetString("AvoidAssignmentToReadOnlyAutomaticVariable", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Changing automtic variables might have undesired side effects. + /// + internal static string AvoidAssignmentToReadOnlyAutomaticVariableCommonName { + get { + return ResourceManager.GetString("AvoidAssignmentToReadOnlyAutomaticVariableCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This automatic variables is built into PowerShell and readonly.. + /// + internal static string AvoidAssignmentToReadOnlyAutomaticVariableDescription { + get { + return ResourceManager.GetString("AvoidAssignmentToReadOnlyAutomaticVariableDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Variable '{0}' cannot be assigned since it is a readonly automatic variable that is built into PowerShell, please use a different name.. + /// + internal static string AvoidAssignmentToReadOnlyAutomaticVariableError { + get { + return ResourceManager.GetString("AvoidAssignmentToReadOnlyAutomaticVariableError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Starting from PowerShell 6.0, the Variable '{0}' cannot be assigned any more since it is a readonly automatic variable that is built into PowerShell, please use a different name.. + /// + internal static string AvoidAssignmentToReadOnlyAutomaticVariableIntroducedInPowerShell6_0Error { + get { + return ResourceManager.GetString("AvoidAssignmentToReadOnlyAutomaticVariableIntroducedInPowerShell6_0Error", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Variable '{0}' is an automatic variable that is built into PowerShell, assigning to it might have undesired side effects. If assignment is not by design, please use a different name.. + /// + internal static string AvoidAssignmentToWritableAutomaticVariableError { + get { + return ResourceManager.GetString("AvoidAssignmentToWritableAutomaticVariableError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid Using ComputerName Hardcoded. + /// + internal static string AvoidComputerNameHardcodedCommonName { + get { + return ResourceManager.GetString("AvoidComputerNameHardcodedCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The ComputerName parameter of a cmdlet should not be hardcoded as this will expose sensitive information about the system.. + /// + internal static string AvoidComputerNameHardcodedDescription { + get { + return ResourceManager.GetString("AvoidComputerNameHardcodedDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The ComputerName parameter of cmdlet '{0}' is hardcoded. This will expose sensitive information about the system if the script is shared.. + /// + internal static string AvoidComputerNameHardcodedError { + get { + return ResourceManager.GetString("AvoidComputerNameHardcodedError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidUsingComputerNameHardcoded. + /// + internal static string AvoidComputerNameHardcodedName { + get { + return ResourceManager.GetString("AvoidComputerNameHardcodedName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid Default Value For Mandatory Parameter. + /// + internal static string AvoidDefaultValueForMandatoryParameterCommonName { + get { + return ResourceManager.GetString("AvoidDefaultValueForMandatoryParameterCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Mandatory parameter should not be initialized with a default value in the param block because this value will be ignored.. To fix a violation of this rule, please avoid initializing a value for the mandatory parameter in the param block.. + /// + internal static string AvoidDefaultValueForMandatoryParameterDescription { + get { + return ResourceManager.GetString("AvoidDefaultValueForMandatoryParameterDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Mandatory Parameter '{0}' is initialized in the Param block. To fix a violation of this rule, please leave it uninitialized.. + /// + internal static string AvoidDefaultValueForMandatoryParameterError { + get { + return ResourceManager.GetString("AvoidDefaultValueForMandatoryParameterError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidDefaultValueForMandatoryParameter. + /// + internal static string AvoidDefaultValueForMandatoryParameterName { + get { + return ResourceManager.GetString("AvoidDefaultValueForMandatoryParameterName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Switch Parameters Should Not Default To True. + /// + internal static string AvoidDefaultValueSwitchParameterCommonName { + get { + return ResourceManager.GetString("AvoidDefaultValueSwitchParameterCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Switch parameter should not default to true.. + /// + internal static string AvoidDefaultValueSwitchParameterDescription { + get { + return ResourceManager.GetString("AvoidDefaultValueSwitchParameterDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to File '{0}' has a switch parameter default to true.. + /// + internal static string AvoidDefaultValueSwitchParameterError { + get { + return ResourceManager.GetString("AvoidDefaultValueSwitchParameterError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Script definition has a switch parameter default to true.. + /// + internal static string AvoidDefaultValueSwitchParameterErrorScriptDefinition { + get { + return ResourceManager.GetString("AvoidDefaultValueSwitchParameterErrorScriptDefinition", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidDefaultValueSwitchParameter. + /// + internal static string AvoidDefaultValueSwitchParameterName { + get { + return ResourceManager.GetString("AvoidDefaultValueSwitchParameterName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Empty catch block is used. Please use Write-Error or throw statements in catch blocks.. + /// + internal static string AvoidEmptyCatchBlockError { + get { + return ResourceManager.GetString("AvoidEmptyCatchBlockError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid global aliases.. + /// + internal static string AvoidGlobalAliasesCommonName { + get { + return ResourceManager.GetString("AvoidGlobalAliasesCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Checks that global aliases are not used. Global aliases are strongly discouraged as they overwrite desired aliases with name conflicts.. + /// + internal static string AvoidGlobalAliasesDescription { + get { + return ResourceManager.GetString("AvoidGlobalAliasesDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid creating aliases with a Global scope.. + /// + internal static string AvoidGlobalAliasesError { + get { + return ResourceManager.GetString("AvoidGlobalAliasesError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidGlobalAliases. + /// + internal static string AvoidGlobalAliasesName { + get { + return ResourceManager.GetString("AvoidGlobalAliasesName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid global functiosn and aliases. + /// + internal static string AvoidGlobalFunctionsCommonName { + get { + return ResourceManager.GetString("AvoidGlobalFunctionsCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Checks that global functions and aliases are not used. Global functions are strongly discouraged as they can cause errors across different systems.. + /// + internal static string AvoidGlobalFunctionsDescription { + get { + return ResourceManager.GetString("AvoidGlobalFunctionsDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid creating functions with a Global scope.. + /// + internal static string AvoidGlobalFunctionsError { + get { + return ResourceManager.GetString("AvoidGlobalFunctionsError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidGlobalFunctions. + /// + internal static string AvoidGlobalFunctionsName { + get { + return ResourceManager.GetString("AvoidGlobalFunctionsName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No Global Variables. + /// + internal static string AvoidGlobalVarsCommonName { + get { + return ResourceManager.GetString("AvoidGlobalVarsCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Checks that global variables are not used. Global variables are strongly discouraged as they can cause errors across different systems.. + /// + internal static string AvoidGlobalVarsDescription { + get { + return ResourceManager.GetString("AvoidGlobalVarsDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Found global variable '{0}'.. + /// + internal static string AvoidGlobalVarsError { + get { + return ResourceManager.GetString("AvoidGlobalVarsError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidGlobalVars. + /// + internal static string AvoidGlobalVarsName { + get { + return ResourceManager.GetString("AvoidGlobalVarsName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid Invoking Empty Members. + /// + internal static string AvoidInvokingEmptyMembersCommonName { + get { + return ResourceManager.GetString("AvoidInvokingEmptyMembersCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invoking non-constant members would cause potential bugs. Please double check the syntax to make sure members invoked are non-constant.. + /// + internal static string AvoidInvokingEmptyMembersDescription { + get { + return ResourceManager.GetString("AvoidInvokingEmptyMembersDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '{0}' has non-constant members. Invoking non-constant members may cause bugs in the script.. + /// + internal static string AvoidInvokingEmptyMembersError { + get { + return ResourceManager.GetString("AvoidInvokingEmptyMembersError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidInvokingEmptyMembers. + /// + internal static string AvoidInvokingEmptyMembersName { + get { + return ResourceManager.GetString("AvoidInvokingEmptyMembersName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid long lines. + /// + internal static string AvoidLongLinesCommonName { + get { + return ResourceManager.GetString("AvoidLongLinesCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Line lengths should be less than the configured maximum. + /// + internal static string AvoidLongLinesDescription { + get { + return ResourceManager.GetString("AvoidLongLinesDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Line exceeds the configured maximum length of {0} characters. + /// + internal static string AvoidLongLinesError { + get { + return ResourceManager.GetString("AvoidLongLinesError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidLongLines. + /// + internal static string AvoidLongLinesName { + get { + return ResourceManager.GetString("AvoidLongLinesName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid using null or empty HelpMessage parameter attribute.. + /// + internal static string AvoidNullOrEmptyHelpMessageAttributeCommonName { + get { + return ResourceManager.GetString("AvoidNullOrEmptyHelpMessageAttributeCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Setting the HelpMessage attribute to an empty string or null value causes PowerShell interpreter to throw an error while executing the corresponding function.. + /// + internal static string AvoidNullOrEmptyHelpMessageAttributeDescription { + get { + return ResourceManager.GetString("AvoidNullOrEmptyHelpMessageAttributeDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to HelpMessage parameter attribute should not be null or empty. To fix a violation of this rule, please set its value to a non-empty string.. + /// + internal static string AvoidNullOrEmptyHelpMessageAttributeError { + get { + return ResourceManager.GetString("AvoidNullOrEmptyHelpMessageAttributeError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidNullOrEmptyHelpMessageAttribute. + /// + internal static string AvoidNullOrEmptyHelpMessageAttributeName { + get { + return ResourceManager.GetString("AvoidNullOrEmptyHelpMessageAttributeName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid overwriting built in cmdlets. + /// + internal static string AvoidOverwritingBuiltInCmdletsCommonName { + get { + return ResourceManager.GetString("AvoidOverwritingBuiltInCmdletsCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Do not overwrite the definition of a cmdlet that is included with PowerShell. + /// + internal static string AvoidOverwritingBuiltInCmdletsDescription { + get { + return ResourceManager.GetString("AvoidOverwritingBuiltInCmdletsDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '{0}' is a cmdlet that is included with PowerShell (version {1}) whose definition should not be overridden. + /// + internal static string AvoidOverwritingBuiltInCmdletsError { + get { + return ResourceManager.GetString("AvoidOverwritingBuiltInCmdletsError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidOverwritingBuiltInCmdlets. + /// + internal static string AvoidOverwritingBuiltInCmdletsName { + get { + return ResourceManager.GetString("AvoidOverwritingBuiltInCmdletsName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid Using ShouldContinue Without Boolean Force Parameter. + /// + internal static string AvoidShouldContinueWithoutForceCommonName { + get { + return ResourceManager.GetString("AvoidShouldContinueWithoutForceCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Functions that use ShouldContinue should have a boolean force parameter to allow user to bypass it.. + /// + internal static string AvoidShouldContinueWithoutForceDescription { + get { + return ResourceManager.GetString("AvoidShouldContinueWithoutForceDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Function '{0}' in file '{1}' uses ShouldContinue but does not have a boolean force parameter. The force parameter will allow users of the script to bypass ShouldContinue prompt. + /// + internal static string AvoidShouldContinueWithoutForceError { + get { + return ResourceManager.GetString("AvoidShouldContinueWithoutForceError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Function '{0}' in script definition uses ShouldContinue but does not have a boolean force parameter. The force parameter will allow users of the script to bypass ShouldContinue prompt. + /// + internal static string AvoidShouldContinueWithoutForceErrorScriptDefinition { + get { + return ResourceManager.GetString("AvoidShouldContinueWithoutForceErrorScriptDefinition", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidShouldContinueWithoutForce. + /// + internal static string AvoidShouldContinueWithoutForceName { + get { + return ResourceManager.GetString("AvoidShouldContinueWithoutForceName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid trailing whitespace. + /// + internal static string AvoidTrailingWhitespaceCommonName { + get { + return ResourceManager.GetString("AvoidTrailingWhitespaceCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Each line should have no trailing whitespace.. + /// + internal static string AvoidTrailingWhitespaceDescription { + get { + return ResourceManager.GetString("AvoidTrailingWhitespaceDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Line has trailing whitespace. + /// + internal static string AvoidTrailingWhitespaceError { + get { + return ResourceManager.GetString("AvoidTrailingWhitespaceError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidTrailingWhitespace. + /// + internal static string AvoidTrailingWhitespaceName { + get { + return ResourceManager.GetString("AvoidTrailingWhitespaceName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Module Must Be Loadable. + /// + internal static string AvoidUnloadableModuleCommonName { + get { + return ResourceManager.GetString("AvoidUnloadableModuleCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to If a script file is in a PowerShell module folder, then that folder must be loadable.. + /// + internal static string AvoidUnloadableModuleDescription { + get { + return ResourceManager.GetString("AvoidUnloadableModuleDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot load the module '{0}' that file '{1}' is in.. + /// + internal static string AvoidUnloadableModuleError { + get { + return ResourceManager.GetString("AvoidUnloadableModuleError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidUnloadableModule. + /// + internal static string AvoidUnloadableModuleName { + get { + return ResourceManager.GetString("AvoidUnloadableModuleName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid Using Username and Password Parameters. + /// + internal static string AvoidUsernameAndPasswordParamsCommonName { + get { + return ResourceManager.GetString("AvoidUsernameAndPasswordParamsCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Functions should take in a Credential parameter of type PSCredential (with a Credential transformation attribute defined after it in PowerShell 4.0 or earlier) or set the Password parameter to type SecureString.. + /// + internal static string AvoidUsernameAndPasswordParamsDescription { + get { + return ResourceManager.GetString("AvoidUsernameAndPasswordParamsDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Function '{0}' has both Username and Password parameters. Either set the type of the Password parameter to SecureString or replace the Username and Password parameters with a Credential parameter of type PSCredential. If using a Credential parameter in PowerShell 4.0 or earlier, please define a credential transformation attribute after the PSCredential type attribute.. + /// + internal static string AvoidUsernameAndPasswordParamsError { + get { + return ResourceManager.GetString("AvoidUsernameAndPasswordParamsError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidUsingUsernameAndPasswordParams. + /// + internal static string AvoidUsernameAndPasswordParamsName { + get { + return ResourceManager.GetString("AvoidUsernameAndPasswordParamsName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid Using Clear-Host. + /// + internal static string AvoidUsingClearHostCommonName { + get { + return ResourceManager.GetString("AvoidUsingClearHostCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Using Clear-Host is not recommended because the cmdlet may not work in some hosts or there may even be no hosts at all.. + /// + internal static string AvoidUsingClearHostDescription { + get { + return ResourceManager.GetString("AvoidUsingClearHostDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to File '{0}' uses Clear-Host. This is not recommended because it may not work in some hosts or there may even be no hosts at all.. + /// + internal static string AvoidUsingClearHostError { + get { + return ResourceManager.GetString("AvoidUsingClearHostError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidUsingClearHost. + /// + internal static string AvoidUsingClearHostName { + get { + return ResourceManager.GetString("AvoidUsingClearHostName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid Using Cmdlet Aliases or omitting the 'Get-' prefix.. + /// + internal static string AvoidUsingCmdletAliasesCommonName { + get { + return ResourceManager.GetString("AvoidUsingCmdletAliasesCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Replace {0} with {1}. + /// + internal static string AvoidUsingCmdletAliasesCorrectionDescription { + get { + return ResourceManager.GetString("AvoidUsingCmdletAliasesCorrectionDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An alias is an alternate name or nickname for a cmdlet or for a command element, such as a function, script, file, or executable file. An implicit alias is also the omission of the 'Get-' prefix for commands with this prefix. But when writing scripts that will potentially need to be maintained over time, either by the original author or another Windows PowerShell scripter, please consider using full cmdlet name instead of alias. Aliases can introduce these problems, readability, understandability and availa [rest of string was truncated]";. + /// + internal static string AvoidUsingCmdletAliasesDescription { + get { + return ResourceManager.GetString("AvoidUsingCmdletAliasesDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '{0}' is an alias of '{1}'. Alias can introduce possible problems and make scripts hard to maintain. Please consider changing alias to its full content.. + /// + internal static string AvoidUsingCmdletAliasesError { + get { + return ResourceManager.GetString("AvoidUsingCmdletAliasesError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '{0}' is implicitly aliasing '{1}' because it is missing the 'Get-' prefix. This can introduce possible problems and make scripts hard to maintain. Please consider changing command to its full name.. + /// + internal static string AvoidUsingCmdletAliasesMissingGetPrefixError { + get { + return ResourceManager.GetString("AvoidUsingCmdletAliasesMissingGetPrefixError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidUsingCmdletAliases. + /// + internal static string AvoidUsingCmdletAliasesName { + get { + return ResourceManager.GetString("AvoidUsingCmdletAliasesName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to File '{0}' uses Console.'{1}'. Using Console to write is not recommended because it may not work in all hosts or there may even be no hosts at all. Use Write-Output instead.. + /// + internal static string AvoidUsingConsoleWriteError { + get { + return ResourceManager.GetString("AvoidUsingConsoleWriteError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid Using SecureString With Plain Text. + /// + internal static string AvoidUsingConvertToSecureStringWithPlainTextCommonName { + get { + return ResourceManager.GetString("AvoidUsingConvertToSecureStringWithPlainTextCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Using ConvertTo-SecureString with plain text will expose secure information.. + /// + internal static string AvoidUsingConvertToSecureStringWithPlainTextDescription { + get { + return ResourceManager.GetString("AvoidUsingConvertToSecureStringWithPlainTextDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to File '{0}' uses ConvertTo-SecureString with plaintext. This will expose secure information. Encrypted standard strings should be used instead.. + /// + internal static string AvoidUsingConvertToSecureStringWithPlainTextError { + get { + return ResourceManager.GetString("AvoidUsingConvertToSecureStringWithPlainTextError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Script definition uses ConvertTo-SecureString with plaintext. This will expose secure information. Encrypted standard strings should be used instead.. + /// + internal static string AvoidUsingConvertToSecureStringWithPlainTextErrorScriptDefinition { + get { + return ResourceManager.GetString("AvoidUsingConvertToSecureStringWithPlainTextErrorScriptDefinition", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidUsingConvertToSecureStringWithPlainText. + /// + internal static string AvoidUsingConvertToSecureStringWithPlainTextName { + get { + return ResourceManager.GetString("AvoidUsingConvertToSecureStringWithPlainTextName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid Using Deprecated Manifest Fields. + /// + internal static string AvoidUsingDeprecatedManifestFieldsCommonName { + get { + return ResourceManager.GetString("AvoidUsingDeprecatedManifestFieldsCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to "ModuleToProcess" is obsolete in the latest PowerShell version. Please update with the latest field "RootModule" in manifest files to avoid PowerShell version inconsistency.. + /// + internal static string AvoidUsingDeprecatedManifestFieldsDescription { + get { + return ResourceManager.GetString("AvoidUsingDeprecatedManifestFieldsDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidUsingDeprecatedManifestFields. + /// + internal static string AvoidUsingDeprecatedManifestFieldsName { + get { + return ResourceManager.GetString("AvoidUsingDeprecatedManifestFieldsName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid Using Empty Catch Block. + /// + internal static string AvoidUsingEmptyCatchBlockCommonName { + get { + return ResourceManager.GetString("AvoidUsingEmptyCatchBlockCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Empty catch blocks are considered poor design decisions because if an error occurs in the try block, this error is simply swallowed and not acted upon. While this does not inherently lead to bad things. It can and this should be avoided if possible. To fix a violation of this rule, using Write-Error or throw statements in catch blocks.. + /// + internal static string AvoidUsingEmptyCatchBlockDescription { + get { + return ResourceManager.GetString("AvoidUsingEmptyCatchBlockDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidUsingEmptyCatchBlock. + /// + internal static string AvoidUsingEmptyCatchBlockName { + get { + return ResourceManager.GetString("AvoidUsingEmptyCatchBlockName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid Using Internal URLs. + /// + internal static string AvoidUsingInternalURLsCommonName { + get { + return ResourceManager.GetString("AvoidUsingInternalURLsCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Using Internal URLs in the scripts may cause security problems.. + /// + internal static string AvoidUsingInternalURLsDescription { + get { + return ResourceManager.GetString("AvoidUsingInternalURLsDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '{0}' could be an internal URL. Using internal URL directly in the script may cause potential information disclosure.. + /// + internal static string AvoidUsingInternalURLsError { + get { + return ResourceManager.GetString("AvoidUsingInternalURLsError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidUsingInternalURLs. + /// + internal static string AvoidUsingInternalURLsName { + get { + return ResourceManager.GetString("AvoidUsingInternalURLsName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invoke-Expression is used. Please remove Invoke-Expression from script and find other options instead.. + /// + internal static string AvoidUsingInvokeExpressionError { + get { + return ResourceManager.GetString("AvoidUsingInvokeExpressionError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid Using Invoke-Expression. + /// + internal static string AvoidUsingInvokeExpressionRuleCommonName { + get { + return ResourceManager.GetString("AvoidUsingInvokeExpressionRuleCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Invoke-Expression cmdlet evaluates or runs a specified string as a command and returns the results of the expression or command. It can be extraordinarily powerful so it is not that you want to never use it but you need to be very careful about using it. In particular, you are probably on safe ground if the data only comes from the program itself. If you include any data provided from the user - you need to protect yourself from Code Injection. To fix a violation of this rule, please remove Invoke-Exp [rest of string was truncated]";. + /// + internal static string AvoidUsingInvokeExpressionRuleDescription { + get { + return ResourceManager.GetString("AvoidUsingInvokeExpressionRuleDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidUsingInvokeExpression. + /// + internal static string AvoidUsingInvokeExpressionRuleName { + get { + return ResourceManager.GetString("AvoidUsingInvokeExpressionRuleName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid Using Plain Text For Password Parameter. + /// + internal static string AvoidUsingPlainTextForPasswordCommonName { + get { + return ResourceManager.GetString("AvoidUsingPlainTextForPasswordCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Set {0} type to SecureString. + /// + internal static string AvoidUsingPlainTextForPasswordCorrectionDescription { + get { + return ResourceManager.GetString("AvoidUsingPlainTextForPasswordCorrectionDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Password parameters that take in plaintext will expose passwords and compromise the security of your system.. + /// + internal static string AvoidUsingPlainTextForPasswordDescription { + get { + return ResourceManager.GetString("AvoidUsingPlainTextForPasswordDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Parameter '{0}' should use SecureString, otherwise this will expose sensitive information. See ConvertTo-SecureString for more information.. + /// + internal static string AvoidUsingPlainTextForPasswordError { + get { + return ResourceManager.GetString("AvoidUsingPlainTextForPasswordError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidUsingPlainTextForPassword. + /// + internal static string AvoidUsingPlainTextForPasswordName { + get { + return ResourceManager.GetString("AvoidUsingPlainTextForPasswordName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid Using Positional Parameters. + /// + internal static string AvoidUsingPositionalParametersCommonName { + get { + return ResourceManager.GetString("AvoidUsingPositionalParametersCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Readability and clarity should be the goal of any script we expect to maintain over time. When calling a command that takes parameters, where possible consider using name parameters as opposed to positional parameters. To fix a violation of this rule, please use named parameters instead of positional parameters when calling a command.. + /// + internal static string AvoidUsingPositionalParametersDescription { + get { + return ResourceManager.GetString("AvoidUsingPositionalParametersDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cmdlet '{0}' has positional parameter. Please use named parameters instead of positional parameters when calling a command.. + /// + internal static string AvoidUsingPositionalParametersError { + get { + return ResourceManager.GetString("AvoidUsingPositionalParametersError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidUsingPositionalParameters. + /// + internal static string AvoidUsingPositionalParametersName { + get { + return ResourceManager.GetString("AvoidUsingPositionalParametersName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid Using Get-WMIObject, Remove-WMIObject, Invoke-WmiMethod, Register-WmiEvent, Set-WmiInstance. + /// + internal static string AvoidUsingWMICmdletCommonName { + get { + return ResourceManager.GetString("AvoidUsingWMICmdletCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Deprecated. Starting in Windows PowerShell 3.0, these cmdlets have been superseded by CIM cmdlets.. + /// + internal static string AvoidUsingWMICmdletDescription { + get { + return ResourceManager.GetString("AvoidUsingWMICmdletDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to File '{0}' uses WMI cmdlet. For PowerShell 3.0 and above, use CIM cmdlet which perform the same tasks as the WMI cmdlets. The CIM cmdlets comply with WS-Management (WSMan) standards and with the Common Information Model (CIM) standard, which enables the cmdlets to use the same techniques to manage Windows computers and those running other operating systems.. + /// + internal static string AvoidUsingWMICmdletError { + get { + return ResourceManager.GetString("AvoidUsingWMICmdletError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Script definition uses WMI cmdlet. For PowerShell 3.0 and above, use CIM cmdlet which perform the same tasks as the WMI cmdlets. The CIM cmdlets comply with WS-Management (WSMan) standards and with the Common Information Model (CIM) standard, which enables the cmdlets to use the same techniques to manage Windows computers and those running other operating systems.. + /// + internal static string AvoidUsingWMICmdletErrorScriptDefinition { + get { + return ResourceManager.GetString("AvoidUsingWMICmdletErrorScriptDefinition", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidUsingWMICmdlet. + /// + internal static string AvoidUsingWMICmdletName { + get { + return ResourceManager.GetString("AvoidUsingWMICmdletName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid Using Write-Host. + /// + internal static string AvoidUsingWriteHostCommonName { + get { + return ResourceManager.GetString("AvoidUsingWriteHostCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid using the Write-Host cmdlet. Instead, use Write-Output, Write-Verbose, or Write-Information. Because Write-Host is host-specific, its implementation might vary unpredictably. Also, prior to PowerShell 5.0, Write-Host did not write to a stream, so users cannot suppress it, capture its value, or redirect it.. + /// + internal static string AvoidUsingWriteHostDescription { + get { + return ResourceManager.GetString("AvoidUsingWriteHostDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to File '{0}' uses Write-Host. Avoid using Write-Host because it might not work in all hosts, does not work when there is no host, and (prior to PS 5.0) cannot be suppressed, captured, or redirected. Instead, use Write-Output, Write-Verbose, or Write-Information.. + /// + internal static string AvoidUsingWriteHostError { + get { + return ResourceManager.GetString("AvoidUsingWriteHostError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Script definition uses Write-Host. Avoid using Write-Host because it might not work in all hosts, does not work when there is no host, and (prior to PS 5.0) cannot be suppressed, captured, or redirected. Instead, use Write-Output, Write-Verbose, or Write-Information.. + /// + internal static string AvoidUsingWriteHostErrorScriptDefinition { + get { + return ResourceManager.GetString("AvoidUsingWriteHostErrorScriptDefinition", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to AvoidUsingWriteHost. + /// + internal static string AvoidUsingWriteHostName { + get { + return ResourceManager.GetString("AvoidUsingWriteHostName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Command Not Found. + /// + internal static string CommandNotFoundCommonName { + get { + return ResourceManager.GetString("CommandNotFoundCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Commands that are undefined or do not exist should not be used.. + /// + internal static string CommandNotFoundDescription { + get { + return ResourceManager.GetString("CommandNotFoundDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Command '{0}' Is Not Found. + /// + internal static string CommandNotFoundError { + get { + return ResourceManager.GetString("CommandNotFoundError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to CommandNotFound. + /// + internal static string CommandNotFoundName { + get { + return ResourceManager.GetString("CommandNotFoundName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to DscExamplesPresent. + /// + internal static string DscExamplesPresent { + get { + return ResourceManager.GetString("DscExamplesPresent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to DSC examples are present. + /// + internal static string DscExamplesPresentCommonName { + get { + return ResourceManager.GetString("DscExamplesPresentCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Every DSC resource module should contain folder "Examples" with sample configurations for every resource. Sample configurations should have resource name they are demonstrating in the title.. + /// + internal static string DscExamplesPresentDescription { + get { + return ResourceManager.GetString("DscExamplesPresentDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No examples found for resource '{0}'. + /// + internal static string DscExamplesPresentNoExamplesError { + get { + return ResourceManager.GetString("DscExamplesPresentNoExamplesError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to PSDSC. + /// + internal static string DSCSourceName { + get { + return ResourceManager.GetString("DSCSourceName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to DscTestsPresent. + /// + internal static string DscTestsPresent { + get { + return ResourceManager.GetString("DscTestsPresent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Dsc tests are present. + /// + internal static string DscTestsPresentCommonName { + get { + return ResourceManager.GetString("DscTestsPresentCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Every DSC resource module should contain folder "Tests" with tests for every resource. Test scripts should have resource name they are testing in the file name.. + /// + internal static string DscTestsPresentDescription { + get { + return ResourceManager.GetString("DscTestsPresentDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No tests found for resource '{0}'. + /// + internal static string DscTestsPresentNoTestsError { + get { + return ResourceManager.GetString("DscTestsPresentNoTestsError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Misleading Backtick. + /// + internal static string MisleadingBacktickCommonName { + get { + return ResourceManager.GetString("MisleadingBacktickCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Ending a line with an escaped whitepsace character is misleading. A trailing backtick is usually used for line continuation. Users typically don't intend to end a line with escaped whitespace.. + /// + internal static string MisleadingBacktickDescription { + get { + return ResourceManager.GetString("MisleadingBacktickDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This line has a backtick at the end trailed by a whitespace character. Did you mean for this to be a line continuation?. + /// + internal static string MisleadingBacktickError { + get { + return ResourceManager.GetString("MisleadingBacktickError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to MisleadingBacktick. + /// + internal static string MisleadingBacktickName { + get { + return ResourceManager.GetString("MisleadingBacktickName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Module Manifest Fields. + /// + internal static string MissingModuleManifestFieldCommonName { + get { + return ResourceManager.GetString("MissingModuleManifestFieldCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Add {0} = {1} to the module manifest. + /// + internal static string MissingModuleManifestFieldCorrectionDescription { + get { + return ResourceManager.GetString("MissingModuleManifestFieldCorrectionDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Some fields of the module manifest (such as ModuleVersion) are required.. + /// + internal static string MissingModuleManifestFieldDescription { + get { + return ResourceManager.GetString("MissingModuleManifestFieldDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to MissingModuleManifestField. + /// + internal static string MissingModuleManifestFieldName { + get { + return ResourceManager.GetString("MissingModuleManifestFieldName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0}{1}. + /// + internal static string NameSpaceFormat { + get { + return ResourceManager.GetString("NameSpaceFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Not all code path in {0} function in DSC Class {1} returns a value. + /// + internal static string NotAllCodePathReturnsDSCFunctionsError { + get { + return ResourceManager.GetString("NotAllCodePathReturnsDSCFunctionsError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot process null Ast. + /// + internal static string NullAstErrorMessage { + get { + return ResourceManager.GetString("NullAstErrorMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot process null CommandInfo. + /// + internal static string NullCommandInfoError { + get { + return ResourceManager.GetString("NullCommandInfoError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error Message is Null.. + /// + internal static string NullErrorMessage { + get { + return ResourceManager.GetString("NullErrorMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to One Char. + /// + internal static string OneCharCommonName { + get { + return ResourceManager.GetString("OneCharCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Checks that cmdlets and parameters have more than one character.. + /// + internal static string OneCharDescription { + get { + return ResourceManager.GetString("OneCharDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The cmdlet name '{0}' only has one character.. + /// + internal static string OneCharErrorCmdlet { + get { + return ResourceManager.GetString("OneCharErrorCmdlet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The cmdlet '{0}' has a parameter '{1}' that only has one character.. + /// + internal static string OneCharErrorParameter { + get { + return ResourceManager.GetString("OneCharErrorParameter", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A script block has a parameter '{0}' that only has one character.. + /// + internal static string OneCharErrorParameterSB { + get { + return ResourceManager.GetString("OneCharErrorParameterSB", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to OneChar. + /// + internal static string OneCharName { + get { + return ResourceManager.GetString("OneCharName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Place close braces. + /// + internal static string PlaceCloseBraceCommonName { + get { + return ResourceManager.GetString("PlaceCloseBraceCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Close brace should be on a new line by itself.. + /// + internal static string PlaceCloseBraceDescription { + get { + return ResourceManager.GetString("PlaceCloseBraceDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Close brace is not on a new line.. + /// + internal static string PlaceCloseBraceErrorShouldBeOnNewLine { + get { + return ResourceManager.GetString("PlaceCloseBraceErrorShouldBeOnNewLine", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Close brace before a branch statement is followed by a new line.. + /// + internal static string PlaceCloseBraceErrorShouldCuddleBranchStatement { + get { + return ResourceManager.GetString("PlaceCloseBraceErrorShouldCuddleBranchStatement", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Close brace does not follow a new line.. + /// + internal static string PlaceCloseBraceErrorShouldFollowNewLine { + get { + return ResourceManager.GetString("PlaceCloseBraceErrorShouldFollowNewLine", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Close brace does not follow a non-empty line.. + /// + internal static string PlaceCloseBraceErrorShouldNotFollowEmptyLine { + get { + return ResourceManager.GetString("PlaceCloseBraceErrorShouldNotFollowEmptyLine", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to PlaceCloseBrace. + /// + internal static string PlaceCloseBraceName { + get { + return ResourceManager.GetString("PlaceCloseBraceName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Place open braces consistently. + /// + internal static string PlaceOpenBraceCommonName { + get { + return ResourceManager.GetString("PlaceOpenBraceCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Place open braces either on the same line as the preceding expression or on a new line.. + /// + internal static string PlaceOpenBraceDescription { + get { + return ResourceManager.GetString("PlaceOpenBraceDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to There is no new line after open brace.. + /// + internal static string PlaceOpenBraceErrorNoNewLineAfterBrace { + get { + return ResourceManager.GetString("PlaceOpenBraceErrorNoNewLineAfterBrace", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Open brace not on same line as preceding keyword. It should be on the same line.. + /// + internal static string PlaceOpenBraceErrorShouldBeOnSameLine { + get { + return ResourceManager.GetString("PlaceOpenBraceErrorShouldBeOnSameLine", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Open brace is not on a new line.. + /// + internal static string PlaceOpenBraceErrorShouldNotBeOnSameLine { + get { + return ResourceManager.GetString("PlaceOpenBraceErrorShouldNotBeOnSameLine", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to PlaceOpenBrace. + /// + internal static string PlaceOpenBraceName { + get { + return ResourceManager.GetString("PlaceOpenBraceName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Null Comparison. + /// + internal static string PossibleIncorrectComparisonWithNullCommonName { + get { + return ResourceManager.GetString("PossibleIncorrectComparisonWithNullCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Checks that $null is on the left side of any equaltiy comparisons (eq, ne, ceq, cne, ieq, ine). When there is an array on the left side of a null equality comparison, PowerShell will check for a $null IN the array rather than if the array is null. If the two sides of the comaprision are switched this is fixed. Therefore, $null should always be on the left side of equality comparisons just in case.. + /// + internal static string PossibleIncorrectComparisonWithNullDescription { + get { + return ResourceManager.GetString("PossibleIncorrectComparisonWithNullDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to $null should be on the left side of equality comparisons.. + /// + internal static string PossibleIncorrectComparisonWithNullError { + get { + return ResourceManager.GetString("PossibleIncorrectComparisonWithNullError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to PossibleIncorrectComparisonWithNull. + /// + internal static string PossibleIncorrectComparisonWithNullName { + get { + return ResourceManager.GetString("PossibleIncorrectComparisonWithNullName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use $null on the left hand side for safe comparison with $null.. + /// + internal static string PossibleIncorrectComparisonWithNullSuggesteCorrectionDescription { + get { + return ResourceManager.GetString("PossibleIncorrectComparisonWithNullSuggesteCorrectionDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '=' is not an assignment operator. Did you mean the equality operator '-eq'?. + /// + internal static string PossibleIncorrectUsageOfAssignmentOperatorCommonName { + get { + return ResourceManager.GetString("PossibleIncorrectUsageOfAssignmentOperatorCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '=' or '==' are not comparison operators in the PowerShell language and rarely needed inside conditional statements.. + /// + internal static string PossibleIncorrectUsageOfAssignmentOperatorDescription { + get { + return ResourceManager.GetString("PossibleIncorrectUsageOfAssignmentOperatorDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Did you mean to use the assignment operator '='? The equality operator in PowerShell is 'eq'.. + /// + internal static string PossibleIncorrectUsageOfAssignmentOperatorError { + get { + return ResourceManager.GetString("PossibleIncorrectUsageOfAssignmentOperatorError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to PossibleIncorrectUsageOfAssignmentOperator. + /// + internal static string PossibleIncorrectUsageOfAssignmentOperatorName { + get { + return ResourceManager.GetString("PossibleIncorrectUsageOfAssignmentOperatorName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '>' is not a comparison operator. Use '-gt' (greater than) or '-ge' (greater or equal).. + /// + internal static string PossibleIncorrectUsageOfRedirectionOperatorCommonName { + get { + return ResourceManager.GetString("PossibleIncorrectUsageOfRedirectionOperatorCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to When switching between different languages it is easy to forget that '>' does not mean 'great than' in PowerShell.. + /// + internal static string PossibleIncorrectUsageOfRedirectionOperatorDescription { + get { + return ResourceManager.GetString("PossibleIncorrectUsageOfRedirectionOperatorDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Did you mean to use the redirection operator '>'? The comparison operators in PowerShell are '-gt' (greater than) or '-ge' (greater or equal).. + /// + internal static string PossibleIncorrectUsageOfRedirectionOperatorError { + get { + return ResourceManager.GetString("PossibleIncorrectUsageOfRedirectionOperatorError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to PossibleIncorrectUsageOfRedirectionOperator. + /// + internal static string PossibleIncorrectUsageOfRedirectionOperatorName { + get { + return ResourceManager.GetString("PossibleIncorrectUsageOfRedirectionOperatorName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Basic Comment Help. + /// + internal static string ProvideCommentHelpCommonName { + get { + return ResourceManager.GetString("ProvideCommentHelpCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Checks that all cmdlets have a help comment. This rule only checks existence. It does not check the content of the comment.. + /// + internal static string ProvideCommentHelpDescription { + get { + return ResourceManager.GetString("ProvideCommentHelpDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The cmdlet '{0}' does not have a help comment.. + /// + internal static string ProvideCommentHelpError { + get { + return ResourceManager.GetString("ProvideCommentHelpError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ProvideCommentHelp. + /// + internal static string ProvideCommentHelpName { + get { + return ResourceManager.GetString("ProvideCommentHelpName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Reserved Cmdlet Chars. + /// + internal static string ReservedCmdletCharCommonName { + get { + return ResourceManager.GetString("ReservedCmdletCharCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Checks for reserved characters in cmdlet names. These characters usually cause a parsing error. Otherwise they will generally cause runtime errors.. + /// + internal static string ReservedCmdletCharDescription { + get { + return ResourceManager.GetString("ReservedCmdletCharDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The cmdlet '{0}' uses a reserved char in its name.. + /// + internal static string ReservedCmdletCharError { + get { + return ResourceManager.GetString("ReservedCmdletCharError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ReservedCmdletChar. + /// + internal static string ReservedCmdletCharName { + get { + return ResourceManager.GetString("ReservedCmdletCharName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The cmdlet '{0}'. + /// + internal static string ReservedParamsCmdletPrefix { + get { + return ResourceManager.GetString("ReservedParamsCmdletPrefix", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Reserved Parameters. + /// + internal static string ReservedParamsCommonName { + get { + return ResourceManager.GetString("ReservedParamsCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Checks for reserved parameters in function definitions. If these parameters are defined by the user, an error generally occurs.. + /// + internal static string ReservedParamsDescription { + get { + return ResourceManager.GetString("ReservedParamsDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '{0}' defines the reserved common parameter '{1}'.. + /// + internal static string ReservedParamsError { + get { + return ResourceManager.GetString("ReservedParamsError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ReservedParams. + /// + internal static string ReservedParamsName { + get { + return ResourceManager.GetString("ReservedParamsName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The script. + /// + internal static string ReservedParamsScriptPrefix { + get { + return ResourceManager.GetString("ReservedParamsScriptPrefix", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to #,(){}[]&/\\$^;:\"'<>|?@`*%+=~. + /// + internal static string ReserverCmdletChars { + get { + return ResourceManager.GetString("ReserverCmdletChars", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ReturnCorrectTypesForDSCFunctions. + /// + internal static string ReturnCorrectTypeDSCFunctionsName { + get { + return ResourceManager.GetString("ReturnCorrectTypeDSCFunctionsName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Return Correct Types For DSC Functions. + /// + internal static string ReturnCorrectTypesForDSCFunctionsCommonName { + get { + return ResourceManager.GetString("ReturnCorrectTypesForDSCFunctionsCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Set function in DSC class and Set-TargetResource in DSC resource must not return anything. Get function in DSC class must return an instance of the DSC class and Get-TargetResource function in DSC resource must return a hashtable. Test function in DSC class and Get-TargetResource function in DSC resource must return a boolean.. + /// + internal static string ReturnCorrectTypesForDSCFunctionsDescription { + get { + return ResourceManager.GetString("ReturnCorrectTypesForDSCFunctionsDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} function in DSC Class {1} should return object of type {2}. + /// + internal static string ReturnCorrectTypesForDSCFunctionsNoTypeError { + get { + return ResourceManager.GetString("ReturnCorrectTypesForDSCFunctionsNoTypeError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} function in DSC Class {1} should return object of type {2} instead of type {3}. + /// + internal static string ReturnCorrectTypesForDSCFunctionsWrongTypeError { + get { + return ResourceManager.GetString("ReturnCorrectTypesForDSCFunctionsWrongTypeError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} function in DSC Resource should return object of type {1} instead of {2}. + /// + internal static string ReturnCorrectTypesForGetTestTargetResourceFunctionsDSCResourceError { + get { + return ResourceManager.GetString("ReturnCorrectTypesForGetTestTargetResourceFunctionsDSCResourceError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Set function in DSC Class {0} should not return anything. + /// + internal static string ReturnCorrectTypesForSetFunctionsDSCError { + get { + return ResourceManager.GetString("ReturnCorrectTypesForSetFunctionsDSCError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Set-TargetResource function in DSC Resource should not output anything to the pipeline.. + /// + internal static string ReturnCorrectTypesForSetTargetResourceFunctionsDSCError { + get { + return ResourceManager.GetString("ReturnCorrectTypesForSetTargetResourceFunctionsDSCError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ReviewUnusedParameter. + /// + internal static string ReviewUnusedParameterCommonName { + get { + return ResourceManager.GetString("ReviewUnusedParameterCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Ensure all parameters are used within the same script, scriptblock, or function where they are declared.. + /// + internal static string ReviewUnusedParameterDescription { + get { + return ResourceManager.GetString("ReviewUnusedParameterDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The parameter '{0}' has been declared but not used. . + /// + internal static string ReviewUnusedParameterError { + get { + return ResourceManager.GetString("ReviewUnusedParameterError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ReviewUnusedParameter. + /// + internal static string ReviewUnusedParameterName { + get { + return ResourceManager.GetString("ReviewUnusedParameterName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ScriptDefinition. + /// + internal static string ScriptDefinitionName { + get { + return ResourceManager.GetString("ScriptDefinitionName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to www.sharepoint.com. + /// + internal static string SharepointURL { + get { + return ResourceManager.GetString("SharepointURL", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Should Process. + /// + internal static string ShouldProcessCommonName { + get { + return ResourceManager.GetString("ShouldProcessCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Checks that if the SupportsShouldProcess is present, the function calls ShouldProcess/ShouldContinue and vice versa. Scripts with one or the other but not both will generally run into an error or unexpected behavior.. + /// + internal static string ShouldProcessDescription { + get { + return ResourceManager.GetString("ShouldProcessDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '{0}' has the ShouldProcess attribute but does not call ShouldProcess/ShouldContinue.. + /// + internal static string ShouldProcessErrorHasAttribute { + get { + return ResourceManager.GetString("ShouldProcessErrorHasAttribute", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A script block has the ShouldProcess attribute but does not call ShouldProcess/ShouldContinue.. + /// + internal static string ShouldProcessErrorHasAttributeSB { + get { + return ResourceManager.GetString("ShouldProcessErrorHasAttributeSB", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '{0}' calls ShouldProcess/ShouldContinue but does not have the ShouldProcess attribute.. + /// + internal static string ShouldProcessErrorHasCmdlet { + get { + return ResourceManager.GetString("ShouldProcessErrorHasCmdlet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A script block calls ShouldProcess/ShouldContinue but does not have the ShouldProcess attribute.. + /// + internal static string ShouldProcessErrorHasCmdletSB { + get { + return ResourceManager.GetString("ShouldProcessErrorHasCmdletSB", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ShouldProcess. + /// + internal static string ShouldProcessName { + get { + return ResourceManager.GetString("ShouldProcessName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to PS. + /// + internal static string SourceName { + get { + return ResourceManager.GetString("SourceName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Type Not Found. + /// + internal static string TypeNotFoundCommonName { + get { + return ResourceManager.GetString("TypeNotFoundCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Undefined type should not be used. + /// + internal static string TypeNotFoundDescription { + get { + return ResourceManager.GetString("TypeNotFoundDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Type '{0}' is not found. Please check that it is defined.. + /// + internal static string TypeNotFoundError { + get { + return ResourceManager.GetString("TypeNotFoundError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to TypeNotFound. + /// + internal static string TypeNotFoundName { + get { + return ResourceManager.GetString("TypeNotFoundName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cmdlet Verbs. + /// + internal static string UseApprovedVerbsCommonName { + get { + return ResourceManager.GetString("UseApprovedVerbsCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Checks that all defined cmdlets use approved verbs. This is in line with PowerShell's best practices.. + /// + internal static string UseApprovedVerbsDescription { + get { + return ResourceManager.GetString("UseApprovedVerbsDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The cmdlet '{0}' uses an unapproved verb.. + /// + internal static string UseApprovedVerbsError { + get { + return ResourceManager.GetString("UseApprovedVerbsError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to UseApprovedVerbs. + /// + internal static string UseApprovedVerbsName { + get { + return ResourceManager.GetString("UseApprovedVerbsName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use BOM encoding for non-ASCII files. + /// + internal static string UseBOMForUnicodeEncodedFileCommonName { + get { + return ResourceManager.GetString("UseBOMForUnicodeEncodedFileCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to For a file encoded with a format other than ASCII, ensure BOM is present to ensure that any application consuming this file can interpret it correctly.. + /// + internal static string UseBOMForUnicodeEncodedFileDescription { + get { + return ResourceManager.GetString("UseBOMForUnicodeEncodedFileDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Missing BOM encoding for non-ASCII encoded file '{0}'. + /// + internal static string UseBOMForUnicodeEncodedFileError { + get { + return ResourceManager.GetString("UseBOMForUnicodeEncodedFileError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to UseBOMForUnicodeEncodedFile. + /// + internal static string UseBOMForUnicodeEncodedFileName { + get { + return ResourceManager.GetString("UseBOMForUnicodeEncodedFileName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use Cmdlet Correctly. + /// + internal static string UseCmdletCorrectlyCommonName { + get { + return ResourceManager.GetString("UseCmdletCorrectlyCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cmdlet should be called with the mandatory parameters.. + /// + internal static string UseCmdletCorrectlyDescription { + get { + return ResourceManager.GetString("UseCmdletCorrectlyDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cmdlet '{0}' may be used incorrectly. Please check that all mandatory parameters are supplied.. + /// + internal static string UseCmdletCorrectlyError { + get { + return ResourceManager.GetString("UseCmdletCorrectlyError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to UseCmdletCorrectly. + /// + internal static string UseCmdletCorrectlyName { + get { + return ResourceManager.GetString("UseCmdletCorrectlyName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use compatible cmdlets. + /// + internal static string UseCompatibleCmdletsCommonName { + get { + return ResourceManager.GetString("UseCompatibleCmdletsCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use cmdlets compatible with the given PowerShell version and edition and operating system. + /// + internal static string UseCompatibleCmdletsDescription { + get { + return ResourceManager.GetString("UseCompatibleCmdletsDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '{0}' is not compatible with PowerShell edition '{1}', version '{2}' and OS '{3}'. + /// + internal static string UseCompatibleCmdletsError { + get { + return ResourceManager.GetString("UseCompatibleCmdletsError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to UseCompatibleCmdlets. + /// + internal static string UseCompatibleCmdletsName { + get { + return ResourceManager.GetString("UseCompatibleCmdletsName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The command '{0}' is not available by default in PowerShell version '{1}' on platform '{2}'. + /// + internal static string UseCompatibleCommandsCommandError { + get { + return ResourceManager.GetString("UseCompatibleCommandsCommandError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use compatible commands. + /// + internal static string UseCompatibleCommandsCommonName { + get { + return ResourceManager.GetString("UseCompatibleCommandsCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use commands compatible with the given PowerShell version and operating system. + /// + internal static string UseCompatibleCommandsDescription { + get { + return ResourceManager.GetString("UseCompatibleCommandsDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to UseCompatibleCommands. + /// + internal static string UseCompatibleCommandsName { + get { + return ResourceManager.GetString("UseCompatibleCommandsName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The parameter '{0}' is not available for command '{1}' by default in PowerShell version '{2}' on platform '{3}'. + /// + internal static string UseCompatibleCommandsParameterError { + get { + return ResourceManager.GetString("UseCompatibleCommandsParameterError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use compatible syntax. + /// + internal static string UseCompatibleSyntaxCommonName { + get { + return ResourceManager.GetString("UseCompatibleSyntaxCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use the '{0}' syntax instead for compatibility with PowerShell versions {1}. + /// + internal static string UseCompatibleSyntaxCorrection { + get { + return ResourceManager.GetString("UseCompatibleSyntaxCorrection", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use script syntax compatible with the given PowerShell versions. + /// + internal static string UseCompatibleSyntaxDescription { + get { + return ResourceManager.GetString("UseCompatibleSyntaxDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The {0} syntax '{1}' is not available by default in PowerShell versions {2}. + /// + internal static string UseCompatibleSyntaxError { + get { + return ResourceManager.GetString("UseCompatibleSyntaxError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to UseCompatibleSyntax. + /// + internal static string UseCompatibleSyntaxName { + get { + return ResourceManager.GetString("UseCompatibleSyntaxName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use compatible types. + /// + internal static string UseCompatibleTypesCommonName { + get { + return ResourceManager.GetString("UseCompatibleTypesCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use types compatible with the given PowerShell version and operating system. + /// + internal static string UseCompatibleTypesDescription { + get { + return ResourceManager.GetString("UseCompatibleTypesDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The member '{0}' is not available on type '{1}' by default in PowerShell version '{2}' on platform '{3}'. + /// + internal static string UseCompatibleTypesMemberError { + get { + return ResourceManager.GetString("UseCompatibleTypesMemberError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The method '{0}' is not available on type '{1}' by default in PowerShell version '{2}' on platform '{3}'. + /// + internal static string UseCompatibleTypesMethodError { + get { + return ResourceManager.GetString("UseCompatibleTypesMethodError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to UseCompatibleTypes. + /// + internal static string UseCompatibleTypesName { + get { + return ResourceManager.GetString("UseCompatibleTypesName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type accelerator '{0}' is not available by default in PowerShell version '{1}' on platform '{2}'. + /// + internal static string UseCompatibleTypesTypeAcceleratorError { + get { + return ResourceManager.GetString("UseCompatibleTypesTypeAcceleratorError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' is not available by default in PowerShell version '{1}' on platform '{2}'. + /// + internal static string UseCompatibleTypesTypeError { + get { + return ResourceManager.GetString("UseCompatibleTypesTypeError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use consistent indentation. + /// + internal static string UseConsistentIndentationCommonName { + get { + return ResourceManager.GetString("UseConsistentIndentationCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Each statement block should have a consistent indenation.. + /// + internal static string UseConsistentIndentationDescription { + get { + return ResourceManager.GetString("UseConsistentIndentationDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Indentation not consistent. + /// + internal static string UseConsistentIndentationError { + get { + return ResourceManager.GetString("UseConsistentIndentationError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to UseConsistentIndentation. + /// + internal static string UseConsistentIndentationName { + get { + return ResourceManager.GetString("UseConsistentIndentationName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use whitespaces. + /// + internal static string UseConsistentWhitespaceCommonName { + get { + return ResourceManager.GetString("UseConsistentWhitespaceCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Check for whitespace between keyword and open paren/curly, around assigment operator ('='), around arithmetic operators and after separators (',' and ';'). + /// + internal static string UseConsistentWhitespaceDescription { + get { + return ResourceManager.GetString("UseConsistentWhitespaceDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use space after open brace.. + /// + internal static string UseConsistentWhitespaceErrorAfterOpeningBrace { + get { + return ResourceManager.GetString("UseConsistentWhitespaceErrorAfterOpeningBrace", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use space before closing brace.. + /// + internal static string UseConsistentWhitespaceErrorBeforeClosingInnerBrace { + get { + return ResourceManager.GetString("UseConsistentWhitespaceErrorBeforeClosingInnerBrace", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use space before open brace.. + /// + internal static string UseConsistentWhitespaceErrorBeforeOpeningBrace { + get { + return ResourceManager.GetString("UseConsistentWhitespaceErrorBeforeOpeningBrace", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use space before open parenthesis.. + /// + internal static string UseConsistentWhitespaceErrorBeforeParen { + get { + return ResourceManager.GetString("UseConsistentWhitespaceErrorBeforeParen", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use space before and after binary and assignment operators.. + /// + internal static string UseConsistentWhitespaceErrorOperator { + get { + return ResourceManager.GetString("UseConsistentWhitespaceErrorOperator", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use space after a comma.. + /// + internal static string UseConsistentWhitespaceErrorSeparatorComma { + get { + return ResourceManager.GetString("UseConsistentWhitespaceErrorSeparatorComma", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use space after a semicolon.. + /// + internal static string UseConsistentWhitespaceErrorSeparatorSemi { + get { + return ResourceManager.GetString("UseConsistentWhitespaceErrorSeparatorSemi", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use space after pipe.. + /// + internal static string UseConsistentWhitespaceErrorSpaceAfterPipe { + get { + return ResourceManager.GetString("UseConsistentWhitespaceErrorSpaceAfterPipe", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use space before pipe.. + /// + internal static string UseConsistentWhitespaceErrorSpaceBeforePipe { + get { + return ResourceManager.GetString("UseConsistentWhitespaceErrorSpaceBeforePipe", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use only 1 whitespace between parameter names or values.. + /// + internal static string UseConsistentWhitespaceErrorSpaceBetweenParameter { + get { + return ResourceManager.GetString("UseConsistentWhitespaceErrorSpaceBetweenParameter", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to UseConsistentWhitespace. + /// + internal static string UseConsistentWhitespaceName { + get { + return ResourceManager.GetString("UseConsistentWhitespaceName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use exact casing of cmdlet/function/parameter name.. + /// + internal static string UseCorrectCasingCommonName { + get { + return ResourceManager.GetString("UseCorrectCasingCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to For better readability and consistency, use the exact casing of the cmdlet/function/parameter.. + /// + internal static string UseCorrectCasingDescription { + get { + return ResourceManager.GetString("UseCorrectCasingDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cmdlet/Function/Parameter does not match its exact casing '{0}'.. + /// + internal static string UseCorrectCasingError { + get { + return ResourceManager.GetString("UseCorrectCasingError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to UseCorrectCasing. + /// + internal static string UseCorrectCasingName { + get { + return ResourceManager.GetString("UseCorrectCasingName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Extra Variables. + /// + internal static string UseDeclaredVarsMoreThanAssignmentsCommonName { + get { + return ResourceManager.GetString("UseDeclaredVarsMoreThanAssignmentsCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Ensure declared variables are used elsewhere in the script and not just during assignment.. + /// + internal static string UseDeclaredVarsMoreThanAssignmentsDescription { + get { + return ResourceManager.GetString("UseDeclaredVarsMoreThanAssignmentsDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The variable '{0}' is assigned but never used.. + /// + internal static string UseDeclaredVarsMoreThanAssignmentsError { + get { + return ResourceManager.GetString("UseDeclaredVarsMoreThanAssignmentsError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to UseDeclaredVarsMoreThanAssignments. + /// + internal static string UseDeclaredVarsMoreThanAssignmentsName { + get { + return ResourceManager.GetString("UseDeclaredVarsMoreThanAssignmentsName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use identical mandatory parameters for DSC Get/Test/Set TargetResource functions. + /// + internal static string UseIdenticalMandatoryParametersDSCCommonName { + get { + return ResourceManager.GetString("UseIdenticalMandatoryParametersDSCCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Get/Test/Set TargetResource functions of DSC resource must have the same mandatory parameters.. + /// + internal static string UseIdenticalMandatoryParametersDSCDescription { + get { + return ResourceManager.GetString("UseIdenticalMandatoryParametersDSCDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The '{0}' parameter '{1}' is not present in '{2}' DSC resource function(s).. + /// + internal static string UseIdenticalMandatoryParametersDSCError { + get { + return ResourceManager.GetString("UseIdenticalMandatoryParametersDSCError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to UseIdenticalMandatoryParametersForDSC. + /// + internal static string UseIdenticalMandatoryParametersDSCName { + get { + return ResourceManager.GetString("UseIdenticalMandatoryParametersDSCName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use Identical Parameters For DSC Test and Set Functions. + /// + internal static string UseIdenticalParametersDSCCommonName { + get { + return ResourceManager.GetString("UseIdenticalParametersDSCCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Test and Set-TargetResource functions of DSC Resource must have the same parameters.. + /// + internal static string UseIdenticalParametersDSCDescription { + get { + return ResourceManager.GetString("UseIdenticalParametersDSCDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Test and Set-TargetResource functions of DSC Resource must have the same parameters.. + /// + internal static string UseIdenticalParametersDSCError { + get { + return ResourceManager.GetString("UseIdenticalParametersDSCError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to UseIdenticalParametersForDSC. + /// + internal static string UseIdenticalParametersDSCName { + get { + return ResourceManager.GetString("UseIdenticalParametersDSCName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Create hashtables with literal initializers. + /// + internal static string UseLiteralInitilializerForHashtableCommonName { + get { + return ResourceManager.GetString("UseLiteralInitilializerForHashtableCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use literal initializer, @{{}}, for creating a hashtable as they are case-insensitive by default. + /// + internal static string UseLiteralInitilializerForHashtableDescription { + get { + return ResourceManager.GetString("UseLiteralInitilializerForHashtableDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Create hashtables with literal initliazers. + /// + internal static string UseLiteralInitilializerForHashtableError { + get { + return ResourceManager.GetString("UseLiteralInitilializerForHashtableError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to UseLiteralInitializerForHashtable. + /// + internal static string UseLiteralInitilializerForHashtableName { + get { + return ResourceManager.GetString("UseLiteralInitilializerForHashtableName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use OutputType Correctly. + /// + internal static string UseOutputTypeCorrectlyCommonName { + get { + return ResourceManager.GetString("UseOutputTypeCorrectlyCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The return types of a cmdlet should be declared using the OutputType attribute.. + /// + internal static string UseOutputTypeCorrectlyDescription { + get { + return ResourceManager.GetString("UseOutputTypeCorrectlyDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The cmdlet '{0}' returns an object of type '{1}' but this type is not declared in the OutputType attribute.. + /// + internal static string UseOutputTypeCorrectlyError { + get { + return ResourceManager.GetString("UseOutputTypeCorrectlyError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to UseOutputTypeCorrectly. + /// + internal static string UseOutputTypeCorrectlyName { + get { + return ResourceManager.GetString("UseOutputTypeCorrectlyName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use process block for command that accepts input from pipeline.. + /// + internal static string UseProcessBlockForPipelineCommandCommonName { + get { + return ResourceManager.GetString("UseProcessBlockForPipelineCommandCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to If a command parameter takes its value from the pipeline, the command must use a process block to bind the input objects from the pipeline to that parameter.. + /// + internal static string UseProcessBlockForPipelineCommandDescription { + get { + return ResourceManager.GetString("UseProcessBlockForPipelineCommandDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Command accepts pipeline input but has not defined a process block.. + /// + internal static string UseProcessBlockForPipelineCommandError { + get { + return ResourceManager.GetString("UseProcessBlockForPipelineCommandError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to UseProcessBlockForPipelineCommand. + /// + internal static string UseProcessBlockForPipelineCommandName { + get { + return ResourceManager.GetString("UseProcessBlockForPipelineCommandName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use PSCredential type.. + /// + internal static string UsePSCredentialTypeCommonName { + get { + return ResourceManager.GetString("UsePSCredentialTypeCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to For PowerShell 4.0 and earlier, a parameter named Credential with type PSCredential must have a credential transformation attribute defined after the PSCredential type attribute. . + /// + internal static string UsePSCredentialTypeDescription { + get { + return ResourceManager.GetString("UsePSCredentialTypeDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Credential parameter in '{0}' must be of type PSCredential. For PowerShell 4.0 and earlier, please define a credential transformation attribute, e.g. [System.Management.Automation.Credential()], after the PSCredential type attribute.. + /// + internal static string UsePSCredentialTypeError { + get { + return ResourceManager.GetString("UsePSCredentialTypeError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Credential parameter found in the script block must be of type PSCredential. For PowerShell 4.0 and earlier please define a credential transformation attribute, e.g. [System.Management.Automation.Credential()], after the PSCredential type attribute. . + /// + internal static string UsePSCredentialTypeErrorSB { + get { + return ResourceManager.GetString("UsePSCredentialTypeErrorSB", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to UsePSCredentialType. + /// + internal static string UsePSCredentialTypeName { + get { + return ResourceManager.GetString("UsePSCredentialTypeName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use ShouldProcess For State Changing Functions. + /// + internal static string UseShouldProcessForStateChangingFunctionsCommonName { + get { + return ResourceManager.GetString("UseShouldProcessForStateChangingFunctionsCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Functions that have verbs like New, Start, Stop, Set, Reset, Restart that change system state should support 'ShouldProcess'.. + /// + internal static string UseShouldProcessForStateChangingFunctionsDescrption { + get { + return ResourceManager.GetString("UseShouldProcessForStateChangingFunctionsDescrption", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Function '{0}' has verb that could change system state. Therefore, the function has to support 'ShouldProcess'.. + /// + internal static string UseShouldProcessForStateChangingFunctionsError { + get { + return ResourceManager.GetString("UseShouldProcessForStateChangingFunctionsError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to UseShouldProcessForStateChangingFunctions. + /// + internal static string UseShouldProcessForStateChangingFunctionsName { + get { + return ResourceManager.GetString("UseShouldProcessForStateChangingFunctionsName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cmdlet Singular Noun. + /// + internal static string UseSingularNounsCommonName { + get { + return ResourceManager.GetString("UseSingularNounsCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cmdlet should use singular instead of plural nouns.. + /// + internal static string UseSingularNounsDescription { + get { + return ResourceManager.GetString("UseSingularNounsDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The cmdlet '{0}' uses a plural noun. A singular noun should be used instead.. + /// + internal static string UseSingularNounsError { + get { + return ResourceManager.GetString("UseSingularNounsError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to UseSingularNouns. + /// + internal static string UseSingularNounsName { + get { + return ResourceManager.GetString("UseSingularNounsName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Missing '{0}' function. DSC Class must implement Get, Set and Test functions.. + /// + internal static string UseStandardDSCFunctionsInClassError { + get { + return ResourceManager.GetString("UseStandardDSCFunctionsInClassError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use Standard Get/Set/Test TargetResource functions in DSC Resource . + /// + internal static string UseStandardDSCFunctionsInResourceCommonName { + get { + return ResourceManager.GetString("UseStandardDSCFunctionsInResourceCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to DSC Resource must implement Get, Set and Test-TargetResource functions. DSC Class must implement Get, Set and Test functions.. + /// + internal static string UseStandardDSCFunctionsInResourceDescription { + get { + return ResourceManager.GetString("UseStandardDSCFunctionsInResourceDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Missing '{0}' function. DSC Resource must implement Get, Set and Test-TargetResource functions.. + /// + internal static string UseStandardDSCFunctionsInResourceError { + get { + return ResourceManager.GetString("UseStandardDSCFunctionsInResourceError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to StandardDSCFunctionsInResource. + /// + internal static string UseStandardDSCFunctionsInResourceName { + get { + return ResourceManager.GetString("UseStandardDSCFunctionsInResourceName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use SupportsShouldProcess. + /// + internal static string UseSupportsShouldProcessCommonName { + get { + return ResourceManager.GetString("UseSupportsShouldProcessCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Commands typically provide Confirm and Whatif parameters to give more control on its execution in an interactive environment. In PowerShell, a command can use a SupportsShouldProcess attribute to provide this capability. Hence, manual addition of these parameters to a command is discouraged. If a commands need Confirm and Whatif parameters, then it should support ShouldProcess.. + /// + internal static string UseSupportsShouldProcessDescription { + get { + return ResourceManager.GetString("UseSupportsShouldProcessDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Whatif and/or Confirm manually defined in function {0}. Instead, please use SupportsShouldProcess attribute.. + /// + internal static string UseSupportsShouldProcessError { + get { + return ResourceManager.GetString("UseSupportsShouldProcessError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to UseSupportsShouldProcess. + /// + internal static string UseSupportsShouldProcessName { + get { + return ResourceManager.GetString("UseSupportsShouldProcessName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use the *ToExport module manifest fields.. + /// + internal static string UseToExportFieldsInManifestCommonName { + get { + return ResourceManager.GetString("UseToExportFieldsInManifestCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Replace {0} with {1}. + /// + internal static string UseToExportFieldsInManifestCorrectionDescription { + get { + return ResourceManager.GetString("UseToExportFieldsInManifestCorrectionDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to In a module manifest, AliasesToExport, CmdletsToExport, FunctionsToExport and VariablesToExport fields should not use wildcards or $null in their entries. During module auto-discovery, if any of these entries are missing or $null or wildcard, PowerShell does some potentially expensive work to analyze the rest of the module.. + /// + internal static string UseToExportFieldsInManifestDescription { + get { + return ResourceManager.GetString("UseToExportFieldsInManifestDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Do not use wildcard or $null in this field. Explicitly specify a list for {0}. . + /// + internal static string UseToExportFieldsInManifestError { + get { + return ResourceManager.GetString("UseToExportFieldsInManifestError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to UseToExportFieldsInManifest. + /// + internal static string UseToExportFieldsInManifestName { + get { + return ResourceManager.GetString("UseToExportFieldsInManifestName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use Type At Variable Assignment. + /// + internal static string UseTypeAtVariableAssignmentCommonName { + get { + return ResourceManager.GetString("UseTypeAtVariableAssignmentCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Types should be specified at variable assignments to maintain readability and maintainability of script.. + /// + internal static string UseTypeAtVariableAssignmentDescription { + get { + return ResourceManager.GetString("UseTypeAtVariableAssignmentDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Specify type at the assignment of variable '{0}'. + /// + internal static string UseTypeAtVariableAssignmentError { + get { + return ResourceManager.GetString("UseTypeAtVariableAssignmentError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to UseTypeAtVariableAssignment. + /// + internal static string UseTypeAtVariableAssignmentName { + get { + return ResourceManager.GetString("UseTypeAtVariableAssignmentName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use UTF8 Encoding For Help File. + /// + internal static string UseUTF8EncodingForHelpFileCommonName { + get { + return ResourceManager.GetString("UseUTF8EncodingForHelpFileCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to PowerShell help file needs to use UTF8 Encoding.. + /// + internal static string UseUTF8EncodingForHelpFileDescription { + get { + return ResourceManager.GetString("UseUTF8EncodingForHelpFileDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to File {0} has to use UTF8 instead of {1} encoding because it is a powershell help file.. + /// + internal static string UseUTF8EncodingForHelpFileError { + get { + return ResourceManager.GetString("UseUTF8EncodingForHelpFileError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to UseUTF8EncodingForHelpFile. + /// + internal static string UseUTF8EncodingForHelpFileName { + get { + return ResourceManager.GetString("UseUTF8EncodingForHelpFileName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use verbose message in DSC resource. + /// + internal static string UseVerboseMessageInDSCResourceCommonName { + get { + return ResourceManager.GetString("UseVerboseMessageInDSCResourceCommonName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to It is a best practice to emit informative, verbose messages in DSC resource functions. This helps in debugging issues when a DSC configuration is executed.. + /// + internal static string UseVerboseMessageInDSCResourceDescription { + get { + return ResourceManager.GetString("UseVerboseMessageInDSCResourceDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to There is no call to Write-Verbose in DSC function '{0}'. If you are using Write-Verbose in a helper function, suppress this rule application.. + /// + internal static string UseVerboseMessageInDSCResourceErrorFunction { + get { + return ResourceManager.GetString("UseVerboseMessageInDSCResourceErrorFunction", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to UseVerboseMessageInDSCResource. + /// + internal static string UseVerboseMessageInDSCResourceName { + get { + return ResourceManager.GetString("UseVerboseMessageInDSCResourceName", resourceCulture); + } + } + } +} diff --git a/ScriptAnalyzer2/Builtin/Rules/Strings.resx b/ScriptAnalyzer2/Builtin/Rules/Strings.resx new file mode 100644 index 000000000..fc47fc6bd --- /dev/null +++ b/ScriptAnalyzer2/Builtin/Rules/Strings.resx @@ -0,0 +1,1125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + An alias is an alternate name or nickname for a cmdlet or for a command element, such as a function, script, file, or executable file. An implicit alias is also the omission of the 'Get-' prefix for commands with this prefix. But when writing scripts that will potentially need to be maintained over time, either by the original author or another Windows PowerShell scripter, please consider using full cmdlet name instead of alias. Aliases can introduce these problems, readability, understandability and availability. + + + Avoid Using Cmdlet Aliases or omitting the 'Get-' prefix. + + + Empty catch blocks are considered poor design decisions because if an error occurs in the try block, this error is simply swallowed and not acted upon. While this does not inherently lead to bad things. It can and this should be avoided if possible. To fix a violation of this rule, using Write-Error or throw statements in catch blocks. + + + Avoid Using Empty Catch Block + + + The Invoke-Expression cmdlet evaluates or runs a specified string as a command and returns the results of the expression or command. It can be extraordinarily powerful so it is not that you want to never use it but you need to be very careful about using it. In particular, you are probably on safe ground if the data only comes from the program itself. If you include any data provided from the user - you need to protect yourself from Code Injection. To fix a violation of this rule, please remove Invoke-Expression from script and find other options instead. + + + Avoid Using Invoke-Expression + + + Readability and clarity should be the goal of any script we expect to maintain over time. When calling a command that takes parameters, where possible consider using name parameters as opposed to positional parameters. To fix a violation of this rule, please use named parameters instead of positional parameters when calling a command. + + + Avoid Using Positional Parameters + + + Checks that all cmdlets have a help comment. This rule only checks existence. It does not check the content of the comment. + + + The cmdlet '{0}' does not have a help comment. + + + Basic Comment Help + + + Checks that all defined cmdlets use approved verbs. This is in line with PowerShell's best practices. + + + The cmdlet '{0}' uses an unapproved verb. + + + Cmdlet Verbs + + + Ensure declared variables are used elsewhere in the script and not just during assignment. + + + The variable '{0}' is assigned but never used. + + + Extra Variables + + + Checks that global variables are not used. Global variables are strongly discouraged as they can cause errors across different systems. + + + Found global variable '{0}'. + + + No Global Variables + + + Checks that $null is on the left side of any equaltiy comparisons (eq, ne, ceq, cne, ieq, ine). When there is an array on the left side of a null equality comparison, PowerShell will check for a $null IN the array rather than if the array is null. If the two sides of the comaprision are switched this is fixed. Therefore, $null should always be on the left side of equality comparisons just in case. + + + $null should be on the left side of equality comparisons. + + + Null Comparison + + + Checks that cmdlets and parameters have more than one character. + + + The cmdlet name '{0}' only has one character. + + + The cmdlet '{0}' has a parameter '{1}' that only has one character. + + + A script block has a parameter '{0}' that only has one character. + + + One Char + + + For PowerShell 4.0 and earlier, a parameter named Credential with type PSCredential must have a credential transformation attribute defined after the PSCredential type attribute. + + + The Credential parameter in '{0}' must be of type PSCredential. For PowerShell 4.0 and earlier, please define a credential transformation attribute, e.g. [System.Management.Automation.Credential()], after the PSCredential type attribute. + + + The Credential parameter found in the script block must be of type PSCredential. For PowerShell 4.0 and earlier please define a credential transformation attribute, e.g. [System.Management.Automation.Credential()], after the PSCredential type attribute. + + + Use PSCredential type. + + + Checks for reserved characters in cmdlet names. These characters usually cause a parsing error. Otherwise they will generally cause runtime errors. + + + The cmdlet '{0}' uses a reserved char in its name. + + + Reserved Cmdlet Chars + + + The cmdlet '{0}' + + + Checks for reserved parameters in function definitions. If these parameters are defined by the user, an error generally occurs. + + + '{0}' defines the reserved common parameter '{1}'. + + + Reserved Parameters + + + The script + + + #,(){}[]&/\\$^;:\"'<>|?@`*%+=~ + + + Checks that if the SupportsShouldProcess is present, the function calls ShouldProcess/ShouldContinue and vice versa. Scripts with one or the other but not both will generally run into an error or unexpected behavior. + + + '{0}' has the ShouldProcess attribute but does not call ShouldProcess/ShouldContinue. + + + A script block has the ShouldProcess attribute but does not call ShouldProcess/ShouldContinue. + + + '{0}' calls ShouldProcess/ShouldContinue but does not have the ShouldProcess attribute. + + + A script block calls ShouldProcess/ShouldContinue but does not have the ShouldProcess attribute. + + + Should Process + + + PS + + + It is a best practice to emit informative, verbose messages in DSC resource functions. This helps in debugging issues when a DSC configuration is executed. + + + There is no call to Write-Verbose in DSC function '{0}'. If you are using Write-Verbose in a helper function, suppress this rule application. + + + Use verbose message in DSC resource + + + Some fields of the module manifest (such as ModuleVersion) are required. + + + Module Manifest Fields + + + If a script file is in a PowerShell module folder, then that folder must be loadable. + + + Cannot load the module '{0}' that file '{1}' is in. + + + Module Must Be Loadable + + + Error Message is Null. + + + Password parameters that take in plaintext will expose passwords and compromise the security of your system. + + + Parameter '{0}' should use SecureString, otherwise this will expose sensitive information. See ConvertTo-SecureString for more information. + + + Avoid Using Plain Text For Password Parameter + + + Using ConvertTo-SecureString with plain text will expose secure information. + + + File '{0}' uses ConvertTo-SecureString with plaintext. This will expose secure information. Encrypted standard strings should be used instead. + + + Avoid Using SecureString With Plain Text + + + Switch parameter should not default to true. + + + File '{0}' has a switch parameter default to true. + + + Switch Parameters Should Not Default To True + + + Functions that use ShouldContinue should have a boolean force parameter to allow user to bypass it. + + + Function '{0}' in file '{1}' uses ShouldContinue but does not have a boolean force parameter. The force parameter will allow users of the script to bypass ShouldContinue prompt + + + Avoid Using ShouldContinue Without Boolean Force Parameter + + + Using Clear-Host is not recommended because the cmdlet may not work in some hosts or there may even be no hosts at all. + + + File '{0}' uses Clear-Host. This is not recommended because it may not work in some hosts or there may even be no hosts at all. + + + Avoid Using Clear-Host + + + File '{0}' uses Console.'{1}'. Using Console to write is not recommended because it may not work in all hosts or there may even be no hosts at all. Use Write-Output instead. + + + Avoid using the Write-Host cmdlet. Instead, use Write-Output, Write-Verbose, or Write-Information. Because Write-Host is host-specific, its implementation might vary unpredictably. Also, prior to PowerShell 5.0, Write-Host did not write to a stream, so users cannot suppress it, capture its value, or redirect it. + + + File '{0}' uses Write-Host. Avoid using Write-Host because it might not work in all hosts, does not work when there is no host, and (prior to PS 5.0) cannot be suppressed, captured, or redirected. Instead, use Write-Output, Write-Verbose, or Write-Information. + + + Avoid Using Write-Host + + + Cmdlet should use singular instead of plural nouns. + + + The cmdlet '{0}' uses a plural noun. A singular noun should be used instead. + + + Cmdlet Singular Noun + + + AvoidUsingCmdletAliases + + + AvoidDefaultValueSwitchParameter + + + AvoidGlobalVars + + + AvoidShouldContinueWithoutForce + + + AvoidUnloadableModule + + + AvoidUsingClearHost + + + AvoidUsingConvertToSecureStringWithPlainText + + + AvoidUsingEmptyCatchBlock + + + AvoidUsingInvokeExpression + + + AvoidUsingPlainTextForPassword + + + AvoidUsingPositionalParameters + + + AvoidUsingWriteHost + + + OneChar + + + PossibleIncorrectComparisonWithNull + + + ProvideCommentHelp + + + ReservedCmdletChar + + + ReservedParams + + + ShouldProcess + + + UseApprovedVerbs + + + UseDeclaredVarsMoreThanAssignments + + + UsePSCredentialType + + + UseSingularNouns + + + MissingModuleManifestField + + + UseVerboseMessageInDSCResource + + + Command Not Found + + + Commands that are undefined or do not exist should not be used. + + + Command '{0}' Is Not Found + + + CommandNotFound + + + Type Not Found + + + Undefined type should not be used + + + Type '{0}' is not found. Please check that it is defined. + + + TypeNotFound + + + Use Cmdlet Correctly + + + Cmdlet should be called with the mandatory parameters. + + + Cmdlet '{0}' may be used incorrectly. Please check that all mandatory parameters are supplied. + + + UseCmdletCorrectly + + + Use Type At Variable Assignment + + + Types should be specified at variable assignments to maintain readability and maintainability of script. + + + Specify type at the assignment of variable '{0}' + + + UseTypeAtVariableAssignment + + + Avoid Using Username and Password Parameters + + + Functions should take in a Credential parameter of type PSCredential (with a Credential transformation attribute defined after it in PowerShell 4.0 or earlier) or set the Password parameter to type SecureString. + + + Function '{0}' has both Username and Password parameters. Either set the type of the Password parameter to SecureString or replace the Username and Password parameters with a Credential parameter of type PSCredential. If using a Credential parameter in PowerShell 4.0 or earlier, please define a credential transformation attribute after the PSCredential type attribute. + + + AvoidUsingUsernameAndPasswordParams + + + Avoid Invoking Empty Members + + + Invoking non-constant members would cause potential bugs. Please double check the syntax to make sure members invoked are non-constant. + + + '{0}' has non-constant members. Invoking non-constant members may cause bugs in the script. + + + AvoidInvokingEmptyMembers + + + Avoid Using ComputerName Hardcoded + + + The ComputerName parameter of a cmdlet should not be hardcoded as this will expose sensitive information about the system. + + + The ComputerName parameter of cmdlet '{0}' is hardcoded. This will expose sensitive information about the system if the script is shared. + + + AvoidUsingComputerNameHardcoded + + + Empty catch block is used. Please use Write-Error or throw statements in catch blocks. + + + '{0}' is an alias of '{1}'. Alias can introduce possible problems and make scripts hard to maintain. Please consider changing alias to its full content. + + + Invoke-Expression is used. Please remove Invoke-Expression from script and find other options instead. + + + Cmdlet '{0}' has positional parameter. Please use named parameters instead of positional parameters when calling a command. + + + {0}{1} + + + Cannot process null Ast + + + Cannot process null CommandInfo + + + PSDSC + + + Use Standard Get/Set/Test TargetResource functions in DSC Resource + + + DSC Resource must implement Get, Set and Test-TargetResource functions. DSC Class must implement Get, Set and Test functions. + + + Missing '{0}' function. DSC Resource must implement Get, Set and Test-TargetResource functions. + + + StandardDSCFunctionsInResource + + + Avoid Using Internal URLs + + + Using Internal URLs in the scripts may cause security problems. + + + '{0}' could be an internal URL. Using internal URL directly in the script may cause potential information disclosure. + + + AvoidUsingInternalURLs + + + www.sharepoint.com + + + Use Identical Parameters For DSC Test and Set Functions + + + The Test and Set-TargetResource functions of DSC Resource must have the same parameters. + + + The Test and Set-TargetResource functions of DSC Resource must have the same parameters. + + + UseIdenticalParametersForDSC + + + Missing '{0}' function. DSC Class must implement Get, Set and Test functions. + + + Use identical mandatory parameters for DSC Get/Test/Set TargetResource functions + + + The Get/Test/Set TargetResource functions of DSC resource must have the same mandatory parameters. + + + The '{0}' parameter '{1}' is not present in '{2}' DSC resource function(s). + + + UseIdenticalMandatoryParametersForDSC + + + Not all code path in {0} function in DSC Class {1} returns a value + + + ReturnCorrectTypesForDSCFunctions + + + Return Correct Types For DSC Functions + + + Set function in DSC class and Set-TargetResource in DSC resource must not return anything. Get function in DSC class must return an instance of the DSC class and Get-TargetResource function in DSC resource must return a hashtable. Test function in DSC class and Get-TargetResource function in DSC resource must return a boolean. + + + {0} function in DSC Class {1} should return object of type {2} + + + {0} function in DSC Class {1} should return object of type {2} instead of type {3} + + + Set function in DSC Class {0} should not return anything + + + {0} function in DSC Resource should return object of type {1} instead of {2} + + + Set-TargetResource function in DSC Resource should not output anything to the pipeline. + + + Use ShouldProcess For State Changing Functions + + + Functions that have verbs like New, Start, Stop, Set, Reset, Restart that change system state should support 'ShouldProcess'. + + + Function '{0}' has verb that could change system state. Therefore, the function has to support 'ShouldProcess'. + + + UseShouldProcessForStateChangingFunctions + + + Avoid Using Get-WMIObject, Remove-WMIObject, Invoke-WmiMethod, Register-WmiEvent, Set-WmiInstance + + + Deprecated. Starting in Windows PowerShell 3.0, these cmdlets have been superseded by CIM cmdlets. + + + File '{0}' uses WMI cmdlet. For PowerShell 3.0 and above, use CIM cmdlet which perform the same tasks as the WMI cmdlets. The CIM cmdlets comply with WS-Management (WSMan) standards and with the Common Information Model (CIM) standard, which enables the cmdlets to use the same techniques to manage Windows computers and those running other operating systems. + + + AvoidUsingWMICmdlet + + + Use OutputType Correctly + + + The return types of a cmdlet should be declared using the OutputType attribute. + + + The cmdlet '{0}' returns an object of type '{1}' but this type is not declared in the OutputType attribute. + + + UseOutputTypeCorrectly + + + DscTestsPresent + + + Dsc tests are present + + + Every DSC resource module should contain folder "Tests" with tests for every resource. Test scripts should have resource name they are testing in the file name. + + + No tests found for resource '{0}' + + + DscExamplesPresent + + + DSC examples are present + + + Every DSC resource module should contain folder "Examples" with sample configurations for every resource. Sample configurations should have resource name they are demonstrating in the title. + + + No examples found for resource '{0}' + + + Avoid Default Value For Mandatory Parameter + + + Mandatory parameter should not be initialized with a default value in the param block because this value will be ignored.. To fix a violation of this rule, please avoid initializing a value for the mandatory parameter in the param block. + + + Mandatory Parameter '{0}' is initialized in the Param block. To fix a violation of this rule, please leave it uninitialized. + + + AvoidDefaultValueForMandatoryParameter + + + Avoid Using Deprecated Manifest Fields + + + "ModuleToProcess" is obsolete in the latest PowerShell version. Please update with the latest field "RootModule" in manifest files to avoid PowerShell version inconsistency. + + + AvoidUsingDeprecatedManifestFields + + + Use UTF8 Encoding For Help File + + + PowerShell help file needs to use UTF8 Encoding. + + + File {0} has to use UTF8 instead of {1} encoding because it is a powershell help file. + + + UseUTF8EncodingForHelpFile + + + Use BOM encoding for non-ASCII files + + + For a file encoded with a format other than ASCII, ensure BOM is present to ensure that any application consuming this file can interpret it correctly. + + + Missing BOM encoding for non-ASCII encoded file '{0}' + + + UseBOMForUnicodeEncodedFile + + + Script definition has a switch parameter default to true. + + + Function '{0}' in script definition uses ShouldContinue but does not have a boolean force parameter. The force parameter will allow users of the script to bypass ShouldContinue prompt + + + Script definition uses ConvertTo-SecureString with plaintext. This will expose secure information. Encrypted standard strings should be used instead. + + + Script definition uses WMI cmdlet. For PowerShell 3.0 and above, use CIM cmdlet which perform the same tasks as the WMI cmdlets. The CIM cmdlets comply with WS-Management (WSMan) standards and with the Common Information Model (CIM) standard, which enables the cmdlets to use the same techniques to manage Windows computers and those running other operating systems. + + + Script definition uses Write-Host. Avoid using Write-Host because it might not work in all hosts, does not work when there is no host, and (prior to PS 5.0) cannot be suppressed, captured, or redirected. Instead, use Write-Output, Write-Verbose, or Write-Information. + + + ScriptDefinition + + + Misleading Backtick + + + Ending a line with an escaped whitepsace character is misleading. A trailing backtick is usually used for line continuation. Users typically don't intend to end a line with escaped whitespace. + + + MisleadingBacktick + + + This line has a backtick at the end trailed by a whitespace character. Did you mean for this to be a line continuation? + + + Avoid using null or empty HelpMessage parameter attribute. + + + Setting the HelpMessage attribute to an empty string or null value causes PowerShell interpreter to throw an error while executing the corresponding function. + + + HelpMessage parameter attribute should not be null or empty. To fix a violation of this rule, please set its value to a non-empty string. + + + AvoidNullOrEmptyHelpMessageAttribute + + + Use the *ToExport module manifest fields. + + + In a module manifest, AliasesToExport, CmdletsToExport, FunctionsToExport and VariablesToExport fields should not use wildcards or $null in their entries. During module auto-discovery, if any of these entries are missing or $null or wildcard, PowerShell does some potentially expensive work to analyze the rest of the module. + + + Do not use wildcard or $null in this field. Explicitly specify a list for {0}. + + + UseToExportFieldsInManifest + + + Replace {0} with {1} + + + Set {0} type to SecureString + + + Add {0} = {1} to the module manifest + + + Replace {0} with {1} + + + Create hashtables with literal initializers + + + Use literal initializer, @{{}}, for creating a hashtable as they are case-insensitive by default + + + Create hashtables with literal initliazers + + + UseLiteralInitializerForHashtable + + + UseCompatibleCmdlets + + + Use compatible cmdlets + + + Use cmdlets compatible with the given PowerShell version and edition and operating system + + + '{0}' is not compatible with PowerShell edition '{1}', version '{2}' and OS '{3}' + + + AvoidOverwritingBuiltInCmdlets + + + Avoid overwriting built in cmdlets + + + Do not overwrite the definition of a cmdlet that is included with PowerShell + + + '{0}' is a cmdlet that is included with PowerShell (version {1}) whose definition should not be overridden + + + UseCompatibleCommands + + + Use compatible commands + + + Use commands compatible with the given PowerShell version and operating system + + + The command '{0}' is not available by default in PowerShell version '{1}' on platform '{2}' + + + The parameter '{0}' is not available for command '{1}' by default in PowerShell version '{2}' on platform '{3}' + + + UseCompatibleTypes + + + Use compatible types + + + Use types compatible with the given PowerShell version and operating system + + + The type '{0}' is not available by default in PowerShell version '{1}' on platform '{2}' + + + The method '{0}' is not available on type '{1}' by default in PowerShell version '{2}' on platform '{3}' + + + The member '{0}' is not available on type '{1}' by default in PowerShell version '{2}' on platform '{3}' + + + UseCompatibleSyntax + + + Use compatible syntax + + + Use script syntax compatible with the given PowerShell versions + + + The {0} syntax '{1}' is not available by default in PowerShell versions {2} + + + Use the '{0}' syntax instead for compatibility with PowerShell versions {1} + + + The type accelerator '{0}' is not available by default in PowerShell version '{1}' on platform '{2}' + + + Avoid global functiosn and aliases + + + Checks that global functions and aliases are not used. Global functions are strongly discouraged as they can cause errors across different systems. + + + Avoid creating functions with a Global scope. + + + AvoidGlobalFunctions + + + Avoid global aliases. + + + Checks that global aliases are not used. Global aliases are strongly discouraged as they overwrite desired aliases with name conflicts. + + + Avoid creating aliases with a Global scope. + + + AvoidGlobalAliases + + + AvoidTrailingWhitespace + + + Avoid trailing whitespace + + + Each line should have no trailing whitespace. + + + Line has trailing whitespace + + + AvoidLongLines + + + Avoid long lines + + + Line lengths should be less than the configured maximum + + + Line exceeds the configured maximum length of {0} characters + + + PlaceOpenBrace + + + Place open braces consistently + + + Place open braces either on the same line as the preceding expression or on a new line. + + + Open brace not on same line as preceding keyword. It should be on the same line. + + + Open brace is not on a new line. + + + There is no new line after open brace. + + + PlaceCloseBrace + + + Place close braces + + + Close brace should be on a new line by itself. + + + Close brace is not on a new line. + + + Close brace does not follow a non-empty line. + + + Close brace does not follow a new line. + + + Close brace before a branch statement is followed by a new line. + + + UseConsistentIndentation + + + Use consistent indentation + + + Each statement block should have a consistent indenation. + + + Indentation not consistent + + + UseConsistentWhitespace + + + Use whitespaces + + + Check for whitespace between keyword and open paren/curly, around assigment operator ('='), around arithmetic operators and after separators (',' and ';') + + + Use space before open brace. + + + Use space before open parenthesis. + + + Use space before and after binary and assignment operators. + + + Use space after a comma. + + + Use space after a semicolon. + + + UseSupportsShouldProcess + + + Use SupportsShouldProcess + + + Commands typically provide Confirm and Whatif parameters to give more control on its execution in an interactive environment. In PowerShell, a command can use a SupportsShouldProcess attribute to provide this capability. Hence, manual addition of these parameters to a command is discouraged. If a commands need Confirm and Whatif parameters, then it should support ShouldProcess. + + + Whatif and/or Confirm manually defined in function {0}. Instead, please use SupportsShouldProcess attribute. + + + AlignAssignmentStatement + + + Align assignment statement + + + Line up assignment statements such that the assignment operator are aligned. + + + Assignment statements are not aligned + + + '=' is not an assignment operator. Did you mean the equality operator '-eq'? + + + PossibleIncorrectUsageOfAssignmentOperator + + + Use a different variable name + + + Changing automtic variables might have undesired side effects + + + This automatic variables is built into PowerShell and readonly. + + + The Variable '{0}' cannot be assigned since it is a readonly automatic variable that is built into PowerShell, please use a different name. + + + AvoidAssignmentToAutomaticVariable + + + Starting from PowerShell 6.0, the Variable '{0}' cannot be assigned any more since it is a readonly automatic variable that is built into PowerShell, please use a different name. + + + '{0}' is implicitly aliasing '{1}' because it is missing the 'Get-' prefix. This can introduce possible problems and make scripts hard to maintain. Please consider changing command to its full name. + + + '=' or '==' are not comparison operators in the PowerShell language and rarely needed inside conditional statements. + + + Did you mean to use the assignment operator '='? The equality operator in PowerShell is 'eq'. + + + '>' is not a comparison operator. Use '-gt' (greater than) or '-ge' (greater or equal). + + + When switching between different languages it is easy to forget that '>' does not mean 'great than' in PowerShell. + + + Did you mean to use the redirection operator '>'? The comparison operators in PowerShell are '-gt' (greater than) or '-ge' (greater or equal). + + + PossibleIncorrectUsageOfRedirectionOperator + + + Use $null on the left hand side for safe comparison with $null. + + + Use space after open brace. + + + Use space before closing brace. + + + Use space after pipe. + + + Use space before pipe. + + + Use exact casing of cmdlet/function/parameter name. + + + For better readability and consistency, use the exact casing of the cmdlet/function/parameter. + + + Cmdlet/Function/Parameter does not match its exact casing '{0}'. + + + UseCorrectCasing + + + Use process block for command that accepts input from pipeline. + + + If a command parameter takes its value from the pipeline, the command must use a process block to bind the input objects from the pipeline to that parameter. + + + Command accepts pipeline input but has not defined a process block. + + + UseProcessBlockForPipelineCommand + + + The Variable '{0}' is an automatic variable that is built into PowerShell, assigning to it might have undesired side effects. If assignment is not by design, please use a different name. + + + Use only 1 whitespace between parameter names or values. + + + ReviewUnusedParameter + + + Ensure all parameters are used within the same script, scriptblock, or function where they are declared. + + + The parameter '{0}' has been declared but not used. + + + ReviewUnusedParameter + + \ No newline at end of file diff --git a/ScriptAnalyzer2/Builtin/Rules/UseDeclaredVarsMoreThanAssignments.cs b/ScriptAnalyzer2/Builtin/Rules/UseDeclaredVarsMoreThanAssignments.cs new file mode 100644 index 000000000..b200e4594 --- /dev/null +++ b/ScriptAnalyzer2/Builtin/Rules/UseDeclaredVarsMoreThanAssignments.cs @@ -0,0 +1,224 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Management.Automation.Language; +using System.Globalization; +using System.Linq; +using Microsoft.PowerShell.ScriptAnalyzer.Rules; +using Microsoft.PowerShell.ScriptAnalyzer; +using Microsoft.PowerShell.ScriptAnalyzer.Builtin.Rules; +using Microsoft.PowerShell.ScriptAnalyzer.Tools; + +namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules +{ + /// + /// UseDeclaredVarsMoreThanAssignments: Analyzes the ast to check that variables are used in more than just their assignment. + /// + [IdempotentRule] + [ThreadsafeRule] + [RuleDescription(typeof(Strings), nameof(Strings.UseDeclaredVarsMoreThanAssignmentsDescription))] + [Rule("UseDeclaredVarsMoreThanAssignments")] + public class UseDeclaredVarsMoreThanAssignments : ScriptRule + { + public UseDeclaredVarsMoreThanAssignments(RuleInfo ruleInfo) : base(ruleInfo) + { + } + + /// + /// AnalyzeScript: Analyzes the ast to check that variables are used in more than just there assignment. + /// + /// The script's ast + /// The script's file name + /// A List of results from this rule + public override IEnumerable AnalyzeScript(Ast ast, IReadOnlyList tokens, string fileName) + { + var scriptBlockAsts = ast.FindAll(x => x is ScriptBlockAst, true); + + if (scriptBlockAsts == null) + { + yield break; + } + + foreach (var scriptBlockAst in scriptBlockAsts) + { + var sbAst = scriptBlockAst as ScriptBlockAst; + foreach (var diagnosticRecord in AnalyzeScriptBlockAst(sbAst, fileName)) + { + yield return diagnosticRecord; + } + } + } + + /// + /// Checks if a variable is initialized and referenced in either its assignment or children scopes + /// + /// Ast of type ScriptBlock + /// Name of file containing the ast + /// An enumerable containing diagnostic records + private IEnumerable AnalyzeScriptBlockAst(ScriptBlockAst scriptBlockAst, string fileName) + { + IEnumerable assignmentAsts = scriptBlockAst.FindAll(testAst => testAst is AssignmentStatementAst, false); + IEnumerable varAsts = scriptBlockAst.FindAll(testAst => testAst is VariableExpressionAst, true); + IEnumerable varsInAssignment; + + Dictionary assignmentsDictionary_OrdinalIgnoreCase = new Dictionary(StringComparer.OrdinalIgnoreCase); + + string varKey; + bool inAssignment; + + if (assignmentAsts == null) + { + yield break; + } + + foreach (AssignmentStatementAst assignmentAst in assignmentAsts) + { + // Only checks for the case where lhs is a variable. Ignore things like $foo.property + VariableExpressionAst assignmentVarAst = assignmentAst.Left as VariableExpressionAst; + + if (assignmentVarAst == null) + { + // If the variable is declared in a strongly typed way, e.g. [string]$s = 'foo' then the type is ConvertExpressionAst. + // Therefore we need to the VariableExpressionAst from its Child property. + var assignmentVarAstAsConvertExpressionAst = assignmentAst.Left as ConvertExpressionAst; + if (assignmentVarAstAsConvertExpressionAst != null && assignmentVarAstAsConvertExpressionAst.Child != null) + { + assignmentVarAst = assignmentVarAstAsConvertExpressionAst.Child as VariableExpressionAst; + } + } + + if (assignmentVarAst != null) + { + // Ignore if variable is global or environment variable or scope is drive qualified variable + if (!assignmentVarAst.VariablePath.IsScript + && !assignmentVarAst.VariablePath.IsGlobal + && assignmentVarAst.VariablePath.DriveName == null) + { + string variableName = assignmentVarAst.GetNameWithoutScope(); + + if (!assignmentsDictionary_OrdinalIgnoreCase.ContainsKey(variableName)) + { + assignmentsDictionary_OrdinalIgnoreCase.Add(variableName, assignmentAst); + } + } + } + } + + if (varAsts != null) + { + foreach (VariableExpressionAst varAst in varAsts) + { + varKey = varAst.GetNameWithoutScope(); + inAssignment = false; + + if (assignmentsDictionary_OrdinalIgnoreCase.ContainsKey(varKey)) + { + varsInAssignment = assignmentsDictionary_OrdinalIgnoreCase[varKey].Left.FindAll(testAst => testAst is VariableExpressionAst, true); + + // Checks if this variableAst is part of the logged assignment + foreach (VariableExpressionAst varInAssignment in varsInAssignment) + { + // Try casting to AssignmentStatementAst to be able to catch case where a variable is assigned more than once (https://github.com/PowerShell/PSScriptAnalyzer/issues/833) + var varInAssignmentAsStatementAst = varInAssignment.Parent as AssignmentStatementAst; + var varAstAsAssignmentStatementAst = varAst.Parent as AssignmentStatementAst; + if (varAstAsAssignmentStatementAst != null) + { + if (varAstAsAssignmentStatementAst.Operator == TokenKind.Equals) + { + if (varInAssignmentAsStatementAst != null) + { + inAssignment = varInAssignmentAsStatementAst.Left.Extent.Text.Equals(varAstAsAssignmentStatementAst.Left.Extent.Text, StringComparison.OrdinalIgnoreCase); + } + else + { + inAssignment = varInAssignment.Equals(varAst); + } + } + } + else + { + inAssignment = varInAssignment.Equals(varAst); + } + } + + if (!inAssignment) + { + assignmentsDictionary_OrdinalIgnoreCase.Remove(varKey); + } + + // Check if variable belongs to PowerShell built-in variables + if (SpecialVariables.IsSpecialVariable(varKey)) + { + assignmentsDictionary_OrdinalIgnoreCase.Remove(varKey); + } + } + } + } + + AnalyzeGetVariableCommands(scriptBlockAst, assignmentsDictionary_OrdinalIgnoreCase); + + foreach (string key in assignmentsDictionary_OrdinalIgnoreCase.Keys) + { + yield return CreateDiagnostic( + string.Format(CultureInfo.CurrentCulture, Strings.UseDeclaredVarsMoreThanAssignmentsError, key), + assignmentsDictionary_OrdinalIgnoreCase[key].Left); + } + } + + /// + /// Detects variables retrieved by usage of Get-Variable and remove those + /// variables from the entries in . + /// + /// + /// + private void AnalyzeGetVariableCommands( + ScriptBlockAst scriptBlockAst, + Dictionary assignmentsDictionary_OrdinalIgnoreCase) + { + throw new NotImplementedException(); + + /* + IReadOnlyList getVariableCmdletNamesAndAliases = null; //Helper.Instance.CmdletNameAndAliases("Get-Variable"); + IEnumerable getVariableCommandAsts = scriptBlockAst.FindAll(testAst => testAst is CommandAst commandAst && + getVariableCmdletNamesAndAliases.Contains(commandAst.GetCommandName(), StringComparer.OrdinalIgnoreCase), true); + + foreach (CommandAst getVariableCommandAst in getVariableCommandAsts) + { + var commandElements = getVariableCommandAst.CommandElements.ToList(); + // The following extracts the variable name(s) only in the simplest possible usage of Get-Variable. + // Usage of a named parameter and an array of variables is accounted for though. + if (commandElements.Count < 2 || commandElements.Count > 3) { continue; } + + var commandElementAstOfVariableName = commandElements[commandElements.Count - 1]; + if (commandElements.Count == 3) + { + if (!(commandElements[1] is CommandParameterAst commandParameterAst)) { continue; } + // Check if the named parameter -Name is used (PowerShell does not need the full + // parameter name and there is no other parameter of Get-Variable starting with n). + if (!commandParameterAst.ParameterName.StartsWith("n", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + } + + if (commandElementAstOfVariableName is StringConstantExpressionAst constantExpressionAst) + { + assignmentsDictionary_OrdinalIgnoreCase.Remove(constantExpressionAst.Value); + continue; + } + + if (!(commandElementAstOfVariableName is ArrayLiteralAst arrayLiteralAst)) { continue; } + foreach (var expressionAst in arrayLiteralAst.Elements) + { + if (expressionAst is StringConstantExpressionAst constantExpressionAstOfArray) + { + assignmentsDictionary_OrdinalIgnoreCase.Remove(constantExpressionAstOfArray.Value); + } + } + } + */ + } + } +} diff --git a/ScriptAnalyzer2/Builtin/Rules/UseShouldProcessForStateChangingFunctions.cs b/ScriptAnalyzer2/Builtin/Rules/UseShouldProcessForStateChangingFunctions.cs new file mode 100644 index 000000000..946ab696a --- /dev/null +++ b/ScriptAnalyzer2/Builtin/Rules/UseShouldProcessForStateChangingFunctions.cs @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Management.Automation.Language; +using System.Globalization; +using Microsoft.PowerShell.ScriptAnalyzer.Rules; +using Microsoft.PowerShell.ScriptAnalyzer.Tools; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Builtin.Rules +{ + /// + /// UseShouldProcessForStateChangingFunctions: Analyzes the ast to check if ShouldProcess is included in Advanced functions if the Verb of the function could change system state. + /// + [RuleDescription(typeof(Strings), nameof(Strings.UseShouldProcessForStateChangingFunctionsDescrption))] + [Rule("UseShouldProcessForStateChangingFunctions")] + public class UseShouldProcessForStateChangingFunctions : ScriptRule + { + private static readonly IReadOnlyList s_stateChangingVerbs = new List + { + { "New-" }, + { "Set-" }, + { "Remove-" }, + { "Start-" }, + { "Stop-" }, + { "Restart-" }, + { "Reset-" }, + { "Update-" } + }; + + public UseShouldProcessForStateChangingFunctions(RuleInfo ruleInfo) + : base(ruleInfo) + { + } + + /// + /// AnalyzeScript: Analyzes the ast to check if ShouldProcess is included in Advanced functions if the Verb of the function could change system state. + /// + /// The script's ast + /// The script's file name + /// A List of diagnostic results of this rule + public override IEnumerable AnalyzeScript(Ast ast, IReadOnlyList tokens, string fileName) + { + IEnumerable funcDefWithNoShouldProcessAttrAsts = ast.FindAll(IsStateChangingFunctionWithNoShouldProcessAttribute, true); + + foreach (FunctionDefinitionAst funcDefAst in funcDefWithNoShouldProcessAttrAsts) + { + yield return new ScriptDiagnostic( + RuleInfo, + string.Format(CultureInfo.CurrentCulture, Strings.UseShouldProcessForStateChangingFunctionsError, funcDefAst.Name), + funcDefAst.GetFunctionNameExtent(tokens), + DiagnosticSeverity.Warning); + } + + } + /// + /// Checks if the ast defines a state changing function + /// + /// + /// Returns true or false + private bool IsStateChangingFunctionWithNoShouldProcessAttribute(Ast ast) + { + var funcDefAst = ast as FunctionDefinitionAst; + // SupportsShouldProcess is not supported in workflows + if (funcDefAst == null || funcDefAst.IsWorkflow) + { + return false; + } + + return IsStateChangingFunctionName(funcDefAst.Name) + && (funcDefAst.Body.ParamBlock == null + || funcDefAst.Body.ParamBlock.Attributes == null + || !HasShouldProcessTrue(funcDefAst.Body.ParamBlock.Attributes)); + } + + /// + /// Checks if an attribute has SupportShouldProcess set to $true + /// + /// + /// Returns true or false + private bool HasShouldProcessTrue(IEnumerable attributeAsts) + { + return AstTools.TryGetShouldProcessAttributeArgumentAst(attributeAsts, out NamedAttributeArgumentAst shouldProcessArgument) + && object.Equals(shouldProcessArgument.GetValue(), true); + } + + private static bool IsStateChangingFunctionName(string functionName) + { + foreach (string verb in s_stateChangingVerbs) + { + if (functionName.StartsWith(verb, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } + } +} + + + + diff --git a/ScriptAnalyzer2/Commands/InvokeScriptAnalyzerCommand.cs b/ScriptAnalyzer2/Commands/InvokeScriptAnalyzerCommand.cs new file mode 100644 index 000000000..08ab34176 --- /dev/null +++ b/ScriptAnalyzer2/Commands/InvokeScriptAnalyzerCommand.cs @@ -0,0 +1,106 @@ +using Microsoft.PowerShell.ScriptAnalyzer.Builder; +using Microsoft.PowerShell.ScriptAnalyzer.Configuration; +using System; +using System.Collections.Concurrent; +using System.Management.Automation; + +#if !CORECLR +using Microsoft.PowerShell.ScriptAnalyzer.Internal; +#endif + +namespace Microsoft.PowerShell.ScriptAnalyzer.Commands +{ + [Cmdlet(VerbsLifecycle.Invoke, "ScriptAnalyzer2")] + public class InvokeScriptAnalyzerCommand : Cmdlet + { + private static readonly ConcurrentDictionary s_configuredScriptAnalyzers = new ConcurrentDictionary(); + + private ScriptAnalyzer _scriptAnalyzer; + + [ValidateNotNullOrEmpty] + [Parameter(Position = 0, Mandatory = true, ParameterSetName = "FilePath")] + public string[] Path { get; set; } + + [ValidateNotNullOrEmpty] + [Parameter(Position = 0, Mandatory = true, ParameterSetName = "Input")] + public string[] ScriptDefinition { get; set; } + + [Parameter] + public string ConfigurationPath { get; set; } + + [Parameter] + public string[] ExcludeRules { get; set; } + + protected override void BeginProcessing() + { + _scriptAnalyzer = GetScriptAnalyzer(); + } + + protected override void ProcessRecord() + { + if (Path != null) + { + foreach (string path in Path) + { + foreach (ScriptDiagnostic diagnostic in _scriptAnalyzer.AnalyzeScriptPath(path)) + { + WriteObject(diagnostic); + } + } + + return; + } + + if (ScriptDefinition != null) + { + foreach (string input in ScriptDefinition) + { + foreach (ScriptDiagnostic diagnostic in _scriptAnalyzer.AnalyzeScriptInput(input)) + { + WriteObject(diagnostic); + } + } + } + } + + private ScriptAnalyzer GetScriptAnalyzer() + { + var parameters = new ParameterSetting(this); + return s_configuredScriptAnalyzers.GetOrAdd(parameters, CreateScriptAnalyzerWithParameters); + } + + private ScriptAnalyzer CreateScriptAnalyzerWithParameters(ParameterSetting parameters) + { + var configBuilder = new ScriptAnalyzerConfigurationBuilder() + .WithBuiltinRuleSet(BuiltinRulePreference.Default); + + if (parameters.ConfigurationPath != null) + { + configBuilder.AddConfigurationFile(parameters.ConfigurationPath); + } + + return configBuilder.Build().CreateScriptAnalyzer(); + } + + private struct ParameterSetting + { + public ParameterSetting(InvokeScriptAnalyzerCommand command) + { + ConfigurationPath = command.ConfigurationPath; + } + + public string ConfigurationPath { get; } + + public override int GetHashCode() + { +#if CORECLR + return HashCode.Combine(ConfigurationPath); +#else + return HashCodeCombinator.Create() + .Add(ConfigurationPath) + .GetHashCode(); +#endif + } + } + } +} diff --git a/ScriptAnalyzer2/Configuration/BuiltinRuleMode.cs b/ScriptAnalyzer2/Configuration/BuiltinRuleMode.cs new file mode 100644 index 000000000..69dba5e81 --- /dev/null +++ b/ScriptAnalyzer2/Configuration/BuiltinRuleMode.cs @@ -0,0 +1,20 @@ + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System.Runtime.Serialization; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Configuration +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum BuiltinRulePreference + { + [EnumMember(Value = "none")] + None = 0, + + [EnumMember(Value = "default")] + Default, + + [EnumMember(Value = "comprehensive")] + Aggressive, + } +} diff --git a/ScriptAnalyzer2/Configuration/ConfigurationKeys.cs b/ScriptAnalyzer2/Configuration/ConfigurationKeys.cs new file mode 100644 index 000000000..c39d2efa9 --- /dev/null +++ b/ScriptAnalyzer2/Configuration/ConfigurationKeys.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Configuration +{ + internal static class ConfigurationKeys + { + public const string BuiltinRulePreference = "BuiltinRulePreference"; + + public const string RuleExecutionMode = "RuleExecutionMode"; + + public const string RulePaths = "RulePaths"; + + public const string RuleConfigurations = "Rules"; + + public const string CommonConfiguration = "Common"; + } +} diff --git a/ScriptAnalyzer2/Configuration/IRuleConfiguration.cs b/ScriptAnalyzer2/Configuration/IRuleConfiguration.cs new file mode 100644 index 000000000..3e8346566 --- /dev/null +++ b/ScriptAnalyzer2/Configuration/IRuleConfiguration.cs @@ -0,0 +1,88 @@ +using Microsoft.PowerShell.ScriptAnalyzer.Builtin; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Configuration +{ + public class CommonConfiguration + { + public static CommonConfiguration Default = new CommonConfiguration(enabled: true); + + [JsonConstructor] + public CommonConfiguration(bool enabled) + { + Enabled = enabled; + } + + public bool Enabled { get; } = true; + } + + public interface IRuleConfiguration + { + CommonConfiguration Common { get; } + + IRuleConfiguration AsTypedConfiguration(Type configurationType); + } + + public class RuleConfiguration : IRuleConfiguration + { + public static RuleConfiguration Default { get; } = new RuleConfiguration(CommonConfiguration.Default); + + public RuleConfiguration(CommonConfiguration common) + { + Common = common; + } + + public CommonConfiguration Common { get; } + + public virtual IRuleConfiguration AsTypedConfiguration(Type configurationType) + { + if (configurationType == typeof(RuleConfiguration)) + { + return this; + } + + return null; + } + } + + public abstract class LazyConvertedRuleConfiguration : IRuleConfiguration + { + private readonly TConfiguration _configurationObject; + + private IRuleConfiguration _convertedObject; + + private Type _convertedObjectType; + + protected LazyConvertedRuleConfiguration( + CommonConfiguration commonConfiguration, + TConfiguration configurationObject) + { + _configurationObject = configurationObject; + Common = commonConfiguration; + } + + public CommonConfiguration Common { get; } + + public abstract bool TryConvertObject(Type type, TConfiguration configuration, out IRuleConfiguration result); + + public IRuleConfiguration AsTypedConfiguration(Type configurationType) + { + if (_convertedObject != null + && configurationType.IsAssignableFrom(_convertedObjectType)) + { + return _convertedObject; + } + + if (TryConvertObject(configurationType, _configurationObject, out _convertedObject)) + { + _convertedObjectType = configurationType; + return _convertedObject; + } + + return null; + } + } +} diff --git a/ScriptAnalyzer2/Configuration/IScriptAnalyzerConfiguration.cs b/ScriptAnalyzer2/Configuration/IScriptAnalyzerConfiguration.cs new file mode 100644 index 000000000..3368d364b --- /dev/null +++ b/ScriptAnalyzer2/Configuration/IScriptAnalyzerConfiguration.cs @@ -0,0 +1,32 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Configuration +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum RuleExecutionMode + { + [EnumMember(Value = "default")] + Default = 0, + + [EnumMember(Value = "parallel")] + Parallel = 1, + + [EnumMember(Value = "sequential")] + Sequential = 2, + } + + public interface IScriptAnalyzerConfiguration + { + BuiltinRulePreference? BuiltinRules { get; } + + RuleExecutionMode? RuleExecution { get; } + + IReadOnlyList RulePaths { get; } + + IReadOnlyDictionary RuleConfiguration { get; } + } +} diff --git a/ScriptAnalyzer2/Configuration/Json/JsonConfigurationConverter.cs b/ScriptAnalyzer2/Configuration/Json/JsonConfigurationConverter.cs new file mode 100644 index 000000000..382caac80 --- /dev/null +++ b/ScriptAnalyzer2/Configuration/Json/JsonConfigurationConverter.cs @@ -0,0 +1,48 @@ +using Microsoft.PowerShell.ScriptAnalyzer.Internal; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Configuration.Json +{ + internal class JsonConfigurationConverter : JsonConverter + { + public override bool CanRead => true; + + public override bool CanWrite => false; + + public override JsonScriptAnalyzerConfiguration ReadJson( + JsonReader reader, + Type objectType, + JsonScriptAnalyzerConfiguration existingValue, + bool hasExistingValue, + JsonSerializer serializer) + { + JObject configObject = JObject.Load(reader); + + var ruleConfigurationsObject = (JObject)configObject[ConfigurationKeys.RuleConfigurations]; + var configDictionary = new Dictionary(ruleConfigurationsObject.Count, StringComparer.OrdinalIgnoreCase); + foreach (KeyValuePair configEntry in ruleConfigurationsObject) + { + var ruleConfigObject = (JObject)configEntry.Value; + + var commonConfiguration = ruleConfigObject[ConfigurationKeys.CommonConfiguration]?.ToObject() ?? CommonConfiguration.Default; + + configDictionary[configEntry.Key] = new JsonRuleConfiguration(commonConfiguration, (JObject)configEntry.Value); + } + + return new JsonScriptAnalyzerConfiguration( + configObject[ConfigurationKeys.BuiltinRulePreference]?.ToObject(), + configObject[ConfigurationKeys.RuleExecutionMode]?.ToObject(), + configObject[ConfigurationKeys.RulePaths]?.ToObject() ?? Polyfill.GetEmptyArray(), + configDictionary); + } + + public override void WriteJson(JsonWriter writer, JsonScriptAnalyzerConfiguration value, JsonSerializer serializer) + { + // Not needed - CanWrite is false + throw new NotImplementedException(); + } + } +} diff --git a/ScriptAnalyzer2/Configuration/Json/JsonScriptAnalyzerConfiguration.cs b/ScriptAnalyzer2/Configuration/Json/JsonScriptAnalyzerConfiguration.cs new file mode 100644 index 000000000..9087082f3 --- /dev/null +++ b/ScriptAnalyzer2/Configuration/Json/JsonScriptAnalyzerConfiguration.cs @@ -0,0 +1,79 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Configuration.Json +{ + public class JsonScriptAnalyzerConfiguration : IScriptAnalyzerConfiguration + { + private static readonly JsonConfigurationConverter s_jsonConfigurationConverter = new JsonConfigurationConverter(); + + public static JsonScriptAnalyzerConfiguration FromString(string jsonString) + { + return JsonConvert.DeserializeObject(jsonString, s_jsonConfigurationConverter); + } + + public static JsonScriptAnalyzerConfiguration FromFile(string filePath) + { + var serializer = new JsonSerializer() + { + Converters = { s_jsonConfigurationConverter }, + }; + + using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var streamReader = new StreamReader(fileStream)) + using (var jsonReader = new JsonTextReader(streamReader)) + { + return serializer.Deserialize(jsonReader); + } + } + + private readonly IReadOnlyDictionary _ruleConfigurations; + + public JsonScriptAnalyzerConfiguration( + BuiltinRulePreference? builtinRulePreference, + RuleExecutionMode? ruleExecutionMode, + IReadOnlyList rulePaths, + IReadOnlyDictionary ruleConfigurations) + { + _ruleConfigurations = ruleConfigurations; + RulePaths = rulePaths; + RuleExecution = ruleExecutionMode; + } + + public RuleExecutionMode? RuleExecution { get; } + + public BuiltinRulePreference? BuiltinRules { get; } + + public IReadOnlyList RulePaths { get; } + + public IReadOnlyDictionary RuleConfiguration { get; } + } + + public class JsonRuleConfiguration : LazyConvertedRuleConfiguration + { + public JsonRuleConfiguration( + CommonConfiguration commonConfiguration, + JObject configurationJson) + : base(commonConfiguration, configurationJson) + { + } + + public override bool TryConvertObject(Type type, JObject configuration, out IRuleConfiguration result) + { + try + { + result = (IRuleConfiguration)configuration.ToObject(type); + return true; + } + catch + { + result = null; + return false; + } + } + } +} diff --git a/ScriptAnalyzer2/Configuration/Psd/PsdDataParser.cs b/ScriptAnalyzer2/Configuration/Psd/PsdDataParser.cs new file mode 100644 index 000000000..ae1109630 --- /dev/null +++ b/ScriptAnalyzer2/Configuration/Psd/PsdDataParser.cs @@ -0,0 +1,189 @@ +using Microsoft.PowerShell.ScriptAnalyzer.Utils; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Management.Automation.Language; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Configuration.Psd +{ + internal class PsdDataParser + { + /// + /// Evaluates all statically evaluable, side-effect-free expressions under an + /// expression AST to return a value. + /// Throws if an expression cannot be safely evaluated. + /// Attempts to replicate the GetSafeValue() method on PowerShell AST methods from PSv5. + /// + /// The expression AST to try to evaluate. + /// The .NET value represented by the PowerShell expression. + public object ConvertAstValue(ExpressionAst exprAst) + { + switch (exprAst) + { + case ConstantExpressionAst constExprAst: + // Note, this parses top-level command invocations as bareword strings + // However, forbidding this causes hashtable parsing to fail + // It is probably not worth the complexity to isolate this case + return constExprAst.Value; + + case VariableExpressionAst varExprAst: + // $true and $false are VariableExpressionAsts, so look for them here + string varName = varExprAst.VariablePath.UserPath; + + if (varName.CaseInsensitiveEquals("true")) + { + return true; + } + + if (varName.CaseInsensitiveEquals("false")) + { + return false; + } + + if (varName.CaseInsensitiveEquals("null")) + { + return null; + } + + throw CreateInvalidDataExceptionFromAst(varExprAst); + + case ArrayExpressionAst arrExprAst: + + // Most cases are handled by the inner array handling, + // but we may have an empty array + if (arrExprAst.SubExpression?.Statements == null) + { + throw CreateInvalidDataExceptionFromAst(arrExprAst); + } + + if (arrExprAst.SubExpression.Statements.Count == 0) + { + return new object[0]; + } + + var listComponents = new List(); + // Arrays can either be array expressions (1, 2, 3) or array literals with statements @(1 `n 2 `n 3) + // Or they can be a combination of these + // We go through each statement (line) in an array and read the whole subarray + // This will also mean that @(1; 2) is parsed as an array of two elements, but there's not much point defending against this + foreach (StatementAst statement in arrExprAst.SubExpression.Statements) + { + if (!(statement is PipelineAst pipelineAst)) + { + throw CreateInvalidDataExceptionFromAst(arrExprAst); + } + + ExpressionAst pipelineExpressionAst = pipelineAst.GetPureExpression(); + if (pipelineExpressionAst == null) + { + throw CreateInvalidDataExceptionFromAst(arrExprAst); + } + + object arrayValue = ConvertAstValue(pipelineExpressionAst); + // We might hit arrays like @(\n1,2,3\n4,5,6), which the parser sees as two statements containing array expressions + if (arrayValue is object[] subArray) + { + listComponents.AddRange(subArray); + continue; + } + + listComponents.Add(arrayValue); + } + return listComponents.ToArray(); + + + case ArrayLiteralAst arrLiteralAst: + return ConvertAstValue(arrLiteralAst); + + case HashtableAst hashtableAst: + return ConvertAstValue(hashtableAst); + + default: + // Other expression types are too complicated or fundamentally unsafe + throw CreateInvalidDataExceptionFromAst(exprAst); + } + } + + /// + /// Process a PowerShell array literal with statically evaluable/safe contents + /// into a .NET value. + /// + /// The PowerShell array AST to turn into a value. + /// The .NET value represented by PowerShell syntax. + public object[] ConvertAstValue(ArrayLiteralAst arrLiteralAst) + { + if (arrLiteralAst == null) + { + throw new ArgumentNullException(nameof(arrLiteralAst)); + } + + if (arrLiteralAst.Elements == null) + { + throw CreateInvalidDataExceptionFromAst(arrLiteralAst); + } + + var elements = new List(); + foreach (ExpressionAst exprAst in arrLiteralAst.Elements) + { + elements.Add(ConvertAstValue(exprAst)); + } + + return elements.ToArray(); + } + + /// + /// Create a hashtable value from a PowerShell AST representing one, + /// provided that the PowerShell expression is statically evaluable and safe. + /// + /// The PowerShell representation of the hashtable value. + /// The Hashtable as a hydrated .NET value. + public Hashtable ConvertAstValue(HashtableAst hashtableAst) + { + if (hashtableAst == null) + { + throw new ArgumentNullException(nameof(hashtableAst)); + } + + if (hashtableAst.KeyValuePairs == null) + { + throw CreateInvalidDataExceptionFromAst(hashtableAst); + } + + // Enforce string keys, since that's always what we want + var hashtable = new Hashtable(); + foreach (Tuple entry in hashtableAst.KeyValuePairs) + { + object key = ConvertAstValue(entry.Item1); + + // Get the value + ExpressionAst valueExprAst = (entry.Item2 as PipelineAst)?.GetPureExpression(); + if (valueExprAst == null) + { + throw CreateInvalidDataExceptionFromAst(entry.Item2); + } + + // Add the key/value entry into the hydrated hashtable + hashtable[key] = ConvertAstValue(valueExprAst); + } + + return hashtable; + } + + private static InvalidDataException CreateInvalidDataExceptionFromAst(Ast ast) + { + if (ast == null) + { + throw new ArgumentNullException(nameof(ast)); + } + + return CreateInvalidDataException(ast.Extent); + } + + private static InvalidDataException CreateInvalidDataException(IScriptExtent extent) + { + return new InvalidDataException( + $"Invalid PSD setting '{extent.Text}' at line {extent.StartLineNumber}, column {extent.StartColumnNumber} in file '{extent.File}'"); + } + } +} diff --git a/ScriptAnalyzer2/Configuration/Psd/PsdScriptAnalyzerConfiguration.cs b/ScriptAnalyzer2/Configuration/Psd/PsdScriptAnalyzerConfiguration.cs new file mode 100644 index 000000000..83fa158ad --- /dev/null +++ b/ScriptAnalyzer2/Configuration/Psd/PsdScriptAnalyzerConfiguration.cs @@ -0,0 +1,91 @@ +using Microsoft.PowerShell.ScriptAnalyzer.Tools; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Management.Automation.Language; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Configuration.Psd +{ + public class PsdScriptAnalyzerConfiguration : IScriptAnalyzerConfiguration + { + public static PsdScriptAnalyzerConfiguration FromFile(string filePath) + { + return FromAst(PowerShellParsing.ParseHashtableFromFile(filePath)); + } + + public static PsdScriptAnalyzerConfiguration FromString(string hashtableString) + { + return FromAst(PowerShellParsing.ParseHashtableFromInput(hashtableString)); + } + + public static PsdScriptAnalyzerConfiguration FromAst(HashtableAst ast) + { + var psdConverter = new PsdTypedObjectConverter(); + + var configuration = psdConverter.Convert>(ast); + var builtinRulePreference = psdConverter.Convert(configuration[ConfigurationKeys.BuiltinRulePreference]); + var ruleExecutionMode = psdConverter.Convert(configuration[ConfigurationKeys.RuleExecutionMode]); + var rulePaths = psdConverter.Convert>(configuration[ConfigurationKeys.RulePaths]); + var ruleConfigurations = psdConverter.Convert>(configuration[ConfigurationKeys.RuleConfigurations]); + + return new PsdScriptAnalyzerConfiguration(psdConverter, builtinRulePreference, ruleExecutionMode, rulePaths, ruleConfigurations); + } + + private readonly IReadOnlyDictionary _ruleConfigurations; + + private readonly ConcurrentDictionary _ruleConfigurationCache; + + private readonly PsdTypedObjectConverter _psdConverter; + + public PsdScriptAnalyzerConfiguration( + PsdTypedObjectConverter psdConverter, + BuiltinRulePreference? builtinRulePreference, + RuleExecutionMode? ruleExecutionMode, + IReadOnlyList rulePaths, + IReadOnlyDictionary ruleConfigurations) + { + _ruleConfigurations = ruleConfigurations; + _ruleConfigurationCache = new ConcurrentDictionary(); + _psdConverter = psdConverter; + BuiltinRules = builtinRulePreference; + RuleExecution = ruleExecutionMode; + RulePaths = rulePaths; + } + + public BuiltinRulePreference? BuiltinRules { get; } + + public RuleExecutionMode? RuleExecution { get; } + + public IReadOnlyList RulePaths { get; } + + public IReadOnlyDictionary RuleConfiguration { get; } + } + + public class PsdRuleConfiguration : LazyConvertedRuleConfiguration + { + private readonly PsdTypedObjectConverter _psdConverter; + + public PsdRuleConfiguration( + PsdTypedObjectConverter psdConverter, + CommonConfiguration common, + HashtableAst configurationHashtableAst) + : base(common, configurationHashtableAst) + { + _psdConverter = psdConverter; + } + + public override bool TryConvertObject(Type type, HashtableAst configuration, out IRuleConfiguration result) + { + try + { + result = (IRuleConfiguration)_psdConverter.Convert(type, configuration); + return true; + } + catch + { + result = null; + return false; + } + } + } +} diff --git a/ScriptAnalyzer2/Configuration/Psd/PsdTypedObjectConverter.cs b/ScriptAnalyzer2/Configuration/Psd/PsdTypedObjectConverter.cs new file mode 100644 index 000000000..fa22e37ce --- /dev/null +++ b/ScriptAnalyzer2/Configuration/Psd/PsdTypedObjectConverter.cs @@ -0,0 +1,955 @@ +using Microsoft.PowerShell.ScriptAnalyzer.Tools; +using Newtonsoft.Json; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Management.Automation.Language; +using System.Reflection; +using System.Runtime.Serialization; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Configuration.Psd +{ + public abstract class PsdTypeConverter + { + public abstract bool CanConvert(Type type); + + public abstract object ConvertPsdType(Type type, ExpressionAst psdAst); + } + + public abstract class PsdTypeConverter : PsdTypeConverter + { + public abstract TTarget ConvertPsdType(ExpressionAst psdAst); + + public override bool CanConvert(Type type) + { + return type == typeof(TTarget); + } + + public override object ConvertPsdType(Type type, ExpressionAst psdAst) + { + return ConvertPsdType(psdAst); + } + } + + public class PsdTypedObjectConverter + { + private readonly PsdDataParser _psdDataParser; + + private readonly IReadOnlyList _converters; + + private readonly Dictionary _converterCache; + + private readonly Dictionary> _enumMembers; + + public PsdTypedObjectConverter(IReadOnlyList converters) + { + _converters = converters; + _psdDataParser = new PsdDataParser(); + _converterCache = new Dictionary(); + _enumMembers = new Dictionary>(); + } + + public PsdTypedObjectConverter() : this(converters: null) + { + } + + public TTarget Convert(string str) + { + return (TTarget)Convert(typeof(TTarget), str); + } + + public object Convert(Type type, string str) + { + ExpressionAst expressionAst = PowerShellParsing.ParseExpressionFromInput(str); + return Convert(type, expressionAst); + } + + public TTarget Convert(ExpressionAst ast) + { + return (TTarget)Convert(typeof(TTarget), ast); + } + + public object Convert(Type type, ExpressionAst ast) + { + if (type == typeof(ExpressionAst)) + { + return ast; + } + + if (type == typeof(object)) + { + return _psdDataParser.ConvertAstValue(ast); + } + + object conversionResult; + + if (TryCustomConversion(type, ast, out conversionResult)) + { + return conversionResult; + } + + if (TryEnumConversion(type, ast, out conversionResult)) + { + return conversionResult; + } + + if (TryConvertVariableExpression(type, ast, out conversionResult)) + { + return conversionResult; + } + + // Check primitive types the fast way + switch (Type.GetTypeCode(type)) + { + case TypeCode.String: + return ConvertString(ast); + + case TypeCode.DateTime: + return ConvertDateTime(ast); + + case TypeCode.DBNull: + case TypeCode.Empty: + throw new ArgumentException($"Type '{type.FullName}' has invalid type code '{Type.GetTypeCode(type)}'"); + + case TypeCode.Boolean: + throw new ArgumentException($"Ast '{ast.Extent.Text}' cannot be cast to boolean type"); + } + + if (TryConvertNumber(type, ast, out conversionResult)) + { + return conversionResult; + } + + if (TryConvertNullable(type, ast, out conversionResult)) + { + return conversionResult; + } + + if (TryConvertDictionary(type, ast, out conversionResult)) + { + return conversionResult; + } + + if (TryConvertEnumerable(type, ast, out conversionResult)) + { + return conversionResult; + } + + return ConvertPoco(type, ast); + } + + private bool TryEnumConversion(Type target, ExpressionAst ast, out object result) + { + if (!target.IsEnum) + { + result = null; + return false; + } + + string str = ConvertString(ast); + + if (!_enumMembers.TryGetValue(target, out Dictionary enumValues)) + { + enumValues = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (FieldInfo enumMember in target.GetFields(BindingFlags.Public | BindingFlags.Static)) + { + var enumMemberAttribute = enumMember.GetCustomAttribute(); + if (enumMemberAttribute != null) + { + enumValues[enumMemberAttribute.Value ?? enumMember.Name] = enumMember.GetValue(null); + } + } + + // If no members have the [EnumMember] attribute, we include all of them + if (enumValues.Count == 0) + { + foreach (FieldInfo enumMember in target.GetFields(BindingFlags.Public | BindingFlags.Static)) + { + enumValues[enumMember.Name] = enumMember.GetValue(null); + } + } + + _enumMembers[target] = enumValues; + } + + if (!enumValues.TryGetValue(str, out result)) + { + throw new ArgumentException($"Enum type '{target.FullName}' does not define a member '{str}'"); + } + + return true; + } + + private bool TryConvertNullable(Type target, ExpressionAst ast, out object result) + { + if (!target.IsGenericType + || target.GetGenericTypeDefinition() != typeof(Nullable<>)) + { + result = null; + return false; + } + + Type innerType = target.GetGenericArguments()[0]; + object innerValue = Convert(innerType, ast); + + result = typeof(Nullable<>) + .MakeGenericType(innerType) + .GetConstructor(new Type[] { innerType }) + .Invoke(new object[] { innerValue }); + return true; + } + + private bool TryCustomConversion(Type target, ExpressionAst ast, out object result) + { + if (_converterCache.TryGetValue(target, out PsdTypeConverter cachedConverter)) + { + result = cachedConverter.ConvertPsdType(target, ast); + return true; + } + + if (_converters != null) + { + foreach (PsdTypeConverter converter in _converters) + { + if (converter.CanConvert(target)) + { + _converterCache[target] = converter; + result = converter.ConvertPsdType(target, ast); + return true; + } + } + } + + result = null; + return false; + } + + private Dictionary GetHashtableDict(Type target, ExpressionAst ast) + { + if (!(ast is HashtableAst hashtableAst)) + { + throw CreateConversionMismatchException(ast, target); + } + + var hashtableFields = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (Tuple entry in hashtableAst.KeyValuePairs) + { + string key = (string)_psdDataParser.ConvertAstValue(entry.Item1); + hashtableFields[key] = GetExpressionFromStatementAst(entry.Item2); + } + + return hashtableFields; + } + + private ConstructorInfo GetDesignatedConstructor(Type target) + { + // Work out if we'll be able to construct this type + ConstructorInfo[] constructors = target.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + + ConstructorInfo designatedConstructor = null; + if (constructors.Length == 1) + { + designatedConstructor = constructors[0]; + } + else + { + foreach (ConstructorInfo ctorInfo in target.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) + { + if (ctorInfo.GetParameters().Length == 0) + { + designatedConstructor = ctorInfo; + continue; + } + + if (ctorInfo.GetCustomAttribute() != null) + { + designatedConstructor = ctorInfo; + break; + } + } + } + + if (designatedConstructor == null) + { + throw new ArgumentException($"Unable to instantiate type '{target.FullName}': no constructor available for instantiation"); + } + + return designatedConstructor; + } + + private Dictionary GetMembersToInstantiate(Type target) + { + var jsonObjectAttribute = target.GetCustomAttribute(); + + var memberSerialization = MemberSerialization.OptOut; + if (jsonObjectAttribute != null) + { + memberSerialization = jsonObjectAttribute.MemberSerialization; + } + else if (target.GetCustomAttribute() != null) + { + memberSerialization = MemberSerialization.OptIn; + } + + var membersToInstantiate = new Dictionary(StringComparer.OrdinalIgnoreCase); + AddFieldsToInstantiate(target, memberSerialization, jsonObjectAttribute, membersToInstantiate); + AddPropertiesToInstantiate(target, memberSerialization, jsonObjectAttribute, membersToInstantiate); + return membersToInstantiate; + } + + private void AddFieldsToInstantiate( + Type target, + MemberSerialization memberSerialization, + JsonObjectAttribute jsonObjectAttribute, + Dictionary membersToInstantiate) + { + foreach (FieldInfo field in target.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) + { + JsonPropertyAttribute jsonPropertyAttribute = field.GetCustomAttribute(); + DataMemberAttribute dataMemberAttribute = field.GetCustomAttribute(); + + switch (memberSerialization) + { + case MemberSerialization.Fields: + membersToInstantiate[field.Name] = CreateSerializedPropertyObject(field, jsonPropertyAttribute, dataMemberAttribute); + break; + + case MemberSerialization.OptOut: + if (field.IsPublic + && field.GetCustomAttribute() == null) + { + membersToInstantiate[field.Name] = CreateSerializedPropertyObject(field, jsonPropertyAttribute, dataMemberAttribute); + } + break; + + case MemberSerialization.OptIn: + if (jsonPropertyAttribute != null + || dataMemberAttribute != null) + { + membersToInstantiate[field.Name] = CreateSerializedPropertyObject(field, jsonPropertyAttribute, dataMemberAttribute); + } + break; + } + } + } + + private void AddPropertiesToInstantiate( + Type target, + MemberSerialization memberSerialization, + JsonObjectAttribute jsonObjectAttribute, + Dictionary membersToInstantiate) + { + + foreach (PropertyInfo property in target.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) + { + JsonPropertyAttribute jsonPropertyAttribute = property.GetCustomAttribute(); + DataMemberAttribute dataMemberAttribute = property.GetCustomAttribute(); + + switch (memberSerialization) + { + case MemberSerialization.Fields: + case MemberSerialization.OptOut: + if (property.GetGetMethod().IsPublic + && property.GetCustomAttribute() == null) + { + membersToInstantiate[property.Name] = CreateSerializedPropertyObject(property, jsonPropertyAttribute, dataMemberAttribute); + } + break; + + case MemberSerialization.OptIn: + if (jsonPropertyAttribute != null + || dataMemberAttribute != null) + { + membersToInstantiate[property.Name] = CreateSerializedPropertyObject(property, jsonPropertyAttribute, dataMemberAttribute); + } + break; + } + } + } + + private SerializedProperty CreateSerializedPropertyObject(MemberInfo member, JsonPropertyAttribute jsonPropertyAttribute, DataMemberAttribute dataMemberAttribute) + { + return new SerializedProperty + { + MemberInfo = member, + JsonPropertyAttribute = jsonPropertyAttribute, + DataMemberAttribute = dataMemberAttribute, + Name = jsonPropertyAttribute?.PropertyName ?? dataMemberAttribute?.Name ?? member.Name, + }; + } + + private object InstantiateObject( + Type target, + ConstructorInfo ctor, + Dictionary hashtableDict, + Dictionary membersToInstantiate) + { + var ctorParameters = new List(); + foreach (ParameterInfo ctorParameter in ctor.GetParameters()) + { + if (!membersToInstantiate.TryGetValue(ctorParameter.Name, out SerializedProperty serializedProperty)) + { + throw new ArgumentException($"The constructor for type '{target.FullName}' requires parameter '{ctorParameter.Name}' but has no such member"); + } + + ctorParameters.Add(InstantiateHashtablePropertyAsMember( + serializedProperty.MemberInfo, + hashtableDict, + serializedProperty.Name)); + + hashtableDict.Remove(serializedProperty.Name); + } + + return ctor.Invoke(ctorParameters.ToArray()); + } + + private void SetObjectProperties( + object instance, + Dictionary hashtableDict, + IReadOnlyDictionary membersToInstantiate) + { + foreach (SerializedProperty memberToInstantiate in membersToInstantiate.Values) + { + if (!hashtableDict.TryGetValue(memberToInstantiate.Name, out ExpressionAst expressionAst)) + { + continue; + } + + hashtableDict.Remove(memberToInstantiate.Name); + + switch (memberToInstantiate.MemberInfo.MemberType) + { + case MemberTypes.Field: + var fieldInfo = (FieldInfo)memberToInstantiate.MemberInfo; + + if (fieldInfo.IsInitOnly) + { + throw new ArgumentException($"Field '{fieldInfo.Name}' on type '{fieldInfo.DeclaringType.FullName}' is readonly and cannot be set"); + } + + fieldInfo.SetValue(instance, Convert(fieldInfo.FieldType, expressionAst)); + break; + + case MemberTypes.Property: + var propertyInfo = (PropertyInfo)memberToInstantiate.MemberInfo; + MethodInfo propertySetter = propertyInfo.GetSetMethod() ?? propertyInfo.GetSetMethod(nonPublic: true); + + if (propertySetter == null) + { + throw new ArgumentException($"Property '{propertyInfo.Name}' on type '{propertyInfo.DeclaringType.FullName}' has no setter and cannot be set"); + } + + propertySetter.Invoke(instance, new[] { Convert(propertyInfo.PropertyType, expressionAst) }); + break; + } + } + + if (hashtableDict.Count > 0) + { + throw new ArgumentException($"Unknown key(s) in hashtable: {string.Join(",", hashtableDict.Keys)}"); + } + } + + private object ConvertPoco(Type target, ExpressionAst ast) + { + // Validate hashtable and convert to a key value dict + Dictionary hashtableDict = GetHashtableDict(target, ast); + + // Get the designated constructor + ConstructorInfo designatedConstructor = GetDesignatedConstructor(target); + + // Get the properties we need to instantiate + Dictionary membersToInstantiate = GetMembersToInstantiate(target); + + // Instantiate object + object instance = InstantiateObject(target, designatedConstructor, hashtableDict, membersToInstantiate); + + // Set the properties + SetObjectProperties(instance, hashtableDict, membersToInstantiate); + + // return + return instance; + } + + private object InstantiateHashtablePropertyAsMember(MemberInfo memberInfo, IReadOnlyDictionary hashtable, string name) + { + if (!hashtable.TryGetValue(name, out ExpressionAst expressionAst)) + { + throw new ArgumentException($"Hashtable has no property '{name}'"); + } + + switch (memberInfo.MemberType) + { + case MemberTypes.Field: + return Convert(((FieldInfo)memberInfo).FieldType, expressionAst); + + case MemberTypes.Property: + return Convert(((PropertyInfo)memberInfo).PropertyType, expressionAst); + + default: + throw new ArgumentException($"Bad member type '{memberInfo.MemberType}' on member {memberInfo.Name}"); + } + } + + private bool TryConvertDictionary(Type target, ExpressionAst ast, out object result) + { + var hashtableAst = ast as HashtableAst; + + // In case a partial AST is requested + if (target == typeof(HashtableAst)) + { + if (hashtableAst == null) + { + throw CreateConversionMismatchException(ast, target); + } + + result = hashtableAst; + return true; + } + + // If the types are specified as well known ones, + // just instantiate them now + if (target == typeof(Hashtable) + || target == typeof(IDictionary)) + { + if (hashtableAst == null) + { + throw CreateConversionMismatchException(ast, target); + } + + var hashtable = new Hashtable(); + SetHashtableValuesInDictionary(hashtable, hashtableAst); + result = hashtable; + return true; + } + + // If there's an unknown type that implements IDictionary, + // look for a convenient constructor to instantiate it + if (!target.IsGenericType) + { + if (!typeof(IDictionary).IsAssignableFrom(target)) + { + result = null; + return false; + } + + if (hashtableAst == null) + { + throw CreateConversionMismatchException(ast, target); + } + + ConstructorInfo defaultCtor = target.GetConstructor(new Type[0]); + + if (defaultCtor != null) + { + var dict = (IDictionary)defaultCtor.Invoke(null); + SetHashtableValuesInDictionary(dict, hashtableAst); + result = dict; + return true; + } + + ConstructorInfo injectionCtor = target.GetConstructor(new Type[] { typeof(IDictionary) }) + ?? target.GetConstructor(new Type[] { typeof(Hashtable) }); + + if (injectionCtor != null) + { + var dict = new Hashtable(); + SetHashtableValuesInDictionary(dict, hashtableAst); + result = injectionCtor.Invoke(new object[] { dict }); + return true; + } + + throw new ArgumentException($"Unable to instantiate type '{target}'"); + } + + // We have a generic type + Type targetBaseGenericType = target.GetGenericTypeDefinition(); + Type[] genericTypeParameters = target.GetGenericArguments(); + + // Guess that this is a normal dictionary type + if (targetBaseGenericType == typeof(Dictionary<,>) + || targetBaseGenericType == typeof(IDictionary<,>) + || targetBaseGenericType == typeof(IReadOnlyDictionary<,>)) + { + if (hashtableAst == null) + { + throw CreateConversionMismatchException(ast, target); + } + + Type dictionaryType = typeof(Dictionary<,>) + .MakeGenericType(genericTypeParameters); + + object dict = dictionaryType + .GetConstructor(new Type[0]) + .Invoke(null); + + SetHashtableValuesInDictionary(dictionaryType, genericTypeParameters[0], genericTypeParameters[1], dict, hashtableAst); + result = dict; + return true; + } + + // Otherwise, it might implement a dictionary and have a default constructor + if (ImplementsGenericInterface(target, typeof(IDictionary<,>), out IReadOnlyList _)) + { + if (hashtableAst == null) + { + throw CreateConversionMismatchException(ast, target); + } + + ConstructorInfo defaultCtor = target.GetConstructor(new Type[0]); + + if (defaultCtor != null) + { + var dict = defaultCtor.Invoke(null); + SetHashtableValuesInDictionary(target, genericTypeParameters[0], genericTypeParameters[1], dict, hashtableAst); + result = dict; + return true; + } + } + + result = null; + return false; + } + + private void SetHashtableValuesInDictionary(IDictionary dictionary, HashtableAst hashtableAst) + { + foreach (Tuple entry in hashtableAst.KeyValuePairs) + { + object key = _psdDataParser.ConvertAstValue(entry.Item1); + object value = _psdDataParser.ConvertAstValue(GetExpressionFromStatementAst(entry.Item2)); + dictionary[key] = value; + } + } + + private void SetHashtableValuesInDictionary(Type dictionaryType, Type keyType, Type valueType, object dictionary, HashtableAst hashtableAst) + { + MethodInfo setter = dictionaryType.GetProperty("Item").GetSetMethod(); + + foreach (Tuple entry in hashtableAst.KeyValuePairs) + { + object key = Convert(keyType, entry.Item1); + object value = Convert(valueType, GetExpressionFromStatementAst(entry.Item2)); + setter.Invoke(dictionary, new object[] { key, value }); + } + } + + + private bool TryConvertEnumerable(Type target, ExpressionAst ast, out object result) + { + if (target.IsArray) + { + if (!TryExtractExpressionArray(ast, out IReadOnlyList arrayExpressions)) + { + throw CreateConversionMismatchException(ast, target); + } + + Type elementType = target.GetElementType(); + + Array array = Array.CreateInstance(elementType, arrayExpressions.Count); + + for (int i = 0; i < arrayExpressions.Count; i++) + { + array.SetValue(Convert(elementType, arrayExpressions[i]), i); + } + + result = array; + return true; + } + + if (target == typeof(IList) + || target == typeof(IEnumerable)) + { + if (!TryExtractExpressionArray(ast, out IReadOnlyList arrayExpressions)) + { + throw CreateConversionMismatchException(ast, target); + } + + var list = new ArrayList(); + AddItemsToList(list, arrayExpressions); + + result = list; + return true; + } + + if (typeof(IList).IsAssignableFrom(target)) + { + if (!TryExtractExpressionArray(ast, out IReadOnlyList arrayExpressions)) + { + throw CreateConversionMismatchException(ast, target); + } + + ConstructorInfo defaultCtor = target.GetConstructor(new Type[0]); + + if (defaultCtor != null) + { + var list = (IList)defaultCtor.Invoke(null); + AddItemsToList(list, arrayExpressions); + result = list; + return true; + } + + throw new ArgumentException($"Unable to instantiate type '{target.FullName}'"); + } + + if (!target.IsGenericType) + { + result = null; + return false; + } + + // We have a generic type + Type targetBaseGenericType = target.GetGenericTypeDefinition(); + Type[] genericTypeParameters = target.GetGenericArguments(); + + if (targetBaseGenericType == typeof(IEnumerable<>) + || targetBaseGenericType == typeof(List<>) + || targetBaseGenericType == typeof(IList<>) + || targetBaseGenericType == typeof(IReadOnlyList<>) + || targetBaseGenericType == typeof(IReadOnlyCollection<>)) + { + if (!TryExtractExpressionArray(ast, out IReadOnlyList arrayExpressions)) + { + throw CreateConversionMismatchException(ast, target); + } + + var list = (IList)typeof(List<>) + .MakeGenericType(genericTypeParameters) + .GetConstructor(new Type[0]) + .Invoke(null); + AddItemsToList(genericTypeParameters[0], list, arrayExpressions); + result = list; + return true; + } + + if (ImplementsGenericInterface(target, typeof(IList<>), out IReadOnlyList _)) + { + if (!TryExtractExpressionArray(ast, out IReadOnlyList arrayExpressions)) + { + throw CreateConversionMismatchException(ast, target); + } + + ConstructorInfo defaultConstructor = target.GetConstructor(new Type[0]); + if (defaultConstructor != null) + { + object list = defaultConstructor.Invoke(null); + AddItemsToList(target, genericTypeParameters[0], list, arrayExpressions); + result = list; + return true; + } + } + + result = null; + return false; + } + + private void AddItemsToList(IList list, IEnumerable expressions) + { + foreach (ExpressionAst expression in expressions) + { + list.Add(Convert(typeof(object), expression)); + } + } + + private void AddItemsToList(Type elementType, IList list, IEnumerable expressions) + { + foreach (ExpressionAst expression in expressions) + { + list.Add(Convert(elementType, expression)); + } + } + + private void AddItemsToList(Type targetType, Type elementType, object list, IEnumerable expressions) + { + MethodInfo setter = targetType.GetProperty("Item").GetSetMethod(); + + foreach (ExpressionAst expression in expressions) + { + setter.Invoke(list, new object[] { Convert(elementType, expression) }); + } + } + + private bool TryConvertVariableExpression(Type target, ExpressionAst ast, out object result) + { + if (!(ast is VariableExpressionAst variableExpressionAst)) + { + result = null; + return false; + } + + string variableName = variableExpressionAst.VariablePath.UserPath; + + if (variableName.Equals("null", StringComparison.OrdinalIgnoreCase)) + { + if (target.IsValueType + && !(target.IsGenericType && target.GetGenericTypeDefinition() == typeof(Nullable<>))) + { + throw new ArgumentException($"Cannot convert value type '{target.FullName}' to null"); + } + + result = null; + return true; + } + + if (target == typeof(bool)) + { + if (variableName.Equals("true", StringComparison.OrdinalIgnoreCase)) + { + result = true; + return true; + } + + if (variableName.Equals("false", StringComparison.OrdinalIgnoreCase)) + { + result = false; + return true; + } + + throw new ArgumentException($"Cannot convert '{ast}' to boolean type"); + } + + throw new ArgumentException($"Cannot convert non-constant variable '{ast}' to value"); + } + + private string ConvertString(ExpressionAst ast) + { + if (ast is StringConstantExpressionAst stringConstantExpression) + { + return stringConstantExpression.Value; + } + + throw CreateConversionMismatchException(ast, typeof(string)); + } + + private bool TryConvertNumber(Type target, ExpressionAst ast, out object result) + { + if (IsNumericType(target)) + { + if (!(ast is ConstantExpressionAst constantExpression) + || !IsNumericType(constantExpression.StaticType)) + { + throw CreateConversionMismatchException(ast, target); + } + + result = System.Convert.ChangeType(constantExpression.Value, target); + return true; + } + + result = null; + return false; + } + + private DateTime ConvertDateTime(ExpressionAst ast) + { + if (ast is StringConstantExpressionAst stringConstantExpressionAst + && DateTime.TryParse(stringConstantExpressionAst.Value, out DateTime dateTime)) + { + return dateTime; + } + + throw CreateConversionMismatchException(ast, typeof(DateTime)); + } + + private bool TryExtractExpressionArray(ExpressionAst ast, out IReadOnlyList arrayItems) + { + switch (ast) + { + case ArrayExpressionAst arrayExpressionAst: + var expressions = new List(); + + foreach (StatementAst statementAst in arrayExpressionAst.SubExpression.Statements) + { + ExpressionAst expression = GetExpressionFromStatementAst(statementAst); + + switch (expression) + { + case ArrayLiteralAst subArrayLiteral: + expressions.AddRange(subArrayLiteral.Elements); + break; + + default: + expressions.Add(expression); + break; + } + } + + arrayItems = expressions; + return true; + + case ArrayLiteralAst arrayLiteralAst: + arrayItems = arrayLiteralAst.Elements; + return true; + + default: + arrayItems = null; + return false; + } + } + + private static bool IsNumericType(Type type) + { + switch (Type.GetTypeCode(type)) + { + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + case TypeCode.Byte: + case TypeCode.SByte: + return true; + } + + return false; + } + + private ExpressionAst GetExpressionFromStatementAst(StatementAst stmtAst) + { + return (stmtAst as PipelineAst)?.GetPureExpression() + ?? throw new ArgumentException($"'{stmtAst}' must be a pure pipeline AST to convert a value"); + } + + private static Exception CreateConversionMismatchException(Ast ast, Type type) + { + return new ArgumentException($"Unable to convert ast '{ast.Extent.Text}' of type '{ast.GetType().FullName}' to type '{type.FullName}'"); + } + + private static bool ImplementsGenericInterface( + Type target, + Type genericInterface, + out IReadOnlyList genericParameters) + { + foreach (Type implementedInterface in target.GetInterfaces()) + { + if (!implementedInterface.IsGenericType) + { + continue; + } + + if (implementedInterface.GetGenericTypeDefinition() == genericInterface) + { + genericParameters = implementedInterface.GetGenericArguments(); + return true; + } + } + + genericParameters = null; + return false; + } + + private class SerializedProperty + { + public MemberInfo MemberInfo { get; set; } + + public JsonPropertyAttribute JsonPropertyAttribute { get; set; } + + public DataMemberAttribute DataMemberAttribute { get; set; } + + public string Name { get; set; } + } + } +} diff --git a/ScriptAnalyzer2/Configuration/ScriptAnalyzerConfigurationBuilder.cs b/ScriptAnalyzer2/Configuration/ScriptAnalyzerConfigurationBuilder.cs new file mode 100644 index 000000000..b60031a5d --- /dev/null +++ b/ScriptAnalyzer2/Configuration/ScriptAnalyzerConfigurationBuilder.cs @@ -0,0 +1,153 @@ +using Microsoft.PowerShell.ScriptAnalyzer.Configuration.Json; +using Microsoft.PowerShell.ScriptAnalyzer.Configuration.Psd; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Configuration +{ + public class ScriptAnalyzerConfigurationBuilder + { + private readonly List _rulePaths; + + private readonly Dictionary _ruleConfigurations; + + private BuiltinRulePreference? _builtinRulePreference; + + private RuleExecutionMode? _executionMode; + + public ScriptAnalyzerConfigurationBuilder() + { + _rulePaths = new List(); + _ruleConfigurations = new Dictionary(); + } + + public ScriptAnalyzerConfigurationBuilder WithBuiltinRuleSet(BuiltinRulePreference builtinRulePreference) + { + _builtinRulePreference = builtinRulePreference; + return this; + } + + public ScriptAnalyzerConfigurationBuilder WithRuleExecutionMode(RuleExecutionMode executionMode) + { + _executionMode = executionMode; + return this; + } + + public ScriptAnalyzerConfigurationBuilder AddNonConfiguredRule(string ruleName) + { + _ruleConfigurations[ruleName] = RuleConfiguration.Default; + return this; + } + + public ScriptAnalyzerConfigurationBuilder AddRuleConfiguration(string ruleName, IRuleConfiguration ruleConfiguration) + { + _ruleConfigurations[ruleName] = ruleConfiguration; + return this; + } + + public ScriptAnalyzerConfigurationBuilder AddRuleConfigurations(IReadOnlyDictionary ruleConfigurations) + { + foreach (KeyValuePair entry in ruleConfigurations) + { + _ruleConfigurations[entry.Key] = entry.Value; + } + return this; + } + + public ScriptAnalyzerConfigurationBuilder AddRulePath(string rulePath) + { + _rulePaths.Add(rulePath); + return this; + } + + public ScriptAnalyzerConfigurationBuilder AddRulePaths(IEnumerable rulePaths) + { + _rulePaths.AddRange(rulePaths); + return this; + } + + public ScriptAnalyzerConfigurationBuilder ExcludeRule(string rule) + { + _ruleConfigurations.Remove(rule); + return this; + } + + public ScriptAnalyzerConfigurationBuilder ExcludeRules(IEnumerable rules) + { + foreach (string rule in rules) + { + _ruleConfigurations.Remove(rule); + } + return this; + } + + public ScriptAnalyzerConfigurationBuilder AddConfiguration(IScriptAnalyzerConfiguration configuration) + { + if (configuration.BuiltinRules != null) + { + WithBuiltinRuleSet(configuration.BuiltinRules.Value); + } + + if (configuration.RuleExecution != null) + { + WithRuleExecutionMode(configuration.RuleExecution.Value); + } + + AddRulePaths(configuration.RulePaths); + AddRuleConfigurations(configuration.RuleConfiguration); + + return this; + } + + public ScriptAnalyzerConfigurationBuilder AddConfiguration(Action configureSubConfiguration) + { + var subConfiguration = new ScriptAnalyzerConfigurationBuilder(); + configureSubConfiguration(subConfiguration); + return AddConfiguration(subConfiguration.Build()); + } + + public ScriptAnalyzerConfigurationBuilder AddConfigurationFile(string filePath) + { + if (string.Equals(Path.GetExtension(filePath), ".json", StringComparison.OrdinalIgnoreCase)) + { + AddConfiguration(JsonScriptAnalyzerConfiguration.FromFile(filePath)); + } + else + { + AddConfiguration(PsdScriptAnalyzerConfiguration.FromFile(filePath)); + } + + return this; + } + + public IScriptAnalyzerConfiguration Build() + { + return new MemoryScriptAnalyzerConfiguration(_builtinRulePreference, _executionMode, _rulePaths, _ruleConfigurations); + } + } + + internal class MemoryScriptAnalyzerConfiguration : IScriptAnalyzerConfiguration + { + public MemoryScriptAnalyzerConfiguration( + BuiltinRulePreference? builtinRulePreference, + RuleExecutionMode? ruleExecutionMode, + IReadOnlyList rulePaths, + IReadOnlyDictionary ruleConfigurations) + { + BuiltinRules = builtinRulePreference; + RuleExecution = ruleExecutionMode; + RulePaths = rulePaths; + RuleConfiguration = ruleConfigurations; + } + + public IReadOnlyList RulePaths { get; } + + public RuleExecutionMode? RuleExecution { get; } + + public IReadOnlyDictionary RuleConfiguration { get; } + + public BuiltinRulePreference? BuiltinRules { get; } + } +} diff --git a/ScriptAnalyzer2/Execution/IRuleExecutorFactory.cs b/ScriptAnalyzer2/Execution/IRuleExecutorFactory.cs new file mode 100644 index 000000000..1282eda3b --- /dev/null +++ b/ScriptAnalyzer2/Execution/IRuleExecutorFactory.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Management.Automation.Language; +using System.Text; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Execution +{ + public interface IRuleExecutorFactory + { + IRuleExecutor CreateRuleExecutor(Ast ast, IReadOnlyList tokens, string scriptPath); + } + + public class SequentialRuleExecutorFactory : IRuleExecutorFactory + { + public IRuleExecutor CreateRuleExecutor(Ast ast, IReadOnlyList tokens, string scriptPath) + { + return new SequentialRuleExecutor(ast, tokens, scriptPath); + } + } + + public class ParallelLinqRuleExecutorFactory : IRuleExecutorFactory + { + public IRuleExecutor CreateRuleExecutor(Ast ast, IReadOnlyList tokens, string scriptPath) + { + return new ParallelLinqRuleExecutor(ast, tokens, scriptPath); + } + } +} diff --git a/ScriptAnalyzer2/Execution/RuleExecutor.cs b/ScriptAnalyzer2/Execution/RuleExecutor.cs new file mode 100644 index 000000000..e8a49fc78 --- /dev/null +++ b/ScriptAnalyzer2/Execution/RuleExecutor.cs @@ -0,0 +1,150 @@ +using Microsoft.PowerShell.ScriptAnalyzer.Rules; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation.Language; + +namespace Microsoft.PowerShell.ScriptAnalyzer +{ + public interface IRuleExecutor + { + void AddRule(ScriptRule rule); + + IReadOnlyCollection CollectDiagnostics(); + } + + internal class SequentialRuleExecutor : IRuleExecutor + { + private readonly Ast _scriptAst; + + private readonly IReadOnlyList _scriptTokens; + + private readonly string _scriptPath; + + private readonly List _diagnostics; + + public SequentialRuleExecutor(Ast ast, IReadOnlyList tokens, string scriptPath) + { + _scriptAst = ast; + _scriptTokens = tokens; + _scriptPath = scriptPath; + _diagnostics = new List(); + } + + public void AddRule(ScriptRule rule) + { + _diagnostics.AddRange(rule.AnalyzeScript(_scriptAst, _scriptTokens, _scriptPath)); + } + + public IReadOnlyCollection CollectDiagnostics() + { + return _diagnostics; + } + } + + internal class ParallelLinqRuleExecutor : IRuleExecutor + { + private readonly Ast _scriptAst; + + private readonly IReadOnlyList _scriptTokens; + + private readonly string _scriptPath; + + private readonly List _parallelRules; + + private readonly List _sequentialRules; + + public ParallelLinqRuleExecutor(Ast scriptAst, IReadOnlyList scriptTokens, string scriptPath) + { + _scriptAst = scriptAst; + _scriptTokens = scriptTokens; + _scriptPath = scriptPath; + _parallelRules = new List(); + _sequentialRules = new List(); + } + + public void AddRule(ScriptRule rule) + { + if (rule.RuleInfo.IsThreadsafe) + { + _parallelRules.Add(rule); + return; + } + + _sequentialRules.Add(rule); + } + + public IReadOnlyCollection CollectDiagnostics() + { + List diagnostics = _parallelRules.AsParallel() + .SelectMany(rule => rule.AnalyzeScript(_scriptAst, _scriptTokens, _scriptPath)) + .ToList(); + + foreach (ScriptRule sequentialRule in _sequentialRules) + { + diagnostics.AddRange(sequentialRule.AnalyzeScript(_scriptAst, _scriptTokens, _scriptPath)); + } + + return diagnostics; + } + } + + /* + internal class DataflowRuleExecutor : IRuleExecutor + { + private static readonly ExecutionDataflowBlockOptions s_parallelExecutionOptions = new ExecutionDataflowBlockOptions() + { + MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded, + EnsureOrdered = false, + }; + + private readonly Ast _scriptAst; + + private readonly IReadOnlyList _scriptTokens; + + private readonly string _scriptPath; + + private readonly TransformManyBlock _parallelRulePipeline; + + private readonly List _sequentialRules; + + public DataflowRuleExecutor(Ast scriptAst, IReadOnlyList scriptTokens, string scriptPath) + { + _scriptAst = scriptAst; + _scriptTokens = scriptTokens; + _scriptPath = scriptPath; + _parallelRulePipeline = new TransformManyBlock(RunScriptRule, s_parallelExecutionOptions); + _sequentialRules = new List(); + } + + public void AddRule(ScriptRule rule) + { + if (rule.RuleInfo.IsThreadsafe) + { + _parallelRulePipeline.Post(rule); + return; + } + + _sequentialRules.Add(rule); + } + + public IReadOnlyCollection CollectDiagnostics() + { + _parallelRulePipeline.Complete(); + _parallelRulePipeline.TryReceiveAll(out IList parallelDiagnostics); + + var diagnostics = new List(parallelDiagnostics); + foreach (ScriptRule rule in _sequentialRules) + { + diagnostics.AddRange(rule.AnalyzeScript(_scriptAst, _scriptTokens, _scriptPath)); + } + + return diagnostics; + } + + private IEnumerable RunScriptRule(ScriptRule rule) + { + return rule.AnalyzeScript(_scriptAst, _scriptTokens, _scriptPath); + } + } + */ +} diff --git a/ScriptAnalyzer2/Instantiation/CompositeRuleProvider.cs b/ScriptAnalyzer2/Instantiation/CompositeRuleProvider.cs new file mode 100644 index 000000000..75816340f --- /dev/null +++ b/ScriptAnalyzer2/Instantiation/CompositeRuleProvider.cs @@ -0,0 +1,53 @@ +using Microsoft.PowerShell.ScriptAnalyzer.Rules; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Management.Automation.Language; +using System.Text; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Instantiation +{ + public class CompositeRuleProvider : IRuleProvider + { + private readonly IReadOnlyList _ruleProviders; + + private readonly ConcurrentDictionary _ruleReturnDictionary; + + public CompositeRuleProvider(IReadOnlyList ruleProviders) + { + _ruleReturnDictionary = new ConcurrentDictionary(); + _ruleProviders = ruleProviders; + } + + public IEnumerable GetRuleInfos() + { + foreach (IRuleProvider ruleProvider in _ruleProviders) + { + foreach (RuleInfo ruleInfo in ruleProvider.GetRuleInfos()) + { + yield return ruleInfo; + } + } + } + + public IEnumerable GetScriptRules() + { + foreach (IRuleProvider ruleProvider in _ruleProviders) + { + foreach (ScriptRule rule in ruleProvider.GetScriptRules()) + { + _ruleReturnDictionary.TryAdd(rule.RuleInfo, ruleProvider); + yield return rule; + } + } + } + + public void ReturnRule(Rule rule) + { + if (_ruleReturnDictionary.TryGetValue(rule.RuleInfo, out IRuleProvider ruleProvider)) + { + ruleProvider.ReturnRule(rule); + } + } + } +} diff --git a/ScriptAnalyzer2/Instantiation/IRuleProvider.cs b/ScriptAnalyzer2/Instantiation/IRuleProvider.cs new file mode 100644 index 000000000..1595c759a --- /dev/null +++ b/ScriptAnalyzer2/Instantiation/IRuleProvider.cs @@ -0,0 +1,16 @@ +using Microsoft.PowerShell.ScriptAnalyzer.Rules; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Instantiation +{ + public interface IRuleProvider + { + IEnumerable GetRuleInfos(); + + IEnumerable GetScriptRules(); + + void ReturnRule(Rule rule); + } +} diff --git a/ScriptAnalyzer2/Instantiation/RuleComponentProvider.cs b/ScriptAnalyzer2/Instantiation/RuleComponentProvider.cs new file mode 100644 index 000000000..33497c44c --- /dev/null +++ b/ScriptAnalyzer2/Instantiation/RuleComponentProvider.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Builder +{ + public interface IRuleComponentProvider + { + bool TryGetComponentInstance(Type componentType, out object component); + } + + internal class SimpleRuleComponentProvider : IRuleComponentProvider + { + private readonly IReadOnlyDictionary> _componentRegistrations; + + private readonly IReadOnlyDictionary _singletonComponents; + + public SimpleRuleComponentProvider( + IReadOnlyDictionary> componentRegistrations, + IReadOnlyDictionary singletonComponents) + { + _componentRegistrations = componentRegistrations; + _singletonComponents = singletonComponents; + } + + public bool TryGetComponentInstance(Type componentType, out object component) + { + if (_singletonComponents.TryGetValue(componentType, out component)) + { + return true; + } + + if (_componentRegistrations.TryGetValue(componentType, out Func componentFactory)) + { + component = componentFactory(); + return true; + } + + return false; + } + } + + public class RuleComponentProviderBuilder + { + private readonly Dictionary _singletonComponents; + + private readonly Dictionary> _componentRegistrations; + + public RuleComponentProviderBuilder() + { + _singletonComponents = new Dictionary(); + _componentRegistrations = new Dictionary>(); + } + + public RuleComponentProviderBuilder AddSingleton() where T : new() + { + _singletonComponents[typeof(T)] = new T(); + return this; + } + + public RuleComponentProviderBuilder AddSingleton(T instance) + { + _singletonComponents[typeof(T)] = instance; + return this; + } + + public RuleComponentProviderBuilder AddSingleton() where TInstance : TRegistered, new() + { + _singletonComponents[typeof(TRegistered)] = new TInstance(); + return this; + } + + public RuleComponentProviderBuilder AddSingleton(TInstance instance) + { + _singletonComponents[typeof(TRegistered)] = instance; + return this; + } + + public RuleComponentProviderBuilder AddSingleton(Type registeredType, object instance) + { + if (!registeredType.IsAssignableFrom(instance.GetType())) + { + throw new ArgumentException($"Cannot register object '{instance}' of type '{instance.GetType()}' for type '{registeredType}'"); + } + + _singletonComponents[registeredType] = instance; + return this; + } + + public RuleComponentProviderBuilder AddScoped() where T : new() + { + _componentRegistrations[typeof(T)] = () => new T(); + return this; + } + + public RuleComponentProviderBuilder AddScoped(Func factory) where T : class + { + _componentRegistrations[typeof(T)] = factory; + return this; + } + + public RuleComponentProviderBuilder AddScoped() where TInstance : TRegistered, new() + { + _componentRegistrations[typeof(TRegistered)] = () => new TInstance(); + return this; + } + + public RuleComponentProviderBuilder AddScoped(Func factory) where TInstance : class, TRegistered + { + _componentRegistrations[typeof(TRegistered)] = factory; + return this; + } + + public IRuleComponentProvider Build() + { + return new SimpleRuleComponentProvider(_componentRegistrations, _singletonComponents); + } + } +} diff --git a/ScriptAnalyzer2/Instantiation/RuleGeneration.cs b/ScriptAnalyzer2/Instantiation/RuleGeneration.cs new file mode 100644 index 000000000..4034f8c7c --- /dev/null +++ b/ScriptAnalyzer2/Instantiation/RuleGeneration.cs @@ -0,0 +1,98 @@ +using Microsoft.PowerShell.ScriptAnalyzer.Builder; +using Microsoft.PowerShell.ScriptAnalyzer.Configuration; +using Microsoft.PowerShell.ScriptAnalyzer.Rules; +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Instantiation +{ + internal static class RuleGeneration + { + public static bool TryGetRuleFromType( + IReadOnlyDictionary ruleConfigurationCollection, + IRuleComponentProvider ruleComponentProvider, + Type type, + out RuleInfo ruleInfo, + out TypeRuleFactory ruleFactory) + { + ruleFactory = null; + return RuleInfo.TryGetFromRuleType(type, out ruleInfo) + && typeof(ScriptRule).IsAssignableFrom(type) + && TryGetRuleFactory(ruleInfo, type, ruleConfigurationCollection, ruleComponentProvider, out ruleFactory); + } + + private static bool TryGetRuleFactory( + RuleInfo ruleInfo, + Type ruleType, + IReadOnlyDictionary ruleConfigurationCollection, + IRuleComponentProvider ruleComponentProvider, + out TypeRuleFactory factory) + { + ConstructorInfo[] ruleConstructors = ruleType.GetConstructors(); + if (ruleConstructors.Length != 1) + { + factory = null; + return false; + } + ConstructorInfo ruleConstructor = ruleConstructors[0]; + + Type baseType = ruleType.BaseType; + Type configurationType = null; + while (baseType != null) + { + if (baseType.IsGenericType + && baseType.GetGenericTypeDefinition() == typeof(Rule<>)) + { + configurationType = baseType.GetGenericArguments()[0]; + break; + } + + baseType = baseType.BaseType; + } + + if (ruleConfigurationCollection.TryGetValue(ruleInfo.FullName, out IRuleConfiguration ruleConfiguration) + && configurationType != null) + { + ruleConfiguration = ruleConfiguration.AsTypedConfiguration(configurationType); + } + + if (ruleInfo.IsIdempotent) + { + factory = new ConstructorInjectionIdempotentRuleFactory( + ruleComponentProvider, + ruleInfo, + ruleConstructor, + ruleConfiguration); + return true; + } + + if (typeof(IResettable).IsAssignableFrom(ruleType)) + { + factory = new ConstructorInjectingResettableRulePoolingFactory( + ruleComponentProvider, + ruleInfo, + ruleConstructor, + ruleConfiguration); + return true; + } + + if (typeof(IDisposable).IsAssignableFrom(ruleType)) + { + factory = new ConstructorInjectingDisposableRuleFactory( + ruleComponentProvider, + ruleInfo, + ruleConstructor, + ruleConfiguration); + return true; + } + + factory = new ConstructorInjectingRuleFactory( + ruleComponentProvider, + ruleInfo, + ruleConstructor, + ruleConfiguration); + return true; + } + } +} diff --git a/ScriptAnalyzer2/Instantiation/TypeRuleFactory.cs b/ScriptAnalyzer2/Instantiation/TypeRuleFactory.cs new file mode 100644 index 000000000..3e54f926e --- /dev/null +++ b/ScriptAnalyzer2/Instantiation/TypeRuleFactory.cs @@ -0,0 +1,216 @@ +using Microsoft.PowerShell.ScriptAnalyzer.Builder; +using Microsoft.PowerShell.ScriptAnalyzer.Configuration; +using Microsoft.PowerShell.ScriptAnalyzer.Rules; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Instantiation +{ + public abstract class TypeRuleFactory + { + public TypeRuleFactory(RuleInfo ruleInfo) + { + RuleInfo = ruleInfo; + } + + public RuleInfo RuleInfo { get; } + + public abstract TRule GetRuleInstance(); + + public abstract void ReturnRuleInstance(TRule rule); + } + + public class ConstructorInjectingRuleFactory : TypeRuleFactory + { + private readonly IRuleComponentProvider _ruleComponentProvider; + + private readonly RuleInfo _ruleInfo; + + private readonly ConstructorInfo _ctorInfo; + + private readonly IRuleConfiguration _ruleConfiguration; + + private readonly Lazy> _factoryDelegateLazy; + + private int _callCount; + + public ConstructorInjectingRuleFactory( + IRuleComponentProvider ruleComponentProvider, + RuleInfo ruleInfo, + ConstructorInfo ctorInfo) + : this(ruleComponentProvider, ruleInfo, ctorInfo, ruleConfiguration: null) + { + } + + public ConstructorInjectingRuleFactory( + IRuleComponentProvider ruleComponentProvider, + RuleInfo ruleInfo, + ConstructorInfo ctorInfo, + IRuleConfiguration ruleConfiguration) + : base(ruleInfo) + { + _ruleComponentProvider = ruleComponentProvider; + _ruleInfo = ruleInfo; + _ctorInfo = ctorInfo; + _ruleConfiguration = ruleConfiguration; + _factoryDelegateLazy = new Lazy>(CreateFactoryDelegate); + _callCount = 0; + } + + private Func FactoryDelegate => _factoryDelegateLazy.Value; + + public override TRule GetRuleInstance() => InstantiateRuleInstance(); + + public override void ReturnRuleInstance(TRule rule) + { + // Do nothing + } + + protected TRule InstantiateRuleInstance() + { + // If the rule is being run repeatedly, optimise the constructor invocation + return Interlocked.Increment(ref _callCount) > 4 + ? FactoryDelegate() + : (TRule)_ctorInfo.Invoke(GetCtorArgs()); + } + + private object[] GetCtorArgs() + { + var ctorArgs = new List(); + foreach (ParameterInfo ctorParameter in _ctorInfo.GetParameters()) + { + if (ctorParameter.ParameterType == typeof(RuleInfo)) + { + ctorArgs.Add(_ruleInfo); + continue; + } + + if (_ruleConfiguration != null + && ctorParameter.ParameterType == _ruleConfiguration.GetType()) + { + ctorArgs.Add(_ruleConfiguration); + continue; + } + + if (_ruleComponentProvider.TryGetComponentInstance(ctorParameter.ParameterType, out object ctorArg)) + { + ctorArgs.Add(ctorArg); + continue; + } + + throw new ArgumentException($"Rule constructor requires unknown argument: '{ctorParameter.Name}' of type '{ctorParameter.ParameterType.FullName}'"); + } + + return ctorArgs.ToArray(); + } + + private Func CreateFactoryDelegate() + { + MethodInfo getCtorArgsMethod = typeof(ConstructorInjectingRuleFactory<>).GetMethod( + nameof(GetCtorArgs), + BindingFlags.NonPublic | BindingFlags.Instance); + + MethodCallExpression getArgsCall = Expression.Call(getCtorArgsMethod); + + return Expression.Lambda>( + Expression.New( + _ctorInfo, + getArgsCall)).Compile(); + } + } + + public class ConstructorInjectingDisposableRuleFactory : ConstructorInjectingRuleFactory + { + public ConstructorInjectingDisposableRuleFactory( + IRuleComponentProvider ruleComponentProvider, + RuleInfo ruleInfo, + ConstructorInfo ctorInfo, + IRuleConfiguration ruleConfiguration) + : base(ruleComponentProvider, ruleInfo, ctorInfo, ruleConfiguration) + { + } + + public override void ReturnRuleInstance(TRule rule) + { + ((IDisposable)rule).Dispose(); + } + } + + public class ConstructorInjectionIdempotentRuleFactory : ConstructorInjectingRuleFactory + { + private readonly TRule _instance; + + public ConstructorInjectionIdempotentRuleFactory( + IRuleComponentProvider ruleComponentProvider, + RuleInfo ruleInfo, + ConstructorInfo ctorInfo, + IRuleConfiguration ruleConfiguration) + : base(ruleComponentProvider, ruleInfo, ctorInfo, ruleConfiguration) + { + _instance = InstantiateRuleInstance(); + } + + public override TRule GetRuleInstance() + { + return _instance; + } + } + + public class ConstructorInjectingResettableRulePoolingFactory : ConstructorInjectingRuleFactory + { + private readonly ResettablePool _pool; + + public ConstructorInjectingResettableRulePoolingFactory( + IRuleComponentProvider ruleComponentProvider, + RuleInfo ruleInfo, + ConstructorInfo ctorInfo, + IRuleConfiguration ruleConfiguration) + : base(ruleComponentProvider, ruleInfo, ctorInfo, ruleConfiguration) + { + _pool = new ResettablePool(() => (IResettable)InstantiateRuleInstance()); + } + + public override TRule GetRuleInstance() + { + return (TRule)_pool.Take(); + } + + public override void ReturnRuleInstance(TRule rule) + { + _pool.Release((IResettable)rule); + } + } + + internal class ResettablePool + { + private readonly Func _factory; + + private readonly ConcurrentBag _instances; + + public ResettablePool(Func factory) + { + _factory = factory; + _instances = new ConcurrentBag(); + } + + public IResettable Take() + { + if (_instances.TryTake(out IResettable instance)) + { + return instance; + } + + return _factory(); + } + + public void Release(IResettable instance) + { + instance.Reset(); + _instances.Add(instance); + } + } +} diff --git a/ScriptAnalyzer2/Instantiation/TypeRuleProvider.cs b/ScriptAnalyzer2/Instantiation/TypeRuleProvider.cs new file mode 100644 index 000000000..26a81abef --- /dev/null +++ b/ScriptAnalyzer2/Instantiation/TypeRuleProvider.cs @@ -0,0 +1,94 @@ +using Microsoft.PowerShell.ScriptAnalyzer.Builder; +using Microsoft.PowerShell.ScriptAnalyzer.Configuration; +using Microsoft.PowerShell.ScriptAnalyzer.Rules; +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Instantiation +{ + public class TypeRuleProvider : IRuleProvider + { + public static TypeRuleProvider FromAssemblyFile( + IReadOnlyDictionary ruleConfigurationCollection, + IRuleComponentProvider ruleComponentProvider, + string assemblyPath) + { + return FromAssembly(ruleConfigurationCollection, ruleComponentProvider, Assembly.LoadFile(assemblyPath)); + } + + public static TypeRuleProvider FromAssembly( + IReadOnlyDictionary ruleConfigurationCollection, + IRuleComponentProvider ruleComponentProvider, + Assembly ruleAssembly) + { + return FromTypes(ruleConfigurationCollection, ruleComponentProvider, ruleAssembly.GetExportedTypes()); + } + + public static TypeRuleProvider FromTypes( + IReadOnlyDictionary ruleConfigurationCollection, + IRuleComponentProvider ruleComponentProvider, + IReadOnlyList types) + { + return new TypeRuleProvider(GetRuleFactoriesFromTypes(ruleConfigurationCollection, ruleComponentProvider, types)); + } + + internal static IReadOnlyDictionary> GetRuleFactoriesFromTypes( + IReadOnlyDictionary ruleConfigurationCollection, + IRuleComponentProvider ruleComponentProvider, + IReadOnlyList types) + { + var ruleFactories = new Dictionary>(); + + foreach (Type type in types) + { + if (RuleGeneration.TryGetRuleFromType( + ruleConfigurationCollection, + ruleComponentProvider, + type, + out RuleInfo ruleInfo, + out TypeRuleFactory factory)) + { + ruleFactories[ruleInfo] = factory; + } + } + + return ruleFactories; + } + + private readonly IReadOnlyDictionary> _scriptRuleFactories; + + internal TypeRuleProvider( + IReadOnlyDictionary> scriptRuleFactories) + { + _scriptRuleFactories = scriptRuleFactories; + } + + public IEnumerable GetRuleInfos() + { + return _scriptRuleFactories.Keys; + } + + public IEnumerable GetScriptRules() + { + foreach (TypeRuleFactory ruleFactory in _scriptRuleFactories.Values) + { + yield return ruleFactory.GetRuleInstance(); + } + } + + public void ReturnRule(Rule rule) + { + if (!(rule is ScriptRule scriptRule)) + { + return; + } + + if (_scriptRuleFactories.TryGetValue(rule.RuleInfo, out TypeRuleFactory astRuleFactory)) + { + astRuleFactory.ReturnRuleInstance(scriptRule); + } + } + + } +} diff --git a/ScriptAnalyzer2/Legacy/CorrectionExtentExtensions.cs b/ScriptAnalyzer2/Legacy/CorrectionExtentExtensions.cs new file mode 100644 index 000000000..808798374 --- /dev/null +++ b/ScriptAnalyzer2/Legacy/CorrectionExtentExtensions.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Legacy +{ + internal static class CorrectionExtentExtensions + { + public static Correction ToCorrection(this CorrectionExtent correctionExtent, string fullScriptText, string scriptPath) + { + return new Correction( + ScriptExtent.FromPositions( + fullScriptText, + scriptPath, + correctionExtent.StartLineNumber, + correctionExtent.StartColumnNumber, + correctionExtent.EndLineNumber, + correctionExtent.EndColumnNumber), + correctionExtent.Text, + correctionExtent.Description); + } + } +} diff --git a/ScriptAnalyzer2/Legacy/DiagnosticRecordExtensions.cs b/ScriptAnalyzer2/Legacy/DiagnosticRecordExtensions.cs new file mode 100644 index 000000000..e8e24c4c9 --- /dev/null +++ b/ScriptAnalyzer2/Legacy/DiagnosticRecordExtensions.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Legacy +{ + internal static class DiagnosticRecordExtensions + { + public static ScriptDiagnostic ToScriptDiagnostic(this DiagnosticRecord diagnostic, string scriptText) + { + if (diagnostic.SuggestedCorrections == null + || !diagnostic.SuggestedCorrections.Any()) + { + return new ScriptDiagnostic( + diagnostic.Message, + diagnostic.Extent, + diagnostic.Severity); + } + + var corrections = new List(); + foreach (CorrectionExtent legacyCorrection in diagnostic.SuggestedCorrections) + { + corrections.Add(legacyCorrection.ToCorrection(scriptText, diagnostic.ScriptPath)); + } + + return new ScriptDiagnostic( + diagnostic.Message, + diagnostic.Extent, + diagnostic.Severity, + corrections); + } + } +} diff --git a/ScriptAnalyzer2/Legacy/LegacyScriptRuleAdapter.cs b/ScriptAnalyzer2/Legacy/LegacyScriptRuleAdapter.cs new file mode 100644 index 000000000..a7bf9f2f5 --- /dev/null +++ b/ScriptAnalyzer2/Legacy/LegacyScriptRuleAdapter.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; + +namespace Microsoft.PowerShell.ScriptAnalyzer.BackwardCompatibility +{ + internal class LegacyScriptRuleAdapter : IAstRule + { + private readonly IScriptRule _rule; + + public LegacyScriptRuleAdapter(IScriptRule rule) + { + _rule = rule; + } + + public string Name => _rule.GetName(); + + public string Namespace => _rule.GetSourceName(); + + public string Description => _rule.GetDescription(); + + public string SourcePath => null; + + public SourceType SourceType => _rule.GetSourceType(); + + public DiagnosticSeverity Severity => (DiagnosticSeverity)_rule.GetSeverity(); + + public IReadOnlyList AnalyzeScript(Ast ast, string scriptPath) + { + var diagnostics = new List(); + foreach (DiagnosticRecord legacyDiagnostic in _rule.AnalyzeScript(ast, scriptPath)) + { + diagnostics.Add(ScriptDiagnostic.FromLegacyDiagnostic(ast.Extent.Text, legacyDiagnostic)); + } + return diagnostics; + } + } + +} diff --git a/ScriptAnalyzer2/Legacy/LegacyTokenRuleAdapter.cs b/ScriptAnalyzer2/Legacy/LegacyTokenRuleAdapter.cs new file mode 100644 index 000000000..bbccd22ed --- /dev/null +++ b/ScriptAnalyzer2/Legacy/LegacyTokenRuleAdapter.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.PowerShell.ScriptAnalyzer.BackwardCompatibility +{ + internal class LegacyTokenRuleAdapter : ITokenRule + { + private readonly Windows.PowerShell.ScriptAnalyzer.Generic.ITokenRule _rule; + + public LegacyTokenRuleAdapter(Windows.PowerShell.ScriptAnalyzer.Generic.ITokenRule rule) + { + _rule = rule; + } + + public string Name => _rule.GetName(); + + public string Namespace => _rule.GetSourceName(); + + public string Description => _rule.GetDescription(); + + public string SourcePath => null; + + public SourceType SourceType => _rule.GetSourceType(); + + public DiagnosticSeverity Severity => (DiagnosticSeverity)_rule.GetSeverity(); + + public IReadOnlyList AnalyzeScript(IReadOnlyList tokens, string scriptPath) + { + var tokenArray = new Token[tokens.Count]; + for (int i = 0; i < tokens.Count; i++) + { + tokenArray[i] = tokens[i]; + } + + string scriptText = tokens[0].Extent.StartScriptPosition.GetFullScript(); + var diagnostics = new List(); + foreach (DiagnosticRecord legacyDiagnostic in _rule.AnalyzeTokens(tokenArray, scriptPath)) + { + diagnostics.Add(ScriptDiagnostic.FromLegacyDiagnostic(scriptText, legacyDiagnostic)); + } + return diagnostics; + } + } +} diff --git a/ScriptAnalyzer2/Output/Correction.cs b/ScriptAnalyzer2/Output/Correction.cs new file mode 100644 index 000000000..378397cd4 --- /dev/null +++ b/ScriptAnalyzer2/Output/Correction.cs @@ -0,0 +1,43 @@ + +using System.Management.Automation.Language; + +namespace Microsoft.PowerShell.ScriptAnalyzer +{ + public class Correction + { + public Correction(IScriptExtent extent, string correctionText, string description) + { + Extent = extent; + CorrectionText = correctionText; + } + + public IScriptExtent Extent { get; } + + public string CorrectionText { get; } + + public string Description { get; } + } + + public class AstCorrection : Correction + { + public AstCorrection(Ast correctedAst, string correctionText, string description) + : base(correctedAst.Extent, correctionText, description) + { + Ast = correctedAst; + } + + public Ast Ast { get; } + } + + public class TokenCorrection : Correction + { + public TokenCorrection(Token correctedToken, string correctionText, string description) + : base(correctedToken.Extent, correctionText, description) + { + Token = correctedToken; + } + + public Token Token { get; } + } + +} diff --git a/ScriptAnalyzer2/Output/DiagnosticSeverity.cs b/ScriptAnalyzer2/Output/DiagnosticSeverity.cs new file mode 100644 index 000000000..a9e12951b --- /dev/null +++ b/ScriptAnalyzer2/Output/DiagnosticSeverity.cs @@ -0,0 +1,13 @@ +namespace Microsoft.PowerShell.ScriptAnalyzer +{ + public enum DiagnosticSeverity + { + Information = 1, + + Warning = 2, + + Error = 3, + + ParseError = 4, + } +} diff --git a/ScriptAnalyzer2/Output/Exceptions.cs b/ScriptAnalyzer2/Output/Exceptions.cs new file mode 100644 index 000000000..4a3aae573 --- /dev/null +++ b/ScriptAnalyzer2/Output/Exceptions.cs @@ -0,0 +1,45 @@ +using System; + +namespace Microsoft.PowerShell.ScriptAnalyzer +{ + public class ScriptAnalyzerException : Exception + { + protected ScriptAnalyzerException() : base() + { + } + + public ScriptAnalyzerException(string message) : base(message) + { + } + + public ScriptAnalyzerException(string message, Exception innerException) : base(message, innerException) + { + } + } + + public class ScriptAnalyzerConfigurationException : ScriptAnalyzerException + { + public ScriptAnalyzerConfigurationException() : base() + { + } + + public ScriptAnalyzerConfigurationException(string message) : base(message) + { + } + + public ScriptAnalyzerConfigurationException(string message, Exception innerException) : base(message, innerException) + { + } + } + + public class ConfigurationNotFoundException : ScriptAnalyzerConfigurationException + { + public ConfigurationNotFoundException() : base() + { + } + + public ConfigurationNotFoundException(string message) : base(message) + { + } + } +} diff --git a/ScriptAnalyzer2/Output/ScriptDiagnostic.cs b/ScriptAnalyzer2/Output/ScriptDiagnostic.cs new file mode 100644 index 000000000..18c066709 --- /dev/null +++ b/ScriptAnalyzer2/Output/ScriptDiagnostic.cs @@ -0,0 +1,81 @@ +using Microsoft.PowerShell.ScriptAnalyzer.Rules; +using System.Collections.Generic; +using System.Management.Automation.Language; + +namespace Microsoft.PowerShell.ScriptAnalyzer +{ + public class ScriptDiagnostic + { + public ScriptDiagnostic( + RuleInfo rule, + string message, + IScriptExtent scriptExtent, + DiagnosticSeverity severity) + : this(rule, message, scriptExtent, severity, corrections: null) + { + } + + public ScriptDiagnostic( + RuleInfo rule, + string message, + IScriptExtent scriptExtent, + DiagnosticSeverity severity, + IReadOnlyList corrections) + { + Rule = rule; + Corrections = corrections; + Message = message; + ScriptExtent = scriptExtent; + Severity = severity; + } + + public RuleInfo Rule { get; } + + public string Message { get; } + + public IScriptExtent ScriptExtent { get; } + + public DiagnosticSeverity Severity { get; } + + public IReadOnlyList Corrections { get; } + } + + public class ScriptAstDiagnostic : ScriptDiagnostic + { + public ScriptAstDiagnostic(RuleInfo rule, string message, Ast ast, DiagnosticSeverity severity) + : this(rule, message, ast, severity, corrections: null) + { + } + + public ScriptAstDiagnostic( + RuleInfo rule, + string message, + Ast ast, + DiagnosticSeverity severity, + IReadOnlyList corrections) + : base(rule, message, ast.Extent, severity, corrections) + { + Ast = ast; + } + + + public Ast Ast { get; } + } + + public class ScriptTokenDiagnostic : ScriptDiagnostic + { + public ScriptTokenDiagnostic(RuleInfo rule, string message, Token token, DiagnosticSeverity severity) + : this(rule, message, token, severity, corrections: null) + { + } + + public ScriptTokenDiagnostic(RuleInfo rule, string message, Token token, DiagnosticSeverity severity, IReadOnlyList corrections) + : base(rule, message, token.Extent, severity, corrections) + { + Token = token; + } + + public Token Token { get; } + } + +} diff --git a/ScriptAnalyzer2/Output/ScriptExtent.cs b/ScriptAnalyzer2/Output/ScriptExtent.cs new file mode 100644 index 000000000..24a3d21f1 --- /dev/null +++ b/ScriptAnalyzer2/Output/ScriptExtent.cs @@ -0,0 +1,112 @@ + +using System.Management.Automation.Language; + +namespace Microsoft.PowerShell.ScriptAnalyzer +{ + public class ScriptPosition : IScriptPosition + { + public static ScriptPosition FromOffset(string scriptText, string scriptPath, int offset) + { + int currLine = 1; + int i = 0; + int lastLineOffset = -1; + while (i < offset) + { + lastLineOffset = i; + i = scriptText.IndexOf('\n', i); + currLine++; + } + + return new ScriptPosition(scriptText, scriptPath, scriptText.Substring(lastLineOffset, offset), offset, currLine, offset - lastLineOffset); + } + + public static ScriptPosition FromPosition(string scriptText, string scriptPath, int line, int column) + { + int offset = 0; + int currLine = 1; + while (currLine < line) + { + offset = scriptText.IndexOf('\n', offset); + currLine++; + } + + string lineText = scriptText.Substring(offset, offset + column - 1); + offset += column - 1; + + return new ScriptPosition(scriptText, scriptPath, lineText, offset, line, column); + } + + private readonly string _scriptText; + + public ScriptPosition(string scriptText, string scriptPath, string line, int offset, int lineNumber, int columnNumber) + { + _scriptText = scriptText; + File = scriptPath; + Line = line; + Offset = offset; + LineNumber = lineNumber; + ColumnNumber = columnNumber; + } + + public int ColumnNumber { get; } + + public string File { get; } + + public string Line { get; } + + public int LineNumber { get; } + + public int Offset { get; } + + public string GetFullScript() => _scriptText; + } + + public class ScriptExtent : IScriptExtent + { + public static ScriptExtent FromOffsets(string scriptText, string scriptPath, int startOffset, int endOffset) + { + return new ScriptExtent( + scriptText.Substring(startOffset, endOffset - startOffset), + ScriptPosition.FromOffset(scriptText, scriptPath, startOffset), + ScriptPosition.FromOffset(scriptText, scriptPath, endOffset)); + } + + public static ScriptExtent FromPositions(string scriptText, string scriptPath, int startLine, int startColumn, int endLine, int endColumn) + { + var startPosition = ScriptPosition.FromPosition(scriptText, scriptPath, startLine, startColumn); + var endPosition = ScriptPosition.FromPosition(scriptText, scriptPath, endLine, endColumn); + return new ScriptExtent( + scriptText.Substring(startPosition.Offset, endPosition.Offset - startPosition.Offset), + startPosition, + endPosition); + } + + public ScriptExtent(string text, IScriptPosition start, IScriptPosition end) + { + StartScriptPosition = start; + EndScriptPosition = end; + Text = text; + } + + public int EndColumnNumber => EndScriptPosition.ColumnNumber; + + public int EndLineNumber => EndScriptPosition.LineNumber; + + public int EndOffset => EndScriptPosition.Offset; + + public IScriptPosition EndScriptPosition { get; } + + public string File => StartScriptPosition.File; + + public int StartColumnNumber => StartScriptPosition.ColumnNumber; + + public int StartLineNumber => StartScriptPosition.LineNumber; + + public int StartOffset => StartScriptPosition.Offset; + + public IScriptPosition StartScriptPosition { get; } + + public string Text { get; } + } + +} diff --git a/ScriptAnalyzer2/PSScriptAnalyzer.psd1 b/ScriptAnalyzer2/PSScriptAnalyzer.psd1 new file mode 100644 index 000000000..2b91c511d --- /dev/null +++ b/ScriptAnalyzer2/PSScriptAnalyzer.psd1 @@ -0,0 +1,79 @@ +# +# Module manifest for module 'PSScriptAnalyzer' +# + +@{ + +# Author of this module +Author = 'Microsoft Corporation' + +# Script module or binary module file associated with this manifest. +RootModule = if ($PSEdition -eq 'Core') + { + 'netcoreapp3.1/Microsoft.PowerShell.ScriptAnalyzer.dll' + } + else + { + 'net452/Microsoft.PowerShell.ScriptAnalyzer.dll' + } + +# Version number of this module. +ModuleVersion = '2.0.0' + +# ID used to uniquely identify this module +GUID = 'd6245802-193d-4068-a631-8863a4342a18' + +# Company or vendor of this module +CompanyName = 'Microsoft Corporation' + +# Copyright statement for this module +Copyright = '(c) Microsoft Corporation' + +# Description of the functionality provided by this module +Description = 'PSScriptAnalyzer is a static analyzer and formatter for PowerShell, checking for potential code defects in the scripts by applying a group of built-in or customized rules to analyzed scripts.' + +# Minimum version of the Windows PowerShell engine required by this module +PowerShellVersion = '5.1' + +# Minimum version of Microsoft .NET Framework required by this module +DotNetFrameworkVersion = '4.6.1' + +# Type files (.ps1xml) to be loaded when importing this module +TypesToProcess = @() + +# Format files (.ps1xml) to be loaded when importing this module +FormatsToProcess = @() + +# Functions to export from this module +FunctionsToExport = @() + +# Cmdlets to export from this module +CmdletsToExport = @('Get-ScriptAnalyzerRule', 'Invoke-ScriptAnalyzer') + +# Variables to export from this module +VariablesToExport = @() + +# Aliases to export from this module +AliasesToExport = @() + +# Private data to pass to the module specified in RootModule/ModuleToProcess +PrivateData = @{ + PSData = @{ + Tags = 'lint', 'bestpractice' + LicenseUri = 'https://github.com/PowerShell/PSScriptAnalyzer/blob/master/LICENSE' + ProjectUri = 'https://github.com/PowerShell/PSScriptAnalyzer' + IconUri = 'https://raw.githubusercontent.com/powershell/psscriptanalyzer/master/logo.png' + ReleaseNotes = '' + Prerelease = 'preview.1' + } +} + +CompatiblePSEditions = @('Core', 'Desktop') + +# HelpInfo URI of this module +# HelpInfoURI = '' + +# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. +# DefaultCommandPrefix = '' + +} diff --git a/ScriptAnalyzer2/Properties/AssemblyInfo.cs b/ScriptAnalyzer2/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..3c5e9c0e6 --- /dev/null +++ b/ScriptAnalyzer2/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ + +using Microsoft.PowerShell.ScriptAnalyzer.Rules; +using System.Runtime.CompilerServices; + +[assembly: RuleCollection(Name = "PS")] +[assembly: InternalsVisibleTo("ScriptAnalyzer2.Test")] \ No newline at end of file diff --git a/ScriptAnalyzer2/README.md b/ScriptAnalyzer2/README.md new file mode 100644 index 000000000..2c60e13f8 --- /dev/null +++ b/ScriptAnalyzer2/README.md @@ -0,0 +1,67 @@ +# PSScriptAnalyzer 2.0 + +PSScriptAnalyzer 2.0 seeks to re-architect the core of the PSScriptAnalyzer engine +with the following goals: + +- Performance: both startup and repeated use should be fast +- Hostability: it should be possible to embed PSScriptAnalyzer in other projects with a minimum of difficulty or overhead +- Code reuse: problems should be solved once and shared, solutions that are applicable to other projects should ideally go upstream +- Static reproducibility: analysis should work the same everywhere when possible and not depend on the analysis host's state +- Configurability: configuration should be preferred over opinion, and configuration mechanisms should be discoverable and self-validating + +## Building + +To build the project, run: + +```powershell +./build.ps1 +``` + +For now this will just produce the module. + +## Support + +PSScriptAnalyzer 2.0 supports PowerShell 5.1 and 7, +and also seeks to support PowerShell versions post-7. + +There is no plan to support older versions of PowerShell to host PSScriptAnalyzer, +but analyzing scripts with PowerShell 3 or 4 as a target platform is a goal. + +## Defining rules + +A rule can currently be defined like this: + +```csharp +[RuleDescription("")] +[Rule("")] +public class MyRule : ScriptRule +{ + public MyRule(RuleInfo ruleInfo) + : base(ruleInfo) + { + } + + public override IEnumerable AnalyzeScript(Ast ast, IReadOnlyList tokens, string fileName) + { + // Implementation + } +} +``` + +To improve performance with a `TypeRuleProvider`, it's possible to add other hints about the lifetime of a rule: + +- The `[IdempotentRule]` attribute indicates that a rule instance can be reused, rather than reinstantiated each time +- The `[ThreadsafeRule]` attribute indicates that a rule instance can be run in parallel with other rules in an analysis run +- Implementing the `IResettable` interface will mean that a rule's `Reset()` method is called before the same instance is reused for runs +- Implementing the `IDisposable` interface will mean that the rule will be disposed after an analysis run + +## Architecture + +The main entry point of PSScriptAnalyzer is the `ScriptAnalyzer` class. +This composes: + +- An `IRuleExecutorFactory`, which produces `IRuleExecutor`s, + which provide an execution strategy for rules (such as executing them in parallel). +- An `IRuleProvider`, which provides rule instances. + Currently the main form of this is `TypeRuleProvider`, + which wraps a rule type and creates a factory internally to instantiate rules of that type. diff --git a/ScriptAnalyzer2/Rules/Rule.cs b/ScriptAnalyzer2/Rules/Rule.cs new file mode 100644 index 000000000..617356b01 --- /dev/null +++ b/ScriptAnalyzer2/Rules/Rule.cs @@ -0,0 +1,92 @@ +using Microsoft.PowerShell.ScriptAnalyzer.Configuration; +using System.Collections.Generic; +using System.Management.Automation.Language; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Rules +{ + public interface IResettable + { + void Reset(); + } + + public abstract class Rule + { + protected Rule(RuleInfo ruleInfo) + { + RuleInfo = ruleInfo; + } + + public RuleInfo RuleInfo { get; } + + protected ScriptDiagnostic CreateDiagnostic(string message, IScriptExtent extent) + => CreateDiagnostic(message, extent, RuleInfo.Severity); + + protected ScriptDiagnostic CreateDiagnostic(string message, IScriptExtent extent, DiagnosticSeverity severity) + => CreateDiagnostic(message, extent, severity, corrections: null); + + protected ScriptDiagnostic CreateDiagnostic(string message, IScriptExtent extent, IReadOnlyList corrections) + => CreateDiagnostic(message, extent, RuleInfo.Severity, corrections); + + protected ScriptDiagnostic CreateDiagnostic(string message, IScriptExtent extent, DiagnosticSeverity severity, IReadOnlyList corrections) + { + return new ScriptDiagnostic(RuleInfo, message, extent, severity, corrections); + } + } + + public abstract class Rule : Rule where TConfiguration : IRuleConfiguration + { + protected Rule(RuleInfo ruleInfo, TConfiguration ruleConfiguration) + : base(ruleInfo) + { + Configuration = ruleConfiguration; + } + + public TConfiguration Configuration { get; } + } + + public abstract class ScriptRule : Rule + { + protected ScriptRule(RuleInfo ruleInfo) : base(ruleInfo) + { + } + + public abstract IEnumerable AnalyzeScript(Ast ast, IReadOnlyList tokens, string scriptPath); + + protected ScriptAstDiagnostic CreateDiagnostic(string message, Ast ast) + => CreateDiagnostic(message, ast, RuleInfo.Severity); + + protected ScriptAstDiagnostic CreateDiagnostic(string message, Ast ast, DiagnosticSeverity severity) + => CreateDiagnostic(message, ast, severity, corrections: null); + + protected ScriptAstDiagnostic CreateDiagnostic(string message, Ast ast, IReadOnlyList corrections) + => CreateDiagnostic(message, ast, RuleInfo.Severity, corrections); + + protected ScriptAstDiagnostic CreateDiagnostic(string message, Ast ast, DiagnosticSeverity severity, IReadOnlyList corrections) + { + return new ScriptAstDiagnostic(RuleInfo, message, ast, severity, corrections); + } + + protected ScriptTokenDiagnostic CreateDiagnostic(string message, Token token) + => CreateDiagnostic(message, token, RuleInfo.Severity); + + protected ScriptTokenDiagnostic CreateDiagnostic(string message, Token token, DiagnosticSeverity severity) + => CreateDiagnostic(message, token, severity, corrections: null); + + protected ScriptTokenDiagnostic CreateDiagnostic(string message, Token token, IReadOnlyList corrections) + => CreateDiagnostic(message, token, RuleInfo.Severity, corrections); + + protected ScriptTokenDiagnostic CreateDiagnostic(string message, Token token, DiagnosticSeverity severity, IReadOnlyList corrections) + { + return new ScriptTokenDiagnostic(RuleInfo, message, token, severity, corrections); + } + } + + public abstract class ScriptRule : Rule where TConfiguration : IRuleConfiguration + { + protected ScriptRule(RuleInfo ruleInfo, TConfiguration ruleConfiguration) : base(ruleInfo, ruleConfiguration) + { + } + + public abstract IReadOnlyList AnalyzeScript(Ast ast, IReadOnlyList tokens, string scriptPath); + } +} diff --git a/ScriptAnalyzer2/Rules/RuleAttribute.cs b/ScriptAnalyzer2/Rules/RuleAttribute.cs new file mode 100644 index 000000000..e27a2f6f9 --- /dev/null +++ b/ScriptAnalyzer2/Rules/RuleAttribute.cs @@ -0,0 +1,58 @@ +using System; +using System.Reflection; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Rules +{ + public abstract class ScriptAnalyzerAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public sealed class RuleAttribute : ScriptAnalyzerAttribute + { + public RuleAttribute(string name) + { + Name = name; + } + + public string Name { get; } + + public DiagnosticSeverity Severity { get; set; } = DiagnosticSeverity.Warning; + + public string Namespace { get; set; } + } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public sealed class RuleDescriptionAttribute : ScriptAnalyzerAttribute + { + private readonly Lazy _descriptionLazy; + + public RuleDescriptionAttribute(string description) + { + _descriptionLazy = new Lazy(() => description); + } + + public RuleDescriptionAttribute(Type resourceProvider, string resourceKey) + { + _descriptionLazy = new Lazy(() => GetStringFromResourceProvider(resourceProvider, resourceKey)); + } + + public string Description => _descriptionLazy.Value; + + private static string GetStringFromResourceProvider(Type resourceProvider, string resourceKey) + { + PropertyInfo resourceProperty = resourceProvider.GetProperty(resourceKey, BindingFlags.Static | BindingFlags.NonPublic); + return (string)resourceProperty.GetValue(null); + } + } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public sealed class ThreadsafeRuleAttribute : ScriptAnalyzerAttribute + { + } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public sealed class IdempotentRuleAttribute : ScriptAnalyzerAttribute + { + } +} diff --git a/ScriptAnalyzer2/Rules/RuleCollectionAttribute.cs b/ScriptAnalyzer2/Rules/RuleCollectionAttribute.cs new file mode 100644 index 000000000..2ada6fc60 --- /dev/null +++ b/ScriptAnalyzer2/Rules/RuleCollectionAttribute.cs @@ -0,0 +1,10 @@ +using System; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Rules +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)] + public sealed class RuleCollectionAttribute : ScriptAnalyzerAttribute + { + public string Name { get; set; } + } +} diff --git a/ScriptAnalyzer2/Rules/RuleInfo.cs b/ScriptAnalyzer2/Rules/RuleInfo.cs new file mode 100644 index 000000000..269b190d5 --- /dev/null +++ b/ScriptAnalyzer2/Rules/RuleInfo.cs @@ -0,0 +1,74 @@ + +using System; +using System.Data; +using System.Reflection; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Rules +{ + public class RuleInfo + { + public static bool TryGetFromRuleType(Type ruleType, out RuleInfo ruleInfo) + { + return TryGetFromRuleType(ruleType, SourceType.Assembly, out ruleInfo); + } + + internal static bool TryGetFromRuleType(Type ruleType, SourceType source, out RuleInfo ruleInfo) + { + var ruleAttr = ruleType.GetCustomAttribute(); + + if (ruleAttr == null) + { + ruleInfo = null; + return false; + } + + var ruleDescriptionAttr = ruleType.GetCustomAttribute(); + var threadsafeAttr = ruleType.GetCustomAttribute(); + var idempotentAttr = ruleType.GetCustomAttribute(); + + string ruleNamespace = ruleAttr.Namespace + ?? ruleType.Assembly.GetCustomAttribute()?.Name + ?? ruleType.Assembly.GetName().Name; + + ruleInfo = new RuleInfo(ruleAttr.Name, ruleNamespace) + { + Description = ruleDescriptionAttr.Description, + Severity = ruleAttr.Severity, + Source = source, + IsIdempotent = idempotentAttr != null, + IsThreadsafe = threadsafeAttr != null, + }; + return true; + } + + internal static bool TryGetBuiltinRule(Type ruleType, out RuleInfo ruleInfo) + { + return TryGetFromRuleType(ruleType, SourceType.Builtin, out ruleInfo); + } + + private RuleInfo( + string name, + string @namespace) + { + Name = name; + Namespace = @namespace; + FullName = $"{@namespace}/{name}"; + } + + public string Name { get; } + + public string Namespace { get; } + + public string FullName { get; } + + public string Description { get; private set; } + + public DiagnosticSeverity Severity { get; private set; } + + public bool IsThreadsafe { get; private set; } + + public bool IsIdempotent { get; private set; } + + public SourceType Source { get; private set; } + } +} diff --git a/ScriptAnalyzer2/Rules/SourceType.cs b/ScriptAnalyzer2/Rules/SourceType.cs new file mode 100644 index 000000000..ece025740 --- /dev/null +++ b/ScriptAnalyzer2/Rules/SourceType.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Rules +{ + public enum SourceType + { + Builtin = 0, + Assembly = 1, + PowerShellModule = 2, + } +} diff --git a/ScriptAnalyzer2/Runtime/IPowerShellRuntime.cs b/ScriptAnalyzer2/Runtime/IPowerShellRuntime.cs new file mode 100644 index 000000000..a5e77b657 --- /dev/null +++ b/ScriptAnalyzer2/Runtime/IPowerShellRuntime.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Management.Automation; +using System.Threading.Tasks; +using SMA = System.Management.Automation; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Runtime +{ + public interface IPowerShellCommandDatabase + { + IReadOnlyList GetCommandAliases(string command); + + string GetAliasTarget(string alias); + + IReadOnlyList GetAllNamesForCommand(string command); + } + + public class SessionStateCommandDatabase : IPowerShellCommandDatabase + { + public static SessionStateCommandDatabase Create(CommandInvocationIntrinsics invokeCommandProvider) + { + var commandAliases = new Dictionary>(StringComparer.OrdinalIgnoreCase); + var aliasTargets = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (AliasInfo aliasInfo in invokeCommandProvider.GetCommands("*", CommandTypes.Alias, nameIsPattern: true)) + { + aliasTargets[aliasInfo.Name] = aliasInfo.Definition; + + if (commandAliases.TryGetValue(aliasInfo.Definition, out IReadOnlyList aliases)) + { + ((List)aliases).Add(aliasInfo.Name); + } + else + { + commandAliases[aliasInfo.Definition] = new List { aliasInfo.Name }; + } + } + + return new SessionStateCommandDatabase(commandAliases, aliasTargets); + } + + private readonly IReadOnlyDictionary _aliasTargets; + + private readonly IReadOnlyDictionary> _commandAliases; + + private readonly ConcurrentDictionary> _commandNames; + + private SessionStateCommandDatabase( + IReadOnlyDictionary> commandAliases, + IReadOnlyDictionary aliasTargets) + { + _aliasTargets = aliasTargets; + _commandAliases = commandAliases; + _commandNames = new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase); + } + + public string GetAliasTarget(string alias) + { + if (_aliasTargets.TryGetValue(alias, out string target)) + { + return target; + } + + return null; + } + + public IReadOnlyList GetCommandAliases(string command) + { + if (_commandAliases.TryGetValue(command, out IReadOnlyList aliases)) + { + return aliases; + } + + return null; + } + + public IReadOnlyList GetAllNamesForCommand(string command) + { + return _commandNames.GetOrAdd(command, GenerateCommandNameList); + } + + private IReadOnlyList GenerateCommandNameList(string command) + { + var names = new List(); + + if (_commandAliases.TryGetValue(command, out IReadOnlyList aliases)) + { + names.AddRange(aliases); + } + + if (_aliasTargets.TryGetValue(command, out string target)) + { + names.Add(target); + } + + return names.Count > 0 ? names : null; + } + } +} diff --git a/ScriptAnalyzer2/ScriptAnalyzer.cs b/ScriptAnalyzer2/ScriptAnalyzer.cs new file mode 100644 index 000000000..4c62056bd --- /dev/null +++ b/ScriptAnalyzer2/ScriptAnalyzer.cs @@ -0,0 +1,51 @@ +using Microsoft.PowerShell.ScriptAnalyzer.Builder; +using Microsoft.PowerShell.ScriptAnalyzer.Execution; +using Microsoft.PowerShell.ScriptAnalyzer.Instantiation; +using Microsoft.PowerShell.ScriptAnalyzer.Rules; +using System.Collections.Generic; +using System.Management.Automation.Language; + +namespace Microsoft.PowerShell.ScriptAnalyzer +{ + public class ScriptAnalyzer + { + private readonly IRuleExecutorFactory _executorFactory; + + public ScriptAnalyzer( + IRuleProvider ruleProvider, + IRuleExecutorFactory executorFactory) + { + RuleProvider = ruleProvider; + _executorFactory = executorFactory; + } + + public IRuleProvider RuleProvider { get; } + + public IReadOnlyCollection AnalyzeScriptPath(string path) + { + Ast ast = Parser.ParseFile(path, out Token[] tokens, out ParseError[] parseErrors); + return AnalyzeScript(ast, tokens, path); + } + + public IReadOnlyCollection AnalyzeScriptInput(string input) + { + Ast ast = Parser.ParseInput(input, out Token[] tokens, out ParseError[] parseErrors); + return AnalyzeScript(ast, tokens, scriptPath: null); + } + + public IReadOnlyCollection AnalyzeScript(Ast scriptAst, Token[] scriptTokens) => + AnalyzeScript(scriptAst, scriptTokens, scriptPath: null); + + public IReadOnlyCollection AnalyzeScript(Ast scriptAst, Token[] scriptTokens, string scriptPath) + { + IRuleExecutor ruleExecutor = _executorFactory.CreateRuleExecutor(scriptAst, scriptTokens, scriptPath); + + foreach (ScriptRule rule in RuleProvider.GetScriptRules()) + { + ruleExecutor.AddRule(rule); + } + + return ruleExecutor.CollectDiagnostics(); + } + } +} diff --git a/ScriptAnalyzer2/ScriptAnalyzer2.csproj b/ScriptAnalyzer2/ScriptAnalyzer2.csproj new file mode 100644 index 000000000..5f3427b0c --- /dev/null +++ b/ScriptAnalyzer2/ScriptAnalyzer2.csproj @@ -0,0 +1,51 @@ + + + + netcoreapp3.1;net452 + Microsoft.PowerShell.ScriptAnalyzer + Microsoft.PowerShell.ScriptAnalyzer + Debug;Release + + + + $(DefineConstants);CORECLR + + + + + + + + + + + + + + + + + + + + + + + + + + + Strings.resx + True + True + + + + + + Strings.Designer.cs + ResXFileCodeGenerator + + + + diff --git a/ScriptAnalyzer2/Tools/AstExtensions.cs b/ScriptAnalyzer2/Tools/AstExtensions.cs new file mode 100644 index 000000000..1529e7473 --- /dev/null +++ b/ScriptAnalyzer2/Tools/AstExtensions.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Management.Automation.Language; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Tools +{ + public static class AstExtensions + { + public static IScriptExtent GetFunctionNameExtent(this FunctionDefinitionAst functionDefinitionAst, IReadOnlyList tokens) + { + foreach (Token token in tokens) + { + if (functionDefinitionAst.Extent.Contains(token.Extent) + && token.Text.Equals(functionDefinitionAst.Name)) + { + return token.Extent; + } + } + + return null; + } + + public static object GetValue(this NamedAttributeArgumentAst namedAttributeArgumentAst) + { + if (namedAttributeArgumentAst.ExpressionOmitted) + { + return true; + } + + return AstTools.GetSafeValueFromAst(namedAttributeArgumentAst.Argument); + } + + public static bool IsEnvironmentVariable(this VariableExpressionAst variableExpressionAst) + { + return string.Equals(variableExpressionAst.VariablePath.DriveName, "env", StringComparison.OrdinalIgnoreCase); + } + + public static string GetNameWithoutScope(this VariableExpressionAst variableExpressionAst) + { + int colonIndex = variableExpressionAst.VariablePath.UserPath.IndexOf(":"); + + return colonIndex == -1 + ? variableExpressionAst.VariablePath.UserPath + : variableExpressionAst.VariablePath.UserPath.Substring(colonIndex + 1); + } + + public static bool IsSpecialVariable(this VariableExpressionAst variableExpressionAst) + { + string variableName = variableExpressionAst.VariablePath.UserPath; + if (variableExpressionAst.VariablePath.IsGlobal + || variableExpressionAst.VariablePath.IsScript + || variableExpressionAst.VariablePath.IsLocal) + { + variableName = variableExpressionAst.GetNameWithoutScope(); + } + + return SpecialVariables.IsSpecialVariable(variableName); + } + } +} diff --git a/ScriptAnalyzer2/Tools/AstTools.cs b/ScriptAnalyzer2/Tools/AstTools.cs new file mode 100644 index 000000000..abe1a4bc2 --- /dev/null +++ b/ScriptAnalyzer2/Tools/AstTools.cs @@ -0,0 +1,104 @@ +using Microsoft.PowerShell.ScriptAnalyzer.Configuration.Psd; +using System; +using System.Collections.Generic; +using System.Management.Automation; +using System.Management.Automation.Language; +using System.Text; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Tools +{ + public static class AstTools + { + private readonly static PsdDataParser s_psdDataParser = new PsdDataParser(); + + public static object GetSafeValueFromAst(ExpressionAst ast) + { + return s_psdDataParser.ConvertAstValue(ast); + } + + public static bool TryGetCmdletBindingAttributeAst( + IEnumerable attributes, + out AttributeAst cmdletBindingAttributeAst) + { + foreach (var attributeAst in attributes) + { + if (attributeAst == null || attributeAst.NamedArguments == null) + { + continue; + } + + if (attributeAst.TypeName.GetReflectionAttributeType() == typeof(CmdletBindingAttribute)) + { + cmdletBindingAttributeAst = attributeAst; + return true; + } + } + + cmdletBindingAttributeAst = null; + return false; + } + + public static bool TryGetShouldProcessAttributeArgumentAst( + IEnumerable attributes, + out NamedAttributeArgumentAst shouldProcessArgument) + { + if (!TryGetCmdletBindingAttributeAst(attributes, out AttributeAst cmdletBindingAttributeAst) + || cmdletBindingAttributeAst.NamedArguments == null + || cmdletBindingAttributeAst.NamedArguments.Count == 0) + { + shouldProcessArgument = null; + return false; + } + + foreach (NamedAttributeArgumentAst namedAttributeAst in cmdletBindingAttributeAst.NamedArguments) + { + if (namedAttributeAst.ArgumentName.Equals("SupportsShouldProcess", StringComparison.OrdinalIgnoreCase)) + { + shouldProcessArgument = namedAttributeAst; + return true; + } + } + + shouldProcessArgument = null; + return false; + } + + internal static ExpressionAst GetExpressionAstFromScriptAst(Ast ast) + { + var scriptBlockAst = (ScriptBlockAst)ast; + + if (scriptBlockAst.EndBlock == null) + { + throw new InvalidPowerShellExpressionException("Expected 'end' block in PowerShell input"); + } + + if (scriptBlockAst.EndBlock.Statements == null + || scriptBlockAst.EndBlock.Statements.Count == 0) + { + throw new InvalidPowerShellExpressionException("No statements to parse expression from in input"); + } + + if (scriptBlockAst.EndBlock.Statements.Count != 1) + { + throw new InvalidPowerShellExpressionException("Expected a single expression in input"); + } + + if (!(scriptBlockAst.EndBlock.Statements[0] is PipelineAst pipelineAst)) + { + throw new InvalidPowerShellExpressionException($"Statement '{scriptBlockAst.EndBlock.Statements[0].Extent.Text}' is not a valid expression"); + } + + if (pipelineAst.PipelineElements.Count != 0) + { + throw new InvalidPowerShellExpressionException("Cannot use pipelines in expressions"); + } + + if (!(pipelineAst.PipelineElements[0] is CommandExpressionAst commandExpressionAst)) + { + throw new InvalidPowerShellExpressionException($"Pipeline element '{pipelineAst.PipelineElements[0]}' is not a command expression"); + } + + return commandExpressionAst.Expression; + } + } +} diff --git a/ScriptAnalyzer2/Tools/Exceptions.cs b/ScriptAnalyzer2/Tools/Exceptions.cs new file mode 100644 index 000000000..707567c6c --- /dev/null +++ b/ScriptAnalyzer2/Tools/Exceptions.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Management.Automation.Language; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Tools +{ + public class PowerShellParseErrorException : ScriptAnalyzerException + { + public PowerShellParseErrorException(string message, Ast parsedAst, IReadOnlyList parseErrors) + : base(message) + { + ParsedAst = parsedAst; + ParseErrors = parseErrors; + } + + public Ast ParsedAst { get; } + + public IReadOnlyList ParseErrors { get; } + } + + public class InvalidPowerShellExpressionException : ScriptAnalyzerException + { + public InvalidPowerShellExpressionException(string message) + : base(message) + { + } + } +} diff --git a/ScriptAnalyzer2/Tools/ExtentExtensions.cs b/ScriptAnalyzer2/Tools/ExtentExtensions.cs new file mode 100644 index 000000000..2d5cf152b --- /dev/null +++ b/ScriptAnalyzer2/Tools/ExtentExtensions.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Management.Automation.Language; +using System.Text; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Tools +{ + public static class ExtentExtensions + { + public static bool Contains(this IScriptExtent thisExtent, IScriptExtent thatExtent) + { + return thisExtent.StartOffset <= thatExtent.StartOffset + && thisExtent.EndOffset >= thatExtent.EndOffset; + } + } +} diff --git a/ScriptAnalyzer2/Tools/PowerShellParsing.cs b/ScriptAnalyzer2/Tools/PowerShellParsing.cs new file mode 100644 index 000000000..5ec0d34d5 --- /dev/null +++ b/ScriptAnalyzer2/Tools/PowerShellParsing.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Management.Automation.Language; +using System.Text; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Tools +{ + public static class PowerShellParsing + { + public static HashtableAst ParseHashtableFromInput(string input) + { + ExpressionAst ast = ParseExpressionFromInput(input); + + if (!(ast is HashtableAst hashtableAst)) + { + throw new InvalidPowerShellExpressionException($"Expression '{ast.Extent.Text}' was expected to be a hashtable"); + } + + return hashtableAst; + } + + public static HashtableAst ParseHashtableFromFile(string filePath) + { + ExpressionAst ast = ParseExpressionFromFile(filePath); + + if (!(ast is HashtableAst hashtableAst)) + { + throw new InvalidPowerShellExpressionException($"Expression '{ast.Extent.Text}' was expected to be a hashtable"); + } + + return hashtableAst; + } + + public static ExpressionAst ParseExpressionFromInput(string input) + { + Ast ast = Parser.ParseInput(input, out Token[] _, out ParseError[] errors); + + if (errors != null && errors.Length > 0) + { + throw new PowerShellParseErrorException("Unable to parse input", ast, errors); + } + + return AstTools.GetExpressionAstFromScriptAst(ast); + } + + public static ExpressionAst ParseExpressionFromFile(string filePath) + { + Ast ast = Parser.ParseFile(filePath, out Token[] _, out ParseError[] errors); + + if (errors != null && errors.Length > 0) + { + throw new PowerShellParseErrorException("Unable to parse input", ast, errors); + } + + return AstTools.GetExpressionAstFromScriptAst(ast); + } + + } +} diff --git a/ScriptAnalyzer2/Tools/PowerShellRuntime.cs b/ScriptAnalyzer2/Tools/PowerShellRuntime.cs new file mode 100644 index 000000000..9acc75414 --- /dev/null +++ b/ScriptAnalyzer2/Tools/PowerShellRuntime.cs @@ -0,0 +1,6 @@ +namespace Microsoft.PowerShell.ScriptAnalyzer.Tools +{ + public abstract class PowerShellRuntime + { + } +} diff --git a/ScriptAnalyzer2/Tools/SpecialVariables.cs b/ScriptAnalyzer2/Tools/SpecialVariables.cs new file mode 100644 index 000000000..325536abf --- /dev/null +++ b/ScriptAnalyzer2/Tools/SpecialVariables.cs @@ -0,0 +1,196 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Tools +{ + internal static class SpecialVariables + { + public static bool IsSpecialVariable(string unscopedVariableName) + { + foreach (string specialVariable in SpecialVariables.InitializedVariables) + { + if (unscopedVariableName.Equals(specialVariable, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } + + internal const string @foreach = "foreach"; + internal const string @switch = "switch"; + internal const string Question = "?"; + + internal const string Underbar = "_"; + internal const string Args = "args"; + internal const string This = "this"; + internal const string Input = "input"; + internal const string PSCmdlet = "PSCmdlet"; + internal const string PSBoundParameters = "PSBoundParameters"; + internal const string MyInvocation = "MyInvocation"; + internal const string PSScriptRoot = "PSScriptRoot"; + internal const string PSCommandPath = "PSCommandPath"; + internal const string ExecutionContext = "ExecutionContext"; + internal const string Matches = "Matches"; + internal const string PSVersionTable = "PSVersionTable"; + internal const string OFS = "OFS"; + + internal static readonly string[] InitializedVariables; + + static SpecialVariables() + { + InitializedVariables = new string[] + { + @foreach, + @switch, + Question + }; + + InitializedVariables = InitializedVariables.Concat( + AutomaticVariables.Concat( + PreferenceVariables.Concat( + OtherInitializedVariables))).ToArray(); + } + + internal static readonly string[] AutomaticVariables = new string[] + { + Underbar, + Args, + This, + Input, + PSCmdlet, + PSBoundParameters, + MyInvocation, + PSScriptRoot, + PSCommandPath, + ExecutionContext, + Matches, + PSVersionTable, + OFS + }; + internal static readonly Type[] AutomaticVariableTypes = new Type[] + { + /* Underbar */ typeof(object), + /* Args */ typeof(object[]), + /* This */ typeof(object), + /* Input */ typeof(object), + /* PSCmdlet */ typeof(PSCmdlet), + /* PSBoundParameters */ typeof(Dictionary), + /* MyInvocation */ typeof(InvocationInfo), + /* PSScriptRoot */ typeof(string), + /* PSCommandPath */ typeof(string), + /* ExecutionContext */ typeof(EngineIntrinsics), + /* Matches */ typeof(System.Collections.Hashtable), + /* PSVersionTable */ typeof(System.Collections.Hashtable), + /* OFS */ typeof(object) + }; + + + internal const string DebugPreference = "DebugPreference"; + internal const string VerbosePreference = "VerbosePreference"; + internal const string ErrorActionPreference = "ErrorActionPreference"; + internal const string WhatIfPreference = "WhatIfPreference"; + internal const string WarningPreference = "WarningPreference"; + internal const string ConfirmPreference = "ConfirmPreference"; + internal const string ProgressPreference = "ProgressPreference"; + internal const string InformationPreference = "InformationPreference"; + + internal static readonly string[] PreferenceVariables = new string[] + { + DebugPreference, + VerbosePreference, + ErrorActionPreference, + WhatIfPreference, + WarningPreference, + ConfirmPreference, + ProgressPreference, + InformationPreference + }; + + internal static readonly Type[] PreferenceVariableTypes = new Type[] + { + /* DebugPreference */ typeof(ActionPreference), + /* VerbosePreference */ typeof(ActionPreference), + /* ErrorPreference */ typeof(ActionPreference), + /* WhatIfPreference */ typeof(SwitchParameter), + /* WarningPreference */ typeof(ActionPreference), + /* ConfirmPreference */ typeof(ConfirmImpact), + /* ProgressPreference */ typeof(Enum), + /* InformationPreference */ typeof(ActionPreference), + }; + + internal enum AutomaticVariable + { + Underbar = 0, + Args = 1, + This = 2, + Input = 3, + PSCmdlet = 4, + PSBoundParameters = 5, + MyInvocation = 6, + PSScriptRoot = 7, + PSCommandPath = 8, + NumberOfAutomaticVariables // 1 + the last, used to initialize global scope. + } + + internal enum PreferenceVariable + { + Debug = 9, + Verbose = 10, + Error = 11, + WhatIf = 12, + Warning = 13, + Confirm = 14, + } + + internal const string Host = "Host"; + internal const string HistorySize = "MaximumHistoryCount"; + internal const string OutputEncoding = "OutputEncoding"; + internal const string NestedPromptLevel = "NestedPromptLevel"; + internal const string StackTrace = "StackTrace"; + internal const string FirstToken = "^"; + internal const string LastToken = "$"; + internal const string PSItem = "PSItem"; // simple alias for $_ + internal const string Error = "error"; + internal const string EventError = "error"; + internal const string PathExt = "env:PATHEXT"; + internal const string PSEmailServer = "PSEmailServer"; + internal const string PSDefaultParameterValues = "PSDefaultParameterValues"; + internal const string PSModuleAutoLoadingPreference = "PSModuleAutoLoadingPreference"; + internal const string pwd = "PWD"; + internal const string Null = "null"; + internal const string True = "true"; + internal const string False = "false"; + internal const string LastExitCode = "LastExitCode"; + + internal static readonly string[] OtherInitializedVariables = new string[] + { + Host, + HistorySize, + OutputEncoding, + NestedPromptLevel, + StackTrace, + FirstToken, + LastToken, + PSItem, + Error, + EventError, + PathExt, + PSEmailServer, + PSDefaultParameterValues, + PSModuleAutoLoadingPreference, + pwd, + Null, + True, + False, + LastExitCode + }; + + } +} diff --git a/ScriptAnalyzer2/Utils/HashCodeCombinator.cs b/ScriptAnalyzer2/Utils/HashCodeCombinator.cs new file mode 100644 index 000000000..a268cbef5 --- /dev/null +++ b/ScriptAnalyzer2/Utils/HashCodeCombinator.cs @@ -0,0 +1,43 @@ +namespace Microsoft.PowerShell.ScriptAnalyzer.Internal +{ + // From https://stackoverflow.com/questions/1646807/quick-and-simple-hash-code-combinations + internal struct HashCodeCombinator + { + public static HashCodeCombinator Create() + { + return new HashCodeCombinator(iv: 17); + } + + private int _hash; + + private HashCodeCombinator(int iv) + { + _hash = iv; + } + + public HashCodeCombinator Add(int hashCode) + { + unchecked + { + _hash = 31 * _hash + hashCode; + return this; + } + } + + public HashCodeCombinator Add(object obj) + { + unchecked + { + return obj == null + ? Add(17) + : Add(obj.GetHashCode()); + } + } + + public override int GetHashCode() + { + return _hash; + } + } + +} \ No newline at end of file diff --git a/ScriptAnalyzer2/Utils/Polyfill.cs b/ScriptAnalyzer2/Utils/Polyfill.cs new file mode 100644 index 000000000..0d351cd08 --- /dev/null +++ b/ScriptAnalyzer2/Utils/Polyfill.cs @@ -0,0 +1,25 @@ +using System; + +#if !CORECLR +using System.Collections.Concurrent; +#endif + +namespace Microsoft.PowerShell.ScriptAnalyzer.Internal +{ + internal static class Polyfill + { + +#if !CORECLR + private static ConcurrentDictionary s_emptyArrays = new ConcurrentDictionary(); +#endif + + public static T[] GetEmptyArray() + { +#if CORECLR + return Array.Empty(); +#else + return (T[])s_emptyArrays.GetOrAdd(typeof(T), (_) => new T[0]); +#endif + } + } +} \ No newline at end of file diff --git a/ScriptAnalyzer2/Utils/StringExtensions.cs b/ScriptAnalyzer2/Utils/StringExtensions.cs new file mode 100644 index 000000000..31f7dbee9 --- /dev/null +++ b/ScriptAnalyzer2/Utils/StringExtensions.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Microsoft.PowerShell.ScriptAnalyzer.Utils +{ + internal static class StringExtensions + { + public static bool CaseInsensitiveEquals(this string s, string other) + { + return string.Equals(s, other, StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/ScriptAnalyzer2/build.ps1 b/ScriptAnalyzer2/build.ps1 new file mode 100644 index 000000000..cc7b61a7e --- /dev/null +++ b/ScriptAnalyzer2/build.ps1 @@ -0,0 +1,46 @@ +param( + [Parameter()] + [ValidateSet('Release', 'Debug')] + [string] + $Configuration = 'Debug', + + [Parameter()] + [ValidateSet('netcoreapp3.1', 'net452')] + [string[]] + $TargetFramework = $(if ($IsWindows -eq $false) { 'netcoreapp3.1' } else { 'netcoreapp3.1', 'net452' }) +) + +$ErrorActionPreference = 'Stop' + +$moduleName = "PSScriptAnalyzer" +$outLocation = "$PSScriptRoot/out" +$moduleOutPath = "$outLocation/$moduleName" + +if (Test-Path $moduleOutPath) +{ + Remove-Item -Recurse -Force $moduleOutPath +} + +Push-Location $PSScriptRoot +try +{ + foreach ($framework in $TargetFramework) + { + dotnet publish -f $framework + + if ($LASTEXITCODE -ne 0) + { + throw 'Dotnet publish failed' + } + + New-Item -ItemType Directory -Path "$moduleOutPath/$framework" + Copy-Item -Path "$PSScriptRoot/bin/$Configuration/$framework/publish/*.dll" -Destination "$moduleOutPath/$framework" + Copy-Item -Path "$PSScriptRoot/bin/$Configuration/$framework/publish/*.pdb" -Destination "$moduleOutPath/$framework" -ErrorAction Ignore + } +} +finally +{ + Pop-Location +} + +Copy-Item -Path "$PSScriptRoot/$moduleName.psd1" -Destination $moduleOutPath \ No newline at end of file diff --git a/Tests/ScriptAnalyzer2.Test/PsdTypedObjectConverterTests.cs b/Tests/ScriptAnalyzer2.Test/PsdTypedObjectConverterTests.cs new file mode 100644 index 000000000..dbc210d68 --- /dev/null +++ b/Tests/ScriptAnalyzer2.Test/PsdTypedObjectConverterTests.cs @@ -0,0 +1,551 @@ + +using Microsoft.PowerShell.ScriptAnalyzer.Configuration.Psd; +using Newtonsoft.Json; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Management.Automation.Language; +using System.Management.Automation.Runspaces; +using System.Runtime.Serialization; +using Xunit; + +namespace ScriptAnalyzer2.Test +{ + public class PsdTypedObjectConverterTests + { + private readonly PsdTypedObjectConverter _converter; + + public PsdTypedObjectConverterTests() + { + _converter = new PsdTypedObjectConverter(); + } + + [Fact] + public void TestSimpleFieldObject() + { + var obj = _converter.Convert("@{ Field = 'Banana' }"); + Assert.Equal("Banana", obj.Field); + } + + [Fact] + public void TestSimplePropertyObject() + { + var obj = _converter.Convert("@{ Property = 'Goose' }"); + Assert.Equal("Goose", obj.Property); + } + + [Fact] + public void TestSimpleReadOnlyFieldObject() + { + var obj = _converter.Convert("@{ ReadOnlyField = 'Goose' }"); + Assert.Equal("Goose", obj.ReadOnlyField); + } + + [Fact] + public void TestSimpleReadOnlyPropertyObject() + { + var obj = _converter.Convert("@{ ReadOnlyProperty = 'Goose' }"); + Assert.Equal("Goose", obj.ReadOnlyProperty); + } + + [Fact] + public void TestNumerics() + { + var obj = _converter.Convert(@" +@{ + SByte = 3 + Byte = 4 + Short = 5 + Int = 6 + Long = 7 + UShort = 8 + UInt = 9 + ULong = 10 + Decimal = 11.1 + Float = 12.2 + Double = 13.3 +}"); + Assert.Equal((sbyte)3, obj.SByte); + Assert.Equal((byte)4, obj.Byte); + Assert.Equal((short)5, obj.Short); + Assert.Equal(6, obj.Int); + Assert.Equal(7, obj.Long); + Assert.Equal((ushort)8u, obj.UShort); + Assert.Equal(9u, obj.UInt); + Assert.Equal(10u, obj.ULong); + Assert.Equal(11.1m, obj.Decimal); + Assert.Equal(12.2f, obj.Float); + Assert.Equal(13.3, obj.Double); + } + + [Fact] + public void DateTimeTest() + { + var expected = new DateTime(ticks: 637191090850000000); + var obj = _converter.Convert("@{ DateTime = '6/03/2020 4:31:25 PM' }"); + Assert.Equal(expected, obj.DateTime); + } + + [Fact] + public void DataContractTest() + { + var obj = _converter.Convert("@{ FirstName = 'Raymond'; FamilyName = 'Yacht' }"); + Assert.Equal("Raymond", obj.FirstName); + Assert.Equal("Yacht", obj.LastName); + Assert.Null(obj.MiddleName); + } + + [Fact] + public void DataContractThrowsTest() + { + Assert.Throws(() => _converter.Convert("@{ FirstName = 'Raymond'; MiddleName = 'Luxury'; FamilyName = 'Yacht' }")); + } + + [Fact] + public void PartiallySetObjectTest() + { + var obj = _converter.Convert("@{ Name = 'Moose'; Count = 4 }"); + Assert.Equal("Moose", obj.Name); + Assert.Equal(4, obj.Count); + } + + [Fact] + public void PartiallySetObjectDefaultTest() + { + var obj = _converter.Convert("@{ Name = 'Moose' }"); + Assert.Equal("Moose", obj.Name); + Assert.Equal(1, obj.Count); + } + + [Fact] + public void JsonObjectTest() + { + var obj = _converter.Convert("@{ FullName = 'Moo'; Count = 10 }"); + Assert.Equal("Moo", obj.Name); + Assert.Equal(10, obj.Count); + } + + + [Fact] + public void JsonObjectBadMemberTest() + { + Assert.Throws(() => _converter.Convert("@{ FullName = 'Moo'; ShortName = 'M'; Count = 10 }")); + } + + [Fact] + public void TestJsonObjectOptOut() + { + var obj = _converter.Convert("@{ Count = 2; ShortName = 'F'; FullName = 'Farquad' }"); + Assert.Equal(2, obj.Count); + Assert.Equal("F", obj.ShortName); + Assert.Equal("Farquad", obj.Name); + } + + + [Fact] + public void TestJsonObjectOptOutThrows() + { + Assert.Throws(() => _converter.Convert("@{ Count = 2; Sign = 'Libra'; ShortName = 'F'; FullName = 'Farquad' }")); + } + + [Fact] + public void TestCompositeObject() + { + var obj = _converter.Convert("@{ Name = 'Thing'; Simple = @{ Field = 'Moo' }; SubObject = @{ Count = 3; FullName = 'X' } }"); + + Assert.Equal("Thing", obj.Name); + Assert.Equal("Moo", obj.Simple.Field); + Assert.Equal(3, obj.SubObject.Count); + Assert.Equal("X", obj.SubObject.Name); + } + + [Fact] + public void TestReadOnlyCompositeObject() + { + var obj = _converter.Convert("@{ Name = 'Thing'; Simple = @{ Field = 'Moo' }; SubObject = @{ Count = 3; FullName = 'X' } }"); + + Assert.Equal("Thing", obj.Name); + Assert.Equal("Moo", obj.Simple.Field); + Assert.Equal(3, obj.SubObject.Count); + Assert.Equal("X", obj.SubObject.Name); + } + + [Fact] + public void TestDictionary() + { + var obj = _converter.Convert>("@{ '1' = @{ Field = 'x' }; '2' = @{ Field = 'y' } }"); + + Assert.Equal("x", obj["1"].Field); + Assert.Equal("y", obj["2"].Field); + } + + [Fact] + public void TestIDictionary() + { + var obj = _converter.Convert>("@{ '1' = @{ Field = 'x' }; '2' = @{ Field = 'y' } }"); + + Assert.Equal("x", obj["1"].Field); + Assert.Equal("y", obj["2"].Field); + } + + [Fact] + public void TestReadOnlyDictionary() + { + var obj = _converter.Convert>("@{ '1' = @{ Field = 'x' }; '2' = @{ Field = 'y' } }"); + + Assert.Equal("x", obj["1"].Field); + Assert.Equal("y", obj["2"].Field); + } + + [Fact] + public void TestHashtable() + { + var obj = _converter.Convert("@{ 1 = @{ X = 'x' }; 'hi there' = 2 }"); + + Assert.Equal("x", ((Hashtable)obj[1])["X"]); + Assert.Equal(2, obj["hi there"]); + } + + [Fact] + public void TestArray() + { + var obj = _converter.Convert("@( 2.1, 3.8, 4.0 )"); + Assert.Equal(new double[] { 2.1, 3.8, 4.0 }, obj); + } + + [Fact] + public void TestList() + { + var obj = _converter.Convert>("'hi','THERE','Friend'"); + Assert.Equal(new List { "hi", "THERE", "Friend" }, obj); + } + + [Fact] + public void TestGenericIEnumerable() + { + var obj = _converter.Convert>("@('hi','THERE','Friend')"); + Assert.Equal(new[] { "hi", "THERE", "Friend" }, obj); + } + + [Fact] + public void TestNonGenericIEnumerable() + { + var obj = _converter.Convert("'hi', 7, $true"); + Assert.Equal(new object[] { "hi", 7, true }, obj); + } + + [Fact] + public void TestArrayOfObject() + { + var obj = _converter.Convert("@{ Name = 'a'; Count = 1 }, @{ Name = 'b'; Count = 2 }"); + Assert.Equal("a", obj[0].Name); + Assert.Equal(1, obj[0].Count); + Assert.Equal("b", obj[1].Name); + Assert.Equal(2, obj[1].Count); + } + + [Fact] + public void TestBool() + { + var obj = _converter.Convert("$false"); + Assert.False(obj); + } + + [Fact] + public void TestNull() + { + var obj = _converter.Convert("$null"); + Assert.Null(obj); + } + + [Fact] + public void TestTypedNull() + { + var obj = _converter.Convert("$null"); + Assert.Null(obj); + } + + [Fact] + public void TestNullableValue() + { + var obj = _converter.Convert("4"); + Assert.Equal(4, obj); + } + + [Fact] + public void TestNullableValue_Null() + { + var obj = _converter.Convert("$null"); + Assert.Null(obj); + } + + [Fact] + public void TestNonNullableValue() + { + Assert.Throws(() => _converter.Convert("$null")); + } + + [Fact] + public void TestConversionToObject_Bool() + { + var obj = _converter.Convert("$true"); + Assert.Equal(true, obj); + } + + [Fact] + public void TestConversionToObject_Array() + { + var obj = _converter.Convert("1, $true, 'x'"); + Assert.Equal(new object[] { 1, true, "x" }, (object[])obj); + } + + [Fact] + public void TestConversionToObject_Hashtable() + { + var obj = _converter.Convert("@{ 1 = 'a',5; 'b' = @{ t = $false } }"); + + Assert.IsType(obj); + + var hashtable = (Hashtable)obj; + + Assert.Equal(new object[] { "a", 5 }, hashtable[1]); + Assert.IsType(hashtable["b"]); + + var subtable = (Hashtable)hashtable["b"]; + + Assert.Equal(false, subtable["t"]); + } + + [Fact] + public void TestPartialInstantiation() + { + var obj = _converter.Convert("@{ x = 1; y = 2 }"); + Assert.Equal(1, ((ConstantExpressionAst)GetExpressionAstFromStatementAst(obj.KeyValuePairs[0].Item2)).Value); + Assert.Equal(2, ((ConstantExpressionAst)GetExpressionAstFromStatementAst(obj.KeyValuePairs[1].Item2)).Value); + } + + [Fact] + public void TestPartialInstantiation_ReadOnlyDict() + { + var obj = _converter.Convert>("@{ x = 1; y = 2 }"); + Assert.Equal(1, ((ConstantExpressionAst)obj["x"]).Value); + Assert.Equal(2, ((ConstantExpressionAst)obj["y"]).Value); + } + + [Fact] + public void TestPartialInstantiation_ReadOnlyHashtableDict() + { + var obj = _converter.Convert>("@{ x = @{ m = 'hi' }; y = @{ m = 'bye' } }"); + var x = _converter.Convert>(obj["x"]); + var y = _converter.Convert>(obj["y"]); + Assert.Equal("hi", x["m"]); + Assert.Equal("bye", y["m"]); + } + + [Fact] + public void TestPartialInstantiation_IEnumerable() + { + var obj = _converter.Convert>("1, 'hi', $true"); + var one = _converter.Convert(obj[0]); + var two = _converter.Convert(obj[1]); + var three = _converter.Convert(obj[2]); + Assert.Equal(1, one); + Assert.Equal("hi", two); + Assert.True(three); + } + + [Fact] + public void TestEnum_Undecorated() + { + var one = _converter.Convert("'one'"); + var twoThree = _converter.Convert("'two','three'"); + + Assert.Equal(UndecoratedEnum.One, one); + Assert.Equal(new[] { UndecoratedEnum.Two, UndecoratedEnum.Three }, twoThree); + } + + [Fact] + public void TestEnum_WithEnumMember() + { + var goose = _converter.Convert("'goose'"); + var dog = _converter.Convert("'dog'"); + + Assert.Equal(DecoratedEnum.Duck, goose); + Assert.Equal(DecoratedEnum.Dog, dog); + Assert.Throws(() => _converter.Convert("'missing'")); + } + + private ExpressionAst GetExpressionAstFromStatementAst(StatementAst statementAst) + { + return ((PipelineAst)statementAst).GetPureExpression(); + } + } + + public enum UndecoratedEnum + { + One, + Two, + Three + } + + public enum DecoratedEnum + { + [EnumMember(Value = "goose")] + Duck, + + Missing, + + [EnumMember] + Dog, + } + + public class SimpleFieldObject + { + public string Field; + } + + public class SimplePropertyObject + { + public string Property { get; set; } + } + + public class SimpleReadOnlyFieldObject + { + public SimpleReadOnlyFieldObject(string readOnlyField) + { + ReadOnlyField = readOnlyField; + } + + public readonly string ReadOnlyField; + } + + public class SimpleReadOnlyPropertyObject + { + public SimpleReadOnlyPropertyObject(string readOnlyProperty) + { + ReadOnlyProperty = readOnlyProperty; + } + + public string ReadOnlyProperty { get; } + } + + public class PartiallySettableObject + { + public PartiallySettableObject(string name) + { + Name = name; + } + + public string Name { get; } + + public int Count { get; set; } = 1; + } + + public class NumericObject + { + public sbyte SByte; + + public byte Byte; + + public short Short; + + public int Int; + + public long Long; + + public ushort UShort; + + public uint UInt; + + public ulong ULong; + + public decimal Decimal; + + public float Float; + + public double Double; + } + + public class DateTimeObject + { + public DateTime DateTime; + } + + [DataContract] + public class DataContractObject + { + [DataMember] + public string FirstName { get; set; } + + [DataMember(Name = "FamilyName")] + public string LastName { get; set; } + + public string MiddleName { get; set; } + } + + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] + public class JsonObject + { + [JsonConstructor] + public JsonObject(string name) + { + Name = name; + } + + [JsonProperty] + public int Count { get; set; } + + [JsonProperty(PropertyName = "FullName")] + public string Name { get; } + + public string ShortName { get; set; } + } + + [JsonObject(MemberSerialization = MemberSerialization.OptOut)] + public class JsonObject2 + { + [JsonConstructor] + public JsonObject2(string name) + { + Name = name; + } + + [JsonProperty] + public int Count { get; set; } + + [JsonProperty(PropertyName = "FullName")] + public string Name { get; } + + public string ShortName { get; set; } + + [JsonIgnore] + public string Sign { get; set; } + } + + public class CompositeObject + { + public string Name { get; set; } + + public SimpleFieldObject Simple { get; set; } + + public JsonObject SubObject { get; set; } + } + + + public class InjectedCompositeObject + { + [JsonConstructor] + public InjectedCompositeObject(string name, SimpleFieldObject simple, JsonObject subObject) + { + Name = name; + Simple = simple; + SubObject = subObject; + } + + public string Name { get; } + + public SimpleFieldObject Simple { get; } + + public JsonObject SubObject { get; } + } +} diff --git a/Tests/ScriptAnalyzer2.Test/ScriptAnalyzer2.Test.csproj b/Tests/ScriptAnalyzer2.Test/ScriptAnalyzer2.Test.csproj new file mode 100644 index 000000000..ace522536 --- /dev/null +++ b/Tests/ScriptAnalyzer2.Test/ScriptAnalyzer2.Test.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp3.1 + false + ScriptAnalyzer2.Test + + + + + + + + + + + + + +