private static string ReadPatchRegistryAttribute( XPathNodeIterator iterator, string patchName, string attributeName, bool throwIfError) { // Read registry attribute string attributeValue = null; XPathNavigator tempIterator = iterator.Current.SelectSingleNode(attributeName); if (tempIterator != null) { attributeValue = tempIterator.Value.Trim(); } if (throwIfError && string.IsNullOrEmpty(attributeValue)) { LogUtils.WriteTrace(DateTime.UtcNow, string.Format( CultureInfo.InvariantCulture, "{0} attribute not found or is empty in element Registry of Patch name {1}", attributeName, patchName)); throw new InvalidOperationException( string.Format( CultureInfo.InvariantCulture, "{0} attribute not found or is empty in element Registry of Patch name {1}", attributeName, patchName)); } return(attributeValue); }
/// <summary> /// Install patches /// </summary> public void InstallPatches() { string patchConfigFilePath = Path.Combine(Path.GetPathRoot(Assembly.GetExecutingAssembly().Location), "plugins", "patchplugin", PatchConfigurationFileName); PatchConfiguration patchConfiguration = new PatchConfiguration(); patchConfiguration.Load(patchConfigFilePath); if (patchConfiguration.PatchMap == null || patchConfiguration.PatchMap.Count == 0) { LogUtils.WriteTrace(DateTime.UtcNow, "PatchUtil: no patches to install."); return; } // First check if all patches have been installed IList <PatchConfiguration.Patch> uninstalledPatches = GetUninstalledPatches(patchConfiguration); if (uninstalledPatches.Count == 0) { LogUtils.WriteTrace(DateTime.UtcNow, "PatchUtil: all patches have been installed."); return; } try { InstallPatches(uninstalledPatches, patchConfiguration.OverallTimeoutSeconds * 1000); // Wait timeout and return DateTime startTime = DateTime.UtcNow; while (DateTime.UtcNow < startTime + TimeSpan.FromSeconds(patchConfiguration.OverallTimeoutSeconds)) { uninstalledPatches = GetUninstalledPatches(patchConfiguration); if (uninstalledPatches.Count == 0) { LogUtils.WriteTrace(DateTime.UtcNow, "PatchUtil: all patches have been installed. Exit and reboot without waiting until timeout."); return; } LogUtils.WriteTrace(DateTime.UtcNow, "PatchUtil: sleep 1 minutes before checking patch installation again."); Thread.Sleep(TimeSpan.FromMinutes(1)); } } catch (Exception e) { // Log the exception LogUtils.WriteTrace(DateTime.UtcNow, "PatchUtil: Patch installation encountered an Exception: " + e); if (!_rebootRequired) { // We haven't start install patches, so throw and the process will be restarted (without reboot) throw; } // Eat the exception and the VM is going to be rebooted. LogUtils.WriteTrace(DateTime.UtcNow, "PatchUtil: At least one patch is installed, waiting to be rebooted."); } }
private void InstallPatches(IList <PatchConfiguration.Patch> patchesToInstall, int timeoutMs) { string patchInstallFolder = GetPatchInstallationFolder(); LogUtils.WriteTrace(DateTime.UtcNow, string.Format("PatchUtil: set patch installation folder: '{0}'", patchInstallFolder)); foreach (PatchConfiguration.Patch patch in patchesToInstall) { StringBuilder outString = new StringBuilder(); StringBuilder errString = new StringBuilder(); LogUtils.WriteTrace(DateTime.UtcNow, string.Format("PatchUtil: install patch '{0}'", patch.Name)); string command = patch.CommandLine.TrimStart(); LogUtils.WriteTrace(DateTime.UtcNow, string.Format("PatchUtil: run commandline '{0}'", command)); // Prepare the log folder if (!string.IsNullOrEmpty(patch.LogFolderPath)) { string expandedPath = Environment.ExpandEnvironmentVariables(patch.LogFolderPath); LogUtils.WriteTrace(DateTime.UtcNow, "PatchUtil: patch log folder is at: " + expandedPath); if (!Directory.Exists(expandedPath)) { Directory.CreateDirectory(expandedPath); LogUtils.WriteTrace(DateTime.UtcNow, "PatchUtil: patch log folder is created."); } else { LogUtils.WriteTrace(DateTime.UtcNow, "PatchUtil: patch log folder exist."); } } // Start to run the command to install patch. Set _rebootRequired to be true _rebootRequired = true; int exitCode = ScriptingHelper.Execute( command, new StringWriter(outString, CultureInfo.InvariantCulture), new StringWriter(errString, CultureInfo.InvariantCulture), timeoutMs, patchInstallFolder); LogUtils.WriteTrace(DateTime.UtcNow, "PatchUtil: run commandline exit with code: " + exitCode); if (!string.IsNullOrEmpty(outString.ToString())) { LogUtils.WriteTrace(DateTime.UtcNow, "PatchUtil: run commandline output: " + outString); } if (!string.IsNullOrEmpty(errString.ToString())) { LogUtils.WriteTrace(DateTime.UtcNow, "PatchUtil: run commandline error: " + errString); } // Do not check the exit code because we don't know what is expected } }
private static void Halt() { while (true) { Thread.Sleep(TimeSpan.FromMinutes(1)); LogUtils.WriteTrace(DateTime.UtcNow, "Waiting for reboot to occur..."); } // ReSharper disable FunctionNeverReturns }
public static int Execute(string binaryPath, string parameters, TextWriter outStream, TextWriter errStream, StreamReader inputStream, int timeoutInMS, string workingDirectory) { ScriptExecution se = new ScriptExecution(outStream, errStream); int returnCode = -1; LogUtils.WriteTrace(DateTime.UtcNow, string.Format("Binary path : {0}", binaryPath)); LogUtils.WriteTrace(DateTime.UtcNow, string.Format("Binary parameters : {0}", parameters)); LogUtils.WriteTrace(DateTime.UtcNow, string.Format("Working directory : {0}", workingDirectory)); using (Process proc = CreateProcess(binaryPath, parameters, outStream, errStream, inputStream, workingDirectory)) { if (-1 != timeoutInMS) { if (!proc.WaitForExit(timeoutInMS)) { proc.ErrorDataReceived -= se.ErrorDataHandler; proc.OutputDataReceived -= se.OutputDataHandler; // The ScriptingHelper before throwing TimeoutException exception should // disable the output/input handlers of the ScriptExecution class as there // can be already scheduled output write events by the Process object in // the ThreadPool that will be executed asynchronously after the exception // is thrown. // As throwing the exception usually causes the output/input streams in // upper layer to be disposed the scheduled output write events by the // Process in the ThreadPool that are executed after the disposing would // throw in the background thread the ObjectDisposed exception that would cause the process to crash. // Therefore disabling here the handlers explicitly before throwing. se.DisableDataHandlers = true; LogUtils.WriteTrace(DateTime.UtcNow, string.Format(CultureInfo.InvariantCulture, "process {0} {1} timed out!", binaryPath, parameters)); throw new TimeoutException(string.Format(CultureInfo.InvariantCulture, "process {0} {1} timed out!", binaryPath, parameters)); } // Call to WaitForExit(Int32) returned >>>TRUE<<<, so we've exited without a timeout. But according to documentation, we're not guaranteed to // have completed processing of asynchronous events when redirecting stdout. // // Workaround is to call WaitForExit(void) a second time which won't return until async events finish processing. Waiting is important // so we can safely call StringBuilder.ToString() which is not thread safe, and we don't want event handlers to call StringBuilder.AppendLine // from parallel threads. // COMMENTED OUT: This blocks on opened handles if the child process starts another child process and then exists. // proc.WaitForExit(); proc.ErrorDataReceived -= se.ErrorDataHandler; proc.OutputDataReceived -= se.OutputDataHandler; se.DisableDataHandlers = true; } else { proc.WaitForExit(); } returnCode = proc.ExitCode; } return(returnCode); }
/// <summary> /// This function reboots the local machine using the Win32_OperatingSystem /// WMI object from the local WMI instance /// </summary> internal static void Reboot() { try { string computerName = Environment.MachineName; // computer name or IP address ConnectionOptions options = new ConnectionOptions(); options.EnablePrivileges = true; // To connect to the remote computer using a different account, specify these values: // options.Username = "******"; // [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Comment with example")] // options.Password = "******"; // options.Authority = "ntlmdomain:DOMAIN"; ManagementScope scope = new ManagementScope("\\\\" + computerName + "\\root\\CIMV2", options); scope.Connect(); SelectQuery query = new SelectQuery("Win32_OperatingSystem"); ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query); LogUtils.WriteTrace(DateTime.UtcNow, "Requesting machine reboot"); // DEV NOTE : We are missing adding UploadData to Xstore here as I did not want that to interrupt this work // UploadData(); foreach (ManagementObject os in searcher.Get()) { // Obtain in-parameters for the method ManagementBaseObject inParams = os.GetMethodParameters("Win32Shutdown"); // Add the input parameters for Forced Reboot inParams["Flags"] = 6; // Execute the method and obtain the return values. os.InvokeMethod("Win32Shutdown", inParams, null); } // Halt execution permanently; machine should reboot Halt(); } catch (ManagementException err) { LogUtils.WriteTrace(DateTime.UtcNow, string.Format("An error occurred while trying to execute the WMI method: {0} ", err)); throw; } catch (UnauthorizedAccessException unauthorizedErr) { LogUtils.WriteTrace(DateTime.UtcNow, string.Format("Connection error (user name or password might be incorrect): {0} ", unauthorizedErr)); throw; } }
public void Load(string configFilePath) { if (string.IsNullOrEmpty(configFilePath)) { LogUtils.WriteTrace(DateTime.UtcNow, "configFilePath cannot be null or empty"); throw new ArgumentException("Patch configuration file path cannot be null or empty.", "configFilePath"); } if (!File.Exists(configFilePath)) { LogUtils.WriteTrace(DateTime.UtcNow, string.Format("Cannot find patch configuration file: {0}", configFilePath)); throw new InvalidOperationException("Cannot find patch configuration file: " + configFilePath); } LoadConfiguration(configFilePath); }
private static int Main(string[] args) { bool success = false; LogUtils.WriteTrace(DateTime.UtcNow, "patch plugin started"); // Run patches try { success = RunTasks(); } catch (Exception ex) { LogUtils.WriteTrace(DateTime.UtcNow, "Sterling patch operation ran into Exception: " + ex); } return(success ? 0 : 1); }
private void LoadConfiguration(string configFilePath) { XPathNavigator navigator = GetNavigator(configFilePath); string overallTimeoutString = navigator.SelectSingleNode(OverallTimeoutValueXPath).Value; if (string.IsNullOrEmpty(overallTimeoutString) || (!int.TryParse(overallTimeoutString, out _overallTimeoutSeconds))) { LogUtils.WriteTrace(DateTime.UtcNow, "OverallTimeout value is not defined or is not a valid integer value. OverallTimeout = " + (string.IsNullOrEmpty(overallTimeoutString) ? "null" : overallTimeoutString)); throw new InvalidOperationException( "OverallTimeout value is not defined or is not a valid integer value. OverallTimeout = " + (string.IsNullOrEmpty(overallTimeoutString) ? "null" : overallTimeoutString)); } if (_overallTimeoutSeconds < 0) { LogUtils.WriteTrace(DateTime.UtcNow, "OverallTimeout value cannot be less than 0. OverallTimeout = " + overallTimeoutString); throw new InvalidOperationException( "OverallTimeout value cannot be less than 0. OverallTimeout = " + overallTimeoutString); } // Loop through all patches XPathNodeIterator nodeIterator = navigator.Select(PatchXPath); while (nodeIterator.MoveNext()) { Patch patch = new Patch(); // Read name if (nodeIterator.Current.SelectSingleNode("@name") == null) { LogUtils.WriteTrace(DateTime.UtcNow, "name attribute needed for Patch element"); throw new InvalidOperationException("name attribute needed for Patch element"); } patch.Name = nodeIterator.Current.SelectSingleNode("@name").Value.Trim(); if (string.IsNullOrEmpty(patch.Name)) { LogUtils.WriteTrace(DateTime.UtcNow, "name attribute can't be empty for Patch element"); throw new InvalidOperationException("name attribute can't be empty for Patch element"); } if (_patchMap.ContainsKey(patch.Name)) { LogUtils.WriteTrace(DateTime.UtcNow, string.Format(CultureInfo.InvariantCulture, "Patch '{0}' is defined in multiple places.", patch.Name)); throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Patch '{0}' is defined in multiple places.", patch.Name)); } // Read OS versions the patch applies to and check whether it is applicable to current OS version if (nodeIterator.Current.SelectSingleNode("@appliesToOsVersion") != null) { string osVersionAppliesTo = string.Empty; try { bool invalidVersionFormat = false; osVersionAppliesTo = nodeIterator.Current.SelectSingleNode("@appliesToOsVersion").Value.Trim(); Version currentOsVersion = Environment.OSVersion.Version; char[] versionDelimiter = new char[] { '.' }; string[] versionComponents = osVersionAppliesTo.Split(versionDelimiter); string[] currentOsVersionComponents = currentOsVersion.ToString().Split(versionDelimiter); // OS version the patch applies to should have four components: major.minor.build.revision if (versionComponents.Length != 4) { invalidVersionFormat = true; } else { // Each version component should be an integer or a wildcard character '*' foreach (string t in versionComponents) { int versionComponent; if (t.Equals("*", StringComparison.OrdinalIgnoreCase) || int.TryParse(t, out versionComponent)) { continue; } invalidVersionFormat = true; break; } } if (invalidVersionFormat) { LogUtils.WriteTrace(DateTime.UtcNow, string.Format( @"PathUtil: Invalid version format for OS version patch {0} applies to : {1}; the patch will be installed", patch.Name, osVersionAppliesTo)); } else { bool skipPatch = false; for (int i = 0; i < Math.Min(versionComponents.Length, currentOsVersionComponents.Length); i++) { if (!versionComponents[i].Equals("*", StringComparison.OrdinalIgnoreCase) && !versionComponents[i].Equals(currentOsVersionComponents[i], StringComparison.OrdinalIgnoreCase)) { skipPatch = true; } } // If the patch is not applicable to current OS version, skip it if (skipPatch) { LogUtils.WriteTrace(DateTime.UtcNow, string.Format( @"PatchUtil: Patch {0} will be skipped since it is only applicable to OS version {1} while current OS version is {2}", patch.Name, osVersionAppliesTo, currentOsVersion)); continue; } } } catch (Exception e) { LogUtils.WriteTrace(DateTime.UtcNow, string.Format( @"PathUtil: Exception occurred when checking whether patch {0} (applicable to OS version {1}) is applicable to current OS version, skip version checking and continue installing : {2}", patch.Name, osVersionAppliesTo, e)); } } // Read command line. if (nodeIterator.Current.SelectSingleNode(CommandLineXPath) == null) { LogUtils.WriteTrace(DateTime.UtcNow, string.Format(CultureInfo.InvariantCulture, "Command value attribute needed for Patch '{0}'.", patch.Name)); throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Command value attribute needed for Patch '{0}'.", patch.Name)); } patch.CommandLine = nodeIterator.Current.SelectSingleNode(CommandLineXPath).Value.Trim(); if (string.IsNullOrEmpty(patch.CommandLine)) { LogUtils.WriteTrace(DateTime.UtcNow, string.Format(CultureInfo.InvariantCulture, "Command value can't be empty for Patch '{0}'.", patch.Name)); throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Command value can't be empty for Patch '{0}'.", patch.Name)); } // Read log path (optional element). patch.LogFolderPath = null; if (nodeIterator.Current.SelectSingleNode(LogFolderPathXPath) != null) { string logFolderPath = nodeIterator.Current.SelectSingleNode(LogFolderPathXPath).Value.Trim(); if (!string.IsNullOrEmpty(logFolderPath)) { patch.LogFolderPath = logFolderPath; } } // Read registry settings List <PatchRegistry> registrySettings = new List <PatchRegistry>(); XPathNodeIterator iterator = nodeIterator.Current.Select(PatchRegistryXPath); while (iterator.MoveNext()) { string keyName = ReadPatchRegistryAttribute(iterator, patch.Name, RegistryKeyNameAttribute, true); string valueName = ReadPatchRegistryAttribute(iterator, patch.Name, RegistryValueNameAttribute, true); string expectedValue = ReadPatchRegistryAttribute(iterator, patch.Name, RegistryExpectedValueAttribute, false); PatchRegistry registrySetting = new PatchRegistry { RegistryKeyName = keyName, RegistryValueName = valueName, ExpectedValue = expectedValue, }; registrySettings.Add(registrySetting); } // We need at least one registry setting to confirm the patch is installed. if (!registrySettings.Any()) { LogUtils.WriteTrace(DateTime.UtcNow, string.Format(CultureInfo.InvariantCulture, "There is no registry setting found for Patch '{0}'", patch.Name)); throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "There is no registry setting found for Patch '{0}'", patch.Name)); } patch.RegistrySettings = registrySettings; _patchMap.Add(patch.Name, patch); } }
/// <summary> /// Get uninstalled patches list /// </summary> /// <param name="patchConfiguration"></param> /// <returns></returns> private static IList <PatchConfiguration.Patch> GetUninstalledPatches(PatchConfiguration patchConfiguration) { List <PatchConfiguration.Patch> uninstalledPatches = new List <PatchConfiguration.Patch>(); foreach (PatchConfiguration.Patch patch in patchConfiguration.PatchMap.Values) { LogUtils.WriteTrace(DateTime.UtcNow, string.Format("PatchUtil: check registry setting for patch '{0}'", patch.Name)); if (patch.RegistrySettings == null) { continue; } foreach (PatchConfiguration.PatchRegistry reg in patch.RegistrySettings) { LogUtils.WriteTrace(DateTime.UtcNow, string.Format(@"PatchUtil: read registry value '{0}\{1}' for patch '{2}'", reg.RegistryKeyName, reg.RegistryValueName, patch.Name)); object value = Registry.GetValue(reg.RegistryKeyName, reg.RegistryValueName, null); if (value == null) { LogUtils.WriteTrace(DateTime.UtcNow, string.Format( @"PatchUtil: registry value '{0}\{1}' does not exist or has null value. Need to install patch '{2}'", reg.RegistryKeyName, reg.RegistryValueName, patch.Name)); // PatchConfiguration doesn't allow the same patch be defined multiple times. No need to check it here again. uninstalledPatches.Add(patch); // Go to next patch break; } // Compare registry value. We only support REG_DWORD and REG_SZ for now Type valueType = value.GetType(); if (valueType == typeof(string)) { string stringValue = value as string; LogUtils.WriteTrace(DateTime.UtcNow, string.Format( @"PatchUtil: registry value '{0}\{1}' has string value: '{2}'", reg.RegistryKeyName, reg.RegistryValueName, stringValue)); // If reg.ExpectedValue is null, we only validate there is a reg value there. if (reg.ExpectedValue == null) { LogUtils.WriteTrace(DateTime.UtcNow, @"PatchUtil: skip validation because the ExpectedValue is null"); } else if (!string.Equals(stringValue, reg.ExpectedValue, StringComparison.OrdinalIgnoreCase)) { LogUtils.WriteTrace(DateTime.UtcNow, string.Format( @"PatchUtil: registry value '{0}\{1}' does not match the expected value: '{2}'. Need to install patch '{3}'", reg.RegistryKeyName, reg.RegistryValueName, reg.ExpectedValue, patch.Name)); // Add to the list uninstalledPatches.Add(patch); // Go to next patch break; } } else if (valueType == typeof(int)) { int intValue = (int)value; LogUtils.WriteTrace(DateTime.UtcNow, string.Format(@"PatchUtil: registry value '{0}\{1}' has integer value: '{2}'", reg.RegistryKeyName, reg.RegistryValueName, intValue)); int expectedValue; if (reg.ExpectedValue == null) { LogUtils.WriteTrace(DateTime.UtcNow, @"PatchUtil: skip validation because the ExpectedValue is null"); } else if ((!int.TryParse(reg.ExpectedValue, out expectedValue)) || (expectedValue != intValue)) { LogUtils.WriteTrace(DateTime.UtcNow, string.Format( @"PatchUtil: registry value '{0}\{1}' does not match the expected value: '{2}'. Need to install patch '{3}'", reg.RegistryKeyName, reg.RegistryValueName, reg.ExpectedValue, patch.Name)); // Add to the list uninstalledPatches.Add(patch); // Go to next patch break; } } else { throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "PatchUtil: Registry value type '{0}' is not supported.", valueType)); } } } return(uninstalledPatches); }