diff --git a/Engine/Extensions.cs b/Engine/Extensions.cs index 1db10a2b6..a4698e655 100644 --- a/Engine/Extensions.cs +++ b/Engine/Extensions.cs @@ -30,6 +30,18 @@ public static Range ToRange(this IScriptExtent extent) extent.EndColumnNumber); } + /// + /// Get the parameter Asts from a function definition Ast. + /// + /// If not parameters are found, return null. + /// + public static IEnumerable GetParameterAsts( + this FunctionDefinitionAst functionDefinitionAst) + { + ParamBlockAst paramBlockAst; + return functionDefinitionAst.GetParameterAsts(out paramBlockAst); + } + /// /// Get the parameter Asts from a function definition Ast. /// @@ -41,6 +53,8 @@ public static IEnumerable GetParameterAsts( this FunctionDefinitionAst functionDefinitionAst, out ParamBlockAst paramBlockAst) { + // todo instead of returning null return an empty enumerator if no parameter is found + // this removes the burden from the user for null checking. paramBlockAst = null; if (functionDefinitionAst.Parameters != null) { @@ -114,6 +128,16 @@ public static NamedAttributeArgumentAst GetSupportsShouldProcessAst(this Attribu return null; } + + /// + /// Return the boolean value of a named attribute argument. + /// + public static bool GetValue(this NamedAttributeArgumentAst attrAst) + { + ExpressionAst argumentAst; + return attrAst.GetValue(out argumentAst); + } + /// /// Return the boolean value of a named attribute argument. /// diff --git a/RuleDocumentation/UseIdenticalMandatoryParametersForDSC.md b/RuleDocumentation/UseIdenticalMandatoryParametersForDSC.md index 04ae5c6bb..053925f16 100644 --- a/RuleDocumentation/UseIdenticalMandatoryParametersForDSC.md +++ b/RuleDocumentation/UseIdenticalMandatoryParametersForDSC.md @@ -4,50 +4,74 @@ ## Description -The `Get-TargetResource`, `Test-TargetResource` and `Set-TargetResource` functions of DSC Resource must have the same mandatory parameters. +For script based DSC resources, if a property is declared with attributes `Key` of `Required` in a mof file, then is should be present as a mandatory parameter in the corresponding `Get-TargetResource`, `Set-TargetResource` and `Test-TargetResource` functions. ## How -Correct the mandatory parameters for the functions in DSC resource. +Make sure all the properties with `Key` and `Required` attributes have equivalent mandatory parameters in the `Get/Set/Test` functions. ## Example +Consider the following `mof` file. + +```powershell +class WaitForAny : OMI_BaseResource +{ + [key, Description("Name of Resource on remote machine")] + string Name; + + [required, Description("List of remote machines")] + string NodeName[]; +}; +``` + ### Wrong ``` PowerShell function Get-TargetResource { - [OutputType([Hashtable])] + [CmdletBinding()] param ( - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [String] - $Name + $Message ) - ... } function Set-TargetResource { + [CmdletBinding()] param ( - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $Message, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [String] - $TargetName + $Name ) - ... } function Test-TargetResource { - [OutputType([System.Boolean])] + [CmdletBinding()] param ( - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $Message, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [String] $Name ) - ... } ``` @@ -56,36 +80,52 @@ function Test-TargetResource ``` PowerShell function Get-TargetResource { - [OutputType([Hashtable])] + [CmdletBinding()] param ( - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $Message, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [String] $Name ) - ... } function Set-TargetResource { + [CmdletBinding()] param ( - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $Message, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [String] $Name ) - ... } function Test-TargetResource { - [OutputType([System.Boolean])] + [CmdletBinding()] param ( - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $Message, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [String] $Name ) - ... } ``` diff --git a/Rules/Strings.resx b/Rules/Strings.resx index 87e87813f..f954248dc 100644 --- a/Rules/Strings.resx +++ b/Rules/Strings.resx @@ -619,7 +619,7 @@ The Get/Test/Set TargetResource functions of DSC resource must have the same mandatory parameters. - The mandatory parameter '{0}' is not present in '{1}' DSC resource function(s). + The '{0}' parameter '{1}' is not present in '{2}' DSC resource function(s). UseIdenticalMandatoryParametersForDSC diff --git a/Rules/UseIdenticalMandatoryParametersDSC.cs b/Rules/UseIdenticalMandatoryParametersDSC.cs index 396770566..60172a58b 100644 --- a/Rules/UseIdenticalMandatoryParametersDSC.cs +++ b/Rules/UseIdenticalMandatoryParametersDSC.cs @@ -12,13 +12,17 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Management.Automation.Language; -using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; #if !CORECLR using System.ComponentModel.Composition; #endif using System.Globalization; +using System.IO; +using System.Linq; +using System.Management.Automation.Language; +using Microsoft.Management.Infrastructure; +using Microsoft.PowerShell.DesiredStateConfiguration.Internal; +using Microsoft.Windows.PowerShell.ScriptAnalyzer.Extensions; +using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules { @@ -26,11 +30,17 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules /// UseIdenticalMandatoryParametersDSC: Check that the Get/Test/Set TargetResource /// have identical mandatory parameters. /// - #if !CORECLR -[Export(typeof(IDSCResourceRule))] +#if !CORECLR + [Export(typeof(IDSCResourceRule))] #endif public class UseIdenticalMandatoryParametersDSC : IDSCResourceRule { + private bool isDSCClassCacheInitialized = false; + private Ast ast; + private string fileName; + private IDictionary propAttrDict; + private IEnumerable resourceFunctions; + /// /// AnalyzeDSCResource: Analyzes given DSC Resource /// @@ -39,74 +49,53 @@ public class UseIdenticalMandatoryParametersDSC : IDSCResourceRule /// The results of the analysis public IEnumerable AnalyzeDSCResource(Ast ast, string fileName) { - if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage); + if (ast == null) + { + throw new ArgumentNullException(Strings.NullAstErrorMessage); + } - // Expected TargetResource functions in the DSC Resource module - List expectedTargetResourceFunctionNames = new List(new string[] { "Set-TargetResource", "Test-TargetResource", "Get-TargetResource" }); + if (fileName == null) + { + throw new ArgumentNullException(nameof(fileName)); + } - IEnumerable functionDefinitionAsts = Helper.Instance.DscResourceFunctions(ast); + // Get the keys in the corresponding mof file + this.ast = ast; + this.fileName = fileName; + this.propAttrDict = GetKeys(fileName); + this.resourceFunctions = Helper.Instance.DscResourceFunctions(ast) + .Cast() + .ToArray(); - // Dictionary to keep track of Mandatory parameters and their presence in Get/Test/Set TargetResource cmdlets - Dictionary> mandatoryParameters = new Dictionary>(StringComparer.OrdinalIgnoreCase); + var funcManParamMap = resourceFunctions + .ToDictionary( + f => f.Name, + f => Tuple.Create( + f, + GetMandatoryParameters(f) + .Select(p => p.Name.VariablePath.UserPath) + .ToArray())); // Loop through Set/Test/Get TargetResource DSC cmdlets - foreach (FunctionDefinitionAst functionDefinitionAst in functionDefinitionAsts) + foreach (var kvp in funcManParamMap) { - IEnumerable funcParamAsts = functionDefinitionAst.FindAll(item => item is ParameterAst, true); - - // Loop through the parameters for each cmdlet - foreach (ParameterAst paramAst in funcParamAsts) + var functionDefinitionAst = kvp.Value.Item1; + var manParams = kvp.Value.Item2; + foreach (var key in propAttrDict.Keys.Except(manParams)) { - // Loop through the attributes for each of those cmdlets - foreach (var paramAstAttributes in paramAst.Attributes) - { - if (paramAstAttributes is AttributeAst) - { - var namedArguments = (paramAstAttributes as AttributeAst).NamedArguments; - if (namedArguments != null) - { - // Loop through the named attribute arguments for each parameter - foreach (NamedAttributeArgumentAst namedArgument in namedArguments) - { - // Look for Mandatory parameters - if (String.Equals(namedArgument.ArgumentName, "mandatory", StringComparison.OrdinalIgnoreCase)) - { - // Covers Case - [Parameter(Mandatory)] and [Parameter(Mandatory)=$true] - if (namedArgument.ExpressionOmitted || (!namedArgument.ExpressionOmitted && String.Equals(namedArgument.Argument.Extent.Text, "$true", StringComparison.OrdinalIgnoreCase))) - { - if (mandatoryParameters.ContainsKey(paramAst.Name.VariablePath.UserPath)) - { - mandatoryParameters[paramAst.Name.VariablePath.UserPath].Add(functionDefinitionAst.Name); - } - else - { - List functionNames = new List(); - functionNames.Add(functionDefinitionAst.Name); - mandatoryParameters.Add(paramAst.Name.VariablePath.UserPath, functionNames); - } - } - } - } - } - } - } + yield return new DiagnosticRecord( + string.Format( + CultureInfo.InvariantCulture, + Strings.UseIdenticalMandatoryParametersDSCError, + propAttrDict[key], + key, + functionDefinitionAst.Name), + Helper.Instance.GetScriptExtentForFunctionName(functionDefinitionAst), + GetName(), + DiagnosticSeverity.Error, + fileName); } } - - // Get the mandatory parameter names that do not appear in all the DSC Resource cmdlets - IEnumerable paramNames = mandatoryParameters.Where(x => x.Value.Count < expectedTargetResourceFunctionNames.Count).Select(x => x.Key); - - if (paramNames.Count() > 0) - { - foreach (string paramName in paramNames) - { - List functionsNotContainingParam = expectedTargetResourceFunctionNames.Except(mandatoryParameters[paramName]).ToList(); - yield return new DiagnosticRecord(string.Format(CultureInfo.InvariantCulture, Strings.UseIdenticalMandatoryParametersDSCError, paramName, string.Join(", ", functionsNotContainingParam.ToArray())), - ast.Extent, GetName(), DiagnosticSeverity.Error, fileName); - } - - } - } /// @@ -117,7 +106,7 @@ public IEnumerable AnalyzeDSCResource(Ast ast, string fileName /// public IEnumerable AnalyzeDSCClass(Ast ast, string fileName) { - // For DSC Class based resource, this rule is N/A, since the Class Properties + // For DSC Class based resource, this rule is N/A, since the Class Properties // are declared only once and available to Get(), Set(), Test() functions return Enumerable.Empty(); } @@ -127,7 +116,7 @@ public IEnumerable AnalyzeDSCClass(Ast ast, string fileName) /// /// The name of this rule public string GetName() - { + { return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.UseIdenticalMandatoryParametersDSCName); } @@ -173,8 +162,146 @@ public string GetSourceName() { return string.Format(CultureInfo.CurrentCulture, Strings.DSCSourceName); } - } + private IEnumerable GetMandatoryParameters(FunctionDefinitionAst functionDefinitionAst) + { + return functionDefinitionAst.GetParameterAsts()?.Where(IsParameterMandatory) ?? + Enumerable.Empty(); + } + + private bool IsParameterMandatory(ParameterAst paramAst) + { + var attrAsts = from attr in paramAst.Attributes + where IsParameterAttribute(attr) && attr is AttributeAst + select (AttributeAst)attr; + + return attrAsts.Any(a => a.NamedArguments.Any(IsNamedAttributeArgumentMandatory)); + } + + private bool IsParameterAttribute(AttributeBaseAst attributeBaseAst) + { + return attributeBaseAst.TypeName.GetReflectionType().Name.Equals("ParameterAttribute"); + } + + private bool IsNamedAttributeArgumentMandatory(NamedAttributeArgumentAst namedAttrArgAst) + { + return namedAttrArgAst.ArgumentName.Equals("mandatory", StringComparison.OrdinalIgnoreCase) && + namedAttrArgAst.GetValue(); + } + + private IDictionary GetKeys(string fileName) + { + var moduleInfo = GetModuleInfo(fileName); + var emptyDictionary = new Dictionary(); + if (moduleInfo == null) + { + return emptyDictionary; + } + + var mofFilepath = GetMofFilepath(fileName); + if (mofFilepath == null) + { + return emptyDictionary; + } + + var errors = new System.Collections.ObjectModel.Collection(); + var keys = new List(); + List cimClasses = null; + try + { + if (!isDSCClassCacheInitialized) + { + DscClassCache.Initialize(); + isDSCClassCacheInitialized = true; + } + + cimClasses = DscClassCache.ImportClasses(mofFilepath, moduleInfo, errors); + } + catch + { + // todo log the error + } + + var cimClass = cimClasses?.FirstOrDefault(); + var cimSuperClassProperties = new HashSet( + cimClass?.CimSuperClass.CimClassProperties.Select(p => p.Name) ?? + Enumerable.Empty()); + + return cimClass? + .CimClassProperties? + .Where(p => (p.Flags.HasFlag(CimFlags.Key) || + p.Flags.HasFlag(CimFlags.Required)) && + !cimSuperClassProperties.Contains(p.Name)) + .ToDictionary( + p => p.Name, + p => p.Flags.HasFlag(CimFlags.Key) ? + CimFlags.Key.ToString() : + CimFlags.Required.ToString()) ?? + emptyDictionary; + } + + private string GetMofFilepath(string filePath) + { + var mofFilePath = Path.Combine( + Path.GetDirectoryName(filePath), + Path.GetFileNameWithoutExtension(filePath)) + ".schema.mof"; + + return File.Exists(mofFilePath) ? mofFilePath : null; + } + + private Tuple GetModuleInfo(string fileName) + { + var moduleManifest = GetModuleManifest(fileName); + if (moduleManifest == null) + { + return null; + } + + var moduleName = Path.GetFileNameWithoutExtension(moduleManifest.Name); + Token[] tokens; + ParseError[] parseErrors; + var ast = Parser.ParseFile(moduleManifest.FullName, out tokens, out parseErrors); + if ((parseErrors != null && parseErrors.Length > 0) || ast == null) + { + return null; + } + + var foundAst = ast.Find(x => x is HashtableAst, false); + if (foundAst == null) + { + return null; + } + + var moduleVersionKvp = ((HashtableAst)foundAst).KeyValuePairs.FirstOrDefault(t => + { + var keyAst = t.Item1 as StringConstantExpressionAst; + return keyAst != null && + keyAst.Value.Equals("ModuleVersion", StringComparison.OrdinalIgnoreCase); + }); + + if (moduleVersionKvp == null) + { + return null; + } + + var valueAst = moduleVersionKvp.Item2.Find(a => a is StringConstantExpressionAst, false); + var versionText = valueAst == null ? null : ((StringConstantExpressionAst)valueAst).Value; + Version version; + Version.TryParse(versionText, out version); // this handles null so no need to check versionText + return version == null ? null : Tuple.Create(moduleName, version); + } + + private FileInfo GetModuleManifest(string fileName) + { + return Directory + .GetParent(fileName)? + .Parent? + .Parent? + .GetFiles("*.psd1") + .Where(f => Helper.IsModuleManifest(f.FullName)) + .FirstOrDefault(); + } + } } diff --git a/Tests/DisabledRules/ProvideVerboseMessage.tests.ps1 b/Tests/DisabledRules/ProvideVerboseMessage.tests.ps1 index 6ae8c639c..78f3d1a27 100644 --- a/Tests/DisabledRules/ProvideVerboseMessage.tests.ps1 +++ b/Tests/DisabledRules/ProvideVerboseMessage.tests.ps1 @@ -3,7 +3,7 @@ $violationMessage = [regex]::Escape("There is no call to Write-Verbose in the fu $violationName = "PSProvideVerboseMessage" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path $violations = Invoke-ScriptAnalyzer $directory\BadCmdlet.ps1 | Where-Object {$_.RuleName -eq $violationName} -$dscViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $directory\DSCResources\MyDscResource\MyDscResource.psm1 | Where-Object {$_.RuleName -eq $violationName} +$dscViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $directory\DSCResourceModule\DSCResources\MyDscResource\MyDscResource.psm1 | Where-Object {$_.RuleName -eq $violationName} $noViolations = Invoke-ScriptAnalyzer $directory\GoodCmdlet.ps1 | Where-Object {$_.RuleName -eq $violationName} Describe "ProvideVerboseMessage" { @@ -26,4 +26,4 @@ Describe "ProvideVerboseMessage" { $noViolations.Count | Should Be 0 } } -} \ No newline at end of file +} diff --git a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 index fab8f4bd4..77c5be507 100644 --- a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 +++ b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 @@ -321,6 +321,7 @@ Describe "Test Severity" { It "works for dsc rules" { $testDataPath = [System.IO.Path]::Combine($(Split-Path $directory -Parent), ` 'Rules', ` + 'DSCResourceModule', ` 'DSCResources', ` 'MSFT_WaitForAll', ` 'MSFT_WaitForAll.psm1') @@ -398,4 +399,4 @@ Describe "Test CustomizedRulePath" { } } } -} \ No newline at end of file +} diff --git a/Tests/Engine/ModuleDependencyHandler.tests.ps1 b/Tests/Engine/ModuleDependencyHandler.tests.ps1 index 8a28d5028..166bd01e4 100644 --- a/Tests/Engine/ModuleDependencyHandler.tests.ps1 +++ b/Tests/Engine/ModuleDependencyHandler.tests.ps1 @@ -139,8 +139,9 @@ Describe "Resolve DSC Resource Dependency" { Context "Invoke-ScriptAnalyzer without switch but with module in temp path" { $oldEnvVars = Get-Item Env:\* | Sort-Object -Property Key $moduleName = "MyDscResource" - $modulePath = Join-Path (Join-Path (Join-Path (Split-Path $directory) "Rules") "DSCResources") $moduleName - # Save the current environment variables + $modulePath = "$(Split-Path $directory)\Rules\DSCResourceModule\DSCResources\$moduleName" + + # Save the current environment variables $oldLocalAppDataPath = $env:LOCALAPPDATA $oldTempPath = $env:TEMP $oldPSModulePath = $env:PSModulePath @@ -190,4 +191,4 @@ Describe "Resolve DSC Resource Dependency" { Test-EnvironmentVariables($oldEnvVars) } } -} \ No newline at end of file +} diff --git a/Tests/Rules/AvoidGlobalOrUnitializedVars.tests.ps1 b/Tests/Rules/AvoidGlobalOrUnitializedVars.tests.ps1 index 0c5a20d63..92d396412 100644 --- a/Tests/Rules/AvoidGlobalOrUnitializedVars.tests.ps1 +++ b/Tests/Rules/AvoidGlobalOrUnitializedVars.tests.ps1 @@ -10,7 +10,7 @@ $directory = Split-Path -Parent $MyInvocation.MyCommand.Path $violations = Invoke-ScriptAnalyzer $directory\AvoidGlobalOrUnitializedVars.ps1 # PSAvoidUninitializedVariable rule has been deprecated -# $dscResourceViolations = Invoke-ScriptAnalyzer $directory\DSCResources\MSFT_WaitForAny\MSFT_WaitForAny.psm1 | Where-Object {$_.RuleName -eq $nonInitializedName} +# $dscResourceViolations = Invoke-ScriptAnalyzer $directory\DSCResourceModule\DSCResources\MSFT_WaitForAny\MSFT_WaitForAny.psm1 | Where-Object {$_.RuleName -eq $nonInitializedName} $globalViolations = $violations | Where-Object {$_.RuleName -eq $globalName} diff --git a/Tests/Rules/DSCResourceModule/DSCResourceModule.psd1 b/Tests/Rules/DSCResourceModule/DSCResourceModule.psd1 new file mode 100644 index 000000000..192d652f1 --- /dev/null +++ b/Tests/Rules/DSCResourceModule/DSCResourceModule.psd1 @@ -0,0 +1,123 @@ +# +# Module manifest for module 'DSCResources' +# +# Generated by: kborle +# +# Generated on: 6/2/2017 +# + +@{ + +# Script module or binary module file associated with this manifest. +# RootModule = '' + +# Version number of this module. +ModuleVersion = '1.0' + +# Supported PSEditions +# CompatiblePSEditions = @() + +# ID used to uniquely identify this module +GUID = 'f5e6cc2a-5500-4592-bbe2-ef033754b56f' + +# Author of this module +Author = 'kborle' + +# Company or vendor of this module +CompanyName = 'Unknown' + +# Copyright statement for this module +Copyright = '(c) 2017 kborle. All rights reserved.' + +# Description of the functionality provided by this module +# Description = '' + +# Minimum version of the Windows PowerShell engine required by this module +# PowerShellVersion = '' + +# Name of the Windows PowerShell host required by this module +# PowerShellHostName = '' + +# Minimum version of the Windows PowerShell host required by this module +# PowerShellHostVersion = '' + +# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# DotNetFrameworkVersion = '' + +# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# CLRVersion = '' + +# Processor architecture (None, X86, Amd64) required by this module +# ProcessorArchitecture = '' + +# Modules that must be imported into the global environment prior to importing this module +# RequiredModules = @() + +# Assemblies that must be loaded prior to importing this module +# RequiredAssemblies = @() + +# Script files (.ps1) that are run in the caller's environment prior to importing this module. +# ScriptsToProcess = @() + +# Type files (.ps1xml) to be loaded when importing this module +# TypesToProcess = @() + +# Format files (.ps1xml) to be loaded when importing this module +# FormatsToProcess = @() + +# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess +# NestedModules = @() + +# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. +FunctionsToExport = @() + +# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. +CmdletsToExport = @() + +# Variables to export from this module +VariablesToExport = '*' + +# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. +AliasesToExport = @() + +# DSC resources to export from this module +# DscResourcesToExport = @() + +# List of all modules packaged with this module +# ModuleList = @() + +# List of all files packaged with this module +# FileList = @() + +# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. +PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + # Tags = @() + + # A URL to the license for this module. + # LicenseUri = '' + + # A URL to the main website for this project. + # ProjectUri = '' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + # ReleaseNotes = '' + + } # End of PSData hashtable + +} # End of PrivateData hashtable + +# 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/Tests/Rules/DSCResources/BadDscResource/BadDscResource.psd1 b/Tests/Rules/DSCResourceModule/DSCResources/BadDscResource/BadDscResource.psd1 similarity index 100% rename from Tests/Rules/DSCResources/BadDscResource/BadDscResource.psd1 rename to Tests/Rules/DSCResourceModule/DSCResources/BadDscResource/BadDscResource.psd1 diff --git a/Tests/Rules/DSCResources/BadDscResource/BadDscResource.psm1 b/Tests/Rules/DSCResourceModule/DSCResources/BadDscResource/BadDscResource.psm1 similarity index 100% rename from Tests/Rules/DSCResources/BadDscResource/BadDscResource.psm1 rename to Tests/Rules/DSCResourceModule/DSCResources/BadDscResource/BadDscResource.psm1 diff --git a/Tests/Rules/DSCResources/MSFT_WaitForAll/MSFT_WaitForAll.psm1 b/Tests/Rules/DSCResourceModule/DSCResources/MSFT_WaitForAll/MSFT_WaitForAll.psm1 similarity index 100% rename from Tests/Rules/DSCResources/MSFT_WaitForAll/MSFT_WaitForAll.psm1 rename to Tests/Rules/DSCResourceModule/DSCResources/MSFT_WaitForAll/MSFT_WaitForAll.psm1 diff --git a/Tests/Rules/DSCResourceModule/DSCResources/MSFT_WaitForAll/MSFT_WaitForAll.schema.mof b/Tests/Rules/DSCResourceModule/DSCResources/MSFT_WaitForAll/MSFT_WaitForAll.schema.mof new file mode 100644 index 000000000..8271cc337 --- /dev/null +++ b/Tests/Rules/DSCResourceModule/DSCResources/MSFT_WaitForAll/MSFT_WaitForAll.schema.mof @@ -0,0 +1,26 @@ +#pragma namespace("\\\\.\\root\\microsoft\\windows\\DesiredStateConfiguration") + +[ClassVersion("1.0.0"), FriendlyName("WaitForAll")] +class MSFT_WaitForAll : OMI_BaseResource +{ + [key, Description("Name of Resource on remote machine")] + string ResourceName; + + [required, Description("List of remote machines")] + string NodeName[]; + + [required, EmbeddedInstance("MSFT_Credential"), Description("Credential to access all remote machines")] + String Credential; + + [write, Description("Time between various retries. Lower bound is 1.")] + Uint64 RetryIntervalSec; + + [write, Description("Maximum number of retries to check the state of resource.")] + Uint32 RetryCount; + + [write, Description("Number of machines to connect simultaneously. Default is new-cimsession default")] + Uint32 ThrottleLimit; + + [read, Description("List of remote machines in desired state.")] + String NodesInDesiredState; +}; diff --git a/Tests/Rules/DSCResources/MSFT_WaitForAll/en-US/MSFT_WaitForAll.schema.mfl b/Tests/Rules/DSCResourceModule/DSCResources/MSFT_WaitForAll/en-US/MSFT_WaitForAll.schema.mfl similarity index 100% rename from Tests/Rules/DSCResources/MSFT_WaitForAll/en-US/MSFT_WaitForAll.schema.mfl rename to Tests/Rules/DSCResourceModule/DSCResources/MSFT_WaitForAll/en-US/MSFT_WaitForAll.schema.mfl diff --git a/Tests/Rules/DSCResources/MSFT_WaitForAny/MSFT_WaitForAny.psm1 b/Tests/Rules/DSCResourceModule/DSCResources/MSFT_WaitForAny/MSFT_WaitForAny.psm1 similarity index 100% rename from Tests/Rules/DSCResources/MSFT_WaitForAny/MSFT_WaitForAny.psm1 rename to Tests/Rules/DSCResourceModule/DSCResources/MSFT_WaitForAny/MSFT_WaitForAny.psm1 diff --git a/Tests/Rules/DSCResourceModule/DSCResources/MSFT_WaitForAny/MSFT_WaitForAny.schema.mof b/Tests/Rules/DSCResourceModule/DSCResources/MSFT_WaitForAny/MSFT_WaitForAny.schema.mof new file mode 100644 index 000000000..fb03a004d --- /dev/null +++ b/Tests/Rules/DSCResourceModule/DSCResources/MSFT_WaitForAny/MSFT_WaitForAny.schema.mof @@ -0,0 +1,27 @@ +#pragma namespace("\\\\.\\root\\microsoft\\windows\\DesiredStateConfiguration") + +[ClassVersion("1.0.0"), FriendlyName("WaitForAny")] +class MSFT_WaitForAny : OMI_BaseResource +{ + [key, Description("Name of Resource on remote machine")] + string ResourceName; + + [required, Description("List of remote machines")] + string NodeName[]; + + [required, EmbeddedInstance("MSFT_Credential"), Description("Credential to access all remote machines")] + String Credential; + + [write, Description("Time between various retries. Lower bound is 1.")] + Uint64 RetryIntervalSec; + + [write, Description("Maximum number of retries to check the state of resource.")] + Uint32 RetryCount; + + [write, Description("Number of machines to connect simultaneously. Default is new-cimsession default")] + Uint32 ThrottleLimit; + + [read, Description("List of remote machines in desired state.")] + String NodesInDesiredState; + +}; diff --git a/Tests/Rules/DSCResources/MSFT_WaitForAny/en-US/MSFT_WaitForAny.schema.mfl b/Tests/Rules/DSCResourceModule/DSCResources/MSFT_WaitForAny/en-US/MSFT_WaitForAny.schema.mfl similarity index 100% rename from Tests/Rules/DSCResources/MSFT_WaitForAny/en-US/MSFT_WaitForAny.schema.mfl rename to Tests/Rules/DSCResourceModule/DSCResources/MSFT_WaitForAny/en-US/MSFT_WaitForAny.schema.mfl diff --git a/Tests/Rules/DSCResourceModule/DSCResources/MSFT_WaitForAnyNoIdenticalMandatoryParameter/MSFT_WaitForAnyNoIdenticalMandatoryParameter.psm1 b/Tests/Rules/DSCResourceModule/DSCResources/MSFT_WaitForAnyNoIdenticalMandatoryParameter/MSFT_WaitForAnyNoIdenticalMandatoryParameter.psm1 new file mode 100644 index 000000000..d2aa631c7 --- /dev/null +++ b/Tests/Rules/DSCResourceModule/DSCResources/MSFT_WaitForAnyNoIdenticalMandatoryParameter/MSFT_WaitForAnyNoIdenticalMandatoryParameter.psm1 @@ -0,0 +1,161 @@ +# +# WaitForAny +# + +# +# The Get-TargetResource cmdlet. +# +function Get-TargetResource { + + # This is missing `Key` properties `ResourceName` and `Dummy` + param + ( + [parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string[]] $NodeName, + + [parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [PSCredential] $Credential, + + [parameter(Mandatory)] + $ParamOnlyInGet, + + [ValidateRange(1, [Uint64]::MaxValue)] + [Uint64] $RetryIntervalSec = 1, + + [Uint32] $RetryCount = 0, + + [Uint32] $ThrottleLimit = 32 #Powershell New-CimSession default throttle value + ) + + Write-Verbose "In Get-TargetResource" + + Import-Module $PSScriptRoot\..\..\PSDSCxMachine.psm1 + + $b = @{"hash" = "table"} + + if ($true) { + return $b; + } + elseif ($c) { + return @{"hash2" = "table2"} + } + else { + # can't determine type of c so error should not be raised as we're trying to be conservative + return $c; + } +} + +# +# The Set-TargetResource cmdlet. +# +function Set-TargetResource { + + # This is missing `required` property `credential` and `key` property `Dummy` + param + ( + [parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string] $ResourceName, + + [parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string[]] $NodeName, + + [parameter(Mandatory)] + $ParameterOnlyInSet, + + [ValidateRange(1, [Uint64]::MaxValue)] + [Uint64] $RetryIntervalSec = 1, + + [Uint32] $RetryCount = 0, + + [Uint32] $ThrottleLimit = 32 #Powershell New-CimSession default throttle value + ) + + Write-Verbose "In Set-TargetResource" + + Import-Module $PSScriptRoot\..\..\PSDSCxMachine.psm1 + + if ($PSBoundParameters["Verbose"]) { + Write-Verbose "Calling xMachine with Verbose parameter" + + PSDSCxMachine\Set-_InternalPSDscXMachineTR ` + -RemoteResourceId $ResourceName ` + -RemoteMachine $NodeName ` + -RemoteCredential $Credential ` + -MinimalNumberOfMachineInState 1 ` + -RetryIntervalSec $RetryIntervalSec ` + -RetryCount $RetryCount ` + -ThrottleLimit $ThrottleLimit ` + -Verbose + } + else { + PSDSCxMachine\Set-_InternalPSDscXMachineTR ` + -RemoteResourceId $ResourceName ` + -RemoteMachine $NodeName ` + -RemoteCredential $Credential ` + -MinimalNumberOfMachineInState 1 ` + -RetryIntervalSec $RetryIntervalSec ` + -RetryCount $RetryCount ` + -ThrottleLimit $ThrottleLimit + } +} + +# +# Test-TargetResource +# +# +function Test-TargetResource { + + # This is missing `key` property `Dummy` + param + ( + [parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string] $ResourceName, + + [parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string[]] $NodeName, + + [parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [PSCredential] $Credential, + + [parameter(Mandatory)] + $ParameterOnlyInTest, + + [ValidateRange(1,[Uint64]::MaxValue)] + [Uint64] $RetryIntervalSec = 1, + + [Uint32] $RetryCount = 0, + + [Uint32] $ThrottleLimit = 32 #Powershell New-CimSession default throttle value + ) + + Write-Verbose "In Test-TargetResource" + + Import-Module $PSScriptRoot\..\..\PSDSCxMachine.psm1 + + $a = $true + $a + + if ($true) { + $false; + } + elseif ($b) { + return $a -or $true + } + elseif ($c) { + return $false; + } + else { + return $true + } +} + + + +Export-ModuleMember -Function *-TargetResource diff --git a/Tests/Rules/DSCResourceModule/DSCResources/MSFT_WaitForAnyNoIdenticalMandatoryParameter/MSFT_WaitForAnyNoIdenticalMandatoryParameter.schema.mof b/Tests/Rules/DSCResourceModule/DSCResources/MSFT_WaitForAnyNoIdenticalMandatoryParameter/MSFT_WaitForAnyNoIdenticalMandatoryParameter.schema.mof new file mode 100644 index 000000000..2c56539c5 --- /dev/null +++ b/Tests/Rules/DSCResourceModule/DSCResources/MSFT_WaitForAnyNoIdenticalMandatoryParameter/MSFT_WaitForAnyNoIdenticalMandatoryParameter.schema.mof @@ -0,0 +1,31 @@ +#pragma namespace("\\\\.\\root\\microsoft\\windows\\DesiredStateConfiguration") + +[ClassVersion("1.0.0"), FriendlyName("WaitForAny")] + +class MSFT_WaitForAnyNoIdenticalMandatoryParameter : OMI_BaseResource +{ + [key, Description("Name of Resource on remote machine")] + string ResourceName; + + [key, Description("dummy variable")] + string Dummy; + + [required, Description("List of remote machines")] + string NodeName[]; + + [required, EmbeddedInstance("MSFT_Credential"), Description("Credential to access all remote machines")] + String Credential; + + [write, Description("Time between various retries. Lower bound is 1.")] + Uint64 RetryIntervalSec; + + [write, Description("Maximum number of retries to check the state of resource.")] + Uint32 RetryCount; + + [write, Description("Number of machines to connect simultaneously. Default is new-cimsession default")] + Uint32 ThrottleLimit; + + [read, Description("List of remote machines in desired state.")] + String NodesInDesiredState; + +}; diff --git a/Tests/Rules/DSCResourceModule/DSCResources/MSFT_WaitForAnyNoIdenticalMandatoryParameter/en-US/MSFT_WaitForAny.schema.mfl b/Tests/Rules/DSCResourceModule/DSCResources/MSFT_WaitForAnyNoIdenticalMandatoryParameter/en-US/MSFT_WaitForAny.schema.mfl new file mode 100644 index 000000000..5481208e6 Binary files /dev/null and b/Tests/Rules/DSCResourceModule/DSCResources/MSFT_WaitForAnyNoIdenticalMandatoryParameter/en-US/MSFT_WaitForAny.schema.mfl differ diff --git a/Tests/Rules/DSCResources/MyDscResource/MyDscResource.psd1 b/Tests/Rules/DSCResourceModule/DSCResources/MyDscResource/MyDscResource.psd1 similarity index 100% rename from Tests/Rules/DSCResources/MyDscResource/MyDscResource.psd1 rename to Tests/Rules/DSCResourceModule/DSCResources/MyDscResource/MyDscResource.psd1 diff --git a/Tests/Rules/DSCResources/MyDscResource/MyDscResource.psm1 b/Tests/Rules/DSCResourceModule/DSCResources/MyDscResource/MyDscResource.psm1 similarity index 100% rename from Tests/Rules/DSCResources/MyDscResource/MyDscResource.psm1 rename to Tests/Rules/DSCResourceModule/DSCResources/MyDscResource/MyDscResource.psm1 diff --git a/Tests/Rules/DSCResources/MSFT_WaitForAll/MSFT_WaitForAll.schema.mof b/Tests/Rules/DSCResources/MSFT_WaitForAll/MSFT_WaitForAll.schema.mof deleted file mode 100644 index 462d987b8..000000000 Binary files a/Tests/Rules/DSCResources/MSFT_WaitForAll/MSFT_WaitForAll.schema.mof and /dev/null differ diff --git a/Tests/Rules/DSCResources/MSFT_WaitForAny/MSFT_WaitForAny.schema.mof b/Tests/Rules/DSCResources/MSFT_WaitForAny/MSFT_WaitForAny.schema.mof deleted file mode 100644 index fd2a10878..000000000 Binary files a/Tests/Rules/DSCResources/MSFT_WaitForAny/MSFT_WaitForAny.schema.mof and /dev/null differ diff --git a/Tests/Rules/DscExamplesPresent.tests.ps1 b/Tests/Rules/DscExamplesPresent.tests.ps1 index 85ff335e4..3d2ff1488 100644 --- a/Tests/Rules/DscExamplesPresent.tests.ps1 +++ b/Tests/Rules/DscExamplesPresent.tests.ps1 @@ -6,12 +6,12 @@ $ruleName = "PSDSCDscExamplesPresent" if ($PSVersionTable.PSVersion -ge [Version]'5.0.0') { Describe "DscExamplesPresent rule in class based resource" { - - $examplesPath = "$currentPath\DSCResources\MyDscResource\Examples" - $classResourcePath = "$currentPath\DSCResources\MyDscResource\MyDscResource.psm1" + + $examplesPath = "$currentPath\DSCResourceModule\DSCResources\MyDscResource\Examples" + $classResourcePath = "$currentPath\DSCResourceModule\DSCResources\MyDscResource\MyDscResource.psm1" Context "When examples absent" { - + $violations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $classResourcePath | Where-Object {$_.RuleName -eq $ruleName} $violationMessage = "No examples found for resource 'FileResource'" @@ -24,7 +24,7 @@ if ($PSVersionTable.PSVersion -ge [Version]'5.0.0') { } } - Context "When examples present" { + Context "When examples present" { New-Item -Path $examplesPath -ItemType Directory New-Item -Path "$examplesPath\FileResource_Example.psm1" -ItemType File @@ -40,12 +40,12 @@ if ($PSVersionTable.PSVersion -ge [Version]'5.0.0') { } Describe "DscExamplesPresent rule in regular (non-class) based resource" { - - $examplesPath = "$currentPath\Examples" - $resourcePath = "$currentPath\DSCResources\MSFT_WaitForAll\MSFT_WaitForAll.psm1" + + $examplesPath = "$currentPath\DSCResourceModule\Examples" + $resourcePath = "$currentPath\DSCResourceModule\DSCResources\MSFT_WaitForAll\MSFT_WaitForAll.psm1" Context "When examples absent" { - + $violations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $resourcePath | Where-Object {$_.RuleName -eq $ruleName} $violationMessage = "No examples found for resource 'MSFT_WaitForAll'" @@ -58,7 +58,7 @@ Describe "DscExamplesPresent rule in regular (non-class) based resource" { } } - Context "When examples present" { + Context "When examples present" { New-Item -Path $examplesPath -ItemType Directory New-Item -Path "$examplesPath\MSFT_WaitForAll_Example.psm1" -ItemType File @@ -70,4 +70,4 @@ Describe "DscExamplesPresent rule in regular (non-class) based resource" { Remove-Item -Path $examplesPath -Recurse -Force } -} \ No newline at end of file +} diff --git a/Tests/Rules/DscTestsPresent.tests.ps1 b/Tests/Rules/DscTestsPresent.tests.ps1 index 01b85bf7b..d0d0f5e51 100644 --- a/Tests/Rules/DscTestsPresent.tests.ps1 +++ b/Tests/Rules/DscTestsPresent.tests.ps1 @@ -6,12 +6,12 @@ $ruleName = "PSDSCDscTestsPresent" if ($PSVersionTable.PSVersion -ge [Version]'5.0.0') { Describe "DscTestsPresent rule in class based resource" { - - $testsPath = "$currentPath\DSCResources\MyDscResource\Tests" - $classResourcePath = "$currentPath\DSCResources\MyDscResource\MyDscResource.psm1" + + $testsPath = "$currentPath\DSCResourceModule\DSCResources\MyDscResource\Tests" + $classResourcePath = "$currentPath\DSCResourceModule\DSCResources\MyDscResource\MyDscResource.psm1" Context "When tests absent" { - + $violations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $classResourcePath | Where-Object {$_.RuleName -eq $ruleName} $violationMessage = "No tests found for resource 'FileResource'" @@ -24,7 +24,7 @@ if ($PSVersionTable.PSVersion -ge [Version]'5.0.0') { } } - Context "When tests present" { + Context "When tests present" { New-Item -Path $testsPath -ItemType Directory New-Item -Path "$testsPath\FileResource_Test.psm1" -ItemType File @@ -40,12 +40,12 @@ if ($PSVersionTable.PSVersion -ge [Version]'5.0.0') { } Describe "DscTestsPresent rule in regular (non-class) based resource" { - - $testsPath = "$currentPath\Tests" - $resourcePath = "$currentPath\DSCResources\MSFT_WaitForAll\MSFT_WaitForAll.psm1" + + $testsPath = "$currentPath\DSCResourceModule\Tests" + $resourcePath = "$currentPath\DSCResourceModule\DSCResources\MSFT_WaitForAll\MSFT_WaitForAll.psm1" Context "When tests absent" { - + $violations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $resourcePath | Where-Object {$_.RuleName -eq $ruleName} $violationMessage = "No tests found for resource 'MSFT_WaitForAll'" @@ -58,7 +58,7 @@ Describe "DscTestsPresent rule in regular (non-class) based resource" { } } - Context "When tests present" { + Context "When tests present" { New-Item -Path $testsPath -ItemType Directory New-Item -Path "$testsPath\MSFT_WaitForAll_Test.psm1" -ItemType File @@ -70,4 +70,4 @@ Describe "DscTestsPresent rule in regular (non-class) based resource" { Remove-Item -Path $testsPath -Recurse -Force } -} \ No newline at end of file +} diff --git a/Tests/Rules/ProvideCommentHelp.tests.ps1 b/Tests/Rules/ProvideCommentHelp.tests.ps1 index 834765ec2..2a11cf062 100644 --- a/Tests/Rules/ProvideCommentHelp.tests.ps1 +++ b/Tests/Rules/ProvideCommentHelp.tests.ps1 @@ -22,7 +22,7 @@ $settings = @{ $violations = Invoke-ScriptAnalyzer $directory\BadCmdlet.ps1 | Where-Object {$_.RuleName -eq $violationName} if ($PSVersionTable.PSVersion -ge [Version]'5.0.0') { - $dscViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $directory\DSCResources\MyDscResource\MyDscResource.psm1 | Where-Object {$_.RuleName -eq $violationName} + $dscViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $directory\DSCResourceModule\DSCResources\MyDscResource\MyDscResource.psm1 | Where-Object {$_.RuleName -eq $violationName} } $noViolations = Invoke-ScriptAnalyzer $directory\GoodCmdlet.ps1 | Where-Object {$_.RuleName -eq $violationName} diff --git a/Tests/Rules/ReturnCorrectTypesForDSCFunctions.tests.ps1 b/Tests/Rules/ReturnCorrectTypesForDSCFunctions.tests.ps1 index 36a478e3a..34b406aa5 100644 --- a/Tests/Rules/ReturnCorrectTypesForDSCFunctions.tests.ps1 +++ b/Tests/Rules/ReturnCorrectTypesForDSCFunctions.tests.ps1 @@ -4,13 +4,13 @@ $violationMessageDSCResource = "Test-TargetResource function in DSC Resource sho $violationMessageDSCClass = "Get function in DSC Class FileResource should return object of type FileResource instead of type System.Collections.Hashtable" $violationName = "PSDSCReturnCorrectTypesForDSCFunctions" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path -$violations = Invoke-ScriptAnalyzer $directory\DSCResources\MSFT_WaitForAll\MSFT_WaitForAll.psm1 | Where-Object {$_.RuleName -eq $violationName} -$noViolations = Invoke-ScriptAnalyzer $directory\DSCResources\MSFT_WaitForAny\MSFT_WaitForAny.psm1 | Where-Object {$_.RuleName -eq $violationName} +$violations = Invoke-ScriptAnalyzer $directory\DSCResourceModule\DSCResources\MSFT_WaitForAll\MSFT_WaitForAll.psm1 | Where-Object {$_.RuleName -eq $violationName} +$noViolations = Invoke-ScriptAnalyzer $directory\DSCResourceModule\DSCResources\MSFT_WaitForAny\MSFT_WaitForAny.psm1 | Where-Object {$_.RuleName -eq $violationName} if ($PSVersionTable.PSVersion -ge [Version]'5.0.0') { - $classViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $directory\DSCResources\BadDscResource\BadDscResource.psm1 | Where-Object {$_.RuleName -eq $violationName} - $noClassViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $directory\DSCResources\MyDscResource\MyDscResource.psm1 | Where-Object {$_.RuleName -eq $violationName} + $classViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $directory\DSCResourceModule\DSCResources\BadDscResource\BadDscResource.psm1 | Where-Object {$_.RuleName -eq $violationName} + $noClassViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $directory\DSCResourceModule\DSCResources\MyDscResource\MyDscResource.psm1 | Where-Object {$_.RuleName -eq $violationName} } Describe "ReturnCorrectTypesForDSCFunctions" { @@ -49,4 +49,4 @@ if ($PSVersionTable.PSVersion -ge [Version]'5.0.0') { } } } -} \ No newline at end of file +} diff --git a/Tests/Rules/UseDSCResourceFunctions.tests.ps1 b/Tests/Rules/UseDSCResourceFunctions.tests.ps1 index 306bb2718..5bffe1b46 100644 --- a/Tests/Rules/UseDSCResourceFunctions.tests.ps1 +++ b/Tests/Rules/UseDSCResourceFunctions.tests.ps1 @@ -4,13 +4,13 @@ $violationMessage = "Missing 'Get-TargetResource' function. DSC Resource must im $classViolationMessage = "Missing 'Set' function. DSC Class must implement Get, Set and Test functions." $violationName = "PSDSCStandardDSCFunctionsInResource" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path -$violations = Invoke-ScriptAnalyzer $directory\DSCResources\MSFT_WaitForAll\MSFT_WaitForAll.psm1 | Where-Object {$_.RuleName -eq $violationName} -$noViolations = Invoke-ScriptAnalyzer $directory\DSCResources\MSFT_WaitForAny\MSFT_WaitForAny.psm1 | Where-Object {$_.RuleName -eq $violationName} +$violations = Invoke-ScriptAnalyzer $directory\DSCResourceModule\DSCResources\MSFT_WaitForAll\MSFT_WaitForAll.psm1 | Where-Object {$_.RuleName -eq $violationName} +$noViolations = Invoke-ScriptAnalyzer $directory\DSCResourceModule\DSCResources\MSFT_WaitForAny\MSFT_WaitForAny.psm1 | Where-Object {$_.RuleName -eq $violationName} if ($PSVersionTable.PSVersion -ge [Version]'5.0.0') { - $classViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $directory\DSCResources\BadDscResource\BadDscResource.psm1 | Where-Object {$_.RuleName -eq $violationName} - $noClassViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $directory\DSCResources\MyDscResource\MyDscResource.psm1 | Where-Object {$_.RuleName -eq $violationName} + $classViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $directory\DSCResourceModule\DSCResources\BadDscResource\BadDscResource.psm1 | Where-Object {$_.RuleName -eq $violationName} + $noClassViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $directory\DSCResourceModule\DSCResources\MyDscResource\MyDscResource.psm1 | Where-Object {$_.RuleName -eq $violationName} } Describe "StandardDSCFunctionsInResource" { @@ -49,4 +49,4 @@ if ($PSVersionTable.PSVersion -ge [Version]'5.0.0') { } } } -} \ No newline at end of file +} diff --git a/Tests/Rules/UseIdenticalMandatoryParametersForDSC.tests.ps1 b/Tests/Rules/UseIdenticalMandatoryParametersForDSC.tests.ps1 new file mode 100644 index 000000000..4a12eff16 --- /dev/null +++ b/Tests/Rules/UseIdenticalMandatoryParametersForDSC.tests.ps1 @@ -0,0 +1,37 @@ +Import-Module PSScriptAnalyzer +$directory = Split-Path -Parent $MyInvocation.MyCommand.Path +$ruleName = 'PSDSCUseIdenticalMandatoryParametersForDSC' +$resourceBasepath = "$directory\DSCResourceModule\DSCResources" +$badResourceFilepath = [System.IO.Path]::Combine( + $resourceBasepath, + 'MSFT_WaitForAnyNoIdenticalMandatoryParameter', + 'MSFT_WaitForAnyNoIdenticalMandatoryParameter.psm1'); +$goodResourceFilepath = [System.IO.Path]::Combine($resourceBasepath,'MSFT_WaitForAny','MSFT_WaitForAny.psm1'); + + +Describe "UseIdenticalMandatoryParametersForDSC" { + Context "When a mandatory parameters are not present" { + BeforeAll { + $violations = Invoke-ScriptAnalyzer -Path $badResourceFilepath -IncludeRule $ruleName + } + + It "Should find a violations" { + $violations.Count | Should Be 5 + } + + It "Should mark only the function name" { + $violations[0].Extent.Text | Should Be 'Get-TargetResource' + } + } + + Context "When all mandatory parameters are present" { + BeforeAll { + $violations = Invoke-ScriptAnalyzer -Path $goodResourceFilepath -IncludeRule $ruleName + } + + # todo add a test to check one violation per function + It "Should find a violations" { + $violations.Count | Should Be 0 + } + } +} diff --git a/Tests/Rules/UseIdenticalParametersDSC.tests.ps1 b/Tests/Rules/UseIdenticalParametersDSC.tests.ps1 index 0fcddaa96..9fbebb645 100644 --- a/Tests/Rules/UseIdenticalParametersDSC.tests.ps1 +++ b/Tests/Rules/UseIdenticalParametersDSC.tests.ps1 @@ -3,12 +3,12 @@ $violationMessage = "The Test and Set-TargetResource functions of DSC Resource must have the same parameters." $violationName = "PSDSCUseIdenticalParametersForDSC" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path -$violations = Invoke-ScriptAnalyzer $directory\DSCResources\MSFT_WaitForAll\MSFT_WaitForAll.psm1 | Where-Object {$_.RuleName -eq $violationName} -$noViolations = Invoke-ScriptAnalyzer $directory\DSCResources\MSFT_WaitForAny\MSFT_WaitForAny.psm1 | Where-Object {$_.RuleName -eq $violationName} +$violations = Invoke-ScriptAnalyzer $directory\DSCResourceModule\DSCResources\MSFT_WaitForAll\MSFT_WaitForAll.psm1 | Where-Object {$_.RuleName -eq $violationName} +$noViolations = Invoke-ScriptAnalyzer $directory\DSCResourceModule\DSCResources\MSFT_WaitForAny\MSFT_WaitForAny.psm1 | Where-Object {$_.RuleName -eq $violationName} if ($PSVersionTable.PSVersion -ge [Version]'5.0.0') { - $noClassViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $directory\DSCResources\MyDscResource\MyDscResource.psm1 | Where-Object {$_.RuleName -eq $violationName} + $noClassViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $directory\DSCResourceModule\DSCResources\MyDscResource\MyDscResource.psm1 | Where-Object {$_.RuleName -eq $violationName} } Describe "UseIdenticalParametersDSC" { @@ -26,7 +26,7 @@ Describe "UseIdenticalParametersDSC" { It "returns no violations" { $noViolations.Count | Should Be 0 } - + if ($PSVersionTable.PSVersion -ge [Version]'5.0.0') { @@ -35,4 +35,4 @@ Describe "UseIdenticalParametersDSC" { } } } -} \ No newline at end of file +} diff --git a/Tests/Rules/UseOutputTypeCorrectly.tests.ps1 b/Tests/Rules/UseOutputTypeCorrectly.tests.ps1 index ca5d0eee0..eb7029a57 100644 --- a/Tests/Rules/UseOutputTypeCorrectly.tests.ps1 +++ b/Tests/Rules/UseOutputTypeCorrectly.tests.ps1 @@ -6,7 +6,7 @@ $violations = Invoke-ScriptAnalyzer $directory\BadCmdlet.ps1 | Where-Object {$_. if ($PSVersionTable.PSVersion -ge [Version]'5.0.0') { - $dscViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $directory\DSCResources\MyDscResource\MyDscResource.psm1 | Where-Object {$_.RuleName -eq $violationName} + $dscViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $directory\DSCResourceModule\DSCResources\MyDscResource\MyDscResource.psm1 | Where-Object {$_.RuleName -eq $violationName} } $noViolations = Invoke-ScriptAnalyzer $directory\GoodCmdlet.ps1 | Where-Object {$_.RuleName -eq $violationName} @@ -21,7 +21,7 @@ Describe "UseOutputTypeCorrectly" { $violations[1].Message | Should Match $violationMessage } - if ($PSVersionTable.PSVersion -ge [Version]'5.0.0') { + if ($PSVersionTable.PSVersion -ge [Version]'5.0.0') { It "Does not count violation in DSC class" { $dscViolations.Count | Should Be 0 } @@ -33,4 +33,4 @@ Describe "UseOutputTypeCorrectly" { $noViolations.Count | Should Be 0 } } -} \ No newline at end of file +} diff --git a/Tests/Rules/UseVerboseMessageInDSCResource.Tests.ps1 b/Tests/Rules/UseVerboseMessageInDSCResource.Tests.ps1 index 431188a30..d181bb628 100644 --- a/Tests/Rules/UseVerboseMessageInDSCResource.Tests.ps1 +++ b/Tests/Rules/UseVerboseMessageInDSCResource.Tests.ps1 @@ -3,9 +3,9 @@ $violationMessage = "There is no call to Write-Verbose in DSC function ‘Test-TargetResource’. If you are using Write-Verbose in a helper function, suppress this rule application." $violationName = "PSDSCUseVerboseMessageInDSCResource" $directory = Split-Path -Parent $MyInvocation.MyCommand.Path -$violations = Invoke-ScriptAnalyzer $directory\DSCResources\MSFT_WaitForAll\MSFT_WaitForAll.psm1 | Where-Object {$_.RuleName -eq $violationName} -$noViolations = Invoke-ScriptAnalyzer $directory\DSCResources\MSFT_WaitForAny\MSFT_WaitForAny.psm1 | Where-Object {$_.RuleName -eq $violationName} -$noClassViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $directory\DSCResources\MyDscResource\MyDscResource.psm1 | Where-Object {$_.RuleName -eq $violationName} +$violations = Invoke-ScriptAnalyzer $directory\DSCResourceModule\DSCResources\MSFT_WaitForAll\MSFT_WaitForAll.psm1 | Where-Object {$_.RuleName -eq $violationName} +$noViolations = Invoke-ScriptAnalyzer $directory\DSCResourceModule\DSCResources\MSFT_WaitForAny\MSFT_WaitForAny.psm1 | Where-Object {$_.RuleName -eq $violationName} +$noClassViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $directory\DSCResourceModule\DSCResources\MyDscResource\MyDscResource.psm1 | Where-Object {$_.RuleName -eq $violationName} Describe "UseVerboseMessageInDSCResource" { Context "When there are violations" { @@ -21,6 +21,6 @@ Describe "UseVerboseMessageInDSCResource" { Context "When there are no violations" { It "returns no violations" { $noViolations.Count | Should Be 0 - } + } } -} \ No newline at end of file +}