// Copyright (c) 2011, Outercurve Foundation. // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // - Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // - Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // - Neither the name of the Outercurve Foundation nor the names of its // contributors may be used to endorse or promote products derived from this // software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON // ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using System; using System.Configuration; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.ServiceProcess; using System.Management; using System.Timers; using System.Text; using Microsoft.Win32; namespace WebsitePanel.VmConfig { public partial class VmConfigService : ServiceBase { internal const string RegistryInputKey = "SOFTWARE\\Microsoft\\Virtual Machine\\External"; internal const string RegistryOutputKey = "SOFTWARE\\Microsoft\\Virtual Machine\\Guest"; internal const string TaskPrefix = "WSP-"; internal const string CurrentTaskName = "WSP-CurrentTask"; internal const string RAM_SUMMARY_KEY = "VM-RAM-Summary"; internal const string HDD_SUMMARY_KEY = "VM-HDD-Summary"; private Dictionary provisioningModules; private Timer idleTimer; private Timer pollTimer; private Timer summaryTimer; private bool rebootRequired = false; private System.Threading.Thread mainThread; public VmConfigService() { InitializeComponent(); } protected override void OnStart(string[] args) { //start main procedure in separate thread mainThread = new System.Threading.Thread(new System.Threading.ThreadStart(Start)); mainThread.Start(); } private void Start() { //log ServiceLog.WriteApplicationStart(); // delay (for sync with KVP exchange service) DelayOnStart(); //init InitializeProvisioningModules(); InitializeTimers(); // start timer StartSummaryTimer(); //run tasks ProcessTasks(); } protected override void OnStop() { if (this.mainThread.IsAlive) { this.mainThread.Abort(); } this.mainThread.Join(); ServiceLog.WriteApplicationStop(); } private void DelayOnStart() { int startupDelay = 0; if (Int32.TryParse(ConfigurationManager.AppSettings["Service.StartupDelay"], out startupDelay) && startupDelay > 0) { ServiceLog.WriteStart("Delay on service start-up"); System.Threading.Thread.Sleep(startupDelay); ServiceLog.WriteEnd("Delay on service start-up"); } } private void DeleteOldResults() { // get the list of input tasks string[] strTasks = RegistryUtils.GetRegistryKeyValueNames(RegistryInputKey); List tasks = new List(); foreach (string strTask in strTasks) { if (!string.IsNullOrEmpty(strTask) && strTask.StartsWith(TaskPrefix) && strTask != CurrentTaskName) { //save only WebsitePanel tasks tasks.Add(strTask); } } // get the list of task results int deletedResults = 0; string[] strResults = RegistryUtils.GetRegistryKeyValueNames(RegistryOutputKey); foreach (string strResult in strResults) { if (!string.IsNullOrEmpty(strResult) && strResult.StartsWith(TaskPrefix) && strResult != CurrentTaskName) { // check if task result exists in the input tasks if (!tasks.Contains(strResult)) { DeleteRegistryKeyValue(RegistryOutputKey, strResult); ServiceLog.WriteInfo(string.Format("Deleted activity result: {0}", strResult)); deletedResults++; } } } if(deletedResults > 0) ServiceLog.WriteEnd(string.Format("{0} result(s) deleted", deletedResults)); } private void InitializeProvisioningModules() { ServiceLog.WriteStart("Loading provisioning modules..."); provisioningModules = new Dictionary(); Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); ModuleSettingsSection section = config.Sections["moduleSettings"] as ModuleSettingsSection; if ( section != null) { foreach (string key in section.Modules.AllKeys) { provisioningModules.Add(key, section.Modules[key].Value); } } else ServiceLog.WriteError("Modules configuration section not found"); ServiceLog.WriteEnd(string.Format("{0} module(s) loaded", provisioningModules.Count)); } private void InitializeTimers() { // idle timer idleTimer = new Timer(); idleTimer.AutoReset = false; double idleInterval; if (!Double.TryParse(ConfigurationManager.AppSettings["Service.ExitIdleInterval"], out idleInterval)) { ServiceLog.WriteError("Invalid configuration parameter: Service.ExitIdleInterval"); idleInterval = 600000; } idleTimer.Interval = idleInterval; idleTimer.Enabled = false; idleTimer.Elapsed += new ElapsedEventHandler(OnIdleTimerElapsed); // poll timer pollTimer = new Timer(); pollTimer.AutoReset = false; double pollInterval; if (!Double.TryParse(ConfigurationManager.AppSettings["Service.RegistryPollInterval"], out pollInterval)) { ServiceLog.WriteError("Invalid configuration parameter: Service.RegistryPollInterval"); pollInterval = 60000; } pollTimer.Interval = pollInterval; pollTimer.Enabled = false; pollTimer.Elapsed += new ElapsedEventHandler(OnPollTimerElapsed); // system symmary timer summaryTimer = new Timer(); double summaryInterval; if (!Double.TryParse(ConfigurationManager.AppSettings["Service.SystemSummaryInterval"], out summaryInterval)) { ServiceLog.WriteError("Invalid configuration parameter: Service.SystemSummaryInterval"); summaryInterval = 15000; } summaryTimer.Interval = summaryInterval; summaryTimer.Enabled = false; summaryTimer.Elapsed += new ElapsedEventHandler(OnSummaryTimerElapsed); } private void OnIdleTimerElapsed(object sender, ElapsedEventArgs e) { base.Stop(); } private void OnPollTimerElapsed(object sender, ElapsedEventArgs e) { ProcessTasks(); } private void OnSummaryTimerElapsed(object sender, ElapsedEventArgs e) { GetSystemSummary(); } private void GetSystemSummary() { const UInt64 Size1KB = 0x400; const UInt64 Size1GB = 0x40000000; // check free/total RAM WmiUtils wmi = new WmiUtils("root\\cimv2"); ManagementObjectCollection objOses = wmi.ExecuteQuery("SELECT * FROM Win32_OperatingSystem"); foreach (ManagementObject objOs in objOses) { UInt64 freeRam = Convert.ToUInt64(objOs["FreePhysicalMemory"]) / Size1KB; UInt64 totalRam = Convert.ToUInt64(objOs["TotalVisibleMemorySize"]) / Size1KB; // write to the registry RegistryUtils.SetRegistryKeyStringValue(RegistryOutputKey, RAM_SUMMARY_KEY, String.Format("{0}:{1}", freeRam, totalRam)); objOs.Dispose(); } // check local HDD logical drives ManagementObjectCollection objDisks = wmi.ExecuteQuery("SELECT * FROM Win32_LogicalDisk WHERE DriveType = 3"); StringBuilder sb = new StringBuilder(); bool first = true; foreach (ManagementObject objDisk in objDisks) { if(!first) sb.Append(";"); sb.Append(objDisk["DeviceID"]); sb.Append(Convert.ToInt32(Convert.ToDouble(objDisk["FreeSpace"]) / (double)Size1GB)).Append(":"); sb.Append(Convert.ToInt32(Convert.ToDouble(objDisk["Size"]) / (double)Size1GB)); first = false; objDisk.Dispose(); } // write HDD info RegistryUtils.SetRegistryKeyStringValue(RegistryOutputKey, HDD_SUMMARY_KEY, sb.ToString()); // dispose resources objOses.Dispose(); objDisks.Dispose(); } private void ProcessTasks() { // delete old results DeleteOldResults(); //process all tasks while (true) { //load completed tasks results string[] strResults = RegistryUtils.GetRegistryKeyValueNames(RegistryOutputKey); List results = new List(); foreach (string strResult in strResults) { if (!string.IsNullOrEmpty(strResult) && strResult.StartsWith(TaskPrefix) && strResult != CurrentTaskName) { //save only WebsitePanel tasks results.Add(strResult); } } // sorted list of tasks - will be sorted by the TaskID (time in ticks) SortedList tasks = new SortedList(); //load task definitions from input registry key string[] strTasks = RegistryUtils.GetRegistryKeyValueNames(RegistryInputKey); foreach (string strTask in strTasks) { if (results.Contains(strTask)) continue; // skip completed tasks if (!string.IsNullOrEmpty(strTask) && strTask.StartsWith(TaskPrefix)) { // extract Task ID parameter int idx = strTask.LastIndexOf('-'); if(idx == -1) continue; string strTaskId = strTask.Substring(idx + 1); long taskId = 0; try { taskId = Int64.Parse(strTaskId); } catch { continue; // wrong task format } //save only WebsitePanel tasks if(!tasks.ContainsKey(taskId)) tasks.Add(taskId, strTask); } } if (tasks.Count == 0) { if (rebootRequired) { ServiceLog.WriteInfo("Reboot required"); RebootSystem(); return; } //start timers StartPollTimer(); //starts task processing after poll interval StartIdleTimer(); //stops service if idle //no tasks - exit! return; } else { //stop idle timer as we need to process tasks StopIdleTimer(); } ExecutionContext context = null; foreach (long tid in tasks.Keys) { //find first correct task string taskDefinition = tasks[tid]; string taskParameters = RegistryUtils.GetRegistryKeyStringValue(RegistryInputKey, taskDefinition); if (taskDefinition.LastIndexOf("-") == -1 || taskDefinition.LastIndexOf('-') == taskDefinition.Length - 1) { ServiceLog.WriteError(string.Format("Task was deleted from queue as its definition is invalid : {0}", taskDefinition)); DeleteRegistryKeyValue(RegistryInputKey, taskDefinition); //go to next task continue; } string taskName = taskDefinition.Substring(0, taskDefinition.LastIndexOf("-")).Substring(TaskPrefix.Length); string taskId = taskDefinition.Substring(taskDefinition.LastIndexOf('-') + 1); if (!provisioningModules.ContainsKey(taskName)) { ServiceLog.WriteError(string.Format("Task was deleted from queue as its definition was not found : {0}", taskName)); DeleteRegistryKeyValue(RegistryInputKey, taskDefinition); //go to next task continue; } //prepare execution context for correct task context = new ExecutionContext(); context.ActivityID = taskId; context.ActivityName = taskName; ParseParameters(context.Parameters, taskParameters); context.ActivityDefinition = taskDefinition; break; } if (context != null) { string type = provisioningModules[context.ActivityName]; ExecutionResult res = null; DateTime start = DateTime.Now; try { //load module and run task ServiceLog.WriteStart(string.Format("Starting '{0}' module...", context.ActivityName)); context.Progress = 0; res = ModuleLoader.Run(type, ref context); context.Progress = 100; ServiceLog.WriteEnd(string.Format("'{0}' module finished.", context.ActivityName)); } catch (Exception ex) { ServiceLog.WriteError("Unhandled exception:", ex); res = new ExecutionResult(); res.ResultCode = -1; res.ErrorMessage = string.Format("Unhandled exception : {0}", ex); } DateTime end = DateTime.Now; SaveExecutionResult(context.ActivityDefinition, res, start, end); //DeleteRegistryKeyValue(RegistryInputKey, context.ActivityDefinition); if (res.RebootRequired) rebootRequired = true; } } } private void ParseParameters(Dictionary dictionary, string parameters) { if (dictionary == null) throw new ArgumentNullException("dictionary"); if (string.IsNullOrEmpty(parameters)) return; string[] pairs = parameters.Split('|'); foreach (string pair in pairs) { string[] parts = pair.Split(new char[] { '=' }, 2); if (parts.Length != 2) continue; if (!dictionary.ContainsKey(parts[0])) dictionary.Add(parts[0], parts[1]); } } private void DeleteRegistryKeyValue(string key, string valueName) { try { RegistryUtils.DeleteRegistryKeyValue(key, valueName); } catch (Exception ex) { ServiceLog.WriteError("Registry error:", ex); } } private void SaveExecutionResult(string name, ExecutionResult res, DateTime started, DateTime ended) { StringBuilder builder = new StringBuilder(); builder.AppendFormat("ResultCode={0}|", res.ResultCode); builder.AppendFormat("RebootRequired={0}|", res.RebootRequired); builder.AppendFormat("ErrorMessage={0}|", res.ErrorMessage); builder.AppendFormat("Value={0}|", res.Value); builder.AppendFormat("Started={0}|", started.ToString("yyyyMMddHHmmss")); builder.AppendFormat("Ended={0}", started.ToString("yyyyMMddHHmmss")); RegistryUtils.SetRegistryKeyStringValue(RegistryOutputKey, name, builder.ToString()); } private void StopIdleTimer() { idleTimer.Stop(); } private void StartIdleTimer() { if ( idleTimer.Interval > 1 ) idleTimer.Start(); } private void StartPollTimer() { if ( pollTimer.Interval > 1 ) pollTimer.Start(); } private void StartSummaryTimer() { if (summaryTimer.Interval > 1) summaryTimer.Start(); } private void RebootSystem() { try { ServiceLog.WriteStart("RebootSystem"); WmiUtils wmi = new WmiUtils("root\\cimv2"); ManagementObjectCollection objOses = wmi.ExecuteQuery("SELECT * FROM Win32_OperatingSystem"); foreach (ManagementObject objOs in objOses) { objOs.Scope.Options.EnablePrivileges = true; objOs.InvokeMethod("Reboot", null); } ServiceLog.WriteEnd("RebootSystem"); } catch (Exception ex) { ServiceLog.WriteError("Reboot System error:", ex); } } } }