diff --git a/build.psm1 b/build.psm1
index c6eb8624a41..b4548a66725 100644
--- a/build.psm1
+++ b/build.psm1
@@ -587,8 +587,10 @@ Fix steps:
-not ($Runtime -like 'fxdependent*')) {
$json = & $publishPath\pwsh -noprofile -command {
- $expFeatures = [System.Collections.Generic.List[string]]::new()
- Get-ExperimentalFeature | ForEach-Object { $expFeatures.Add($_.Name) }
+ # Special case for DSC code in PS;
+ # this experimental feature requires new DSC module that is not inbox,
+ # so we don't want default DSC use case be broken
+ $expFeatures = Get-ExperimentalFeature | Where-Object Name -NE PS7DscSupport | ForEach-Object -MemberName Name
# Make sure ExperimentalFeatures from modules in PSHome are added
# https://github.com/PowerShell/PowerShell/issues/10550
@@ -598,7 +600,7 @@ Fix steps:
}
}
- ConvertTo-Json $expFeatures.ToArray()
+ ConvertTo-Json $expFeatures
}
$config += @{ ExperimentalFeatures = ([string[]] ($json | ConvertFrom-Json)) }
diff --git a/src/System.Management.Automation/DscSupport/CimDSCParser.cs b/src/System.Management.Automation/DscSupport/CimDSCParser.cs
index 90a14a3946b..2901148813b 100644
--- a/src/System.Management.Automation/DscSupport/CimDSCParser.cs
+++ b/src/System.Management.Automation/DscSupport/CimDSCParser.cs
@@ -41,9 +41,7 @@ public static object ConvertCimInstanceToObject(Type targetType, CimInstance ins
using (System.Management.Automation.PowerShell powerShell = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace))
{
- const string script = "param($targetType,$moduleName) & (Microsoft.PowerShell.Core\\Get-Module $moduleName) { New-Object $targetType } ";
-
- powerShell.AddScript(script);
+ powerShell.AddScript("param($targetType,$moduleName) & (Microsoft.PowerShell.Core\\Get-Module $moduleName) { New-Object $targetType } ");
powerShell.AddArgument(targetType);
powerShell.AddArgument(moduleName);
@@ -945,6 +943,20 @@ private static CimClass MyClassCallback(string serverName, string namespaceName,
return null;
}
+ ///
+ /// Reads CIM MOF schema file and returns classes defined in it.
+ /// This is used MOF->PSClass conversion tool.
+ ///
+ ///
+ /// Path to CIM MOF schema file for reading.
+ ///
+ /// List of classes from MOF schema file.
+ public static List ReadCimSchemaMof(string mofPath)
+ {
+ var parser = new Microsoft.PowerShell.DesiredStateConfiguration.CimDSCParser(MyClassCallback);
+ return parser.ParseSchemaMof(mofPath);
+ }
+
///
/// Import CIM classes from the given file.
///
diff --git a/src/System.Management.Automation/DscSupport/JsonCimDSCParser.cs b/src/System.Management.Automation/DscSupport/JsonCimDSCParser.cs
new file mode 100755
index 00000000000..c2221cb3f33
--- /dev/null
+++ b/src/System.Management.Automation/DscSupport/JsonCimDSCParser.cs
@@ -0,0 +1,68 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.IO;
+using System.Management.Automation;
+using System.Security;
+
+namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform
+{
+ ///
+ /// Class that does high level Cim schema parsing.
+ ///
+ internal class CimDSCParser
+ {
+ private readonly JsonDeserializer _jsonDeserializer;
+
+ internal CimDSCParser()
+ {
+ _jsonDeserializer = JsonDeserializer.Create();
+ }
+
+ internal IEnumerable ParseSchemaJson(string filePath, bool useNewRunspace = false)
+ {
+ try
+ {
+ string json = File.ReadAllText(filePath);
+ string fileNameDefiningClass = Path.GetFileNameWithoutExtension(filePath);
+ int dotIndex = fileNameDefiningClass.IndexOf(".schema", StringComparison.InvariantCultureIgnoreCase);
+ if (dotIndex != -1)
+ {
+ fileNameDefiningClass = fileNameDefiningClass.Substring(0, dotIndex);
+ }
+
+ IEnumerable result = _jsonDeserializer.DeserializeClasses(json, useNewRunspace);
+ foreach (dynamic classObject in result)
+ {
+ string superClassName = classObject.SuperClassName;
+ string className = classObject.ClassName;
+ if (string.Equals(superClassName, "OMI_BaseResource", StringComparison.OrdinalIgnoreCase))
+ {
+ // Get the name of the file without schema.mof/json extension
+ if (!className.Equals(fileNameDefiningClass, StringComparison.OrdinalIgnoreCase))
+ {
+ PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(
+ ParserStrings.ClassNameNotSameAsDefiningFile, className, fileNameDefiningClass);
+ throw e;
+ }
+ }
+ }
+
+ return result;
+ }
+ catch (Exception exception)
+ {
+ PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(
+ exception, ParserStrings.CimDeserializationError, filePath);
+
+ e.SetErrorId("CimDeserializationError");
+ throw e;
+ }
+ }
+ }
+}
diff --git a/src/System.Management.Automation/DscSupport/JsonDeserializer.cs b/src/System.Management.Automation/DscSupport/JsonDeserializer.cs
new file mode 100755
index 00000000000..319560f0a09
--- /dev/null
+++ b/src/System.Management.Automation/DscSupport/JsonDeserializer.cs
@@ -0,0 +1,72 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Management.Automation;
+using System.Management.Automation.Runspaces;
+
+namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform
+{
+ internal class JsonDeserializer
+ {
+ #region Constructors
+
+ ///
+ /// Instantiates a default deserializer.
+ ///
+ /// Default deserializer.
+ public static JsonDeserializer Create()
+ {
+ return new JsonDeserializer();
+ }
+
+ #endregion Constructors
+
+ #region Methods
+
+ ///
+ /// Returns schema of Cim classes from specified json file.
+ ///
+ /// Json text to deserialize.
+ /// If a new runspace should be used.
+ /// Deserialized PSObjects.
+ public IEnumerable DeserializeClasses(string json, bool useNewRunspace = false)
+ {
+ if (string.IsNullOrEmpty(json))
+ {
+ throw new ArgumentNullException(nameof(json));
+ }
+
+ System.Management.Automation.PowerShell powerShell = null;
+
+ if (useNewRunspace)
+ {
+ // currently using RunspaceMode.NewRunspace will reset PSModulePath env var for the entire process
+ // this is something we want to avoid in DSC GuestConfigAgent scenario, so we use following workaround
+ var s_iss = InitialSessionState.CreateDefault();
+ s_iss.EnvironmentVariables.Add(
+ new SessionStateVariableEntry(
+ "PSModulePath",
+ Environment.GetEnvironmentVariable("PSModulePath"),
+ description: null));
+ powerShell = System.Management.Automation.PowerShell.Create(s_iss);
+ }
+ else
+ {
+ powerShell = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace);
+ }
+
+ using (powerShell)
+ {
+ return powerShell.AddCommand("Microsoft.PowerShell.Utility\\ConvertFrom-Json")
+ .AddParameter("InputObject", json)
+ .AddParameter("Depth", 100) // maximum supported by cmdlet
+ .Invoke();
+ }
+ }
+
+ #endregion Methods
+ }
+}
diff --git a/src/System.Management.Automation/DscSupport/JsonDscClassCache.cs b/src/System.Management.Automation/DscSupport/JsonDscClassCache.cs
new file mode 100755
index 00000000000..2fe4f7c2606
--- /dev/null
+++ b/src/System.Management.Automation/DscSupport/JsonDscClassCache.cs
@@ -0,0 +1,2498 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Management.Automation;
+using System.Management.Automation.Language;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Text;
+using System.Text.RegularExpressions;
+
+using Microsoft.PowerShell.Commands;
+
+namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform
+{
+ ///
+ /// Class that defines Dsc cache entries.
+ ///
+ internal class DscClassCacheEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public DscClassCacheEntry()
+ : this(DSCResourceRunAsCredential.Default, isImportedImplicitly: false, cimClassInstance: null, modulePath: string.Empty)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Run as credential value.
+ /// Resource is imported implicitly.
+ /// Class definition.
+ /// Path of module defining the class.
+ public DscClassCacheEntry(DSCResourceRunAsCredential dscResourceRunAsCredential, bool isImportedImplicitly, PSObject cimClassInstance, string modulePath)
+ {
+ DscResRunAsCred = dscResourceRunAsCredential;
+ IsImportedImplicitly = isImportedImplicitly;
+ CimClassInstance = cimClassInstance;
+ ModulePath = modulePath;
+ }
+
+ ///
+ /// Gets or sets the RunAs Credentials that this DSC resource will use.
+ ///
+ public DSCResourceRunAsCredential DscResRunAsCred { get; set; }
+
+ ///
+ /// Gets or sets a value indicating if we have implicitly imported this resource.
+ ///
+ public bool IsImportedImplicitly { get; set; }
+
+ ///
+ /// Gets or sets CimClass instance for this resource.
+ ///
+ public PSObject CimClassInstance { get; set; }
+
+ ///
+ /// Gets or sets path of the implementing module for this resource.
+ ///
+ public string ModulePath { get; set; }
+ }
+
+ ///
+ /// DSC class cache for this runspace.
+ ///
+ [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes",
+ Justification = "Needed Internal use only")]
+ public static class DscClassCache
+ {
+ private static readonly HashSet s_reservedDynamicKeywords = new HashSet(new[] { "Synchronization", "Certificate", "IIS", "SQL" }, StringComparer.OrdinalIgnoreCase);
+
+ private static readonly HashSet s_reservedProperties = new HashSet(new[] { "Require", "Trigger", "Notify", "Before", "After", "Subscribe" }, StringComparer.OrdinalIgnoreCase);
+
+ ///
+ /// Experimental feature name for DSC v3.
+ ///
+ public const string DscExperimentalFeatureName = "PS7DscSupport";
+
+ private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("DSC", "DSC Class Cache");
+
+ // Constants for items in the module qualified name (Module\Version\ClassName)
+ private const int ModuleNameIndex = 0;
+ private const int ModuleVersionIndex = 1;
+ private const int ClassNameIndex = 2;
+ private const int FriendlyNameIndex = 3;
+
+ // Create a HashSet for fast lookup. According to MSDN, the time complexity of search for an element in a HashSet is O(1)
+ private static readonly HashSet s_hiddenResourceCache =
+ new HashSet(StringComparer.OrdinalIgnoreCase) { "MSFT_BaseConfigurationProviderRegistration", "MSFT_CimConfigurationProviderRegistration", "MSFT_PSConfigurationProviderRegistration" };
+
+ // A collection to prevent circular importing case when Import-DscResource does not have a module specified
+ [ThreadStatic]
+ private static readonly HashSet t_currentImportDscResourceInvocations = new(StringComparer.OrdinalIgnoreCase);
+
+ ///
+ /// Gets DSC class cache for this runspace.
+ /// Cache stores the DSCRunAsBehavior, cim class and boolean to indicate if an Inbox resource has been implicitly imported.
+ ///
+ private static Dictionary ClassCache
+ {
+ get => t_classCache ??= new Dictionary(StringComparer.OrdinalIgnoreCase);
+ }
+
+ [ThreadStatic]
+ private static Dictionary t_classCache;
+
+ ///
+ /// Gets DSC class cache for GuestConfig; it is similar to ClassCache, but maintains values between operations.
+ ///
+ private static Dictionary GuestConfigClassCache
+ {
+ get => t_guestConfigClassCache ??= new Dictionary(StringComparer.OrdinalIgnoreCase);
+ }
+
+ [ThreadStatic]
+ private static Dictionary t_guestConfigClassCache;
+
+ ///
+ /// DSC classname to source module mapper.
+ ///
+ private static Dictionary> ByClassModuleCache
+ => t_byClassModuleCache ??= new Dictionary>(StringComparer.OrdinalIgnoreCase);
+
+ [ThreadStatic]
+ private static Dictionary> t_byClassModuleCache;
+
+ ///
+ /// Default ModuleName and ModuleVersion to use.
+ ///
+ private static readonly Tuple s_defaultModuleInfoForResource = new Tuple("PSDesiredStateConfiguration", new Version(3, 0));
+
+ ///
+ /// When this property is set to true, DSC Cache will cache multiple versions of a resource.
+ /// That means it will cache duplicate resource classes (class names for a resource in two different module versions are same).
+ /// NOTE: This property should be set to false for DSC compiler related methods/functionality, such as Import-DscResource,
+ /// because the Mof serializer does not support deserialization of classes with different versions.
+ ///
+ [ThreadStatic]
+ private static bool t_cacheResourcesFromMultipleModuleVersions;
+
+ private static bool CacheResourcesFromMultipleModuleVersions
+ {
+ get
+ {
+ return t_cacheResourcesFromMultipleModuleVersions;
+ }
+
+ set
+ {
+ t_cacheResourcesFromMultipleModuleVersions = value;
+ }
+ }
+
+ [ThreadStatic]
+ private static bool t_newApiIsUsed = false;
+
+ ///
+ /// Flag shows if PS7 DSC APIs were used.
+ ///
+ public static bool NewApiIsUsed
+ {
+ get
+ {
+ return t_newApiIsUsed;
+ }
+
+ set
+ {
+ t_newApiIsUsed = value;
+ }
+ }
+
+ ///
+ /// Initialize the class cache with the default classes in $ENV:SystemDirectory\Configuration.
+ ///
+ public static void Initialize()
+ {
+ Initialize(errors: null, modulePathList: null);
+ }
+
+ ///
+ /// Initialize the class cache with default classes that come with PSDesiredStateConfiguration module.
+ ///
+ /// Collection of any errors encountered during initialization.
+ /// List of module path from where DSC PS modules will be loaded.
+ public static void Initialize(Collection errors, List modulePathList)
+ {
+ s_tracer.WriteLine("Initializing DSC class cache");
+
+ // Load the base schema files.
+ ClearCache();
+ var dscConfigurationDirectory = Environment.GetEnvironmentVariable("DSC_HOME");
+ if (string.IsNullOrEmpty(dscConfigurationDirectory))
+ {
+ var moduleInfos = ModuleCmdletBase.GetModuleIfAvailable(new Microsoft.PowerShell.Commands.ModuleSpecification()
+ {
+ Name = "PSDesiredStateConfiguration",
+
+ // Version in the next line is actually MinimumVersion
+ Version = new Version(3, 0, 0)
+ });
+
+ if (moduleInfos.Count > 0)
+ {
+ // to be consistent with Import-Module behavior, we use the first occurrence that we find in PSModulePath
+ var moduleDirectory = Path.GetDirectoryName(moduleInfos[0].Path);
+ dscConfigurationDirectory = Path.Join(moduleDirectory, "Configuration");
+ }
+ else
+ {
+ // when all else has failed use location of system-wide PS module directory (i.e. /usr/local/share/powershell/Modules) as backup
+ dscConfigurationDirectory = Path.Join(ModuleIntrinsics.GetSharedModulePath(), "PSDesiredStateConfiguration", "Configuration");
+ }
+ }
+
+ if (!Directory.Exists(dscConfigurationDirectory))
+ {
+ throw new DirectoryNotFoundException(string.Format(ParserStrings.PsDscMissingSchemaStore, dscConfigurationDirectory));
+ }
+
+ var resourceBaseFile = Path.Join(dscConfigurationDirectory, "BaseRegistration", "BaseResource.schema.json");
+ ImportBaseClasses(resourceBaseFile, s_defaultModuleInfoForResource, errors, false);
+ var metaConfigFile = Path.Join(dscConfigurationDirectory, "BaseRegistration", "MSFT_DSCMetaConfiguration.json");
+ ImportBaseClasses(metaConfigFile, s_defaultModuleInfoForResource, errors, false);
+ }
+
+ ///
+ /// Import base classes from the given file.
+ ///
+ /// Path to schema file.
+ /// Module information.
+ /// Error collection that will be shown to the user.
+ /// Flag for implicitly imported resource.
+ /// Class objects from schema file.
+ public static IEnumerable ImportBaseClasses(string path, Tuple moduleInfo, Collection errors, bool importInBoxResourcesImplicitly)
+ {
+ if (string.IsNullOrEmpty(path))
+ {
+ throw PSTraceSource.NewArgumentNullException(nameof(path));
+ }
+
+ s_tracer.WriteLine("DSC ClassCache: importing file: {0}", path);
+
+ var parser = new CimDSCParser();
+
+ IEnumerable classes = null;
+ try
+ {
+ classes = parser.ParseSchemaJson(path);
+ }
+ catch (PSInvalidOperationException e)
+ {
+ // Ignore modules with invalid schemas.
+ s_tracer.WriteLine("DSC ClassCache: Error importing file '{0}', with error '{1}'. Skipping file.", path, e);
+ if (errors != null)
+ {
+ errors.Add(e);
+ }
+ }
+
+ if (classes != null)
+ {
+ foreach (dynamic c in classes)
+ {
+ var className = c.ClassName;
+
+ if (string.IsNullOrEmpty(className))
+ {
+ // ClassName is empty - skipping class import
+ continue;
+ }
+
+ string alias = GetFriendlyName(c);
+ var friendlyName = string.IsNullOrEmpty(alias) ? className : alias;
+ string moduleQualifiedResourceName = GetModuleQualifiedResourceName(moduleInfo.Item1, moduleInfo.Item2.ToString(), className, friendlyName);
+ DscClassCacheEntry cimClassInfo;
+
+ if (ClassCache.TryGetValue(moduleQualifiedResourceName, out cimClassInfo))
+ {
+ if (errors != null)
+ {
+ PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(
+ ParserStrings.DuplicateCimClassDefinition, className, path, cimClassInfo.ModulePath);
+
+ e.SetErrorId("DuplicateCimClassDefinition");
+ errors.Add(e);
+ }
+
+ continue;
+ }
+
+ if (s_hiddenResourceCache.Contains(className))
+ {
+ continue;
+ }
+
+ var classCacheEntry = new DscClassCacheEntry(DSCResourceRunAsCredential.NotSupported, importInBoxResourcesImplicitly, c, path);
+ ClassCache[moduleQualifiedResourceName] = classCacheEntry;
+ GuestConfigClassCache[moduleQualifiedResourceName] = classCacheEntry;
+ ByClassModuleCache[className] = moduleInfo;
+ }
+
+ var sb = new System.Text.StringBuilder();
+ foreach (dynamic c in classes)
+ {
+ sb.Append(c.ClassName);
+ sb.Append(',');
+ }
+
+ s_tracer.WriteLine("DSC ClassCache: loading file '{0}' added the following classes to the cache: {1}", path, sb.ToString());
+ }
+ else
+ {
+ s_tracer.WriteLine("DSC ClassCache: loading file '{0}' added no classes to the cache.");
+ }
+
+ return classes;
+ }
+
+ ///
+ /// Get text from SecureString.
+ ///
+ /// Value of SecureString.
+ /// Decoded string.
+ public static string GetStringFromSecureString(SecureString value)
+ {
+ string passwordValueToAdd = string.Empty;
+
+ if (value != null)
+ {
+ IntPtr ptr = Marshal.SecureStringToCoTaskMemUnicode(value);
+ passwordValueToAdd = Marshal.PtrToStringUni(ptr);
+ Marshal.ZeroFreeCoTaskMemUnicode(ptr);
+ }
+
+ return passwordValueToAdd;
+ }
+
+ ///
+ /// Clear out the existing collection of CIM classes and associated keywords.
+ ///
+ public static void ClearCache()
+ {
+ if (!ExperimentalFeature.IsEnabled(DscExperimentalFeatureName))
+ {
+ throw new InvalidOperationException(ParserStrings.PS7DscSupportDisabled);
+ }
+
+ s_tracer.WriteLine("DSC class: clearing the cache and associated keywords.");
+ ClassCache.Clear();
+ ByClassModuleCache.Clear();
+ CacheResourcesFromMultipleModuleVersions = false;
+ t_currentImportDscResourceInvocations.Clear();
+ }
+
+ private static string GetModuleQualifiedResourceName(string moduleName, string moduleVersion, string className, string resourceName)
+ {
+ return string.Format(CultureInfo.InvariantCulture, "{0}\\{1}\\{2}\\{3}", moduleName, moduleVersion, className, resourceName);
+ }
+
+ private static List> FindResourceInCache(string moduleName, string className, string resourceName)
+ {
+ return (from cacheEntry in ClassCache
+ let splittedName = cacheEntry.Key.Split(Utils.Separators.Backslash)
+ let cachedClassName = splittedName[ClassNameIndex]
+ let cachedModuleName = splittedName[ModuleNameIndex]
+ let cachedResourceName = splittedName[FriendlyNameIndex]
+ where (string.Equals(cachedResourceName, resourceName, StringComparison.OrdinalIgnoreCase)
+ || (string.Equals(cachedClassName, className, StringComparison.OrdinalIgnoreCase)
+ && string.Equals(cachedModuleName, moduleName, StringComparison.OrdinalIgnoreCase)))
+ select cacheEntry).ToList();
+ }
+
+ ///
+ /// Returns class declaration from GuestConfigClassCache.
+ ///
+ /// Module name.
+ /// Module version.
+ /// Name of the class.
+ /// Friendly name of the resource.
+ /// Class declaration from cache.
+ public static PSObject GetGuestConfigCachedClass(string moduleName, string moduleVersion, string className, string resourceName)
+ {
+ if (!ExperimentalFeature.IsEnabled(DscExperimentalFeatureName))
+ {
+ throw new InvalidOperationException(ParserStrings.PS7DscSupportDisabled);
+ }
+
+ var moduleQualifiedResourceName = GetModuleQualifiedResourceName(moduleName, moduleVersion, className, string.IsNullOrEmpty(resourceName) ? className : resourceName);
+ DscClassCacheEntry classCacheEntry = null;
+ if (GuestConfigClassCache.TryGetValue(moduleQualifiedResourceName, out classCacheEntry))
+ {
+ return classCacheEntry.CimClassInstance;
+ }
+ else
+ {
+ // if class was not found with current ResourceName then it may be a class with non-empty FriendlyName that caller does not know, so perform a broad search
+ string partialClassPath = string.Join('\\', moduleName, moduleVersion, className, string.Empty);
+ foreach (string key in GuestConfigClassCache.Keys)
+ {
+ if (key.StartsWith(partialClassPath))
+ {
+ return GuestConfigClassCache[key].CimClassInstance;
+ }
+ }
+
+ return null;
+ }
+ }
+
+ ///
+ /// Clears GuestConfigClassCache.
+ ///
+ public static void ClearGuestConfigClassCache()
+ {
+ GuestConfigClassCache.Clear();
+ }
+
+ private static bool IsMagicProperty(string propertyName)
+ {
+ return System.Text.RegularExpressions.Regex.Match(propertyName, "^(ResourceId|SourceInfo|ModuleName|ModuleVersion|ConfigurationName)$", System.Text.RegularExpressions.RegexOptions.IgnoreCase).Success;
+ }
+
+ private static string GetFriendlyName(dynamic cimClass)
+ {
+ return cimClass.FriendlyName;
+ }
+
+ ///
+ /// Method to get the cached classes in the form of DynamicKeyword.
+ ///
+ /// Dynamic keyword collection.
+ public static Collection GetKeywordsFromCachedClasses()
+ {
+ if (!ExperimentalFeature.IsEnabled(DscExperimentalFeatureName))
+ {
+ throw new InvalidOperationException(ParserStrings.PS7DscSupportDisabled);
+ }
+
+ Collection keywords = new Collection();
+
+ foreach (KeyValuePair cachedClass in ClassCache)
+ {
+ string[] splittedName = cachedClass.Key.Split(Utils.Separators.Backslash);
+ string moduleName = splittedName[ModuleNameIndex];
+ string moduleVersion = splittedName[ModuleVersionIndex];
+
+ var keyword = CreateKeywordFromCimClass(moduleName, Version.Parse(moduleVersion), cachedClass.Value.CimClassInstance, cachedClass.Value.DscResRunAsCred);
+ if (keyword is not null)
+ {
+ keywords.Add(keyword);
+ }
+ }
+
+ return keywords;
+ }
+
+ private static void CreateAndRegisterKeywordFromCimClass(string moduleName, Version moduleVersion, PSObject cimClass, Dictionary functionsToDefine, DSCResourceRunAsCredential runAsBehavior)
+ {
+ var keyword = CreateKeywordFromCimClass(moduleName, moduleVersion, cimClass, runAsBehavior);
+ if (keyword is null)
+ {
+ return;
+ }
+
+ // keyword is already defined and we don't allow redefine it
+ if (!CacheResourcesFromMultipleModuleVersions && DynamicKeyword.ContainsKeyword(keyword.Keyword))
+ {
+ var oldKeyword = DynamicKeyword.GetKeyword(keyword.Keyword);
+ if (oldKeyword.ImplementingModule is null ||
+ !oldKeyword.ImplementingModule.Equals(moduleName, StringComparison.OrdinalIgnoreCase) || oldKeyword.ImplementingModuleVersion != moduleVersion)
+ {
+ var e = PSTraceSource.NewInvalidOperationException(ParserStrings.DuplicateKeywordDefinition, keyword.Keyword);
+ e.SetErrorId("DuplicateKeywordDefinition");
+ throw e;
+ }
+ }
+
+ // Add the dynamic keyword to the table
+ DynamicKeyword.AddKeyword(keyword);
+
+ // And now define the driver functions in the current scope...
+ if (functionsToDefine != null)
+ {
+ functionsToDefine[moduleName + "\\" + keyword.Keyword] = CimKeywordImplementationFunction;
+ }
+ }
+
+ private static DynamicKeyword CreateKeywordFromCimClass(string moduleName, Version moduleVersion, dynamic cimClass, DSCResourceRunAsCredential runAsBehavior)
+ {
+ var resourceName = cimClass.ClassName;
+ string alias = GetFriendlyName(cimClass);
+ var keywordString = string.IsNullOrEmpty(alias) ? resourceName : alias;
+
+ // Skip all of the base, meta, registration and other classes that are not intended to be used directly by a script author
+ if (System.Text.RegularExpressions.Regex.Match(keywordString, "^OMI_Base|^OMI_.*Registration", System.Text.RegularExpressions.RegexOptions.IgnoreCase).Success)
+ {
+ return null;
+ }
+
+ var keyword = new DynamicKeyword()
+ {
+ BodyMode = DynamicKeywordBodyMode.Hashtable,
+ Keyword = keywordString,
+ ResourceName = resourceName,
+ ImplementingModule = moduleName,
+ ImplementingModuleVersion = moduleVersion,
+ SemanticCheck = CheckMandatoryPropertiesPresent
+ };
+
+ // If it's one of reserved dynamic keyword, mark it
+ if (s_reservedDynamicKeywords.Contains(keywordString))
+ {
+ keyword.IsReservedKeyword = true;
+ }
+
+ // see if it's a resource type i.e. it inherits from OMI_BaseResource
+ bool isResourceType = false;
+
+ // previous version of this code was the only place that referenced CimSuperClass
+ // so to simplify things we just check superclass to be OMI_BaseResource
+ // with assumption that current code will not work for multi-level class inheritance (which is never used in practice according to DSC team)
+ // this simplification allows us to avoid linking objects together using CimSuperClass field during deserialization
+ if ((!string.IsNullOrEmpty(cimClass.SuperClassName)) && string.Equals("OMI_BaseResource", cimClass.SuperClassName, StringComparison.OrdinalIgnoreCase))
+ {
+ isResourceType = true;
+ }
+
+ // If it's a resource type, then a resource name is required.
+ keyword.NameMode = isResourceType ? DynamicKeywordNameMode.NameRequired : DynamicKeywordNameMode.NoName;
+
+ // Add the settable properties to the keyword object
+ if (cimClass.ClassProperties != null)
+ {
+ foreach (var prop in cimClass.ClassProperties)
+ {
+ // If the property has the Read qualifier, skip it.
+ if (string.Equals(prop.Qualifiers?.Read?.ToString(), "True", StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
+ // If it's one of our magic properties, skip it
+ if (IsMagicProperty(prop.Name))
+ {
+ continue;
+ }
+
+ if (runAsBehavior == DSCResourceRunAsCredential.NotSupported)
+ {
+ if (string.Equals(prop.Name, "PsDscRunAsCredential", StringComparison.OrdinalIgnoreCase))
+ {
+ // skip adding PsDscRunAsCredential to the dynamic word for the dsc resource.
+ continue;
+ }
+ }
+
+ // If it's one of our reserved properties, save it for error reporting
+ if (s_reservedProperties.Contains(prop.Name))
+ {
+ keyword.HasReservedProperties = true;
+ continue;
+ }
+
+ // Otherwise, add it to the Keyword List.
+ var keyProp = new System.Management.Automation.Language.DynamicKeywordProperty();
+ keyProp.Name = prop.Name;
+
+ // Copy the type name string. If it's an embedded instance, need to grab it from the ReferenceClassName
+ bool referenceClassNameIsNullOrEmpty = string.IsNullOrEmpty(prop.ReferenceClassName);
+ if (prop.CimType == "Instance" && !referenceClassNameIsNullOrEmpty)
+ {
+ keyProp.TypeConstraint = prop.ReferenceClassName;
+ }
+ else if (prop.CimType == "InstanceArray" && !referenceClassNameIsNullOrEmpty)
+ {
+ keyProp.TypeConstraint = prop.ReferenceClassName + "[]";
+ }
+ else
+ {
+ keyProp.TypeConstraint = prop.CimType.ToString();
+ }
+
+ // Check to see if there is a Values attribute and save the list of allowed values if so.
+ var values = prop.Qualifiers?.Values;
+ if (values is not null)
+ {
+ foreach (var val in values)
+ {
+ keyProp.Values.Add(val.ToString());
+ }
+ }
+
+ // Check to see if there is a ValueMap attribute and save the list of allowed values if so.
+ var nativeValueMap = prop.Qualifiers?.ValueMap;
+ List valueMap = null;
+ if (nativeValueMap is not null)
+ {
+ valueMap = new List();
+ foreach (var val in nativeValueMap)
+ {
+ valueMap.Add(val.ToString());
+ }
+ }
+
+ // Check to see if this property has the Required qualifier associated with it.
+ if (string.Equals(prop.Qualifiers?.Required?.ToString(), "True", StringComparison.OrdinalIgnoreCase))
+ {
+ keyProp.Mandatory = true;
+ }
+
+ // Check to see if this property has the Key qualifier associated with it.
+ if (string.Equals(prop.Qualifiers?.Key?.ToString(), "True", StringComparison.OrdinalIgnoreCase))
+ {
+ keyProp.Mandatory = true;
+ keyProp.IsKey = true;
+ }
+
+ // set the property to mandatory is specified for the resource.
+ if (runAsBehavior == DSCResourceRunAsCredential.Mandatory)
+ {
+ if (string.Equals(prop.Name, "PsDscRunAsCredential", StringComparison.OrdinalIgnoreCase))
+ {
+ keyProp.Mandatory = true;
+ }
+ }
+
+ if (valueMap is not null && keyProp.Values.Count > 0)
+ {
+ if (valueMap.Count != keyProp.Values.Count)
+ {
+ s_tracer.WriteLine(
+ "DSC CreateDynamicKeywordFromClass: the count of values for qualifier 'Values' and 'ValueMap' doesn't match. count of 'Values': {0}, count of 'ValueMap': {1}. Skip the keyword '{2}'.",
+ keyProp.Values.Count,
+ valueMap.Count,
+ keyword.Keyword);
+ return null;
+ }
+
+ for (int index = 0; index < valueMap.Count; index++)
+ {
+ string key = keyProp.Values[index];
+ string value = valueMap[index];
+
+ if (keyProp.ValueMap.ContainsKey(key))
+ {
+ s_tracer.WriteLine(
+ "DSC CreateDynamicKeywordFromClass: same string value '{0}' appears more than once in qualifier 'Values'. Skip the keyword '{1}'.",
+ key,
+ keyword.Keyword);
+ return null;
+ }
+
+ keyProp.ValueMap.Add(key, value);
+ }
+ }
+
+ keyword.Properties.Add(prop.Name, keyProp);
+ }
+ }
+
+ // update specific keyword with range constraints
+ UpdateKnownRestriction(keyword);
+
+ return keyword;
+ }
+
+ private static void UpdateKnownRestriction(DynamicKeyword keyword)
+ {
+ const int RefreshFrequencyMin = 30;
+ const int RefreshFrequencyMax = 44640;
+
+ const int ConfigurationModeFrequencyMin = 15;
+ const int ConfigurationModeFrequencyMax = 44640;
+
+ if (
+ string.Equals(
+ keyword.ResourceName,
+ "MSFT_DSCMetaConfigurationV2",
+ StringComparison.OrdinalIgnoreCase)
+ ||
+ string.Equals(
+ keyword.ResourceName,
+ "MSFT_DSCMetaConfiguration",
+ StringComparison.OrdinalIgnoreCase))
+ {
+ if (keyword.Properties["RefreshFrequencyMins"] is not null)
+ {
+ keyword.Properties["RefreshFrequencyMins"].Range = new Tuple(RefreshFrequencyMin, RefreshFrequencyMax);
+ }
+
+ if (keyword.Properties["ConfigurationModeFrequencyMins"] != null)
+ {
+ keyword.Properties["ConfigurationModeFrequencyMins"].Range = new Tuple(ConfigurationModeFrequencyMin, ConfigurationModeFrequencyMax);
+ }
+
+ if (keyword.Properties["DebugMode"] is not null)
+ {
+ keyword.Properties["DebugMode"].Values.Remove("ResourceScriptBreakAll");
+ keyword.Properties["DebugMode"].ValueMap.Remove("ResourceScriptBreakAll");
+ }
+ }
+ }
+
+ ///
+ /// Load the default system CIM classes and create the corresponding keywords.
+ ///
+ /// Collection of any errors encountered while loading keywords.
+ public static void LoadDefaultCimKeywords(Collection errors)
+ {
+ LoadDefaultCimKeywords(functionsToDefine: null, errors, modulePathList: null, cacheResourcesFromMultipleModuleVersions: false);
+ }
+
+ ///
+ /// Load the default system CIM classes and create the corresponding keywords.
+ ///
+ /// A dictionary to add the defined functions to, may be null.
+ public static void LoadDefaultCimKeywords(Dictionary functionsToDefine)
+ {
+ LoadDefaultCimKeywords(functionsToDefine, errors: null, modulePathList: null, cacheResourcesFromMultipleModuleVersions: false);
+ }
+
+ ///
+ /// Load the default system CIM classes and create the corresponding keywords.
+ ///
+ /// Collection of any errors encountered while loading keywords.
+ /// Allow caching the resources from multiple versions of modules.
+ public static void LoadDefaultCimKeywords(Collection errors, bool cacheResourcesFromMultipleModuleVersions)
+ {
+ LoadDefaultCimKeywords(functionsToDefine: null, errors, modulePathList: null, cacheResourcesFromMultipleModuleVersions);
+ }
+
+ ///
+ /// Load the default system CIM classes and create the corresponding keywords.
+ ///
+ /// A dictionary to add the defined functions to, may be null.
+ /// Collection of any errors encountered while loading keywords.
+ /// List of module path from where DSC PS modules will be loaded.
+ /// Allow caching the resources from multiple versions of modules.
+ private static void LoadDefaultCimKeywords(
+ Dictionary functionsToDefine,
+ Collection errors,
+ List modulePathList,
+ bool cacheResourcesFromMultipleModuleVersions)
+ {
+ if (!ExperimentalFeature.IsEnabled(DscExperimentalFeatureName))
+ {
+ Exception exception = new InvalidOperationException(ParserStrings.PS7DscSupportDisabled);
+ errors.Add(exception);
+ return;
+ }
+
+ NewApiIsUsed = true;
+ DynamicKeyword.Reset();
+ Initialize(errors, modulePathList);
+
+ // Initialize->ClearCache resets CacheResourcesFromMultipleModuleVersions to false,
+ // workaround is to set it after Initialize method call.
+ // Initialize method imports all the Inbox resources and internal classes which belongs to only one version
+ // of the module, so it is ok if this property is not set during cache initialization.
+ CacheResourcesFromMultipleModuleVersions = cacheResourcesFromMultipleModuleVersions;
+
+ foreach (dynamic cimClass in ClassCache.Values)
+ {
+ var className = cimClass.CimClassInstance.ClassName;
+ var moduleInfo = ByClassModuleCache[className];
+ CreateAndRegisterKeywordFromCimClass(moduleInfo.Item1, moduleInfo.Item2, cimClass.CimClassInstance, functionsToDefine, cimClass.DscResRunAsCred);
+ }
+
+ // And add the Node keyword definitions
+ if (!DynamicKeyword.ContainsKeyword("Node"))
+ {
+ // Implement dispatch to the Node keyword.
+ var nodeKeyword = new DynamicKeyword()
+ {
+ BodyMode = DynamicKeywordBodyMode.ScriptBlock,
+ ImplementingModule = s_defaultModuleInfoForResource.Item1,
+ ImplementingModuleVersion = s_defaultModuleInfoForResource.Item2,
+ NameMode = DynamicKeywordNameMode.NameRequired,
+ Keyword = "Node",
+ };
+ DynamicKeyword.AddKeyword(nodeKeyword);
+ }
+
+ // And add the Import-DscResource keyword definitions
+ if (!DynamicKeyword.ContainsKeyword("Import-DscResource"))
+ {
+ // Implement dispatch to the Node keyword.
+ var nodeKeyword = new DynamicKeyword()
+ {
+ BodyMode = DynamicKeywordBodyMode.Command,
+ ImplementingModule = s_defaultModuleInfoForResource.Item1,
+ ImplementingModuleVersion = s_defaultModuleInfoForResource.Item2,
+ NameMode = DynamicKeywordNameMode.NoName,
+ Keyword = "Import-DscResource",
+ MetaStatement = true,
+ PostParse = ImportResourcePostParse,
+ SemanticCheck = ImportResourceCheckSemantics
+ };
+ DynamicKeyword.AddKeyword(nodeKeyword);
+ }
+ }
+
+ // This function is called after parsing the Import-DscResource keyword and it's arguments, but before parsing anything else.
+ private static ParseError[] ImportResourcePostParse(DynamicKeywordStatementAst ast)
+ {
+ var elements = Ast.CopyElements(ast.CommandElements);
+ var commandAst = new CommandAst(ast.Extent, elements, TokenKind.Unknown, null);
+
+ const string NameParam = "Name";
+ const string ModuleNameParam = "ModuleName";
+ const string ModuleVersionParam = "ModuleVersion";
+
+ StaticBindingResult bindingResult = StaticParameterBinder.BindCommand(commandAst, false);
+
+ var errorList = new List();
+ foreach (var bindingException in bindingResult.BindingExceptions.Values)
+ {
+ errorList.Add(new ParseError(bindingException.CommandElement.Extent, "ParameterBindingException", bindingException.BindingException.Message));
+ }
+
+ ParameterBindingResult moduleNameBindingResult = null;
+ ParameterBindingResult resourceNameBindingResult = null;
+ ParameterBindingResult moduleVersionBindingResult = null;
+
+ foreach (var binding in bindingResult.BoundParameters)
+ {
+ // Error case when positional parameter values are specified
+ var boundParameterName = binding.Key;
+ var parameterBindingResult = binding.Value;
+ if (boundParameterName.All(char.IsDigit))
+ {
+ errorList.Add(new ParseError(parameterBindingResult.Value.Extent, "ImportDscResourcePositionalParamsNotSupported", string.Format(CultureInfo.CurrentCulture, ParserStrings.ImportDscResourcePositionalParamsNotSupported)));
+ continue;
+ }
+
+ if (NameParam.StartsWith(boundParameterName, StringComparison.OrdinalIgnoreCase))
+ {
+ resourceNameBindingResult = parameterBindingResult;
+ }
+ else if (ModuleNameParam.StartsWith(boundParameterName, StringComparison.OrdinalIgnoreCase))
+ {
+ moduleNameBindingResult = parameterBindingResult;
+ }
+ else if (ModuleVersionParam.StartsWith(boundParameterName, StringComparison.OrdinalIgnoreCase))
+ {
+ moduleVersionBindingResult = parameterBindingResult;
+ }
+ else
+ {
+ errorList.Add(new ParseError(parameterBindingResult.Value.Extent, "ImportDscResourceNeedParams", string.Format(CultureInfo.CurrentCulture, ParserStrings.ImportDscResourceNeedParams)));
+ }
+ }
+
+ if (errorList.Count == 0 && moduleNameBindingResult == null && resourceNameBindingResult == null)
+ {
+ errorList.Add(new ParseError(ast.Extent, "ImportDscResourceNeedParams", string.Format(CultureInfo.CurrentCulture, ParserStrings.ImportDscResourceNeedParams)));
+ }
+
+ // Check here if Version is specified but modulename is not specified
+ if (moduleVersionBindingResult != null && moduleNameBindingResult == null)
+ {
+ // only add this error again to the error list if resources is not null
+ // if resources and modules are both null we have already added this error in collection
+ // we do not want to do this twice. since we are giving same error ImportDscResourceNeedParams in both cases
+ // once we have different error messages for 2 scenarios we can remove this check
+ if (resourceNameBindingResult is not null)
+ {
+ errorList.Add(new ParseError(ast.Extent, "ImportDscResourceNeedModuleNameWithModuleVersion", string.Format(CultureInfo.CurrentCulture, ParserStrings.ImportDscResourceNeedParams)));
+ }
+ }
+
+ string[] resourceNames = null;
+ if (resourceNameBindingResult is not null)
+ {
+ object resourceName = null;
+ if (!IsConstantValueVisitor.IsConstant(resourceNameBindingResult.Value, out resourceName, true, true) ||
+ !LanguagePrimitives.TryConvertTo(resourceName, out resourceNames))
+ {
+ errorList.Add(new ParseError(resourceNameBindingResult.Value.Extent, "RequiresInvalidStringArgument", string.Format(CultureInfo.CurrentCulture, ParserStrings.RequiresInvalidStringArgument, NameParam)));
+ }
+ }
+
+ System.Version moduleVersion = null;
+ if (moduleVersionBindingResult is not null)
+ {
+ object moduleVer = null;
+ if (!IsConstantValueVisitor.IsConstant(moduleVersionBindingResult.Value, out moduleVer, true, true))
+ {
+ errorList.Add(new ParseError(moduleVersionBindingResult.Value.Extent, "RequiresArgumentMustBeConstant", ParserStrings.RequiresArgumentMustBeConstant));
+ }
+
+ if (moduleVer is double)
+ {
+ // this happens in case -ModuleVersion 1.0, then use extent text for that.
+ // The better way to do it would be define static binding API against CommandInfo, that holds information about parameter types.
+ // This way, we can avoid this ugly special-casing and say that -ModuleVersion has type [System.Version].
+ moduleVer = moduleVersionBindingResult.Value.Extent.Text;
+ }
+
+ if (!LanguagePrimitives.TryConvertTo(moduleVer, out moduleVersion))
+ {
+ errorList.Add(new ParseError(moduleVersionBindingResult.Value.Extent, "RequiresVersionInvalid", ParserStrings.RequiresVersionInvalid));
+ }
+ }
+
+ ModuleSpecification[] moduleSpecifications = null;
+ if (moduleNameBindingResult is not null)
+ {
+ object moduleName = null;
+ if (!IsConstantValueVisitor.IsConstant(moduleNameBindingResult.Value, out moduleName, true, true))
+ {
+ errorList.Add(new ParseError(moduleNameBindingResult.Value.Extent, "RequiresArgumentMustBeConstant", ParserStrings.RequiresArgumentMustBeConstant));
+ }
+
+ if (LanguagePrimitives.TryConvertTo(moduleName, out moduleSpecifications))
+ {
+ // if resourceNames are specified then we can not specify multiple modules name
+ if (moduleSpecifications is not null && moduleSpecifications.Length > 1 && resourceNames is not null)
+ {
+ errorList.Add(new ParseError(moduleNameBindingResult.Value.Extent, "ImportDscResourceMultipleModulesNotSupportedWithName", string.Format(CultureInfo.CurrentCulture, ParserStrings.ImportDscResourceMultipleModulesNotSupportedWithName)));
+ }
+
+ // if moduleversion is specified then we can not specify multiple modules name
+ if (moduleSpecifications is not null && moduleSpecifications.Length > 1 && moduleVersion is not null)
+ {
+ errorList.Add(new ParseError(moduleNameBindingResult.Value.Extent, "ImportDscResourceMultipleModulesNotSupportedWithVersion", string.Format(CultureInfo.CurrentCulture, ParserStrings.ImportDscResourceNeedParams)));
+ }
+
+ // if moduleversion is specified then we can not specify another version in modulespecification object of ModuleName
+ if (moduleSpecifications is not null && (moduleSpecifications[0].Version is not null || moduleSpecifications[0].MaximumVersion is not null) && moduleVersion is not null)
+ {
+ errorList.Add(new ParseError(moduleNameBindingResult.Value.Extent, "ImportDscResourceMultipleModuleVersionsNotSupported", string.Format(CultureInfo.CurrentCulture, ParserStrings.ImportDscResourceNeedParams)));
+ }
+
+ // If moduleVersion is specified we have only one module Name in valid scenario
+ // So update it's version property in module specification object that will be used to load modules
+ if (moduleSpecifications is not null && moduleSpecifications[0].Version is null && moduleSpecifications[0].MaximumVersion is null && moduleVersion is not null)
+ {
+ moduleSpecifications[0].Version = moduleVersion;
+ }
+ }
+ else
+ {
+ errorList.Add(new ParseError(moduleNameBindingResult.Value.Extent, "RequiresInvalidStringArgument", string.Format(CultureInfo.CurrentCulture, ParserStrings.RequiresInvalidStringArgument, ModuleNameParam)));
+ }
+ }
+
+ if (errorList.Count == 0)
+ {
+ // No errors, try to load the resources
+ LoadResourcesFromModuleInImportResourcePostParse(ast.Extent, moduleSpecifications, resourceNames, errorList);
+ }
+
+ return errorList.ToArray();
+ }
+
+ // This function performs semantic checks for Import-DscResource
+ private static ParseError[] ImportResourceCheckSemantics(DynamicKeywordStatementAst ast)
+ {
+ List errorList = null;
+
+ var keywordAst = Ast.GetAncestorAst(ast.Parent);
+ while (keywordAst is not null)
+ {
+ if (keywordAst.Keyword.Keyword.Equals("Node"))
+ {
+ if (errorList is null)
+ {
+ errorList = new List();
+ }
+
+ errorList.Add(new ParseError(ast.Extent, "ImportDscResourceInsideNode", string.Format(CultureInfo.CurrentCulture, ParserStrings.ImportDscResourceInsideNode)));
+ break;
+ }
+
+ keywordAst = Ast.GetAncestorAst(keywordAst.Parent);
+ }
+
+ if (errorList is not null)
+ {
+ return errorList.ToArray();
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ // This function performs semantic checks for all DSC Resources keywords.
+ private static ParseError[] CheckMandatoryPropertiesPresent(DynamicKeywordStatementAst ast)
+ {
+ HashSet mandatoryPropertiesNames = new HashSet(StringComparer.OrdinalIgnoreCase);
+ foreach (var pair in ast.Keyword.Properties)
+ {
+ if (pair.Value.Mandatory)
+ {
+ mandatoryPropertiesNames.Add(pair.Key);
+ }
+ }
+
+ // by design mandatoryPropertiesNames are not empty at this point:
+ // every resource must have at least one Key property.
+ HashtableAst hashtableAst = null;
+ foreach (var commandElementsAst in ast.CommandElements)
+ {
+ hashtableAst = commandElementsAst as HashtableAst;
+ if (hashtableAst != null)
+ {
+ break;
+ }
+ }
+
+ if (hashtableAst is null)
+ {
+ // nothing to validate
+ return null;
+ }
+
+ foreach (var pair in hashtableAst.KeyValuePairs)
+ {
+ object evalResultObject;
+ if (IsConstantValueVisitor.IsConstant(pair.Item1, out evalResultObject, forAttribute: false, forRequires: false))
+ {
+ var presentName = evalResultObject as string;
+ if (presentName is not null)
+ {
+ if (mandatoryPropertiesNames.Remove(presentName) && mandatoryPropertiesNames.Count == 0)
+ {
+ // optimization, once all mandatory properties are specified, we can safely exit.
+ return null;
+ }
+ }
+ }
+ }
+
+ if (mandatoryPropertiesNames.Count > 0)
+ {
+ ParseError[] errors = new ParseError[mandatoryPropertiesNames.Count];
+ var extent = ast.CommandElements[0].Extent;
+ int i = 0;
+ foreach (string name in mandatoryPropertiesNames)
+ {
+ errors[i] = new ParseError(
+ extent,
+ "MissingValueForMandatoryProperty",
+ string.Format(
+ CultureInfo.CurrentCulture,
+ ParserStrings.MissingValueForMandatoryProperty,
+ ast.Keyword.Keyword,
+ ast.Keyword.Properties.First(p => StringComparer.OrdinalIgnoreCase.Equals(p.Value.Name, name)).Value.TypeConstraint,
+ name));
+ i++;
+ }
+
+ return errors;
+ }
+
+ return null;
+ }
+
+ ///
+ /// Load DSC resources from specified module.
+ ///
+ /// Script statement loading the module, can be null.
+ /// Module information, can be null.
+ /// Name of the resource to be loaded from module.
+ /// List of errors reported by the method.
+ internal static void LoadResourcesFromModuleInImportResourcePostParse(
+ IScriptExtent scriptExtent,
+ ModuleSpecification[] moduleSpecifications,
+ string[] resourceNames,
+ List errorList)
+ {
+ // get all required modules
+ var modules = new Collection();
+ if (moduleSpecifications is not null)
+ {
+ foreach (var moduleToImport in moduleSpecifications)
+ {
+ bool foundModule = false;
+ var moduleInfos = ModuleCmdletBase.GetModuleIfAvailable(moduleToImport);
+
+ if (moduleInfos.Count >= 1 && (moduleToImport.Version is not null || moduleToImport.Guid is not null))
+ {
+ foreach (var psModuleInfo in moduleInfos)
+ {
+ if ((moduleToImport.Guid.HasValue && moduleToImport.Guid.Equals(psModuleInfo.Guid)) ||
+ (moduleToImport.Version is not null &&
+ moduleToImport.Version.Equals(psModuleInfo.Version)))
+ {
+ modules.Add(psModuleInfo);
+ foundModule = true;
+ break;
+ }
+ }
+ }
+ else if (moduleInfos.Count == 1)
+ {
+ modules.Add(moduleInfos[0]);
+ foundModule = true;
+ }
+
+ if (!foundModule)
+ {
+ if (moduleInfos.Count > 1)
+ {
+ errorList.Add(
+ new ParseError(
+ scriptExtent,
+ "MultipleModuleEntriesFoundDuringParse",
+ string.Format(CultureInfo.CurrentCulture, ParserStrings.MultipleModuleEntriesFoundDuringParse, moduleToImport.Name)));
+ }
+ else
+ {
+ string moduleString = moduleToImport.Version == null
+ ? moduleToImport.Name
+ : string.Format(CultureInfo.CurrentCulture, "<{0}, {1}>", moduleToImport.Name, moduleToImport.Version);
+
+ errorList.Add(new ParseError(scriptExtent, "ModuleNotFoundDuringParse", string.Format(CultureInfo.CurrentCulture, ParserStrings.ModuleNotFoundDuringParse, moduleString)));
+ }
+
+ return;
+ }
+ }
+ }
+ else if (resourceNames is not null)
+ {
+ // Lookup the required resources under available PowerShell modules when modulename is not specified
+ // Make sure that this is not a circular import/parsing
+ var callLocation = string.Join(':', scriptExtent.File, scriptExtent.StartLineNumber, scriptExtent.StartColumnNumber, scriptExtent.Text);
+ if (!t_currentImportDscResourceInvocations.Contains(callLocation))
+ {
+ t_currentImportDscResourceInvocations.Add(callLocation);
+ using (var powerShell = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace))
+ {
+ powerShell.AddCommand("Get-Module");
+ powerShell.AddParameter("ListAvailable");
+ modules = powerShell.Invoke();
+ }
+ }
+ }
+
+ // When ModuleName only specified, we need to import all resources from that module
+ var resourcesToImport = new List();
+ if (resourceNames is null || resourceNames.Length == 0)
+ {
+ resourcesToImport.Add("*");
+ }
+ else
+ {
+ resourcesToImport.AddRange(resourceNames);
+ }
+
+ foreach (var moduleInfo in modules)
+ {
+ var resourcesFound = new List();
+ var exceptionList = new System.Collections.ObjectModel.Collection();
+ LoadPowerShellClassResourcesFromModule(primaryModuleInfo: moduleInfo, moduleInfo: moduleInfo, resourcesToImport: resourcesToImport, resourcesFound: resourcesFound, errorList: exceptionList, functionsToDefine: null, recurse: true, extent: scriptExtent);
+ foreach (Exception ex in exceptionList)
+ {
+ errorList.Add(new ParseError(scriptExtent, "ClassResourcesLoadingFailed", ex.Message));
+ }
+
+ foreach (var resource in resourcesFound)
+ {
+ resourcesToImport.Remove(resource);
+ }
+
+ if (resourcesToImport.Count == 0)
+ {
+ break;
+ }
+ }
+
+ if (resourcesToImport.Count > 0)
+ {
+ foreach (var resourceNameToImport in resourcesToImport)
+ {
+ if (!resourceNameToImport.Contains('*'))
+ {
+ errorList.Add(new ParseError(scriptExtent, "DscResourcesNotFoundDuringParsing", string.Format(CultureInfo.CurrentCulture, ParserStrings.DscResourcesNotFoundDuringParsing, resourceNameToImport)));
+ }
+ }
+ }
+ }
+
+ private static void LoadPowerShellClassResourcesFromModule(
+ PSModuleInfo primaryModuleInfo,
+ PSModuleInfo moduleInfo,
+ ICollection resourcesToImport,
+ ICollection resourcesFound,
+ Collection errorList,
+ Dictionary functionsToDefine = null,
+ bool recurse = true,
+ IScriptExtent extent = null)
+ {
+ if (primaryModuleInfo._declaredDscResourceExports is null || primaryModuleInfo._declaredDscResourceExports.Count == 0)
+ {
+ return;
+ }
+
+ if (moduleInfo.ModuleType == ModuleType.Binary)
+ {
+ throw PSTraceSource.NewArgumentException("isConfiguration", ParserStrings.ConfigurationNotSupportedInPowerShellCore);
+ }
+ else
+ {
+ string scriptPath = null;
+ if (moduleInfo.RootModule is not null)
+ {
+ scriptPath = Path.Join(moduleInfo.ModuleBase, moduleInfo.RootModule);
+ }
+ else if (moduleInfo.Path is not null)
+ {
+ scriptPath = moduleInfo.Path;
+ }
+
+ LoadPowerShellClassResourcesFromModule(scriptPath, primaryModuleInfo, resourcesToImport, resourcesFound, functionsToDefine, errorList, extent);
+ }
+
+ if (moduleInfo.NestedModules is not null && recurse)
+ {
+ foreach (var nestedModule in moduleInfo.NestedModules)
+ {
+ LoadPowerShellClassResourcesFromModule(primaryModuleInfo, nestedModule, resourcesToImport, resourcesFound, errorList, functionsToDefine, recurse: false, extent: extent);
+ }
+ }
+ }
+
+ ///
+ /// Import class resources from module.
+ ///
+ /// Module information.
+ /// Collection of resources to import.
+ /// Functions to define.
+ /// List of errors to return.
+ /// The list of resources imported from this module.
+ public static List ImportClassResourcesFromModule(PSModuleInfo moduleInfo, ICollection resourcesToImport, Dictionary functionsToDefine, Collection errors)
+ {
+ if (!ExperimentalFeature.IsEnabled(DscExperimentalFeatureName))
+ {
+ throw new InvalidOperationException(ParserStrings.PS7DscSupportDisabled);
+ }
+
+ var resourcesImported = new List();
+ LoadPowerShellClassResourcesFromModule(moduleInfo, moduleInfo, resourcesToImport, resourcesImported, errors, functionsToDefine);
+ return resourcesImported;
+ }
+
+ internal static PSObject[] GenerateJsonClassesForAst(TypeDefinitionAst typeAst, PSModuleInfo module, DSCResourceRunAsCredential runAsBehavior)
+ {
+ var embeddedInstanceTypes = new List