diff --git a/src/System.Management.Automation/AssemblyInfo.cs b/src/System.Management.Automation/AssemblyInfo.cs index fe3ee107e5..3c03c22e04 100644 --- a/src/System.Management.Automation/AssemblyInfo.cs +++ b/src/System.Management.Automation/AssemblyInfo.cs @@ -13,6 +13,7 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo(@"Microsoft.PowerShell.Security" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo(@"System.Management.Automation.Remoting" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo(@"Microsoft.PowerShell.ConsoleHost" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] +[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.DscSubsystem" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] namespace System.Management.Automation { diff --git a/src/System.Management.Automation/DscSupport/JsonCimDSCParser.cs b/src/System.Management.Automation/DscSupport/JsonCimDSCParser.cs deleted file mode 100755 index c2221cb3f3..0000000000 --- a/src/System.Management.Automation/DscSupport/JsonCimDSCParser.cs +++ /dev/null @@ -1,68 +0,0 @@ -// 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 deleted file mode 100755 index 319560f0a0..0000000000 --- a/src/System.Management.Automation/DscSupport/JsonDeserializer.cs +++ /dev/null @@ -1,72 +0,0 @@ -// 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 deleted file mode 100755 index dfda9b8460..0000000000 --- a/src/System.Management.Automation/DscSupport/JsonDscClassCache.cs +++ /dev/null @@ -1,2503 +0,0 @@ -// 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" }; - - /// - /// Gets a collection to prevent circular importing case when Import-DscResource does not have a module specified. - /// - private static HashSet CurrentImportDscResourceInvocations - => t_currentImportDscResourceInvocations ??= new HashSet(StringComparer.OrdinalIgnoreCase); - - [ThreadStatic] - private static HashSet t_currentImportDscResourceInvocations; - - /// - /// 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; - 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 (!CurrentImportDscResourceInvocations.Contains(callLocation)) - { - 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(); - - var result = GenerateJsonClassesForAst(typeAst, embeddedInstanceTypes); - var visitedInstances = new List(); - visitedInstances.Add(typeAst); - var classes = ProcessEmbeddedInstanceTypes(embeddedInstanceTypes, visitedInstances); - AddEmbeddedInstanceTypesToCaches(classes, module, runAsBehavior); - - return result; - } - - private static List ProcessEmbeddedInstanceTypes(List embeddedInstanceTypes, List visitedInstances) - { - var result = new List(); - while (embeddedInstanceTypes.Count > 0) - { - var batchedTypes = embeddedInstanceTypes.Where(x => !visitedInstances.Contains(x)).ToArray(); - embeddedInstanceTypes.Clear(); - - for (int i = batchedTypes.Length - 1; i >= 0; i--) - { - visitedInstances.Add(batchedTypes[i]); - var typeAst = batchedTypes[i] as TypeDefinitionAst; - if (typeAst is not null) - { - var classes = GenerateJsonClassesForAst(typeAst, embeddedInstanceTypes); - result.AddRange(classes); - } - } - } - - return result; - } - - private static void AddEmbeddedInstanceTypesToCaches(IEnumerable classes, PSModuleInfo module, DSCResourceRunAsCredential runAsBehavior) - { - foreach (dynamic c in classes) - { - var className = c.ClassName; - string alias = GetFriendlyName(c); - var friendlyName = string.IsNullOrEmpty(alias) ? className : alias; - var moduleQualifiedResourceName = GetModuleQualifiedResourceName(module.Name, module.Version.ToString(), className, friendlyName); - var classCacheEntry = new DscClassCacheEntry(runAsBehavior, false, c, module.Path); - ClassCache[moduleQualifiedResourceName] = classCacheEntry; - GuestConfigClassCache[moduleQualifiedResourceName] = classCacheEntry; - ByClassModuleCache[className] = new Tuple(module.Name, module.Version); - } - } - - internal static string MapTypeNameToMofType(ITypeName typeName, string memberName, string className, out bool isArrayType, out string embeddedInstanceType, List embeddedInstanceTypes, ref string[] enumNames) - { - TypeName propTypeName; - var arrayTypeName = typeName as ArrayTypeName; - if (arrayTypeName is not null) - { - isArrayType = true; - propTypeName = arrayTypeName.ElementType as TypeName; - } - else - { - isArrayType = false; - propTypeName = typeName as TypeName; - } - - if (propTypeName is null || propTypeName._typeDefinitionAst is null) - { - throw new NotSupportedException(string.Format( - CultureInfo.InvariantCulture, - ParserStrings.UnsupportedPropertyTypeOfDSCResourceClass, - memberName, - typeName.FullName, - typeName)); - } - - if (propTypeName._typeDefinitionAst.IsEnum) - { - enumNames = propTypeName._typeDefinitionAst.Members.Select(m => m.Name).ToArray(); - isArrayType = false; - embeddedInstanceType = null; - return "string"; - } - - if (!embeddedInstanceTypes.Contains(propTypeName._typeDefinitionAst)) - { - embeddedInstanceTypes.Add(propTypeName._typeDefinitionAst); - } - - embeddedInstanceType = propTypeName.Name.Replace('.', '_'); - return "Instance"; - } - - private static PSObject[] GenerateJsonClassesForAst(TypeDefinitionAst typeAst, List embeddedInstanceTypes) - { - // MOF-based implementation of this used to generate MOF string representing classes/typeAst and pass it to MMI/MOF deserializer to get CimClass array - // Here we are avoiding that roundtrip by constructing the resulting PSObjects directly - var className = typeAst.Name; - - string cimSuperClassName = null; - if (typeAst.Attributes.Any(a => a.TypeName.GetReflectionAttributeType() == typeof(DscResourceAttribute))) - { - cimSuperClassName = "OMI_BaseResource"; - } - - var cimClassProperties = ProcessMembers(embeddedInstanceTypes, typeAst, className).ToArray(); - - Queue bases = new Queue(); - foreach (var b in typeAst.BaseTypes) - { - bases.Enqueue(b); - } - - while (bases.Count > 0) - { - var b = bases.Dequeue(); - var tc = b as TypeConstraintAst; - - if (tc is not null) - { - b = tc.TypeName.GetReflectionType(); - if (b is null) - { - var td = tc.TypeName as TypeName; - if (td is not null && td._typeDefinitionAst is not null) - { - ProcessMembers(embeddedInstanceTypes, td._typeDefinitionAst, className); - foreach (var b1 in td._typeDefinitionAst.BaseTypes) - { - bases.Enqueue(b1); - } - } - - continue; - } - } - } - - var result = new PSObject(); - result.Properties.Add(new PSNoteProperty("ClassName", className)); - result.Properties.Add(new PSNoteProperty("FriendlyName", className)); - result.Properties.Add(new PSNoteProperty("SuperClassName", cimSuperClassName)); - result.Properties.Add(new PSNoteProperty("ClassProperties", cimClassProperties)); - - return new[] { result }; - } - - private static List ProcessMembers(List embeddedInstanceTypes, TypeDefinitionAst typeDefinitionAst, string className) - { - List result = new List(); - - foreach (var member in typeDefinitionAst.Members) - { - var property = member as PropertyMemberAst; - - if (property == null || property.IsStatic || - property.Attributes.All(a => a.TypeName.GetReflectionAttributeType() != typeof(DscPropertyAttribute))) - { - continue; - } - - var memberType = property.PropertyType is null - ? typeof(object) - : property.PropertyType.TypeName.GetReflectionType(); - - var attributes = new List(); - for (int i = 0; i < property.Attributes.Count; i++) - { - attributes.Add(property.Attributes[i].GetAttribute()); - } - - string mofType; - bool isArrayType; - string embeddedInstanceType; - string[] enumNames = null; - - if (memberType != null) - { - mofType = MapTypeToMofType(memberType, member.Name, className, out isArrayType, out embeddedInstanceType, embeddedInstanceTypes); - if (memberType.IsEnum) - { - enumNames = Enum.GetNames(memberType); - } - } - else - { - // PropertyType can't be null, we used typeof(object) above in that case so we don't get here. - mofType = MapTypeNameToMofType(property.PropertyType.TypeName, member.Name, className, out isArrayType, out embeddedInstanceType, embeddedInstanceTypes, ref enumNames); - } - - var propertyObject = new PSObject(); - propertyObject.Properties.Add(new PSNoteProperty(@"Name", member.Name)); - propertyObject.Properties.Add(new PSNoteProperty(@"CimType", mofType + (isArrayType ? "Array" : string.Empty))); - if (!string.IsNullOrEmpty(embeddedInstanceType)) - { - propertyObject.Properties.Add(new PSNoteProperty(@"ReferenceClassName", embeddedInstanceType)); - } - - PSObject attributesPSObject = null; - foreach (var attr in attributes) - { - var dscProperty = attr as DscPropertyAttribute; - if (dscProperty is not null) - { - if (attributesPSObject is null) - { - attributesPSObject = new PSObject(); - } - - if (dscProperty.Key) - { - attributesPSObject.Properties.Add(new PSNoteProperty("Key", true)); - } - - if (dscProperty.Mandatory) - { - attributesPSObject.Properties.Add(new PSNoteProperty("Required", true)); - } - - if (dscProperty.NotConfigurable) - { - attributesPSObject.Properties.Add(new PSNoteProperty("Read", true)); - } - - continue; - } - - var validateSet = attr as ValidateSetAttribute; - if (validateSet is not null) - { - if (attributesPSObject is null) - { - attributesPSObject = new PSObject(); - } - - List valueMap = new List(validateSet.ValidValues); - List values = new List(validateSet.ValidValues); - attributesPSObject.Properties.Add(new PSNoteProperty("ValueMap", valueMap)); - attributesPSObject.Properties.Add(new PSNoteProperty("Values", values)); - } - } - - if (attributesPSObject is not null) - { - propertyObject.Properties.Add(new PSNoteProperty(@"Qualifiers", attributesPSObject)); - } - - result.Add(propertyObject); - } - - return result; - } - - private static bool GetResourceDefinitionsFromModule(string fileName, out IEnumerable resourceDefinitions, Collection errorList, IScriptExtent extent) - { - resourceDefinitions = null; - - if (string.IsNullOrEmpty(fileName)) - { - return false; - } - - if (!".psm1".Equals(Path.GetExtension(fileName), StringComparison.OrdinalIgnoreCase) && - !".ps1".Equals(Path.GetExtension(fileName), StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - Token[] tokens; - ParseError[] errors; - var ast = Parser.ParseFile(fileName, out tokens, out errors); - - if (errors is not null && errors.Length > 0) - { - if (errorList is not null && extent is not null) - { - List errorMessages = new List(); - foreach (var error in errors) - { - errorMessages.Add(error.ToString()); - } - - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.FailToParseModuleScriptFile, fileName, string.Join(Environment.NewLine, errorMessages)); - e.SetErrorId("FailToParseModuleScriptFile"); - errorList.Add(e); - } - - return false; - } - - resourceDefinitions = ast.FindAll( - n => - { - var typeAst = n as TypeDefinitionAst; - if (typeAst is not null) - { - for (int i = 0; i < typeAst.Attributes.Count; i++) - { - var a = typeAst.Attributes[i]; - if (a.TypeName.GetReflectionAttributeType() == typeof(DscResourceAttribute)) - { - return true; - } - } - } - - return false; - }, - false); - - return true; - } - - private static bool LoadPowerShellClassResourcesFromModule(string fileName, PSModuleInfo module, ICollection resourcesToImport, ICollection resourcesFound, Dictionary functionsToDefine, Collection errorList, IScriptExtent extent) - { - IEnumerable resourceDefinitions; - if (!GetResourceDefinitionsFromModule(fileName, out resourceDefinitions, errorList, extent)) - { - return false; - } - - var result = false; - - const WildcardOptions options = WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant; - IEnumerable patternList = SessionStateUtilities.CreateWildcardsFromStrings(module._declaredDscResourceExports, options); - - foreach (var r in resourceDefinitions) - { - result = true; - var resourceDefnAst = (TypeDefinitionAst)r; - - if (!SessionStateUtilities.MatchesAnyWildcardPattern(resourceDefnAst.Name, patternList, true)) - { - continue; - } - - bool skip = true; - foreach (var toImport in resourcesToImport) - { - if (WildcardPattern.Get(toImport, WildcardOptions.IgnoreCase).IsMatch(resourceDefnAst.Name)) - { - skip = false; - break; - } - } - - if (skip) - { - continue; - } - - // Parse the Resource Attribute to see if RunAs behavior is specified for the resource. - DSCResourceRunAsCredential runAsBehavior = DSCResourceRunAsCredential.Default; - foreach (var attr in resourceDefnAst.Attributes) - { - if (attr.TypeName.GetReflectionAttributeType() == typeof(DscResourceAttribute)) - { - foreach (var na in attr.NamedArguments) - { - if (na.ArgumentName.Equals("RunAsCredential", StringComparison.OrdinalIgnoreCase)) - { - var dscResourceAttribute = attr.GetAttribute() as DscResourceAttribute; - if (dscResourceAttribute != null) - { - runAsBehavior = dscResourceAttribute.RunAsCredential; - } - } - } - } - } - - var classes = GenerateJsonClassesForAst(resourceDefnAst, module, runAsBehavior); - - ProcessJsonForDynamicKeywords(module, resourcesFound, functionsToDefine, classes, runAsBehavior, errorList); - } - - return result; - } - - private static readonly Dictionary s_mapPrimitiveDotNetTypeToMof = new Dictionary() - { - { typeof(sbyte), "sint8" }, - { typeof(byte), "uint8" }, - { typeof(short), "sint16" }, - { typeof(ushort), "uint16" }, - { typeof(int), "sint32" }, - { typeof(uint), "uint32" }, - { typeof(long), "sint64" }, - { typeof(ulong), "uint64" }, - { typeof(float), "real32" }, - { typeof(double), "real64" }, - { typeof(bool), "boolean" }, - { typeof(string), "string" }, - { typeof(DateTime), "datetime" }, - { typeof(PSCredential), "string" }, - { typeof(char), "char16" }, - }; - - internal static string MapTypeToMofType(Type type, string memberName, string className, out bool isArrayType, out string embeddedInstanceType, List embeddedInstanceTypes) - { - isArrayType = false; - if (type.IsValueType) - { - type = Nullable.GetUnderlyingType(type) ?? type; - } - - if (type.IsEnum) - { - embeddedInstanceType = null; - return "string"; - } - - if (type == typeof(Hashtable)) - { - // Hashtable is obviously not an array, but in the mof, we represent - // it as string[] (really, embeddedinstance of MSFT_KeyValuePair), but - // we need an array to hold each entry in the hashtable. - isArrayType = true; - embeddedInstanceType = "MSFT_KeyValuePair"; - return "string"; - } - - if (type == typeof(PSCredential)) - { - embeddedInstanceType = "MSFT_Credential"; - return "string"; - } - - if (type.IsArray) - { - isArrayType = true; - bool temp; - var elementType = type.GetElementType(); - if (!elementType.IsArray) - { - return MapTypeToMofType(type.GetElementType(), memberName, className, out temp, out embeddedInstanceType, embeddedInstanceTypes); - } - } - else - { - string cimType; - if (s_mapPrimitiveDotNetTypeToMof.TryGetValue(type, out cimType)) - { - embeddedInstanceType = null; - return cimType; - } - } - - bool supported = false; - bool missingDefaultConstructor = false; - if (type.IsValueType) - { - if (s_mapPrimitiveDotNetTypeToMof.ContainsKey(type)) - { - supported = true; - } - } - else if (!type.IsAbstract) - { - // Must have default constructor, at least 1 public property/field, and no base classes - if (type.GetConstructor(Type.EmptyTypes) is null) - { - missingDefaultConstructor = true; - } - else if (type.BaseType == typeof(object) && - (type.GetProperties(BindingFlags.Instance | BindingFlags.Public).Length > 0 || - type.GetFields(BindingFlags.Instance | BindingFlags.Public).Length > 0)) - { - supported = true; - } - } - - if (supported) - { - if (!embeddedInstanceTypes.Contains(type)) - { - embeddedInstanceTypes.Add(type); - } - - // The type is obviously not a string, but in the mof, we represent - // it as string (really, embeddedinstance of the class type) - embeddedInstanceType = type.FullName.Replace('.', '_'); - return "string"; - } - - if (missingDefaultConstructor) - { - throw new NotSupportedException(string.Format( - CultureInfo.InvariantCulture, - ParserStrings.DscResourceMissingDefaultConstructor, - type.Name)); - } - else - { - throw new NotSupportedException(string.Format( - CultureInfo.InvariantCulture, - ParserStrings.UnsupportedPropertyTypeOfDSCResourceClass, - memberName, - type.Name, - className)); - } - } - - private static void ProcessJsonForDynamicKeywords( - PSModuleInfo module, - ICollection resourcesFound, - Dictionary functionsToDefine, - PSObject[] classes, - DSCResourceRunAsCredential runAsBehavior, - Collection errors) - { - foreach (dynamic c in classes) - { - var className = c.ClassName; - string alias = GetFriendlyName(c); - var friendlyName = string.IsNullOrEmpty(alias) ? className : alias; - if (!CacheResourcesFromMultipleModuleVersions) - { - // Find & remove the previous version of the resource. - List> resourceList = FindResourceInCache(module.Name, className, friendlyName); - - if (resourceList.Count > 0 && !string.IsNullOrEmpty(resourceList[0].Key)) - { - ClassCache.Remove(resourceList[0].Key); - - // keyword is already defined and it is a Inbox resource, remove it - if (DynamicKeyword.ContainsKeyword(friendlyName) && resourceList[0].Value.IsImportedImplicitly) - { - DynamicKeyword.RemoveKeyword(friendlyName); - } - } - } - - var moduleQualifiedResourceName = GetModuleQualifiedResourceName(module.Name, module.Version.ToString(), className, friendlyName); - DscClassCacheEntry existingCacheEntry = null; - if (ClassCache.TryGetValue(moduleQualifiedResourceName, out existingCacheEntry)) - { - if (errors is not null) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.DuplicateCimClassDefinition, className, module.Path, existingCacheEntry.ModulePath); - e.SetErrorId("DuplicateCimClassDefinition"); - errors.Add(e); - } - } - else - { - var classCacheEntry = new DscClassCacheEntry(runAsBehavior, false, c, module.Path); - ClassCache[moduleQualifiedResourceName] = classCacheEntry; - GuestConfigClassCache[moduleQualifiedResourceName] = classCacheEntry; - ByClassModuleCache[className] = new Tuple(module.Name, module.Version); - resourcesFound.Add(className); - CreateAndRegisterKeywordFromCimClass(module.Name, module.Version, c, functionsToDefine, runAsBehavior); - } - } - } - - /// - /// Returns an error record to use in the case of a malformed resource reference in the DependsOn list. - /// - /// The malformed resource. - /// The referencing resource instance. - /// Generated error record. - public static ErrorRecord GetBadlyFormedRequiredResourceIdErrorRecord(string badDependsOnReference, string definingResource) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.GetBadlyFormedRequiredResourceId, badDependsOnReference, definingResource); - e.SetErrorId("GetBadlyFormedRequiredResourceId"); - return e.ErrorRecord; - } - - /// - /// Returns an error record to use in the case of a malformed resource reference in the exclusive resources list. - /// - /// The malformed resource. - /// The referencing resource instance. - /// Generated error record. - public static ErrorRecord GetBadlyFormedExclusiveResourceIdErrorRecord(string badExclusiveResourcereference, string definingResource) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.GetBadlyFormedExclusiveResourceId, badExclusiveResourcereference, definingResource); - e.SetErrorId("GetBadlyFormedExclusiveResourceId"); - return e.ErrorRecord; - } - - /// - /// If a partial configuration is in 'Pull' Mode, it needs a configuration source. - /// - /// Resource id. - /// Generated error record. - public static ErrorRecord GetPullModeNeedConfigurationSource(string resourceId) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.GetPullModeNeedConfigurationSource, resourceId); - e.SetErrorId("GetPullModeNeedConfigurationSource"); - return e.ErrorRecord; - } - - /// - /// Refresh Mode can not be Disabled for the Partial Configurations. - /// - /// Resource id. - /// Generated error record. - public static ErrorRecord DisabledRefreshModeNotValidForPartialConfig(string resourceId) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.DisabledRefreshModeNotValidForPartialConfig, resourceId); - e.SetErrorId("DisabledRefreshModeNotValidForPartialConfig"); - return e.ErrorRecord; - } - - /// - /// Returns an error record to use in the case of a malformed resource reference in the DependsOn list. - /// - /// The duplicate resource identifier. - /// The node being defined. - /// The error record to use. - public static ErrorRecord DuplicateResourceIdInNodeStatementErrorRecord(string duplicateResourceId, string nodeName) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.DuplicateResourceIdInNodeStatement, duplicateResourceId, nodeName); - e.SetErrorId("DuplicateResourceIdInNodeStatement"); - return e.ErrorRecord; - } - - /// - /// Returns an error record to use in the case of a configuration name is invalid. - /// - /// Configuration name. - /// Generated error record. - public static ErrorRecord InvalidConfigurationNameErrorRecord(string configurationName) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.InvalidConfigurationName, configurationName); - e.SetErrorId("InvalidConfigurationName"); - return e.ErrorRecord; - } - - /// - /// Returns an error record to use in the case of the given value for a property is invalid. - /// - /// Property name. - /// Property value. - /// Keyword name. - /// Valid property values. - /// Generated error record. - public static ErrorRecord InvalidValueForPropertyErrorRecord(string propertyName, string value, string keywordName, string validValues) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.InvalidValueForProperty, value, propertyName, keywordName, validValues); - e.SetErrorId("InvalidValueForProperty"); - return e.ErrorRecord; - } - - /// - /// Returns an error record to use in case the given property is not valid LocalConfigurationManager property. - /// - /// Property name. - /// Valid properties. - /// Generated error record. - public static ErrorRecord InvalidLocalConfigurationManagerPropertyErrorRecord(string propertyName, string validProperties) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.InvalidLocalConfigurationManagerProperty, propertyName, validProperties); - e.SetErrorId("InvalidLocalConfigurationManagerProperty"); - return e.ErrorRecord; - } - - /// - /// Returns an error record to use in the case of the given value for a property is not supported. - /// - /// Property name. - /// Property value. - /// Keyword name. - /// Valid property values. - /// Generated error record. - public static ErrorRecord UnsupportedValueForPropertyErrorRecord(string propertyName, string value, string keywordName, string validValues) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.UnsupportedValueForProperty, value, propertyName, keywordName, validValues); - e.SetErrorId("UnsupportedValueForProperty"); - return e.ErrorRecord; - } - - /// - /// Returns an error record to use in the case of no value is provided for a mandatory property. - /// - /// Keyword name. - /// Type name. - /// Property name. - /// Generated error record. - public static ErrorRecord MissingValueForMandatoryPropertyErrorRecord(string keywordName, string typeName, string propertyName) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.MissingValueForMandatoryProperty, keywordName, typeName, propertyName); - e.SetErrorId("MissingValueForMandatoryProperty"); - return e.ErrorRecord; - } - - /// - /// Returns an error record to use in the case of more than one values are provided for DebugMode property. - /// - /// Generated error record. - public static ErrorRecord DebugModeShouldHaveOneValue() - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.DebugModeShouldHaveOneValue); - e.SetErrorId("DebugModeShouldHaveOneValue"); - return e.ErrorRecord; - } - - /// - /// Return an error to indicate a value is out of range for a dynamic keyword property. - /// - /// Rroperty name. - /// Resource name. - /// Provided value. - /// Valid range lower bound. - /// Valid range upper bound. - /// Generated error record. - public static ErrorRecord ValueNotInRangeErrorRecord(string property, string name, int providedValue, int lower, int upper) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.ValueNotInRange, property, name, providedValue, lower, upper); - e.SetErrorId("ValueNotInRange"); - return e.ErrorRecord; - } - - /// - /// Returns an error record to use when composite resource and its resource instances both has PsDscRunAsCredentials value. - /// - /// ResourceId of resource. - /// Generated error record. - public static ErrorRecord PsDscRunAsCredentialMergeErrorForCompositeResources(string resourceId) - { - PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(ParserStrings.PsDscRunAsCredentialMergeErrorForCompositeResources, resourceId); - e.SetErrorId("PsDscRunAsCredentialMergeErrorForCompositeResources"); - return e.ErrorRecord; - } - - /// - /// Routine to format a usage string from keyword. The resulting string should look like: - /// User [string] #ResourceName - /// { - /// UserName = [string] - /// [ Description = [string] ] - /// [ Disabled = [bool] ] - /// [ Ensure = [string] { Absent | Present } ] - /// [ Force = [bool] ] - /// [ FullName = [string] ] - /// [ Password = [PSCredential] ] - /// [ PasswordChangeNotAllowed = [bool] ] - /// [ PasswordChangeRequired = [bool] ] - /// [ PasswordNeverExpires = [bool] ] - /// [ DependsOn = [string[]] ] - /// } - /// - /// Dynamic keyword. - /// Usage string. - public static string GetDSCResourceUsageString(DynamicKeyword keyword) - { - StringBuilder usageString; - switch (keyword.NameMode) - { - // Name must be present and simple non-empty bare word - case DynamicKeywordNameMode.SimpleNameRequired: - usageString = new StringBuilder(keyword.Keyword + " [string] # Resource Name"); - break; - - // Name must be present but can also be an expression - case DynamicKeywordNameMode.NameRequired: - usageString = new StringBuilder(keyword.Keyword + " [string[]] # Name List"); - break; - - // Name may be optionally present, but if it is present, it must be a non-empty bare word. - case DynamicKeywordNameMode.SimpleOptionalName: - usageString = new StringBuilder(keyword.Keyword + " [ [string] ] # Optional Name"); - break; - - // Name may be optionally present, expression or bare word - case DynamicKeywordNameMode.OptionalName: - usageString = new StringBuilder(keyword.Keyword + " [ [string[]] ] # Optional NameList"); - break; - - // Does not take a name - default: - usageString = new StringBuilder(keyword.Keyword); - break; - } - - usageString.Append("\n{\n"); - - bool listKeyProperties = true; - while (true) - { - foreach (var prop in keyword.Properties.OrderBy(ob => ob.Key)) - { - if (string.Equals(prop.Key, "ResourceId", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - var propVal = prop.Value; - if ((listKeyProperties && propVal.IsKey) || (!listKeyProperties && !propVal.IsKey)) - { - usageString.Append(propVal.Mandatory ? " " : " [ "); - usageString.Append(prop.Key); - usageString.Append(" = "); - usageString.Append(FormatCimPropertyType(propVal, !propVal.Mandatory)); - } - } - - if (listKeyProperties) - { - listKeyProperties = false; - } - else - { - break; - } - } - - usageString.Append('}'); - - return usageString.ToString(); - } - - /// - /// Format the type name of a CIM property in a presentable way. - /// - /// Dynamic keyword property. - /// If this is optional property or not. - /// CIM property type string. - private static StringBuilder FormatCimPropertyType(DynamicKeywordProperty prop, bool isOptionalProperty) - { - string cimTypeName = prop.TypeConstraint; - StringBuilder formattedTypeString = new StringBuilder(); - - if (string.Equals(cimTypeName, "MSFT_Credential", StringComparison.OrdinalIgnoreCase)) - { - formattedTypeString.Append("[PSCredential]"); - } - else if (string.Equals(cimTypeName, "MSFT_KeyValuePair", StringComparison.OrdinalIgnoreCase) || string.Equals(cimTypeName, "MSFT_KeyValuePair[]", StringComparison.OrdinalIgnoreCase)) - { - formattedTypeString.Append("[Hashtable]"); - } - else - { - string convertedTypeString = System.Management.Automation.LanguagePrimitives.ConvertTypeNameToPSTypeName(cimTypeName); - if (!string.IsNullOrEmpty(convertedTypeString) && !string.Equals(convertedTypeString, "[]", StringComparison.OrdinalIgnoreCase)) - { - formattedTypeString.Append(convertedTypeString); - } - else - { - formattedTypeString.Append("[" + cimTypeName + "]"); - } - } - - // Do the property values map - if (prop.ValueMap is not null && prop.ValueMap.Count > 0) - { - formattedTypeString.Append(" { " + string.Join(" | ", prop.ValueMap.Keys.OrderBy(x => x)) + " }"); - } - - // We prepend optional property with "[" so close out it here. This way it is shown with [ ] to indication optional - if (isOptionalProperty) - { - formattedTypeString.Append(']'); - } - - formattedTypeString.Append('\n'); - - return formattedTypeString; - } - - /// - /// Gets the scriptblock that implements the CIM keyword functionality. - /// - private static ScriptBlock CimKeywordImplementationFunction - { - get - { - // The scriptblock cache will handle mutual exclusion - return s_cimKeywordImplementationFunction ??= ScriptBlock.Create(CimKeywordImplementationFunctionText); - } - } - - private static ScriptBlock s_cimKeywordImplementationFunction; - - private const string CimKeywordImplementationFunctionText = @" - param ( - [Parameter(Mandatory)] - $KeywordData, - [Parameter(Mandatory)] - $Name, - [Parameter(Mandatory)] - [Hashtable] - $Value, - [Parameter(Mandatory)] - $SourceMetadata - ) - -# walk the call stack to get at all of the enclosing configuration resource IDs - $stackedConfigs = @(Get-PSCallStack | - where { ($null -ne $_.InvocationInfo.MyCommand) -and ($_.InvocationInfo.MyCommand.CommandType -eq 'Configuration') }) -# keep all but the top-most - $stackedConfigs = $stackedConfigs[0..(@($stackedConfigs).Length - 2)] -# and build the complex resource ID suffix. - $complexResourceQualifier = ( $stackedConfigs | ForEach-Object { '[' + $_.Command + ']' + $_.InvocationInfo.BoundParameters['InstanceName'] } ) -join '::' - -# -# Utility function used to validate that the DependsOn arguments are well-formed. -# The function also adds them to the define nodes resource collection. -# in the case of resources generated inside a script resource, this routine -# will also fix up the DependsOn references to '[Type]Instance::[OuterType]::OuterInstance -# - function Test-DependsOn - { - -# make sure the references are well-formed - $updatedDependsOn = foreach ($DependsOnVar in $value['DependsOn']) { -# match [ResourceType]ResourceName. ResourceName should starts with [a-z_0-9] followed by [a-z_0-9\p{Zs}\.\\-]* - if ($DependsOnVar -notmatch '^\[[a-z]\w*\][a-z_0-9][a-z_0-9\p{Zs}\.\\-]*$') - { - Update-ConfigurationErrorCount - Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache]::GetBadlyFormedRequiredResourceIdErrorRecord($DependsOnVar, $resourceId)) - } - -# Fix up DependsOn for nested names - if ($MyTypeName -and $typeName -ne $MyTypeName -and $InstanceName) - { - ""$DependsOnVar::$complexResourceQualifier"" - } - else - { - $DependsOnVar - } - } - - $value['DependsOn']= $updatedDependsOn - - if($null -ne $DependsOn) - { -# -# Combine DependsOn with dependson from outer composite resource -# which is set as local variable $DependsOn at the composite resource context -# - $value['DependsOn']= @($value['DependsOn']) + $DependsOn - } - -# Save the resource id in a per-node dictionary to do cross validation at the end - Set-NodeResources $resourceId @( $value['DependsOn']) - -# Remove depends on because it need to be fixed up for composite resources -# We do it in ValidateNodeResource and Update-Depends on in configuration/Node function - $value.Remove('DependsOn') - } - -# A copy of the value object with correctly-cased property names - $canonicalizedValue = @{} - - $typeName = $keywordData.ResourceName # CIM type - $keywordName = $keywordData.Keyword # user-friendly alias that is used in scripts - $keyValues = '' - $debugPrefix = "" ${TypeName}:"" # set up a debug prefix string that makes it easier to track what's happening. - - Write-Debug ""${debugPrefix} RESOURCE PROCESSING STARTED [KeywordName='$keywordName'] Function='$($myinvocation.Invocationname)']"" - -# Check whether it's an old style metaconfig - $OldMetaConfig = $false - if ((-not $IsMetaConfig) -and ($keywordName -ieq 'LocalConfigurationManager')) { - $OldMetaConfig = $true - } - -# Check to see if it's a resource keyword. If so add the meta-properties to the canonical property collection. - $resourceId = $null -# todo: need to include configuration managers and partial configuration - if (($keywordData.Properties.Keys -contains 'DependsOn') -or (($KeywordData.ImplementingModule -ieq 'PSDesiredStateConfigurationEngine') -and ($KeywordData.NameMode -eq [System.Management.Automation.Language.DynamicKeywordNameMode]::NameRequired))) - { - - $resourceId = ""[$keywordName]$name"" - if ($MyTypeName -and $keywordName -ne $MyTypeName -and $InstanceName) - { - $resourceId += ""::$complexResourceQualifier"" - } - - Write-Debug ""${debugPrefix} ResourceID = $resourceId"" - -# copy the meta-properties - $canonicalizedValue['ResourceID'] = $resourceId - $canonicalizedValue['SourceInfo'] = $SourceMetadata - if(-not $IsMetaConfig) - { - $canonicalizedValue['ModuleName'] = $keywordData.ImplementingModule - $canonicalizedValue['ModuleVersion'] = $keywordData.ImplementingModuleVersion -as [string] - } - -# see if there is already a resource with this ID. - if (Test-NodeResources $resourceId) - { - Update-ConfigurationErrorCount - Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache]::DuplicateResourceIdInNodeStatementErrorRecord($resourceId, (Get-PSCurrentConfigurationNode))) - } - else - { -# If there are prerequisite resources, validate that the references are well-formed strings -# This routine also adds the resource to the global node resources table. - Test-DependsOn - -# Check if PsDscRunCredential is being specified as Arguments to Configuration - if($null -ne $PsDscRunAsCredential) - { -# Check if resource is also trying to set the value for RunAsCred -# In that case we will generate error during compilation, this is merge error - if($null -ne $value['PsDscRunAsCredential']) - { - Update-ConfigurationErrorCount - Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache]::PsDscRunAsCredentialMergeErrorForCompositeResources($resourceId)) - } -# Set the Value of RunAsCred to that of outer configuration - else - { - $value['PsDscRunAsCredential'] = $PsDscRunAsCredential - } - } - -# Save the resource id in a per-node dictionary to do cross validation at the end - if($keywordData.ImplementingModule -ieq ""PSDesiredStateConfigurationEngine"") - { -#$keywordName is PartialConfiguration - if($keywordName -eq 'PartialConfiguration') - { -# RefreshMode is 'Pull' and .ConfigurationSource is empty - if($value['RefreshMode'] -eq 'Pull' -and -not $value['ConfigurationSource']) - { - Update-ConfigurationErrorCount - Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache]::GetPullModeNeedConfigurationSource($resourceId)) - } - -# Verify that RefreshMode is not Disabled for Partial configuration - if($value['RefreshMode'] -eq 'Disabled') - { - Update-ConfigurationErrorCount - Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache]::DisabledRefreshModeNotValidForPartialConfig($resourceId)) - } - - if($null -ne $value['ConfigurationSource']) - { - Set-NodeManager $resourceId $value['ConfigurationSource'] - } - - if($null -ne $value['ResourceModuleSource']) - { - Set-NodeResourceSource $resourceId $value['ResourceModuleSource'] - } - } - - if($null -ne $value['ExclusiveResources']) - { -# make sure the references are well-formed - foreach ($ExclusiveResource in $value['ExclusiveResources']) { - if (($ExclusiveResource -notmatch '^[a-z][a-z_0-9]*\\[a-z][a-z_0-9]*$') -and ($ExclusiveResource -notmatch '^[a-z][a-z_0-9]*$') -and ($ExclusiveResource -notmatch '^[a-z][a-z_0-9]*\\\*$')) - { - Update-ConfigurationErrorCount - Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache]::GetBadlyFormedExclusiveResourceIdErrorRecord($ExclusiveResource, $resourceId)) - } - } - -# Save the resource id in a per-node dictionary to do cross validation at the end -# Validate resource exist -# Also update the resource reference from module\friendlyname to module\name - $value['ExclusiveResources'] = @(Set-NodeExclusiveResources $resourceId @( $value['ExclusiveResources'] )) - } - } - } - } - else - { - Write-Debug ""${debugPrefix} TYPE IS NOT AS DSC RESOURCE"" - } - -# -# Copy the user-supplied values into a new collection with canonicalized property names -# - foreach ($key in $keywordData.Properties.Keys) - { - Write-Debug ""${debugPrefix} Processing property '$key' ["" - - if ($value.Contains($key)) - { - if ($OldMetaConfig -and (-not ($V1MetaConfigPropertyList -contains $key))) - { - Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache]::InvalidLocalConfigurationManagerPropertyErrorRecord($key, ($V1MetaConfigPropertyList -join ', '))) - Update-ConfigurationErrorCount - } -# see if there is a list of allowed values for this property (similar to an enum) - $allowedValues = $keywordData.Properties[$key].Values -# If there is and user-provided value is not in that list, write an error. - if ($allowedValues) - { - if(($null -eq $value[$key]) -and ($allowedValues -notcontains $value[$key])) - { - Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache]::InvalidValueForPropertyErrorRecord($key, ""$($value[$key])"", $keywordData.Keyword, ($allowedValues -join ', '))) - Update-ConfigurationErrorCount - } - else - { - $notAllowedValue=$null - foreach($v in $value[$key]) - { - if($allowedValues -notcontains $v) - { - $notAllowedValue +=$v.ToString() + ', ' - } - } - - if($notAllowedValue) - { - $notAllowedValue = $notAllowedValue.Substring(0, $notAllowedValue.Length -2) - Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache]::UnsupportedValueForPropertyErrorRecord($key, $notAllowedValue, $keywordData.Keyword, ($allowedValues -join ', '))) - Update-ConfigurationErrorCount - } - } - } - -# see if a value range is defined for this property - $allowedRange = $keywordData.Properties[$key].Range - if($allowedRange) - { - $castedValue = $value[$key] -as [int] - if((($castedValue -is [int]) -and (($castedValue -lt $keywordData.Properties[$key].Range.Item1) -or ($castedValue -gt $keywordData.Properties[$key].Range.Item2))) -or ($null -eq $castedValue)) - { - Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache]::ValueNotInRangeErrorRecord($key, $keywordName, $value[$key], $keywordData.Properties[$key].Range.Item1, $keywordData.Properties[$key].Range.Item2)) - Update-ConfigurationErrorCount - } - } - - Write-Debug ""${debugPrefix} Canonicalized property '$key' = '$($value[$key])'"" - - if ($keywordData.Properties[$key].IsKey) - { - if($null -eq $value[$key]) - { - $keyValues += ""::__NULL__"" - } - else - { - $keyValues += ""::"" + $value[$key] - } - } - -# see if ValueMap is also defined for this property (actual values) - $allowedValueMap = $keywordData.Properties[$key].ValueMap -#if it is and the ValueMap contains the user-provided value as a key, use the actual value - if ($allowedValueMap -and $allowedValueMap.ContainsKey($value[$key])) - { - $canonicalizedValue[$key] = $allowedValueMap[$value[$key]] - } - else - { - $canonicalizedValue[$key] = $value[$key] - } - } - elseif ($keywordData.Properties[$key].Mandatory) - { -# If the property was mandatory but the user didn't provide a value, write and error. - Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache]::MissingValueForMandatoryPropertyErrorRecord($keywordData.Keyword, $keywordData.Properties[$key].TypeConstraint, $Key)) - Update-ConfigurationErrorCount - } - - Write-Debug ""${debugPrefix} Processing completed '$key' ]"" - } - - if($keyValues) - { - $keyValues = $keyValues.Substring(2) # Remove the leading '::' - Add-NodeKeys $keyValues $keywordName - Test-ConflictingResources $keywordName $canonicalizedValue $keywordData - } - -# update OMI_ConfigurationDocument - if($IsMetaConfig) - { - if($keywordData.ResourceName -eq 'OMI_ConfigurationDocument') - { - if($(Get-PSMetaConfigurationProcessed)) - { - $PSMetaConfigDocumentInstVersionInfo = Get-PSMetaConfigDocumentInstVersionInfo - $canonicalizedValue['MinimumCompatibleVersion']=$PSMetaConfigDocumentInstVersionInfo['MinimumCompatibleVersion'] - } - else - { - Set-PSMetaConfigDocInsProcessedBeforeMeta - $canonicalizedValue['MinimumCompatibleVersion']='1.0.0' - } - } - - if(($keywordData.ResourceName -eq 'MSFT_WebDownloadManager') ` - -or ($keywordData.ResourceName -eq 'MSFT_FileDownloadManager') ` - -or ($keywordData.ResourceName -eq 'MSFT_WebResourceManager') ` - -or ($keywordData.ResourceName -eq 'MSFT_FileResourceManager') ` - -or ($keywordData.ResourceName -eq 'MSFT_WebReportManager') ` - -or ($keywordData.ResourceName -eq 'MSFT_SignatureValidation') ` - -or ($keywordData.ResourceName -eq 'MSFT_PartialConfiguration')) - { - Set-PSMetaConfigVersionInfoV2 - } - } - elseif($keywordData.ResourceName -eq 'OMI_ConfigurationDocument') - { - $canonicalizedValue['MinimumCompatibleVersion']='1.0.0' - $canonicalizedValue['CompatibleVersionAdditionalProperties']=@('Omi_BaseResource:ConfigurationName') - } - - if(($keywordData.ResourceName -eq 'MSFT_DSCMetaConfiguration') -or ($keywordData.ResourceName -eq 'MSFT_DSCMetaConfigurationV2')) - { - if($canonicalizedValue['DebugMode'] -and @($canonicalizedValue['DebugMode']).Length -gt 1) - { -# we only allow one value for debug mode now. - Write-Error -ErrorRecord ([Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache]::DebugModeShouldHaveOneValue()) - Update-ConfigurationErrorCount - } - } - -# Generate the MOF text for this resource instance. -# when generate mof text for OMI_ConfigurationDocument we handle below two cases: -# 1. we will add versioning related property based on meta configuration instance already process -# 2. we update the existing OMI_ConfigurationDocument instance if it already exists when process meta configuration instance - $aliasId = ConvertTo-MOFInstance $keywordName $canonicalizedValue - -# If a OMI_ConfigurationDocument is executed outside of a node statement, it becomes the default -# for all nodes that don't have an explicit OMI_ConfigurationDocument declaration - if ($keywordData.ResourceName -eq 'OMI_ConfigurationDocument' -and -not (Get-PSCurrentConfigurationNode)) - { - $data = Get-MoFInstanceText $aliasId - Write-Debug ""${debugPrefix} DEFINING DEFAULT CONFIGURATION DOCUMENT: $data"" - Set-PSDefaultConfigurationDocument $data - } - - Write-Debug ""${debugPrefix} MOF alias for this resource is '$aliasId'"" - -# always return the aliasId so the generated file will be well-formed if not valid - $aliasId - - Write-Debug ""${debugPrefix} RESOURCE PROCESSING COMPLETED. TOTAL ERROR COUNT: $(Get-ConfigurationErrorCount)"" - - "; - } -} diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs index 82d77de831..76be641880 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs @@ -9,6 +9,8 @@ using System.Management.Automation.Language; using System.Reflection; using System.Text; using System.Text.RegularExpressions; +using System.Management.Automation.Subsystem; +using System.Management.Automation.Subsystem.DSC; namespace System.Management.Automation { @@ -1721,9 +1723,16 @@ namespace System.Management.Automation foreach (var keyword in matchedResults) { - string usageString = Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache.NewApiIsUsed - ? Microsoft.PowerShell.DesiredStateConfiguration.Internal.CrossPlatform.DscClassCache.GetDSCResourceUsageString(keyword) - : Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache.GetDSCResourceUsageString(keyword); + string usageString = string.Empty; + ICrossPlatformDsc dscSubsystem = SubsystemManager.GetSubsystem(); + if (dscSubsystem != null) + { + usageString = dscSubsystem.GetDSCResourceUsageString(keyword); + } + else + { + usageString = Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache.GetDSCResourceUsageString(keyword); + } if (results == null) { diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs index b2c5275509..70d69ba35b 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs @@ -125,9 +125,6 @@ namespace System.Management.Automation new ExperimentalFeature( name: "PSNotApplyErrorActionToStderr", description: "Don't have $ErrorActionPreference affect stderr output"), - new ExperimentalFeature( - name: "PS7DscSupport", - description: "Support the cross-platform class-based DSC"), new ExperimentalFeature( name: "PSSubsystemPluginModel", description: "A plugin model for registering and un-registering PowerShell subsystems"), diff --git a/src/System.Management.Automation/engine/Subsystem/DscSubsystem/ICrossPlatformDsc.cs b/src/System.Management.Automation/engine/Subsystem/DscSubsystem/ICrossPlatformDsc.cs new file mode 100644 index 0000000000..f16c6f245a --- /dev/null +++ b/src/System.Management.Automation/engine/Subsystem/DscSubsystem/ICrossPlatformDsc.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Collections.ObjectModel; +using System.Collections.Generic; +using System.Management.Automation.Language; + +namespace System.Management.Automation.Subsystem.DSC +{ + /// + /// Interface for implementing a cross platform desired state configuration component. + /// + public interface ICrossPlatformDsc : ISubsystem + { + /// + /// Subsystem kind. + /// + SubsystemKind ISubsystem.Kind => SubsystemKind.CrossPlatformDsc; + + /// + /// Default implementation. No function is required for this subsystem. + /// + Dictionary? ISubsystem.FunctionsToDefine => null; + + /// + /// DSC initializer function. + /// + void LoadDefaultKeywords(Collection errors); + + /// + /// Clear internal class caches. + /// + void ClearCache(); + + /// + /// Returns resource usage string. + /// + string GetDSCResourceUsageString(DynamicKeyword keyword); + + /// + /// Checks if a string is one of dynamic keywords that can be used in both configuration and meta configuration. + /// + bool IsSystemResourceName(string name); + + /// + /// Checks if a string matches default module name used for meta configuration resources. + /// + bool IsDefaultModuleNameForMetaConfigResource(string name); + } +} diff --git a/src/System.Management.Automation/engine/Subsystem/ISubsystem.cs b/src/System.Management.Automation/engine/Subsystem/ISubsystem.cs index e19f37f06c..db842f435b 100644 --- a/src/System.Management.Automation/engine/Subsystem/ISubsystem.cs +++ b/src/System.Management.Automation/engine/Subsystem/ISubsystem.cs @@ -17,6 +17,11 @@ namespace System.Management.Automation.Subsystem /// Component that provides predictive suggestions to commandline input. /// CommandPredictor = 1, + + /// + /// Cross platform desired state configuration component. + /// + CrossPlatformDsc = 2, } /// diff --git a/src/System.Management.Automation/engine/Subsystem/SubsystemManager.cs b/src/System.Management.Automation/engine/Subsystem/SubsystemManager.cs index 1d90fc7334..8764cf83cf 100644 --- a/src/System.Management.Automation/engine/Subsystem/SubsystemManager.cs +++ b/src/System.Management.Automation/engine/Subsystem/SubsystemManager.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Management.Automation.Internal; +using System.Management.Automation.Subsystem.DSC; using System.Management.Automation.Subsystem.Prediction; namespace System.Management.Automation.Subsystem @@ -29,6 +30,11 @@ namespace System.Management.Automation.Subsystem SubsystemKind.CommandPredictor, allowUnregistration: true, allowMultipleRegistration: true), + + SubsystemInfo.Create( + SubsystemKind.CrossPlatformDsc, + allowUnregistration: true, + allowMultipleRegistration: false), }; var subSystemTypeMap = new Dictionary(subsystems.Length); diff --git a/src/System.Management.Automation/engine/parser/Parser.cs b/src/System.Management.Automation/engine/parser/Parser.cs index 547abd1037..d5ee729577 100644 --- a/src/System.Management.Automation/engine/parser/Parser.cs +++ b/src/System.Management.Automation/engine/parser/Parser.cs @@ -12,6 +12,8 @@ using System.Management.Automation.Runspaces; using System.Reflection; using System.Runtime.CompilerServices; using System.Threading.Tasks; +using System.Management.Automation.Subsystem; +using System.Management.Automation.Subsystem.DSC; using Dsc = Microsoft.PowerShell.DesiredStateConfiguration.Internal; namespace System.Management.Automation.Language @@ -2933,7 +2935,6 @@ namespace System.Management.Automation.Language // Runspaces.Runspace localRunspace = null; bool topLevel = false; - bool useCrossPlatformSchema = false; try { // At this point, we'll need a runspace to use to hold the metadata for the parse. If there is no @@ -2996,38 +2997,13 @@ namespace System.Management.Automation.Language { // Load the default CIM keywords Collection CIMKeywordErrors = new Collection(); - if (ExperimentalFeature.IsEnabled(Dsc.CrossPlatform.DscClassCache.DscExperimentalFeatureName)) + + // DscSubsystem is auto-registered when PSDesiredStateConfiguration v3 module is loaded + // so if DscSubsystem is registered that means user intention to use v3 APIs. + ICrossPlatformDsc dscSubsystem = SubsystemManager.GetSubsystem(); + if (dscSubsystem != null) { - // In addition to checking if experimental feature is enabled - // also check if PSDesiredStateConfiguration is already loaded - // if pre-v3 is already loaded then use old mof-based APIs - // otherwise use json-based APIs - - p.AddCommand(new CmdletInfo("Get-Module", typeof(Microsoft.PowerShell.Commands.GetModuleCommand))); - p.AddParameter("Name", "PSDesiredStateConfiguration"); - - bool prev3IsLoaded = false; - foreach (PSModuleInfo moduleInfo in p.Invoke()) - { - if (moduleInfo.Version.Major < 3) - { - prev3IsLoaded = true; - break; - } - } - - p.Commands.Clear(); - - useCrossPlatformSchema = !prev3IsLoaded; - - if (useCrossPlatformSchema) - { - Dsc.CrossPlatform.DscClassCache.LoadDefaultCimKeywords(CIMKeywordErrors); - } - else - { - Dsc.DscClassCache.LoadDefaultCimKeywords(CIMKeywordErrors); - } + dscSubsystem.LoadDefaultKeywords(CIMKeywordErrors); } else { @@ -3274,9 +3250,10 @@ namespace System.Management.Automation.Language // Clear out all of the cached classes and keywords. // They will need to be reloaded when the generated function is actually run. // - if (useCrossPlatformSchema) + ICrossPlatformDsc dscSubsystem = SubsystemManager.GetSubsystem(); + if (dscSubsystem != null) { - Dsc.CrossPlatform.DscClassCache.ClearCache(); + dscSubsystem.ClearCache(); } else { diff --git a/src/System.Management.Automation/engine/parser/SemanticChecks.cs b/src/System.Management.Automation/engine/parser/SemanticChecks.cs index b98296815d..f6f752b3af 100644 --- a/src/System.Management.Automation/engine/parser/SemanticChecks.cs +++ b/src/System.Management.Automation/engine/parser/SemanticChecks.cs @@ -11,6 +11,8 @@ using System.Runtime.CompilerServices; using System.Text; using Microsoft.PowerShell; +using System.Management.Automation.Subsystem; +using System.Management.Automation.Subsystem.DSC; using Microsoft.PowerShell.DesiredStateConfiguration.Internal; namespace System.Management.Automation.Language @@ -1405,7 +1407,10 @@ namespace System.Management.Automation.Language { StringConstantExpressionAst nameAst = dynamicKeywordStatementAst.CommandElements[0] as StringConstantExpressionAst; Diagnostics.Assert(nameAst != null, "nameAst should never be null"); - if (!DscClassCache.SystemResourceNames.Contains(nameAst.Extent.Text.Trim())) + var extentText = nameAst.Extent.Text.Trim(); + ICrossPlatformDsc dscSubsystem = SubsystemManager.GetSubsystem(); + var extentTextIsASystemResourceName = (dscSubsystem != null) ? dscSubsystem.IsSystemResourceName(extentText) : DscClassCache.SystemResourceNames.Contains(extentText); + if (!extentTextIsASystemResourceName) { if (configAst.ConfigurationType == ConfigurationType.Meta && !dynamicKeywordStatementAst.Keyword.IsMetaDSCResource()) { diff --git a/src/System.Management.Automation/engine/parser/tokenizer.cs b/src/System.Management.Automation/engine/parser/tokenizer.cs index 7f36000df1..161e53cef0 100644 --- a/src/System.Management.Automation/engine/parser/tokenizer.cs +++ b/src/System.Management.Automation/engine/parser/tokenizer.cs @@ -12,6 +12,8 @@ using System.Runtime.CompilerServices; using System.Text; using Microsoft.PowerShell.Commands; +using System.Management.Automation.Subsystem; +using System.Management.Automation.Subsystem.DSC; using Microsoft.PowerShell.DesiredStateConfiguration.Internal; namespace System.Management.Automation.Language @@ -361,7 +363,15 @@ namespace System.Management.Automation.Language string implementingModule = keyword.ImplementingModule; if (implementingModule != null) { - return implementingModule.Equals(DscClassCache.DefaultModuleInfoForMetaConfigResource.Item1, StringComparison.OrdinalIgnoreCase); + ICrossPlatformDsc dscSubsystem = SubsystemManager.GetSubsystem(); + if (dscSubsystem != null) + { + dscSubsystem.IsDefaultModuleNameForMetaConfigResource(implementingModule); + } + else + { + return implementingModule.Equals(DscClassCache.DefaultModuleInfoForMetaConfigResource.Item1, StringComparison.OrdinalIgnoreCase); + } } return false; diff --git a/test/xUnit/csharp/test_Subsystem.cs b/test/xUnit/csharp/test_Subsystem.cs index 54fdcbf83b..d5bc3b9c72 100644 --- a/test/xUnit/csharp/test_Subsystem.cs +++ b/test/xUnit/csharp/test_Subsystem.cs @@ -4,6 +4,7 @@ using System; using System.Collections.ObjectModel; using System.Management.Automation.Subsystem; +using System.Management.Automation.Subsystem.DSC; using System.Management.Automation.Subsystem.Prediction; using System.Threading; using Xunit; @@ -20,8 +21,7 @@ namespace PSTests.Sequential predictor2 = MyPredictor.SlowPredictor; } - // This method needs to be updated when there are more than 1 subsystem defined. - private static void VerifySubsystemMetadata(SubsystemInfo ssInfo) + private static void VerifyCommandPredictorMetadata(SubsystemInfo ssInfo) { Assert.Equal(SubsystemKind.CommandPredictor, ssInfo.Kind); Assert.Equal(typeof(ICommandPredictor), ssInfo.SubsystemType); @@ -31,26 +31,51 @@ namespace PSTests.Sequential Assert.Empty(ssInfo.RequiredFunctions); } + private static void VerifyCrossPlatformDscMetadata(SubsystemInfo ssInfo) + { + Assert.Equal(SubsystemKind.CrossPlatformDsc, ssInfo.Kind); + Assert.Equal(typeof(ICrossPlatformDsc), ssInfo.SubsystemType); + Assert.True(ssInfo.AllowUnregistration); + Assert.False(ssInfo.AllowMultipleRegistration); + Assert.Empty(ssInfo.RequiredCmdlets); + Assert.Empty(ssInfo.RequiredFunctions); + } + [Fact] public static void GetSubsystemInfo() { - SubsystemInfo ssInfo = SubsystemManager.GetSubsystemInfo(typeof(ICommandPredictor)); + SubsystemInfo predictorInfo = SubsystemManager.GetSubsystemInfo(typeof(ICommandPredictor)); - VerifySubsystemMetadata(ssInfo); - Assert.False(ssInfo.IsRegistered); - Assert.Empty(ssInfo.Implementations); + VerifyCommandPredictorMetadata(predictorInfo); + Assert.False(predictorInfo.IsRegistered); + Assert.Empty(predictorInfo.Implementations); - SubsystemInfo ssInfo2 = SubsystemManager.GetSubsystemInfo(SubsystemKind.CommandPredictor); - Assert.Same(ssInfo2, ssInfo); + SubsystemInfo predictorInfo2 = SubsystemManager.GetSubsystemInfo(SubsystemKind.CommandPredictor); + Assert.Same(predictorInfo2, predictorInfo); + + SubsystemInfo crossPlatformDscInfo = SubsystemManager.GetSubsystemInfo(typeof(ICrossPlatformDsc)); + + VerifyCrossPlatformDscMetadata(crossPlatformDscInfo); + Assert.False(crossPlatformDscInfo.IsRegistered); + Assert.Empty(crossPlatformDscInfo.Implementations); + + SubsystemInfo crossPlatformDscInfo2 = SubsystemManager.GetSubsystemInfo(SubsystemKind.CrossPlatformDsc); + Assert.Same(crossPlatformDscInfo2, crossPlatformDscInfo); ReadOnlyCollection ssInfos = SubsystemManager.GetAllSubsystemInfo(); - Assert.Single(ssInfos); - Assert.Same(ssInfos[0], ssInfo); + Assert.Equal(2, ssInfos.Count); + Assert.Same(ssInfos[0], predictorInfo); + Assert.Same(ssInfos[1], crossPlatformDscInfo); - ICommandPredictor impl = SubsystemManager.GetSubsystem(); - Assert.Null(impl); - ReadOnlyCollection impls = SubsystemManager.GetSubsystems(); - Assert.Empty(impls); + ICommandPredictor predictorImpl = SubsystemManager.GetSubsystem(); + Assert.Null(predictorImpl); + ReadOnlyCollection predictorImpls = SubsystemManager.GetSubsystems(); + Assert.Empty(predictorImpls); + + ICrossPlatformDsc crossPlatformDscImpl = SubsystemManager.GetSubsystem(); + Assert.Null(crossPlatformDscImpl); + ReadOnlyCollection crossPlatformDscImpls = SubsystemManager.GetSubsystems(); + Assert.Empty(crossPlatformDscImpls); } [Fact] @@ -73,7 +98,8 @@ namespace PSTests.Sequential // Now validate the SubsystemInfo of the 'ICommandPredictor' subsystem SubsystemInfo ssInfo = SubsystemManager.GetSubsystemInfo(typeof(ICommandPredictor)); - VerifySubsystemMetadata(ssInfo); + SubsystemInfo crossPlatformDscInfo = SubsystemManager.GetSubsystemInfo(typeof(ICrossPlatformDsc)); + VerifyCommandPredictorMetadata(ssInfo); Assert.True(ssInfo.IsRegistered); Assert.Single(ssInfo.Implementations); @@ -85,11 +111,6 @@ namespace PSTests.Sequential Assert.Equal(SubsystemKind.CommandPredictor, implInfo.Kind); Assert.Same(typeof(MyPredictor), implInfo.ImplementationType); - // Now validate the all-subsystem-info collection. - ReadOnlyCollection ssInfos = SubsystemManager.GetAllSubsystemInfo(); - Assert.Single(ssInfos); - Assert.Same(ssInfos[0], ssInfo); - // Now validate the subsystem implementation itself. ICommandPredictor impl = SubsystemManager.GetSubsystem(); Assert.Same(impl, predictor1); @@ -113,7 +134,7 @@ namespace PSTests.Sequential SubsystemManager.RegisterSubsystem(SubsystemKind.CommandPredictor, predictor2); // Now validate the SubsystemInfo of the 'ICommandPredictor' subsystem - VerifySubsystemMetadata(ssInfo); + VerifyCommandPredictorMetadata(ssInfo); Assert.True(ssInfo.IsRegistered); Assert.Equal(2, ssInfo.Implementations.Count); @@ -158,7 +179,7 @@ namespace PSTests.Sequential SubsystemManager.UnregisterSubsystem(predictor1.Id); SubsystemInfo ssInfo = SubsystemManager.GetSubsystemInfo(SubsystemKind.CommandPredictor); - VerifySubsystemMetadata(ssInfo); + VerifyCommandPredictorMetadata(ssInfo); Assert.True(ssInfo.IsRegistered); Assert.Single(ssInfo.Implementations); @@ -179,7 +200,7 @@ namespace PSTests.Sequential // Unregister 'predictor2' SubsystemManager.UnregisterSubsystem(SubsystemKind.CommandPredictor, predictor2.Id); - VerifySubsystemMetadata(ssInfo); + VerifyCommandPredictorMetadata(ssInfo); Assert.False(ssInfo.IsRegistered); Assert.Empty(ssInfo.Implementations);