LCM/dsc/engine/ca/psinfrastructure/PsPluginManager.cs (1,414 lines of code) (raw):

using System.Diagnostics; using System.Linq; using System.Text; using Microsoft.Management.Infrastructure; using Microsoft.Management.Infrastructure.Native; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System; using System.Security; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.IO; // ReSharper disable RedundantUsingDirective using System.IO.Compression; // ReSharper restore RedundantUsingDirective using System.Management.Automation; using System.Management.Automation.Runspaces; using Microsoft.PowerShell.Commands; namespace Microsoft.PowerShell.DesiredStateConfiguration.Internal { /// <summary> /// DownloadManagerBase handles the interaction between CA and the Powershell download managers. /// </summary> public static class DownloadManagerBase { #region PLUGIN_CONSTANTS // Cmdlets private const string DownloadManagerGetConfiguration = "Get-DscDocument"; private const string DownloadManagerGetModules = "Get-DscModule"; private const string DownloadManagerGetAction = "Get-DscAction"; private const string WebDownloadManagerName = "WebDownloadManager"; private const string WebDownloadManagerLoadPath = "PSDesiredStateConfiguration\\WebDownloadManager"; private const string FileDownloadManagerName = "DSCFileDownloadManager"; private const string FileDownloadManagerLoadPath = "PSDesiredStateConfiguration\\DownloadManager\\DSCFileDownloadManager"; private static readonly Dictionary<string, string> DownloadManagerMap; // PS DSC module cmdlets private const string PsDscCmdletGet = "Get-TargetResource"; private const string PsDscCmdletSet = "Set-TargetResource"; private const string PsDscCmdletTest = "Test-TargetResource"; private const string BaseResourceClass = "OMI_BaseResource"; // Parameters private const string ParamCredential = "Credential"; private const string ParamCredentialUserName = "UserName"; private const string ParamCredentialPassword = "Password"; private const string ParamCredentialDomain = "Domain"; private const string ParamConfigurationId = "ConfigurationID"; private const string ParamDestinationPath = "DestinationPath"; private const string ParamModules = "Module"; private const string ParamFileHash = "FileHash"; private const string ParamStatusCode = "StatusCode"; private const string ParamNotCompliant = "NotCompliant"; //Meta Config private const string MetaConfigConfigurationId = "ConfigurationID"; private const string UseSystemUUIDValue = "UseSystemUUID"; private const string MetaConfigDownloadManagerName = "DownloadManagerName"; private const string MetaConfigDownloadManagerCustomData = "DownloadManagerCustomData"; //This is array of MSFT_KeyValuePair. //static readonly string MetaConfig_Credential = "Credential"; private const string MetaConfigAllowModuleOverwrite = "AllowModuleOverwrite"; // Infrastructure defaults private const int SchemaValidationOption = 4; //Ignore //static readonly string BASE_RESOURCE_CLASSNAME = "OMI_BaseResource"; private const string BaseDocumentClassname = "OMI_ConfigurationDocument"; static readonly string[] NativeModules = { "MSFT_FileDirectoryConfiguration" }; // status result private const string StatusOk = "OK"; private const string StatusGetMof = "GETCONFIGURATION"; private const string StatusRetry = "RETRY"; private const string WriteQualifier = "write"; #endregion PLUGIN_CONSTANTS #region PLUGIN_STATE //This is initialized only once and cached until process is unloaded. // ReSharper disable FieldCanBeMadeReadOnly.Local static private Runspace _pluginRunspace; static private Runspace _validationRunspace; // ReSharper restore FieldCanBeMadeReadOnly.Local static private readonly bool PluginRunspaceInitialized; static private string _pluginModuleName; /// <summary> /// Enumeration for Get Action Status Code /// </summary> internal enum GetActionStatusCodeTypes { Success = 0, DownloadManagerInitializationFailure = 1, GetConfigurationCommandFailure = 2, UnexpectedGetConfigurationResponseFromServer = 3, ConfigurationChecksumFileReadFailure = 4, ConfigurationChecksumValidationFailure = 5, InvalidConfigurationFile = 6, AvailableModulesCheckFailure = 7, InvalidConfigurationIdInMetaConfig = 8, InvalidDownloadManagerCustomDataInMetaConfig = 9, GetDscModuleCommandFailure = 10, GetDscModuleInvalidOutput = 11, ModuleChecksumFileNotFound = 12, InvalidModuleFile = 13, ModuleChecksumValidationFailure = 14, ModuleExtractionFailure = 15, ModuleValidationFailure = 16, InvalidDownloadedModule = 17, ConfigurationFileNotFound = 18, MultipleConfigurationFilesFound = 19, ConfigurationChecksumFileNotFound = 20, ModuleNotFound = 21, InvalidModuleVersionFormat = 22, InvalidConfigurationIdFormat = 23, GetDscActionCommandFailure = 24, InvalidChecksumAlgorithm = 25, }; #endregion PLUGIN_STATE #region PLUGIN_CONSTRUCTORS static DownloadManagerBase() { DownloadManagerMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); DownloadManagerMap.Add(WebDownloadManagerName, WebDownloadManagerLoadPath); DownloadManagerMap.Add(FileDownloadManagerName, FileDownloadManagerLoadPath); _pluginRunspace = RunspaceFactory.CreateRunspace(InitialSessionState.CreateDefault2()); _pluginRunspace.Open(); _validationRunspace = RunspaceFactory.CreateRunspace(InitialSessionState.CreateDefault2()); _validationRunspace.Open(); PluginRunspaceInitialized = false; } #endregion PLUGIN_CONSTRUCTORS #region PLUGIN_GETCONFIGURATION /*If this succeeds, the first object in the collection is a valid object.*/ private static MiResult ValidateGetConfigurationResult(System.Management.Automation.PowerShell powershell, Collection<PSObject> providerImportResult, string downloadManagerName, out string importModuleResult, out ErrorRecord errorRecord, out UInt32 getActionStatusCode) { var result = ValidateGetResult(powershell, providerImportResult, downloadManagerName, out importModuleResult, out errorRecord, out getActionStatusCode); if (result != MiResult.OK) { return result; } if (!(string.Compare(importModuleResult, StatusOk, StringComparison.OrdinalIgnoreCase) == 0 || string.Compare(importModuleResult, StatusRetry, StringComparison.OrdinalIgnoreCase) == 0)) { errorRecord = GetErrorRecord("PsPluginManagerGetConfUnexpectedResult", ErrorCategory.InvalidResult, PSinfrastructureStrings.PsPluginManagerGetConfUnexpectedResult, importModuleResult, _pluginModuleName); getActionStatusCode = (int)GetActionStatusCodeTypes.UnexpectedGetConfigurationResponseFromServer; return MiResult.FAILED; } return result; } /*If this succeeds, the first object in the collection is a valid object.*/ private static MiResult ValidateGetResult(System.Management.Automation.PowerShell powershell, Collection<PSObject> providerImportResult, string downloadManagerName, out string importModuleResult, out ErrorRecord errorRecord, out UInt32 getActionStatusCode) { importModuleResult = null; HandleNonTerminatingErrors(powershell, downloadManagerName, DownloadManagerGetConfiguration, out errorRecord); if (errorRecord != null) { getActionStatusCode = (int) GetActionStatusCodeTypes.GetConfigurationCommandFailure; return MiResult.FAILED; } if (providerImportResult == null || providerImportResult.Count != 1) { errorRecord = GetErrorRecord("GetConfigurationResultNotExpected", ErrorCategory.ObjectNotFound, PSinfrastructureStrings.GetConfigurationResultCountUnexpected, downloadManagerName); getActionStatusCode = (int)GetActionStatusCodeTypes.GetConfigurationCommandFailure; return MiResult.NOT_FOUND; } try { importModuleResult = LanguagePrimitives.ConvertTo<string>(providerImportResult[0]); } catch (PSInvalidCastException ex) { errorRecord = GetErrorRecord("GetConfigurationResultNotExpected", ex, ErrorCategory.InvalidType, PSinfrastructureStrings.GetConfigurationResultCountUnexpected, downloadManagerName); getActionStatusCode = (int)GetActionStatusCodeTypes.GetConfigurationCommandFailure; return MiResult.NOT_FOUND; } if (importModuleResult == null || string.IsNullOrEmpty(importModuleResult)) { errorRecord = GetErrorRecord("GetConfigurationResultNotExpected", ErrorCategory.ObjectNotFound, PSinfrastructureStrings.GetConfigurationResultCountUnexpected, downloadManagerName); getActionStatusCode = (int)GetActionStatusCodeTypes.GetConfigurationCommandFailure; return MiResult.NOT_FOUND; } getActionStatusCode = (int)GetActionStatusCodeTypes.Success; return MiResult.OK; } /*If this succeeds, the first object in the collection is a valid object.*/ private static MiResult ValidateGetActionResult(System.Management.Automation.PowerShell powershell, Collection<PSObject> providerImportResult, string downloadManagerName, out string importModuleResult, out ErrorRecord errorRecord, out UInt32 getActionStatusCode) { var result = ValidateGetResult(powershell, providerImportResult, downloadManagerName, out importModuleResult, out errorRecord, out getActionStatusCode); if (result != MiResult.OK) { return result; } if (!(string.Compare(importModuleResult, StatusOk, StringComparison.OrdinalIgnoreCase) == 0 || string.Compare(importModuleResult, StatusGetMof, StringComparison.OrdinalIgnoreCase) == 0)) { errorRecord = GetErrorRecord("PluginGetActionUnsuccessful", ErrorCategory.InvalidResult, PSinfrastructureStrings.PluginGetActionUnsuccessful, importModuleResult, _pluginModuleName); return MiResult.FAILED; } return result; } #endregion PLUGIN_GETCONFIGURATION #region PLUGIN_GETMODULE /*If this succeeds, the first object in the collection is a valid object.*/ private static MiResult ValidateModuleForVersion(System.Management.Automation.PowerShell powershell, IEnumerable<PSModuleInfo> availableModules, string downloadManagerName, string moduleVersion, out bool isModuleAvailable, out ErrorRecord errorRecord) { isModuleAvailable = false; HandleNonTerminatingErrors(powershell, downloadManagerName, "Import-Module", out errorRecord); if (errorRecord != null) { return MiResult.FAILED; } if (availableModules == null) { return MiResult.OK; // cached module not available. } // ReSharper disable LoopCanBeConvertedToQuery foreach (PSModuleInfo localModule in availableModules) // ReSharper restore LoopCanBeConvertedToQuery { if (string.IsNullOrEmpty(moduleVersion) || string.Compare(moduleVersion, localModule.Version.ToString(), StringComparison.OrdinalIgnoreCase) == 0) { // Module is a default module available in system cache. S_DscCoreR.EventWriteLCMPullModuleSkippedAsModuleIsAvailable(S_DscCoreR.JobGuidString, localModule.Name, localModule.Version.ToString(), localModule.Path); isModuleAvailable = true; return MiResult.OK; } } return MiResult.OK; } private static ModuleSpecification[] GetModuleSpecification(Dictionary<string, Tuple<string, List<string>>> moduleVersionTable) { List<ModuleSpecification> moduleSpecifications = new List<ModuleSpecification>(); foreach (var entry in moduleVersionTable) { if (string.IsNullOrEmpty(entry.Value.Item1)) { moduleSpecifications.Add(new ModuleSpecification(entry.Key)); } else { Hashtable h = new Hashtable(StringComparer.OrdinalIgnoreCase); h.Add("ModuleName", entry.Key); h.Add("ModuleVersion", entry.Value.Item1); moduleSpecifications.Add(new ModuleSpecification(h)); } } return moduleSpecifications.ToArray(); } private static MiResult GetGetModuleParams(IntPtr metaConfigHandle, out PSCredential pscredential, Hashtable arguments, out string configurationId, out ErrorRecord errorRecord, out UInt32 getActionStatusCode) { //Get info from MetaConfig MiResult statusCode = GetMetaConfigParams(metaConfigHandle, out pscredential, arguments, out configurationId, out errorRecord, out getActionStatusCode); return statusCode; } private static MiResult GetModuleNameVersionTable(string mofFileLocation, Dictionary<string, Tuple<string, List<string>>> moduleNameTable, out ErrorRecord errorRecord, out UInt32 getActionStatusCode) { Debug.Assert(moduleNameTable != null, "moduleNameTable can not be null"); //Load the mof file. errorRecord = null; List<CimInstance> cimInstances; try { CimClassCache.InitializeInfraStructureMof(); cimInstances = CimClassCache.ImportInstances(mofFileLocation, SchemaValidationOption); } catch (CimException exception) { errorRecord = GetErrorRecord("ConfigurationFileInvalid", ErrorCategory.InvalidResult, PSinfrastructureStrings.ConfigurationFileInvalid, _pluginModuleName); getActionStatusCode = (int)GetActionStatusCodeTypes.InvalidConfigurationFile; return (MiResult)exception.StatusCode; } foreach (var inst in cimInstances) { string moduleName = inst.CimSystemProperties.ClassName; string moduleVersion = ""; string providerClassName = inst.CimSystemProperties.ClassName; // check for resource class and get module Name and Version if (string.Compare(inst.CimSystemProperties.ClassName, BaseDocumentClassname, StringComparison.OrdinalIgnoreCase) != 0) { if (inst.CimInstanceProperties["ModuleName"] != null && inst.CimInstanceProperties["ModuleName"].Value != null) { moduleName = inst.CimInstanceProperties["ModuleName"].Value.ToString(); } if (inst.CimInstanceProperties["ModuleVersion"] != null && inst.CimInstanceProperties["ModuleVersion"].Value != null) { moduleVersion = inst.CimInstanceProperties["ModuleVersion"].Value.ToString(); } if (!string.IsNullOrEmpty(moduleName) && (string.IsNullOrEmpty(moduleVersion) || IsModuleVersionValidFormat(moduleVersion))) { Tuple<string, List<string>> moduleEntry; if (moduleNameTable.ContainsKey(moduleName)) { moduleEntry = moduleNameTable[moduleName]; if (!moduleEntry.Item2.Contains(providerClassName)) { moduleEntry.Item2.Add(providerClassName); } } else { moduleEntry = new Tuple<string, List<string>>(moduleVersion, new List<string>()); moduleEntry.Item2.Add(providerClassName); moduleNameTable[moduleName] = moduleEntry; } } else { getActionStatusCode = (int) GetActionStatusCodeTypes.InvalidModuleVersionFormat; S_DscCoreR.EventWriteLCMPullModuleInvalidVersionFormat(S_DscCoreR.JobGuidString, moduleName, moduleVersion); errorRecord = GetErrorRecord("InvalidModuleVersionFormat", null, ErrorCategory.InvalidArgument, PSinfrastructureStrings.InvalidModuleVersionFormat, moduleVersion, moduleName); return MiResult.INVALID_PARAMETER; } } } // check with cached modules and remove them from the list. return FilterUsingCachedModules(moduleNameTable, out errorRecord, out getActionStatusCode); } private static bool IsModuleVersionValidFormat(string moduleVersion) { Version throwAwayVersion; return Version.TryParse(moduleVersion, out throwAwayVersion); } private static MiResult FilterUsingCachedModules(Dictionary<string, Tuple<string, List<string>>> moduleNameTable, out ErrorRecord errorRecord, out UInt32 getActionStatusCode) { errorRecord = null; // if it is known Native Module, remove it from the list. foreach (string nativeModule in NativeModules) { if (moduleNameTable.ContainsKey(nativeModule)) { //remove it moduleNameTable.Remove(nativeModule); } } // Find if modules are present locally var cachedModules = new List<string>(); foreach (var entry in moduleNameTable) { // Find if modules are present as internal DSC module var corePsProvidersModuleRelativePath = Path.Combine(Constants.CorePsProvidersRelativePath, entry.Key); var corePsProvidersModulePath = Path.Combine(Environment.SystemDirectory, corePsProvidersModuleRelativePath); if (Directory.Exists(corePsProvidersModulePath)) { S_DscCoreR.EventWriteLCMPullModuleSkippedAsModuleIsAvailable(S_DscCoreR.JobGuidString, entry.Key, entry.Value.Item1, corePsProvidersModulePath); cachedModules.Add(entry.Key); continue; } System.Management.Automation.PowerShell powershell = System.Management.Automation.PowerShell.Create(); powershell.Runspace = _pluginRunspace; Collection<PSModuleInfo> availableModules; try { // check if the module exists. powershell.AddCommand("Get-Module").AddParameter("Name", entry.Key).AddParameter("ListAvailable"); powershell.Streams.Error.Clear(); availableModules = powershell.Invoke<PSModuleInfo>(); if (powershell.Streams.Error.Count > 0) { errorRecord = GetErrorRecord("GetModuleListAvailableError", ErrorCategory.InvalidResult, PSinfrastructureStrings.GetModuleListAvailableError, entry.Key); powershell.Dispose(); getActionStatusCode = (int)GetActionStatusCodeTypes.AvailableModulesCheckFailure; return MiResult.FAILED; } } catch (Exception ex) { errorRecord = GetErrorRecord("GetModuleListAvailableError", ex, ErrorCategory.InvalidResult, PSinfrastructureStrings.GetModuleListAvailableError, entry.Key); getActionStatusCode = (int)GetActionStatusCodeTypes.AvailableModulesCheckFailure; return MiResult.FAILED; } bool isModuleAvailable; var statusCode = ValidateModuleForVersion(powershell, availableModules, entry.Key, entry.Value.Item1, out isModuleAvailable, out errorRecord); powershell.Dispose(); if (statusCode != MiResult.OK) { getActionStatusCode = (int)GetActionStatusCodeTypes.AvailableModulesCheckFailure; return statusCode; } if (isModuleAvailable) { cachedModules.Add(entry.Key); } } // Remove the cached item from the list so we don't need to pull those foreach (var cachedModule in cachedModules) { moduleNameTable.Remove(cachedModule); } getActionStatusCode = (int)GetActionStatusCodeTypes.Success; return MiResult.OK; } private static MiResult ValidatePsdscModule(string moduleName, string schemaFileName, out UInt32 getActionStatusCode, out ErrorRecord errorRecord) { errorRecord = null; CimClassCache.InitializeInfraStructureMof(); List<CimClass> newCimClasses; PSModuleInfo moduleInfo; if (!File.Exists(schemaFileName)) { getActionStatusCode = (int) GetActionStatusCodeTypes.ModuleValidationFailure; errorRecord = Utility.CreateErrorRecord( "ProviderSchemaFileNotFound", ErrorCategory.ObjectNotFound, null, PSinfrastructureStrings.SchemaFileNotFound, new object[] { moduleName, schemaFileName }); return MiResult.NOT_FOUND; } try { newCimClasses = CimClassCache.ImportClasses(schemaFileName); // Get Module Info by importing module. using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create()) { ps.Runspace = _validationRunspace; moduleInfo = ps.AddCommand("Import-Module").AddParameter("Name", moduleName).AddParameter("Force", true).AddParameter("PassThru", true).Invoke<PSModuleInfo>().FirstOrDefault(); _validationRunspace.ResetRunspaceState(); } } catch (Exception e) { getActionStatusCode = (int)GetActionStatusCodeTypes.ModuleValidationFailure; errorRecord = GetErrorRecord( "InvalidModuleOrSchema", e, ErrorCategory.InvalidOperation, PSinfrastructureStrings.InvalidModuleFileOrMOF, moduleName, schemaFileName); return MiResult.NOT_FOUND; } if (moduleInfo == null) { errorRecord = GetErrorRecord( "CannotLoadModuleFileForValidation", null, ErrorCategory.InvalidOperation, PSinfrastructureStrings.MissingModuleFileForValidation, moduleName); getActionStatusCode = (int)GetActionStatusCodeTypes.ModuleValidationFailure; return MiResult.NO_SUCH_PROPERTY; } // Validate schema. var resourceClass = newCimClasses.FirstOrDefault( cimClass => cimClass.CimSuperClassName != null && string.Compare(cimClass.CimSuperClassName, BaseResourceClass, StringComparison.OrdinalIgnoreCase) == 0); if (resourceClass == null) { errorRecord = GetErrorRecord( "InvalidModuleMissingSchemaClass", null, ErrorCategory.InvalidOperation, PSinfrastructureStrings.InvalidModuleMissingSchemaClass, moduleName, schemaFileName); getActionStatusCode = (int)GetActionStatusCodeTypes.ModuleValidationFailure; return MiResult.NOT_FOUND; } // Validate required exported commands. CommandInfo exportedCommand; if (!moduleInfo.ExportedCommands.TryGetValue(PsDscCmdletGet, out exportedCommand)) { errorRecord = GetErrorRecord( "InvalidModuleMissingCommand", null, ErrorCategory.InvalidOperation, PSinfrastructureStrings.InvalidModuleMissingCommand, moduleName, PsDscCmdletGet); getActionStatusCode = (int)GetActionStatusCodeTypes.ModuleValidationFailure; return MiResult.NO_SUCH_PROPERTY; } if (!moduleInfo.ExportedCommands.TryGetValue(PsDscCmdletSet, out exportedCommand)) { errorRecord = GetErrorRecord( "InvalidModuleMissingCommand", null, ErrorCategory.InvalidOperation, PSinfrastructureStrings.InvalidModuleMissingCommand, moduleName, PsDscCmdletSet); getActionStatusCode = (int)GetActionStatusCodeTypes.ModuleValidationFailure; return MiResult.NO_SUCH_PROPERTY; } if (!moduleInfo.ExportedCommands.TryGetValue(PsDscCmdletTest, out exportedCommand)) { errorRecord = GetErrorRecord( "InvalidModuleMissingCommand", null, ErrorCategory.InvalidOperation, PSinfrastructureStrings.InvalidModuleMissingCommand, moduleName, PsDscCmdletTest); getActionStatusCode = (int)GetActionStatusCodeTypes.ModuleValidationFailure; return MiResult.NO_SUCH_PROPERTY; } // Validate required exported command parameters. return ValidateExportedCommandParameters(resourceClass, moduleInfo, out getActionStatusCode, out errorRecord); } private static MiResult ValidateExportedCommandParameters( CimClass resourceClass, PSModuleInfo moduleInfo, out UInt32 getActionStatusCode, out ErrorRecord errorRecord) { // Get key and write parameter data from MOF schema. List<CimPropertyDeclaration> keyProperties; List<CimPropertyDeclaration> writeProperties; List<CimPropertyDeclaration> requiredProperties; Utility.GetKeyWriteRequiredProperties(resourceClass, out keyProperties, out writeProperties, out requiredProperties); // Validate Get command against key parameters. string moduleName = moduleInfo.Name; CommandInfo cmdInfo = moduleInfo.ExportedCommands[PsDscCmdletGet]; MiResult result = ValidateCommandParameters(cmdInfo, keyProperties, moduleName, out getActionStatusCode, out errorRecord); if (result != MiResult.OK) { return result; } // Validate Set command against key and write parameters. cmdInfo = moduleInfo.ExportedCommands[PsDscCmdletSet]; result = ValidateCommandParameters(cmdInfo, keyProperties, moduleName, out getActionStatusCode, out errorRecord); if (result != MiResult.OK) { return result; } result = ValidateCommandParameters(cmdInfo, writeProperties, moduleName, out getActionStatusCode, out errorRecord); if (result != MiResult.OK) { return result; } // Validate Test command against key and write parameters. cmdInfo = moduleInfo.ExportedCommands[PsDscCmdletTest]; result = ValidateCommandParameters(cmdInfo, keyProperties, moduleName, out getActionStatusCode, out errorRecord); if (result != MiResult.OK) { return result; } return ValidateCommandParameters(cmdInfo, writeProperties, moduleName, out getActionStatusCode, out errorRecord); } private static MiResult ValidateCommandParameters( CommandInfo cmdInfo, List<CimPropertyDeclaration> keyOrWriteProperties, string moduleName, out UInt32 getActionStatusCode, out ErrorRecord errorRecord) { errorRecord = null; foreach (var currentKeyOrWriteProperty in keyOrWriteProperties) { if (!cmdInfo.Parameters.ContainsKey(currentKeyOrWriteProperty.Name)) { errorRecord = GetErrorRecord( "KeyParameterNotImplemented", null, ErrorCategory.InvalidOperation, PSinfrastructureStrings.MismatchedPsModuleCommandParameterWithSchema, moduleName, cmdInfo.Name, currentKeyOrWriteProperty.Name); getActionStatusCode = (uint) GetActionStatusCodeTypes.ModuleValidationFailure; return MiResult.INVALID_PARAMETER; } } getActionStatusCode = (uint) GetActionStatusCodeTypes.Success; return MiResult.OK; } private static MiResult ValidateModuleWithChecksum(string moduleFileName, string checksumFileName, out ErrorRecord errorRecord, out UInt32 getActionStatusCode) { errorRecord = null; // Read mof file content and get the checksum. byte[] moduleContent; byte[] checkSumContent; try { using (var fs = File.OpenRead(moduleFileName)) { moduleContent = new byte[fs.Length]; fs.Read(moduleContent, 0, Convert.ToInt32(fs.Length)); fs.Close(); } using (var fs = File.OpenRead(checksumFileName)) { checkSumContent = new byte[fs.Length]; fs.Read(checkSumContent, 0, Convert.ToInt32(fs.Length)); fs.Close(); } } catch (Exception exception) { errorRecord = GetErrorRecord("ModuleFileInvalid", exception, ErrorCategory.InvalidResult, PSinfrastructureStrings.ModuleFileInvalid, moduleFileName, _pluginModuleName); getActionStatusCode = (int)GetActionStatusCodeTypes.InvalidModuleFile; return MiResult.FAILED; } //Compute checksum of mof file. var sha = new SHA256Managed(); byte[] computedHash = sha.ComputeHash(moduleContent); var hashStringFromMof = BitConverter.ToString(computedHash).Replace("-", ""); var hashStringFromChecksum = System.Text.Encoding.Default.GetString(checkSumContent); if (string.Compare(hashStringFromMof, hashStringFromChecksum, StringComparison.OrdinalIgnoreCase) != 0) { errorRecord = GetErrorRecord("ChecksumValidationModuleFailed", ErrorCategory.InvalidResult, PSinfrastructureStrings.ChecksumValidationModuleFailed, moduleFileName, _pluginModuleName); getActionStatusCode = (int)GetActionStatusCodeTypes.ModuleChecksumValidationFailure; return MiResult.FAILED; } getActionStatusCode = (int) GetActionStatusCodeTypes.Success; return MiResult.OK; } private static MiResult ValidateMofWithChecksum(string mofFileName, string checksumFileName, out ErrorRecord errorRecord, out UInt32 getActionStatusCode) { errorRecord = null; // Read mof file content and get the checksum. byte[] mofContent; byte[] checkSumContent; try { using (var fs = File.OpenRead(mofFileName)) { mofContent = new byte[fs.Length]; fs.Read(mofContent, 0, Convert.ToInt32(fs.Length)); fs.Close(); } using (var fs = File.OpenRead(checksumFileName)) { checkSumContent = new byte[fs.Length]; fs.Read(checkSumContent, 0, Convert.ToInt32(fs.Length)); fs.Close(); } } catch (Exception exception) { errorRecord = GetErrorRecord("ConfigurationFileInvalid", exception, ErrorCategory.InvalidResult, PSinfrastructureStrings.ConfigurationFileInvalid, _pluginModuleName); getActionStatusCode = (int)GetActionStatusCodeTypes.ConfigurationChecksumFileReadFailure; return MiResult.FAILED; } //Compute checksum of mof file. var sha = new SHA256Managed(); byte[] computedHash = sha.ComputeHash(mofContent); var hashStringFromMof = BitConverter.ToString(computedHash).Replace("-", ""); var hashStringFromChecksum = System.Text.Encoding.Default.GetString(checkSumContent); if (string.Compare(hashStringFromMof, hashStringFromChecksum, StringComparison.OrdinalIgnoreCase) != 0) { errorRecord = GetErrorRecord("ChecksumValidationConfigurationFailed", ErrorCategory.InvalidResult, PSinfrastructureStrings.ChecksumValidationConfigurationFailed, _pluginModuleName); getActionStatusCode = (int)GetActionStatusCodeTypes.ConfigurationChecksumValidationFailure; return MiResult.FAILED; } getActionStatusCode = (int) GetActionStatusCodeTypes.Success; return MiResult.OK; } private static MiResult ValidateConfigurationChecksum(string downloadLocation, out string mofFileName, out ErrorRecord errorRecord, out UInt32 getActionStatusCode) { mofFileName = null; // DownloadLocation contain .mof file and .mof.checksum file. // We will fail if both the files are not available. string mofFileLocation = null; try { string[] mofFiles = Directory.GetFiles(downloadLocation, "*.mof"); if (mofFiles.Length == 0) { errorRecord = GetErrorRecord("ConfigurationFileNotExist", ErrorCategory.InvalidResult, PSinfrastructureStrings.ConfigurationFileNotExist, _pluginModuleName); getActionStatusCode = (int) GetActionStatusCodeTypes.ConfigurationFileNotFound; return MiResult.NOT_FOUND; } if (mofFiles.Length > 1) { errorRecord = GetErrorRecord("ConfigurationFileMultipleExist", ErrorCategory.InvalidResult, PSinfrastructureStrings.ConfigurationFileMultipleExist, _pluginModuleName); getActionStatusCode = (int)GetActionStatusCodeTypes.MultipleConfigurationFilesFound; return MiResult.FAILED; } mofFileLocation = mofFiles[0]; //get the checksum now. if (!File.Exists(mofFiles[0] + ".checksum")) { errorRecord = GetErrorRecord("ConfigurationChecksumFileNotExist", ErrorCategory.InvalidResult, PSinfrastructureStrings.ConfigurationChecksumFileNotExist, _pluginModuleName, mofFileLocation); getActionStatusCode = (int)GetActionStatusCodeTypes.ConfigurationChecksumFileNotFound; return MiResult.NOT_FOUND; } //compute checksum MiResult statusCode = ValidateMofWithChecksum(mofFileLocation, mofFiles[0] + ".checksum", out errorRecord, out getActionStatusCode); S_DscCoreR.EventWriteLCMPullConfigurationChecksumValidationResult(S_DscCoreR.JobGuidString, mofFileLocation, (uint)statusCode); if (statusCode != MiResult.OK) { return statusCode; } } catch (Exception ex) { errorRecord = GetErrorRecord("ConfigurationFileGenericFailure", ex, ErrorCategory.InvalidResult, PSinfrastructureStrings.ConfigurationFileGenericFailure, _pluginModuleName); getActionStatusCode = (int) GetActionStatusCodeTypes.ConfigurationChecksumValidationFailure; return MiResult.FAILED; } mofFileName = mofFileLocation; return MiResult.OK; } private static MiResult InstallModules(IntPtr metaConfigHandle, string downloadLocation, string mofFileName, bool allowModuleOverwrite, out ErrorRecord errorRecord, out UInt32 getActionStatusCode) { // Get module Names with Version and required resource names var moduleNameVersionTable = new Dictionary<string, Tuple<string, List<string>>>(); MiResult statusCode = GetModuleNameVersionTable(mofFileName, moduleNameVersionTable, out errorRecord, out getActionStatusCode); if (statusCode != MiResult.OK) { return statusCode; } if (moduleNameVersionTable.Count == 0) { S_DscCoreR.EventWriteLCMPullModuleSkippedAsAllModulesAreAvailable(S_DscCoreR.JobGuidString, mofFileName); return MiResult.OK; } string configurationId; // This holds the download manager specific properties - ServerUrl, SourcePath, etc. Hashtable arguments; // We use this collection to hold the PSObjects that we create from arguments. We pass this down to the download manager cmdlets (The parameters have ValueFromPipelineByPropertyName specified) Collection<PSObject> argumentParameters; ModuleSpecification[] moduleSpecifications; using (System.Management.Automation.PowerShell powershell = System.Management.Automation.PowerShell.Create(InitialSessionState.CreateDefault2())) { PSCommand powershellCommand; statusCode = DoInitializationForGetModule(metaConfigHandle, moduleNameVersionTable, downloadLocation, out configurationId, out moduleSpecifications, powershell, out powershellCommand, out arguments, out argumentParameters, out errorRecord, out getActionStatusCode); if (statusCode != MiResult.OK) { return statusCode; } for (int i = 0; i < moduleNameVersionTable.Count; i++) { string downloadedModule; Collection<PSObject> pullOneModuleResult; powershell.Commands.Clear(); powershell.Commands = powershellCommand; powershell.Streams.Error.Clear(); statusCode = PullOneModule(powershell, moduleSpecifications[i], configurationId, downloadLocation, arguments, argumentParameters, out downloadedModule, out pullOneModuleResult, out errorRecord, out getActionStatusCode); if (statusCode != MiResult.OK) { return statusCode; } // This validation is maybe not necessary. // This validation checks if the module that we got from the Pull Server is what we asked for // This used to make sense when we were downloading a bunch of modules. // TODO : Remove this statusCode = ValidateForExpectedModule(powershell, pullOneModuleResult, moduleNameVersionTable, out errorRecord, out getActionStatusCode); if (statusCode != MiResult.OK) { return statusCode; } var moduleSpecification = moduleSpecifications[i]; statusCode = ValidateAndInstallOneModule(downloadLocation, moduleSpecification, moduleNameVersionTable[moduleSpecification.Name].Item2, allowModuleOverwrite, out errorRecord, out getActionStatusCode); if (statusCode != (UInt32)MiResult.OK) { return statusCode; } } } // Log to ETW Channel:Microsoft-Windows-DSC/Operational S_DscCoreR.EventWriteLCMPullGetModuleSuccess(S_DscCoreR.JobGuidString, _pluginModuleName); return MiResult.OK; } // Does the initialization for Get-DscModule. This initialization needs to only happen once for all the modules. // As part of this initialization, the following is done // 1) Download manager is imported // 2) Configuration is parsed to get the list of modules and the download-manager specific properties // 3) For the download manager specific properties, we create PSObjects so we can pass those down to the download manager cmdlets. // 4) We construct a PowerShell that has all the parameters added (except for the Module parameter. This parameter gets added for each module) private static MiResult DoInitializationForGetModule(IntPtr metaConfigHandle, Dictionary<string, Tuple<string, List<string>>> moduleNameVersionTable, string downloadLocation, out string configurationId, out ModuleSpecification[] moduleSpecifications, System.Management.Automation.PowerShell powershell, out PSCommand powershellCommand, out Hashtable arguments, out Collection<PSObject> argumentParameters, out ErrorRecord errorRecord, out UInt32 getActionStatusCode) { configurationId = null; powershellCommand = null; moduleSpecifications = null; argumentParameters = null; arguments = new Hashtable(); // Initialize DownloadManager MiResult statusCode = InitializePlugin(metaConfigHandle, out errorRecord); if (statusCode != MiResult.OK) { getActionStatusCode = (int)GetActionStatusCodeTypes.DownloadManagerInitializationFailure; return statusCode; } //Get parameters for Get-Module cmdlet PSCredential pscredential; statusCode = GetGetModuleParams(metaConfigHandle, out pscredential, arguments, out configurationId, out errorRecord, out getActionStatusCode); if (statusCode != MiResult.OK) { return statusCode; } moduleSpecifications = GetModuleSpecification(moduleNameVersionTable); powershell.Runspace = _pluginRunspace; if (arguments.Count > 0) { argumentParameters = powershell.AddCommand("New-Object").AddParameter("Type", "psobject").AddParameter("Property", arguments).Invoke(); powershell.Commands.Clear(); } powershellCommand = new PSCommand(); powershellCommand.AddCommand(_pluginModuleName + "\\" + DownloadManagerGetModules); powershellCommand.AddParameter(ParamConfigurationId, configurationId); powershellCommand.AddParameter(ParamDestinationPath, downloadLocation); if (pscredential != null) { powershellCommand.AddParameter(ParamCredential, pscredential); } return MiResult.OK; } // Powershell object passed here will be disposed off by the caller private static MiResult PullOneModule(System.Management.Automation.PowerShell powershell, ModuleSpecification moduleSpecification, string configurationId, string downloadLocation, Hashtable arguments, Collection<PSObject> argumentParameters, out string downloadedModule, out Collection<PSObject> pullOneModuleResult, out ErrorRecord errorRecord, out UInt32 getActionStatusCode) { pullOneModuleResult = null; downloadedModule = null; string moduleVersionString = GetModuleVersionStringFromModuleSpecification(moduleSpecification); try { powershell.AddParameter(ParamModules, moduleSpecification); // Log to ETW Channel:Microsoft-Windows-DSC/Operational S_DscCoreR.EventWriteLCMPullGetModuleAttempt(S_DscCoreR.JobGuidString, _pluginModuleName, configurationId, moduleVersionString); powershell.Streams.Error.Clear(); pullOneModuleResult = arguments.Count > 0 ? powershell.Invoke(argumentParameters) : powershell.Invoke(); if (powershell.Streams.Error.Count > 0) { errorRecord = powershell.Streams.Error[0]; powershell.Dispose(); if( string.Compare(errorRecord.FullyQualifiedErrorId, "WebDownloadManagerUnknownChecksumAlgorithm", StringComparison.OrdinalIgnoreCase) == 0 ) { getActionStatusCode = (int)GetActionStatusCodeTypes.InvalidChecksumAlgorithm; } else { getActionStatusCode = (int)GetActionStatusCodeTypes.GetDscModuleCommandFailure; } return MiResult.FAILED; } } catch (Exception ex) { errorRecord = GetErrorRecord("GetModuleExecutionFailure", ex, ErrorCategory.InvalidType, PSinfrastructureStrings.GetModuleExecutionFailure, _pluginModuleName); getActionStatusCode = (int)GetActionStatusCodeTypes.GetDscModuleCommandFailure; return MiResult.FAILED; } S_DscCoreR.EventWriteLCMPullModuleDownloadLocation(S_DscCoreR.JobGuidString, moduleVersionString, downloadLocation); errorRecord = null; getActionStatusCode = (int)GetActionStatusCodeTypes.Success; return MiResult.OK; } // Validates that the module we have is the module we asked for from the Pull Server // This validation is maybe not necessary. // This used to make sense when we were downloading a bunch of modules. // TODO : Remove this private static MiResult ValidateForExpectedModule(System.Management.Automation.PowerShell powershell, Collection<PSObject> pullOneModuleResult, Dictionary<string, Tuple<string, List<string>>> moduleNameVersionTable, out ErrorRecord errorRecord, out UInt32 getActionStatusCode) { ModuleSpecification[] pullOneModuleInternalResult; HandleNonTerminatingErrors(powershell, _pluginModuleName, DownloadManagerGetConfiguration, out errorRecord); if (errorRecord != null) { getActionStatusCode = (int)GetActionStatusCodeTypes.GetDscModuleCommandFailure; return MiResult.FAILED; } if (pullOneModuleResult == null || pullOneModuleResult.Count == 0) { getActionStatusCode = (int)GetActionStatusCodeTypes.GetDscModuleCommandFailure; errorRecord = GetErrorRecord("GetModuleResultNotExpected", ErrorCategory.InvalidResult, PSinfrastructureStrings.GetModuleResultCountUnexpected, _pluginModuleName); return MiResult.NOT_FOUND; } try { pullOneModuleInternalResult = LanguagePrimitives.ConvertTo<ModuleSpecification[]>(pullOneModuleResult); } catch (PSInvalidCastException ex) { getActionStatusCode = (int)GetActionStatusCodeTypes.GetDscModuleInvalidOutput; errorRecord = GetErrorRecord("GetModuleResultNotExpected", ex, ErrorCategory.InvalidResult, PSinfrastructureStrings.GetModuleResultCountUnexpected, _pluginModuleName); return MiResult.NOT_FOUND; } // We are always downloading only one module at at a time if (pullOneModuleInternalResult == null || pullOneModuleInternalResult.Count() > 1) { getActionStatusCode = (int)GetActionStatusCodeTypes.GetDscModuleInvalidOutput; errorRecord = GetErrorRecord("GetModuleResultNotExpected", ErrorCategory.InvalidResult, PSinfrastructureStrings.GetModuleResultCountUnexpected, _pluginModuleName); return MiResult.NOT_FOUND; } if (!moduleNameVersionTable.ContainsKey(pullOneModuleInternalResult[0].Name)) { getActionStatusCode = (int)GetActionStatusCodeTypes.GetDscModuleInvalidOutput; errorRecord = GetErrorRecord("GetModuleResultNotExpected", ErrorCategory.InvalidResult, PSinfrastructureStrings.GetModuleResultCountUnexpected, _pluginModuleName); return MiResult.NO_SUCH_PROPERTY; } getActionStatusCode = (int)GetActionStatusCodeTypes.Success; return MiResult.OK; } private static MiResult ValidateAndInstallOneModule(string downloadLocation, ModuleSpecification moduleSpecification, IEnumerable<string> requiredResources, bool allowModuleOverwrite, out ErrorRecord errorRecord, out UInt32 getActionStatusCode) { // Powershell modules are not supported by Linux DSC. return MiResult.NOT_SUPPORTED; } #endregion PLUGIN_GETMODULE #region PLUGIN_COMMONHELPERS private static string GetModuleVersionStringFromModuleSpecification(ModuleSpecification moduleSpecification) { StringBuilder result = new StringBuilder(); result.Append("(").Append(moduleSpecification.Name); if (moduleSpecification.Version != null) { result.Append(",").Append(moduleSpecification.Version).Append(")"); } else { result.Append(")"); } return result.ToString(); } internal static ErrorRecord GetErrorRecord(string errorId, ErrorCategory errorCategory, string errorResourceId, params object[] errorResourceIdParams) { return Utility.CreateErrorRecord(errorId, errorCategory, null, errorResourceId, errorResourceIdParams); } internal static ErrorRecord GetErrorRecord(string errorId, Exception exception, ErrorCategory errorCategory, string errorResourceId, params object[] errorResourceIdParams) { return Utility.CreateErrorRecord(errorId, errorCategory, exception, errorResourceId, errorResourceIdParams); } // ReSharper disable UnusedMember.Local private static string GetPsProgramFilesModulePath() // ReSharper restore UnusedMember.Local { //return @"c:\Program Files\Microsoft\Windows\Powershell\Modules\"; return Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + @"\WindowsPowerShell\Modules\"; } private static void HandleNonTerminatingErrors(System.Management.Automation.PowerShell powerShell, string providerName, string operationCmd, out ErrorRecord errorRecord) { errorRecord = null; if (powerShell != null && powerShell.Streams != null && powerShell.Streams.Error != null && powerShell.Streams.Error.Count > 0 && !string.IsNullOrEmpty(providerName) && !string.IsNullOrEmpty(operationCmd)) { string errorMessage = string.Format( CultureInfo.CurrentCulture, PSinfrastructureStrings.NonTerminatingErrorFromProvider, providerName, operationCmd); InvalidOperationException invalidOperationException; // If there is only one non-terminting error then the surface it's inner execetion (if it exists) to the commandline. if (powerShell.Streams.Error.Count == 1 && powerShell.Streams.Error[0].Exception != null) { invalidOperationException = new InvalidOperationException(errorMessage, powerShell.Streams.Error[0].Exception); } else { invalidOperationException = new InvalidOperationException(errorMessage); } errorRecord = new ErrorRecord( invalidOperationException, "NonTerminatingErrorFromProvider", ErrorCategory.InvalidOperation, null); } } private static string GetSystemUuid() { var cimSession = CimSession.Create("localhost"); var cimInstance = cimSession.EnumerateInstances(@"root/cimv2", "Win32_ComputerSystemProduct").First(); if (cimInstance.CimInstanceProperties["UUID"] != null) { return cimInstance.CimInstanceProperties["UUID"].Value.ToString(); } return String.Empty; } private static MiResult GetAllowModuleOverwriteFromMetaConfig(IntPtr metaConfigHandle, out Boolean allowModuleOverwrite, out ErrorRecord errorRecord, out UInt32 getActionStatusCode) { errorRecord = null; allowModuleOverwrite = false; var metaConfigInstance = new CimInstance(new InstanceHandle(metaConfigHandle, false), null); if (metaConfigInstance.CimInstanceProperties[MetaConfigAllowModuleOverwrite] != null && metaConfigInstance.CimInstanceProperties[MetaConfigAllowModuleOverwrite].Value != null) { LanguagePrimitives.TryConvertTo(metaConfigInstance.CimInstanceProperties[MetaConfigAllowModuleOverwrite].Value, out allowModuleOverwrite); } getActionStatusCode = (int)GetActionStatusCodeTypes.Success; return MiResult.OK; } private static MiResult GetMetaConfigParams(IntPtr metaConfigHandle, out PSCredential pscredential, Hashtable arguments, out string configurationId, out ErrorRecord errorRecord, out UInt32 getActionStatusCode) { configurationId = null; errorRecord = null; pscredential = null; var metaConfigInstance = new CimInstance(new InstanceHandle(metaConfigHandle, false), null); // Get ConfigurationID. try { if (metaConfigInstance.CimInstanceProperties[MetaConfigConfigurationId] != null && metaConfigInstance.CimInstanceProperties[MetaConfigConfigurationId].Value != null) { string tempConfigurationId = metaConfigInstance.CimInstanceProperties[MetaConfigConfigurationId].Value.ToString(); /* We will use System UUID only if user has explicitly asked for.*/ if (string.Compare(tempConfigurationId, UseSystemUUIDValue, StringComparison.OrdinalIgnoreCase) == 0) { tempConfigurationId = GetSystemUuid(); } Guid throwAwayGuid = Guid.Empty; if (Guid.TryParse(tempConfigurationId, out throwAwayGuid)) { configurationId = tempConfigurationId; } else { getActionStatusCode = (int) GetActionStatusCodeTypes.InvalidConfigurationIdFormat; errorRecord = GetErrorRecord("InvalidConfigurationIdFormat", null, ErrorCategory.InvalidArgument, PSinfrastructureStrings.InvalidConfigurationIdFormat, tempConfigurationId); return MiResult.INVALID_PARAMETER; } } else { getActionStatusCode = (int) GetActionStatusCodeTypes.InvalidConfigurationIdFormat; errorRecord = GetErrorRecord("ConfigurationIdNotSpecified", null, ErrorCategory.InvalidArgument, PSinfrastructureStrings.ConfigurationIdNotSpecified); return MiResult.INVALID_PARAMETER; } } catch (CimException exception) { errorRecord = GetErrorRecord("MetaConfigConfigurationIdInvalid", exception, ErrorCategory.InvalidResult, PSinfrastructureStrings.MetaConfigConfigurationIdInvalid, _pluginModuleName); getActionStatusCode = (int)GetActionStatusCodeTypes.InvalidConfigurationIdInMetaConfig; return (MiResult)exception.StatusCode; } //Get PS Credential if (metaConfigInstance.CimInstanceProperties[ParamCredential] != null && metaConfigInstance.CimInstanceProperties[ParamCredential].Value != null) { string userName = null; string password = null; string domain = null; var cimCredentialInstance = metaConfigInstance.CimInstanceProperties[ParamCredential].Value as CimInstance; if (cimCredentialInstance != null) { if (cimCredentialInstance.CimInstanceProperties[ParamCredentialUserName] != null && cimCredentialInstance.CimInstanceProperties[ParamCredentialUserName].Value != null) { userName = cimCredentialInstance.CimInstanceProperties[ParamCredentialUserName].Value.ToString(); } if (cimCredentialInstance.CimInstanceProperties[ParamCredentialPassword] != null && cimCredentialInstance.CimInstanceProperties[ParamCredentialPassword].Value != null) { password = cimCredentialInstance.CimInstanceProperties[ParamCredentialPassword].Value.ToString(); } if (cimCredentialInstance.CimInstanceProperties[ParamCredentialDomain] != null && cimCredentialInstance.CimInstanceProperties[ParamCredentialDomain].Value != null) { domain = cimCredentialInstance.CimInstanceProperties[ParamCredentialDomain].Value.ToString(); } if (userName != null && password != null) { // Extract the password into a SecureString. var securePassword = new SecureString(); foreach (var t in password) { securePassword.AppendChar(t); } securePassword.MakeReadOnly(); if (!string.IsNullOrEmpty(domain)) { userName = Path.Combine(domain, userName); } pscredential = new PSCredential(userName, securePassword); } } } // Get cmdlet arguments as hashtable. try { if (metaConfigInstance.CimInstanceProperties[MetaConfigDownloadManagerCustomData] != null && metaConfigInstance.CimInstanceProperties[MetaConfigDownloadManagerCustomData].Value != null) { var cimInstances = metaConfigInstance.CimInstanceProperties[MetaConfigDownloadManagerCustomData].Value as CimInstance[]; if (cimInstances != null) { foreach (var inst in cimInstances) { arguments.Add(inst.CimInstanceProperties["Key"].Value.ToString(), inst.CimInstanceProperties["Value"].Value.ToString()); } } } } catch (CimException exception) { errorRecord = GetErrorRecord("MetaConfigCustomDataInvalid", exception, ErrorCategory.InvalidResult, PSinfrastructureStrings.MetaConfigCustomDataInvalid, _pluginModuleName); getActionStatusCode = (int)GetActionStatusCodeTypes.InvalidDownloadManagerCustomDataInMetaConfig; return (MiResult)exception.StatusCode; } getActionStatusCode = (int) GetActionStatusCodeTypes.Success; return MiResult.OK; } private static MiResult InitializePlugin(IntPtr metaConfigHandle, out ErrorRecord errorRecord) { errorRecord = null; if (PluginRunspaceInitialized == false) { MiResult statusCode = ImportDownloadManager(metaConfigHandle, out errorRecord); if (statusCode != MiResult.OK) { return statusCode; } } return MiResult.OK; } private static MiResult ImportDownloadManager(IntPtr metaConfigHandle, out ErrorRecord errorRecord) { string downloadManagerName; // Get Module Name MiResult statusCode = GetDownloadManagerModuleName(metaConfigHandle, out downloadManagerName, out errorRecord); if (statusCode != MiResult.OK) { return statusCode; } // if it is webDownloadManager, it is implemented inside PSDesiredStateConfiguration\WebDownloadManager. // if it is FileDownloadManager, it is implemented inside PSDesiredStateConfiguration\DownloadManager\DSCFileDownloadManager. string downloadManagerPath = GetDownloadManagerModuleLoadPath(downloadManagerName); try { using (System.Management.Automation.PowerShell powershell = System.Management.Automation.PowerShell.Create()) { powershell.Runspace = _pluginRunspace; // Load the module powershell.Streams.Error.Clear(); powershell.AddCommand("Import-Module") .AddParameter("Name", downloadManagerPath) .AddParameter("PassThru", true) .Invoke<PSModuleInfo>() .FirstOrDefault(); if (powershell.Streams.Error.Count > 0) { errorRecord = GetErrorRecord("ImportDownloadManagerException", powershell.Streams.Error[0].Exception, ErrorCategory.InvalidResult, PSinfrastructureStrings.ImportDownloadManagerException, _pluginModuleName); return MiResult.FAILED; } } } catch (Exception exception) { errorRecord = GetErrorRecord("ImportDownloadManagerException", exception, ErrorCategory.InvalidResult, PSinfrastructureStrings.ImportDownloadManagerException, _pluginModuleName); return MiResult.FAILED; } if (statusCode != MiResult.OK) { return statusCode; } _pluginModuleName = downloadManagerName; return MiResult.OK; } private static string GetDownloadManagerModuleLoadPath(string downloadManagerName) { string downloadManagerLoadPath; if (!DownloadManagerMap.TryGetValue(downloadManagerName, out downloadManagerLoadPath)) { downloadManagerLoadPath = downloadManagerName; } return downloadManagerLoadPath; } private static MiResult GetDownloadManagerModuleName(IntPtr metaConfigHandle, out string downloadManagerName, out ErrorRecord errorRecord) { downloadManagerName = null; errorRecord = null; var metaConfigInstance = new CimInstance(new InstanceHandle(metaConfigHandle, false), null); try { if (metaConfigInstance.CimInstanceProperties[MetaConfigDownloadManagerName] != null && metaConfigInstance.CimInstanceProperties[MetaConfigDownloadManagerName].Value != null) { downloadManagerName = metaConfigInstance.CimInstanceProperties[MetaConfigDownloadManagerName].Value.ToString(); } else { errorRecord = GetErrorRecord("MetaConfigDownloadManagerInvalid", ErrorCategory.InvalidResult, PSinfrastructureStrings.MetaConfigDownloadManagerInvalid); return MiResult.NOT_FOUND; } } catch (CimException exception) { errorRecord = GetErrorRecord("MetaConfigDownloadManagerInvalid", exception, ErrorCategory.InvalidResult, PSinfrastructureStrings.MetaConfigDownloadManagerInvalid); return (MiResult)exception.StatusCode; } return MiResult.OK; } #endregion PLUGIN_COMMONHELPERS #region PLUGIN_PUBLICMETHODS /// <summary> /// Install API used to install the modules as necessary from the mof file.. /// </summary> /// <param name="metaConfigHandle"></param> /// <param name="downloadLocation"></param> /// <param name="mofFileName"></param> /// <param name="allowModuleOverwrite"></param> /// <param name="errorInstanceHandle"></param> /// <param name="getActionStatusCode"></param> /// <returns>If InstallModules operation is successful, 0 is returned or else a status code indicating the error is returned.</returns> public static UInt32 GetDscModule(IntPtr metaConfigHandle, string downloadLocation, string mofFileName, bool allowModuleOverwrite, out IntPtr errorInstanceHandle, out UInt32 getActionStatusCode) { ErrorRecord errorRecord; errorInstanceHandle = IntPtr.Zero; MiResult statusCode = InstallModules(metaConfigHandle, downloadLocation, mofFileName, allowModuleOverwrite, out errorRecord, out getActionStatusCode); if (statusCode != MiResult.OK) { errorInstanceHandle = Utility.ConvertErrorRecordToMiInstance((uint)statusCode, errorRecord); return (uint)statusCode; } return (uint)MiResult.OK; } /// <summary> /// Get Api facilitates to execute Get-Configuration functionality on the provider mentioned in the meta configuration.. /// </summary> /// <param name="metaConfigHandle"></param> /// <param name="fileHash"></param> /// <param name="complianceStatus"></param> /// <param name="lastGetActionStatusCode"></param> /// <param name="errorInstanceHandle"></param> /// <param name="outputResult"></param> /// <param name="getActionStatusCode"></param> /// <returns>If GetDscAction operation is successful, 0 is returned or else a status code indicating the error is returned.</returns> public static UInt32 GetDscAction(IntPtr metaConfigHandle, string fileHash, bool complianceStatus, UInt32 lastGetActionStatusCode, out IntPtr errorInstanceHandle, out string outputResult, out UInt32 getActionStatusCode) { ErrorRecord errorRecord; errorInstanceHandle = IntPtr.Zero; MiResult statusCode = InitializePlugin(metaConfigHandle, out errorRecord); outputResult = null; if (statusCode != MiResult.OK) { getActionStatusCode = (int) GetActionStatusCodeTypes.DownloadManagerInitializationFailure; errorInstanceHandle = Utility.ConvertErrorRecordToMiInstance((uint)statusCode, errorRecord); return (uint)statusCode; } //Get parameters for Get-Status cmdlet PSCredential pscredential; var arguments = new Hashtable(); string configurationId; statusCode = GetMetaConfigParams(metaConfigHandle, out pscredential, arguments, out configurationId, out errorRecord, out getActionStatusCode); if (getActionStatusCode != (uint)GetActionStatusCodeTypes.Success) { lastGetActionStatusCode = getActionStatusCode; } if (statusCode != MiResult.OK) { errorInstanceHandle = Utility.ConvertErrorRecordToMiInstance((uint)statusCode, errorRecord); return (uint)statusCode; } System.Management.Automation.PowerShell powershell; Collection<PSObject> providerResult; //Create PowerShell object and invoke the command. try { powershell = System.Management.Automation.PowerShell.Create(); powershell.Runspace = _pluginRunspace; Collection<PSObject> argumentParameters = null; if (arguments.Count > 0) { argumentParameters = powershell.AddCommand("New-Object").AddParameter("Type", "psobject").AddParameter("Property", arguments).Invoke(); powershell.Commands.Clear(); } powershell.Commands.AddCommand(_pluginModuleName + "\\" + DownloadManagerGetAction); powershell.AddParameter(ParamConfigurationId, configurationId); powershell.AddParameter(ParamNotCompliant, !complianceStatus); powershell.AddParameter(ParamFileHash, fileHash); powershell.AddParameter(ParamStatusCode, lastGetActionStatusCode); if (pscredential != null) { powershell.AddParameter(ParamCredential, pscredential); } // Log to ETW Channel:Microsoft-Windows-DSC/Operational S_DscCoreR.EventWriteLCMPullGetActionAttempt(S_DscCoreR.JobGuidString, _pluginModuleName, configurationId, fileHash, complianceStatus); powershell.Streams.Error.Clear(); providerResult = arguments.Count > 0 ? powershell.Invoke(argumentParameters) : powershell.Invoke(); if (powershell.Streams.Error.Count > 0) { errorRecord = powershell.Streams.Error[0]; errorInstanceHandle = Utility.ConvertErrorRecordToMiInstance((uint)MiResult.FAILED, errorRecord); powershell.Dispose(); getActionStatusCode = (int) GetActionStatusCodeTypes.GetDscActionCommandFailure; return (UInt32)MiResult.FAILED; } } catch (Exception exception) { errorRecord = GetErrorRecord("GetActionException", exception, ErrorCategory.InvalidResult, PSinfrastructureStrings.GetActionException, _pluginModuleName); errorInstanceHandle = Utility.ConvertErrorRecordToMiInstance((uint)MiResult.FAILED, errorRecord); getActionStatusCode = (int)GetActionStatusCodeTypes.GetDscActionCommandFailure; return (UInt32)MiResult.FAILED; } statusCode = ValidateGetActionResult(powershell, providerResult, _pluginModuleName, out outputResult, out errorRecord, out getActionStatusCode); powershell.Dispose(); if (statusCode != MiResult.OK) { errorInstanceHandle = Utility.ConvertErrorRecordToMiInstance((uint)statusCode, errorRecord); return (uint)statusCode; } // Log to ETW Channel:Microsoft-Windows-DSC/Operational S_DscCoreR.EventWriteLCMPullGetActionSuccess(S_DscCoreR.JobGuidString, outputResult, _pluginModuleName); return (uint)MiResult.OK; } /// <summary> /// Get Api facilitates to execute Get-Configuration functionality on the provider mentioned in the meta configuration.. /// </summary> /// <param name="metaConfigHandle"></param> /// <param name="errorInstanceHandle"></param> /// <param name="mofFileName"></param> /// <param name="outputResult"></param> /// <param name="getActionStatusCode"></param> /// <returns>If GetDscDocument operation is successful, 0 is returned or else a status code indicating the error is returned.</returns> public static UInt32 GetDscDocument(IntPtr metaConfigHandle, out IntPtr errorInstanceHandle, out string mofFileName, out string outputResult, out UInt32 getActionStatusCode) { ErrorRecord errorRecord; errorInstanceHandle = IntPtr.Zero; string destinationPath = Path.GetTempPath() + "\\" + DateTime.Now.Ticks; MiResult statusCode = InitializePlugin(metaConfigHandle, out errorRecord); outputResult = null; mofFileName = null; if (statusCode != MiResult.OK) { getActionStatusCode = (int)GetActionStatusCodeTypes.DownloadManagerInitializationFailure; errorInstanceHandle = Utility.ConvertErrorRecordToMiInstance((uint)statusCode, errorRecord); return (uint)statusCode; } if (Directory.Exists(destinationPath)) { errorRecord = GetErrorRecord("DownloadLocationDirectoryExists", ErrorCategory.InvalidResult, PSinfrastructureStrings.DownloadLocationDirectoryExists); getActionStatusCode = (int)GetActionStatusCodeTypes.DownloadManagerInitializationFailure; errorInstanceHandle = Utility.ConvertErrorRecordToMiInstance((UInt32)MiResult.ALREADY_EXISTS, errorRecord); return (uint)MiResult.ALREADY_EXISTS; } Directory.CreateDirectory(destinationPath); //Get parameters for Get-Configuration cmdlet PSCredential pscredential; var arguments = new Hashtable(StringComparer.OrdinalIgnoreCase); string configurationId; statusCode = GetMetaConfigParams(metaConfigHandle, out pscredential, arguments, out configurationId, out errorRecord, out getActionStatusCode); if (statusCode != MiResult.OK) { errorInstanceHandle = Utility.ConvertErrorRecordToMiInstance((uint)statusCode, errorRecord); return (uint)statusCode; } bool allowModuleOverwrite; statusCode = GetAllowModuleOverwriteFromMetaConfig(metaConfigHandle, out allowModuleOverwrite, out errorRecord, out getActionStatusCode); if (statusCode != MiResult.OK) { errorInstanceHandle = Utility.ConvertErrorRecordToMiInstance((uint)statusCode, errorRecord); return (uint)statusCode; } System.Management.Automation.PowerShell powershell; Collection<PSObject> providerResult; //Create PowerShell object and invoke the command. try { powershell = System.Management.Automation.PowerShell.Create(); powershell.Runspace = _pluginRunspace; Collection<PSObject> argumentParameters = null; if (arguments.Count > 0) { argumentParameters = powershell.AddCommand("New-Object").AddParameter("Type", "psobject").AddParameter("Property", arguments).Invoke(); powershell.Commands.Clear(); } powershell.Commands.AddCommand(_pluginModuleName + "\\" + DownloadManagerGetConfiguration); powershell.AddParameter(ParamConfigurationId, configurationId); powershell.AddParameter(ParamDestinationPath, destinationPath); if (pscredential != null) { powershell.AddParameter(ParamCredential, pscredential); } // Log to ETW Channel:Microsoft-Windows-DSC/Operational S_DscCoreR.EventWriteLCMPullGetConfigAttempt(S_DscCoreR.JobGuidString, _pluginModuleName, configurationId); powershell.Streams.Error.Clear(); providerResult = arguments.Count > 0 ? powershell.Invoke(argumentParameters) : powershell.Invoke(); if (powershell.Streams.Error.Count > 0) { Directory.Delete(destinationPath, true); errorRecord = powershell.Streams.Error[0]; errorInstanceHandle = Utility.ConvertErrorRecordToMiInstance((uint)MiResult.FAILED, errorRecord); powershell.Dispose(); if( string.Compare(errorRecord.FullyQualifiedErrorId, "WebDownloadManagerUnknownChecksumAlgorithm", StringComparison.OrdinalIgnoreCase) == 0 ) { getActionStatusCode = (int)GetActionStatusCodeTypes.InvalidChecksumAlgorithm; } else { getActionStatusCode = (int)GetActionStatusCodeTypes.GetConfigurationCommandFailure; } return (uint)MiResult.FAILED; } } catch (Exception exception) { Directory.Delete(destinationPath, true); errorRecord = GetErrorRecord("GetConfigurationException", exception, ErrorCategory.InvalidResult, PSinfrastructureStrings.GetConfigurationException, _pluginModuleName); getActionStatusCode = (int)GetActionStatusCodeTypes.GetConfigurationCommandFailure; errorInstanceHandle = Utility.ConvertErrorRecordToMiInstance((uint)MiResult.FAILED, errorRecord); return (uint)MiResult.FAILED; } statusCode = ValidateGetConfigurationResult(powershell, providerResult, _pluginModuleName, out outputResult, out errorRecord, out getActionStatusCode); S_DscCoreR.EventWriteLCMPullConfigurationChecksumValidationResult(S_DscCoreR.JobGuidString, configurationId, (uint)statusCode); powershell.Dispose(); if (statusCode != MiResult.OK) { Directory.Delete(destinationPath, true); errorInstanceHandle = Utility.ConvertErrorRecordToMiInstance((uint)statusCode, errorRecord); return (uint)statusCode; } // if it is ok, we will validate checksum here. if (string.Compare(outputResult, StatusOk, StringComparison.OrdinalIgnoreCase) == 0) { statusCode = ValidateConfigurationChecksum(destinationPath, out mofFileName, out errorRecord, out getActionStatusCode); } if (statusCode != MiResult.OK) { Directory.Delete(destinationPath, true); errorInstanceHandle = Utility.ConvertErrorRecordToMiInstance((uint)statusCode, errorRecord); return (uint)statusCode; } // If checksum validation passes, we go on to pull the module statusCode = InstallModules(metaConfigHandle, destinationPath, mofFileName, allowModuleOverwrite, out errorRecord, out getActionStatusCode); if (statusCode != MiResult.OK) { Directory.Delete(destinationPath, true); errorInstanceHandle = Utility.ConvertErrorRecordToMiInstance((uint)statusCode, errorRecord); return (uint)statusCode; } // Log to ETW Channel:Microsoft-Windows-DSC/Operational S_DscCoreR.EventWriteLCMPullGetConfigSuccess(S_DscCoreR.JobGuidString, _pluginModuleName); return (uint)MiResult.OK; } /// <summary> /// This API installs a private certificate (.pfx). /// </summary> /// <param name="certificatePath"></param> /// <param name="password"></param> /// <param name="certificateThumbprint"></param> /// <param name="errorInstanceHandle"></param> /// <returns>If successfully installed return 0 and certificateThumbprint or else a status code indicating the error is returned.</returns> public static uint InstallCertificate(string certificatePath, SecureString password, out string certificateThumbprint, out IntPtr errorInstanceHandle) { certificateThumbprint = null; errorInstanceHandle = IntPtr.Zero; X509Store store = null; try { var cert = new X509Certificate2(certificatePath, password); // open certificate store store = new X509Store(StoreName.My, StoreLocation.LocalMachine); store.Open(OpenFlags.ReadWrite); // install certificate. if (!store.Certificates.Contains(cert)) { store.Add(cert); } certificateThumbprint = cert.Thumbprint; } catch (Exception ex) { var errorRecord = GetErrorRecord("InstallCertificateException", ex, ErrorCategory.InvalidType, PSinfrastructureStrings.InstallCertificateException, certificatePath); errorInstanceHandle = Utility.ConvertErrorRecordToMiInstance((uint)MiResult.FAILED, errorRecord); return (uint)MiResult.FAILED; } finally { if (store != null) { store.Close(); } } return (uint)MiResult.OK; } #endregion PLUGIN_PUBLICMETHODS } }