Import-DscResource should allow to overwrite DSC built-in resources. (#9879)

This commit is contained in:
Amit Saraf 2019-06-18 10:07:45 -07:00 committed by Travis Plunk
parent f101e07681
commit 0673a481bb

View file

@ -469,6 +469,47 @@ namespace Microsoft.PowerShell.DesiredStateConfiguration
namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal
{
/// <summary>
/// </summary>
[SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes",
Justification = "Needed Internal use only")]
internal class DscClassCacheEntry
{
/// <summary>
/// Store the RunAs Credentials that this DSC resource will use
/// </summary>
public DSCResourceRunAsCredential DscResRunAsCred;
/// <summary>
/// If we have implicitly imported this resource, we will set this field to true. This will
/// only happen to InBox resources.
/// </summary>
public bool IsImportedImplicitly;
/// <summary>
/// A CimClass instance for this resource
/// </summary>
public Microsoft.Management.Infrastructure.CimClass CimClassInstance;
/// <summary>
/// Default constructor to initiale variables with default values
/// </summary>
public DscClassCacheEntry() : this(DSCResourceRunAsCredential.Default, false, null) { }
/// <summary>
/// Constructor used in code to initialze all values
/// </summary>
/// <param name="aDSCResourceRunAsCredential"></param>
/// <param name="aIsImportedImplicitly"></param>
/// <param name="aCimClassInstance"></param>
public DscClassCacheEntry(DSCResourceRunAsCredential aDSCResourceRunAsCredential, bool aIsImportedImplicitly, Microsoft.Management.Infrastructure.CimClass aCimClassInstance)
{
DscResRunAsCred = aDSCResourceRunAsCredential;
IsImportedImplicitly = aIsImportedImplicitly;
CimClassInstance = aCimClassInstance;
}
}
/// <summary>
/// </summary>
[SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes",
@ -486,6 +527,7 @@ namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal
private const int IndexModuleName = 0;
private const int IndexModuleVersion = 1;
private const int IndexClassName = 2;
private const int IndexFriendlyName = 3;
// Create a list of classes which are not actual DSC resources similar to what we do inside PSDesiredStateConfiguration.psm1
private static readonly string[] s_hiddenResourceList =
@ -505,15 +547,15 @@ namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal
/// <summary>
/// DSC class cache for this runspace.
/// Cache stores the DSCRunAsBehavior for the class along with actual cim class.
/// Cache stores the DSCRunAsBehavior, cim class and boolean to indicate if an Inbox resource has been implicitly imported.
/// </summary>
private static Dictionary<string, Tuple<DSCResourceRunAsCredential, Microsoft.Management.Infrastructure.CimClass>> ClassCache
private static Dictionary<string, DscClassCacheEntry> ClassCache
{
get
{
if (t_classCache == null)
{
t_classCache = new Dictionary<string, Tuple<DSCResourceRunAsCredential, Microsoft.Management.Infrastructure.CimClass>>(StringComparer.OrdinalIgnoreCase);
t_classCache = new Dictionary<string, DscClassCacheEntry>(StringComparer.OrdinalIgnoreCase);
}
return t_classCache;
@ -521,7 +563,7 @@ namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal
}
[ThreadStatic]
private static Dictionary<string, Tuple<DSCResourceRunAsCredential, Microsoft.Management.Infrastructure.CimClass>> t_classCache;
private static Dictionary<string, DscClassCacheEntry> t_classCache;
/// <summary>
/// DSC classname to source module mapper.
@ -727,12 +769,12 @@ namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal
}
// Load Regular and DSC PS modules
bool isInboxResource = false;
bool importInBoxResourcesImplicitly = false;
List<string> modulePaths = new List<string>();
if (modulePathList == null || modulePathList.Count == 0)
{
modulePaths.Add(Path.Combine(configSystemPath, inboxModulePath));
isInboxResource = true;
importInBoxResourcesImplicitly = true;
}
else
{
@ -750,7 +792,7 @@ namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal
}
}
LoadDSCResourceIntoCache(errors, modulePaths, isInboxResource);
LoadDSCResourceIntoCache(errors, modulePaths, importInBoxResourcesImplicitly);
}
}
@ -759,10 +801,10 @@ namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal
/// </summary>
/// <param name="errors">Collection of any errors encountered during initialization.</param>
/// <param name="modulePathList">Module path from where DSC PS modules will be loaded.</param>
/// <param name="isInboxResource">
/// <param name="importInBoxResourcesImplicitly">
/// if module is inbox.
/// </param>
private static void LoadDSCResourceIntoCache(Collection<Exception> errors, List<string> modulePathList, bool isInboxResource)
private static void LoadDSCResourceIntoCache(Collection<Exception> errors, List<string> modulePathList, bool importInBoxResourcesImplicitly)
{
foreach (string moduleDir in modulePathList)
{
@ -779,7 +821,7 @@ namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal
continue;
}
Tuple<string, Version> moduleInfo = GetModuleInfoHelper(moduleDir, isInboxResource, isPsProviderModule: false);
Tuple<string, Version> moduleInfo = GetModuleInfoHelper(moduleDir, importInBoxResourcesImplicitly, isPsProviderModule: false);
if (moduleInfo == null)
{
continue;
@ -787,7 +829,7 @@ namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal
foreach (string schemaFile in schemaFiles)
{
ImportClasses(schemaFile, moduleInfo, errors);
ImportClasses(schemaFile, moduleInfo, errors, importInBoxResourcesImplicitly);
}
}
}
@ -800,17 +842,17 @@ namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal
/// <param name="moduleFolderPath">
/// Path to the module folder
/// </param>
/// <param name="isInboxResource">
/// if module is inbox.
/// <param name="importInBoxResourcesImplicitly">
/// if module is inbox and we are importing resources implicitly
/// </param>
/// <param name="isPsProviderModule">
/// Indicate a internal DSC module
/// </param>
/// <returns></returns>
private static Tuple<string, Version> GetModuleInfoHelper(string moduleFolderPath, bool isInboxResource, bool isPsProviderModule)
private static Tuple<string, Version> GetModuleInfoHelper(string moduleFolderPath, bool importInBoxResourcesImplicitly, bool isPsProviderModule)
{
string moduleName = "PsDesiredStateConfiguration";
if (!isInboxResource)
if (!importInBoxResourcesImplicitly)
{
moduleName = Path.GetFileName(moduleFolderPath);
}
@ -875,12 +917,12 @@ namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal
// Callback implementation...
private static CimClass MyClassCallback(string serverName, string namespaceName, string className)
{
foreach (KeyValuePair<string, Tuple<DSCResourceRunAsCredential, Microsoft.Management.Infrastructure.CimClass>> cimClass in ClassCache)
foreach (KeyValuePair<string, DscClassCacheEntry> cimClass in ClassCache)
{
string cachedClassName = cimClass.Key.Split(Utils.Separators.Backslash)[IndexClassName];
if (string.Compare(cachedClassName, className, StringComparison.OrdinalIgnoreCase) == 0)
{
return cimClass.Value.Item2;
return cimClass.Value.CimClassInstance;
}
}
@ -893,8 +935,9 @@ namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal
/// <param name="path"></param>
/// <param name="moduleInfo"></param>
/// <param name="errors"></param>
/// <param name="importInBoxResourcesImplicitly"></param>
/// <returns></returns>
public static List<CimClass> ImportClasses(string path, Tuple<string, Version> moduleInfo, Collection<Exception> errors)
public static List<CimClass> ImportClasses(string path, Tuple<string, Version> moduleInfo, Collection<Exception> errors, bool importInBoxResourcesImplicitly = false)
{
if (string.IsNullOrEmpty(path))
{
@ -926,12 +969,14 @@ namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal
{
// Only add the class once...
var className = c.CimSystemProperties.ClassName;
string moduleQualifiedResourceName = GetModuleQualifiedResourceName(moduleInfo.Item1, moduleInfo.Item2.ToString(), className);
Tuple<DSCResourceRunAsCredential, Microsoft.Management.Infrastructure.CimClass> cimClassInfo;
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))
{
CimClass cimClass = cimClassInfo.Item2;
CimClass cimClass = cimClassInfo.CimClassInstance;
// If this is a nested object and we already have exactly same nested object, we will
// allow sharing of nested objects.
if (!IsSameNestedObject(cimClass, c))
@ -956,14 +1001,19 @@ namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal
if (!CacheResourcesFromMultipleModuleVersions)
{
// Find & remove the previous version of the resource.
List<KeyValuePair<string, Tuple<DSCResourceRunAsCredential, Microsoft.Management.Infrastructure.CimClass>>> resourceList = FindResourceInCache(moduleInfo.Item1, className);
List<KeyValuePair<string, DscClassCacheEntry>> resourceList = FindResourceInCache(moduleInfo.Item1, 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);
}
}
}
ClassCache[moduleQualifiedResourceName] = new Tuple<DSCResourceRunAsCredential, Microsoft.Management.Infrastructure.CimClass>(DSCResourceRunAsCredential.Default, c);
ClassCache[moduleQualifiedResourceName] = new DscClassCacheEntry(DSCResourceRunAsCredential.Default, importInBoxResourcesImplicitly, c);
ByClassModuleCache[className] = moduleInfo;
}
@ -1023,10 +1073,11 @@ namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal
/// <param name="moduleName"></param>
/// <param name="moduleVersion"></param>
/// <param name="className"></param>
/// <param name="resourceName"></param>
/// <returns></returns>
private static string GetModuleQualifiedResourceName(string moduleName, string moduleVersion, string className)
private static string GetModuleQualifiedResourceName(string moduleName, string moduleVersion, string className, string resourceName)
{
return string.Format(CultureInfo.InvariantCulture, "{0}\\{1}\\{2}", moduleName, moduleVersion, className);
return String.Format(CultureInfo.InvariantCulture, "{0}\\{1}\\{2}\\{3}", moduleName, moduleVersion, className, resourceName);
}
/// <summary>
@ -1034,22 +1085,25 @@ namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal
/// </summary>
/// <param name="moduleName">Module name.</param>
/// <param name="className">Resource type name.</param>
/// <param name="resourceName">Resource friendly name</param>
/// <returns>List of found resources in the form of Dictionary{moduleQualifiedName, cimClass}, otherwise empty list.</returns>
private static List<KeyValuePair<string, Tuple<DSCResourceRunAsCredential, Microsoft.Management.Infrastructure.CimClass>>> FindResourceInCache(string moduleName, string className)
private static List<KeyValuePair<string, DscClassCacheEntry>> FindResourceInCache(string moduleName, string className, string resourceName)
{
return (from cacheEntry in ClassCache
let splittedName = cacheEntry.Key.Split(Utils.Separators.Backslash)
let cachedClassName = splittedName[IndexClassName]
let cachedModuleName = splittedName[IndexModuleName]
where string.Compare(cachedClassName, className, StringComparison.OrdinalIgnoreCase) == 0
&& string.Compare(cachedModuleName, moduleName, StringComparison.OrdinalIgnoreCase) == 0
let cachedResourceName = splittedName[IndexFriendlyName]
where ((string.Compare(cachedResourceName, resourceName, StringComparison.OrdinalIgnoreCase) == 0)
|| (string.Compare(cachedClassName, className, StringComparison.OrdinalIgnoreCase) == 0
&& string.Compare(cachedModuleName, moduleName, StringComparison.OrdinalIgnoreCase) == 0))
select cacheEntry).ToList();
}
/// <summary>
/// </summary>
/// <returns></returns>
public static List<Tuple<DSCResourceRunAsCredential, Microsoft.Management.Infrastructure.CimClass>> GetCachedClasses()
private static List<DscClassCacheEntry> GetCachedClasses()
{
return ClassCache.Values.ToList();
}
@ -1063,11 +1117,11 @@ namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal
{
List<Microsoft.Management.Infrastructure.CimClass> cachedClasses = new List<Microsoft.Management.Infrastructure.CimClass>();
var moduleQualifiedName = string.Format(CultureInfo.InvariantCulture, "{0}\\{1}", module.Name, module.Version.ToString());
foreach (var pair in ClassCache)
foreach (var dscClassCacheEntry in ClassCache)
{
if (pair.Key.StartsWith(moduleQualifiedName, StringComparison.OrdinalIgnoreCase))
if(dscClassCacheEntry.Key.StartsWith(moduleQualifiedName, StringComparison.OrdinalIgnoreCase))
{
cachedClasses.Add(pair.Value.Item2);
cachedClasses.Add(dscClassCacheEntry.Value.CimClassInstance);
}
}
@ -1231,13 +1285,13 @@ namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal
{
Collection<DynamicKeyword> keywords = new Collection<DynamicKeyword>();
foreach (KeyValuePair<string, Tuple<DSCResourceRunAsCredential, Microsoft.Management.Infrastructure.CimClass>> cachedClass in ClassCache)
foreach (KeyValuePair<string, DscClassCacheEntry> cachedClass in ClassCache)
{
string[] splittedName = cachedClass.Key.Split(Utils.Separators.Backslash);
string moduleName = splittedName[IndexModuleName];
string moduleVersion = splittedName[IndexModuleVersion];
var keyword = CreateKeywordFromCimClass(moduleName, Version.Parse(moduleVersion), cachedClass.Value.Item2, null, cachedClass.Value.Item1);
var keyword = CreateKeywordFromCimClass(moduleName, Version.Parse(moduleVersion), cachedClass.Value.CimClassInstance, null, cachedClass.Value.DscResRunAsCred);
if (keyword != null)
{
keywords.Add(keyword);
@ -1577,9 +1631,9 @@ namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal
foreach (var cimClass in GetCachedClasses())
{
var className = cimClass.Item2.CimSystemProperties.ClassName;
var className = cimClass.CimClassInstance.CimSystemProperties.ClassName;
var moduleInfo = ByClassModuleCache[className];
CreateAndRegisterKeywordFromCimClass(moduleInfo.Item1, moduleInfo.Item2, cimClass.Item2, functionsToDefine, cimClass.Item1);
CreateAndRegisterKeywordFromCimClass(moduleInfo.Item1, moduleInfo.Item2, cimClass.CimClassInstance, functionsToDefine, cimClass.DscResRunAsCred);
}
// And add the Node keyword definitions
@ -3090,20 +3144,26 @@ namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal
foreach (var c in parser.ParseSchemaMofFileBuffer(mof))
{
var className = c.CimSystemProperties.ClassName;
string alias = GetFriendlyName(c);
var friendlyName = string.IsNullOrEmpty(alias) ? className : alias;
if (!CacheResourcesFromMultipleModuleVersions)
{
// Find & remove the previous version of the resource.
List<KeyValuePair<string, Tuple<DSCResourceRunAsCredential, Microsoft.Management.Infrastructure.CimClass>>> resourceList =
FindResourceInCache(module.Name, className);
List<KeyValuePair<string, DscClassCacheEntry>> 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);
ClassCache[moduleQualifiedResourceName] = new Tuple<DSCResourceRunAsCredential, Microsoft.Management.Infrastructure.CimClass>(runAsBehavior, c);
var moduleQualifiedResourceName = GetModuleQualifiedResourceName(module.Name, module.Version.ToString(), className, friendlyName);
ClassCache[moduleQualifiedResourceName] = new DscClassCacheEntry(runAsBehavior, false, c);
ByClassModuleCache[className] = new Tuple<string, Version>(module.Name, module.Version);
resourcesFound.Add(className);
CreateAndRegisterKeywordFromCimClass(module.Name, module.Version, c, functionsToDefine, runAsBehavior);
@ -3171,6 +3231,7 @@ namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal
foreach (var c in classes)
{
CreateAndRegisterKeywordFromCimClass(module.Name, module.Version, c, functionsToDefine, DSCResourceRunAsCredential.Default);
ClearImplicitlyImportedFlagFromResourceInClassCache(module, c);
}
}
@ -3204,6 +3265,7 @@ namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal
if (string.Equals(alias, resourceName, StringComparison.OrdinalIgnoreCase))
{
CreateAndRegisterKeywordFromCimClass(module.Name, module.Version, c, functionsToDefine, DSCResourceRunAsCredential.Default);
ClearImplicitlyImportedFlagFromResourceInClassCache(module, c);
return true;
}
}
@ -3221,6 +3283,21 @@ namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal
return false;
}
/// <summary>
/// Clear the 'IsImportedImplicitly' flag when explicitly importing a resource
/// </summary>
/// <param name="module"></param>
/// <param name="cimClass"></param>
private static void ClearImplicitlyImportedFlagFromResourceInClassCache(PSModuleInfo module, CimClass cimClass)
{
var className = cimClass.CimSystemProperties.ClassName;
var alias = GetFriendlyName(cimClass);
var friendlyName = string.IsNullOrEmpty(alias) ? className : alias;
var moduleQualifiedResourceName = GetModuleQualifiedResourceName(module.Name, module.Version.ToString(), className, friendlyName);
ClassCache[moduleQualifiedResourceName].IsImportedImplicitly = false;
}
/// <summary>
/// Imports configuration keywords from a .psm1 file.
/// </summary>