//******************************************************************************************************// CustomActions.cs - Gbtc//// Copyright © 2013, Grid Protection Alliance. All Rights Reserved.//// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See// the NOTICE file distributed with this work for additional information regarding copyright ownership.// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may// not use this file except in compliance with the License. You may obtain a copy of the License at://// http://www.opensource.org/licenses/MIT//// Unless agreed to in writing, the subject software distributed under the License is distributed on an// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the// License for the specific language governing permissions and limitations.//// Code Modification History:// ----------------------------------------------------------------------------------------------------// 04/08/2013 - Stephen C. Wills// Generated original version of source code. Portions of code derived from Code Project article:// http://www.codeproject.com/Articles/6164/A-ServiceInstaller-Extension-That-Enables-Recovery// written by Neil Baliga////****************************************************************************************************** using System;using System.Collections;using System.Collections.Generic;using System.Diagnostics;using System.IO;using System.IO.Compression;using System.Linq;using System.Reflection;using System.Runtime.InteropServices;using System.Security.Principal;using System.Text;using System.Text.RegularExpressions;using System.Threading;using System.Windows.Forms;using System.Xml.Linq;using System.Xml.XPath;using GSF.Data;using GSF.Identity;using GSF.Interop;using GSF.Security.Cryptography;using GSF.ServiceProcess;using Microsoft.Deployment.WindowsInstaller; namespace GSF.InstallerActions{ /// <summary> /// Class that contains custom actions for a WiX installer. /// </summary> public class CustomActions { /// <summary> /// Custom action to attempt to authenticate the service account. /// </summary> /// <param name="session">Session object containing data from the installer.</param> /// <returns>Result of the custom action.</returns> [CustomAction] public static ActionResult AuthenticateServiceAccountAction(Session session) { Logger logger = new Logger(session); string serviceDomain = string.Empty; string serviceUser = string.Empty; logger.Log("Begin AuthenticateServiceAccountAction"); string serviceAccount = GetPropertyValue(session, "ServiceAccount"); string servicePassword = GetPropertyValue(session, "ServicePassword"); string[] splitServiceAccount = serviceAccount.Split('\\'); switch (splitServiceAccount.Length) { case 1: serviceDomain = UserInfo.CurrentUserID.Split('\\')[0]; serviceUser = splitServiceAccount[0]; break; case 2: serviceDomain = splitServiceAccount[0]; serviceUser = splitServiceAccount[1]; break; } bool isSystemAccount = serviceAccount.Equals("LocalSystem", StringComparison.OrdinalIgnoreCase) || serviceAccount.StartsWith(@"NT AUTHORITY\", StringComparison.OrdinalIgnoreCase) || serviceAccount.StartsWith(@"NT SERVICE\", StringComparison.OrdinalIgnoreCase); bool isManagedServiceAccount = serviceAccount.EndsWith("$", StringComparison.Ordinal); if (isSystemAccount) { session["SERVICEPASSWORD"] = string.Empty; session["SERVICEAUTHENTICATED"] = "yes"; } else if (isManagedServiceAccount) { using (UserInfo serviceAccountInfo = new UserInfo(serviceAccount)) { bool isManagedServiceAccountValid = serviceAccountInfo.Exists && !serviceAccountInfo.AccountIsDisabled && !serviceAccountInfo.AccountIsLockedOut && serviceAccountInfo.GetUserPropertyValue("msDS-HostServiceAccountBL").Split(',')[0]. Equals("CN=" + Environment.MachineName, StringComparison.CurrentCultureIgnoreCase); if (isManagedServiceAccountValid) { session["SERVICEPASSWORD"] = string.Empty; session["SERVICEAUTHENTICATED"] = "yes"; } else { session["SERVICEAUTHENTICATED"] = null; } } } else { IPrincipal servicePrincipal = UserInfo.AuthenticateUser(serviceDomain, serviceUser, servicePassword); session["SERVICEAUTHENTICATED"] = servicePrincipal?.Identity.IsAuthenticated == true ? "yes" : null; } logger.Log("End AuthenticateServiceAccountAction"); return ActionResult.Success; } /// <summary> /// Custom action to write to an XML file. /// </summary> /// <param name="session">Session object containing data from the installer.</param> /// <returns>Result of the custom action.</returns> [CustomAction] public static ActionResult XmlFileAction(Session session) { Logger logger = new Logger(session); logger.Log("Begin XmlFileAction"); string filePath = GetPropertyValue(session, "FilePath"); string mode = GetPropertyValue(session, "Mode").ToUpper(); string mappings = GetPropertyValue(session, "PropertyMappings"); if (File.Exists(filePath)) { // ReSharper disable PossibleMultipleEnumeration try { XDocument document = XDocument.Load(filePath); foreach (KeyValuePair<string, string> mapping in mappings.ParseKeyValuePairs()) { IEnumerable query = (IEnumerable)document.XPathEvaluate(mapping.Value); if (mode != "WRITE") { session[mapping.Key] = query.OfType<XElement>().Select(element => element.Value) .Concat(query.OfType<XAttribute>().Select(attribute => attribute.Value)) .FirstOrDefault(); } else { string value = GetPropertyValue(session, mapping.Key); query.OfType<XAttribute>().ToList().ForEach(attribute => { if (!string.IsNullOrEmpty(value)) attribute.Value = value; else attribute.Remove(); }); query.OfType<XElement>().ToList().ForEach(element => { if (!string.IsNullOrEmpty(value)) element.Value = value; else element.Remove(); }); document.Save(filePath); } } } catch (Exception ex) { string action = mode == "WRITE" ? "update" : "read"; string message = $"Failed to {action} XML file: {ex.Message}"; logger.Log(InstallMessage.Error, message); logger.Log(EventLogEntryType.Error, ex); logger.Log($"FilePath = {filePath}"); logger.Log($"PropertyMappings = {mappings}"); return ActionResult.Failure; } // ReSharper restore PossibleMultipleEnumeration } logger.Log("End XmlFileAction"); return ActionResult.Success; } [CustomAction] public static ActionResult ConnectionStringAction(Session session) { Logger logger = new Logger(session); logger.Log("Begin ConnectionStringAction"); string connectionString = GetPropertyValue(session, "ConnectionString"); string mappings = GetPropertyValue(session, "PropertyMappings"); Dictionary<string, string> settings = connectionString.ParseKeyValuePairs(); foreach (KeyValuePair<string, string> kvp in mappings.ParseKeyValuePairs()) { if (settings.TryGetValue(kvp.Value, out string setting)) session[kvp.Key] = setting; else session[kvp.Key] = null; } logger.Log("End ConnectionStringAction"); return ActionResult.Success; } /// <summary> /// Custom action to load any previously defined CompanyName, CompanyAcronym and SystemName settings from the configuration file of an installed service. /// </summary> /// <param name="session">Session object containing data from the installer.</param> /// <returns>Result of the custom action.</returns> [CustomAction] public static ActionResult LoadExistingCompanyInfoAction(Session session) { Logger logger = new Logger(session); logger.Log("Begin LoadExistingCompanyInfoAction"); string serviceName = GetPropertyValue(session, "SERVICENAME"); string configPath = Path.Combine(GetPropertyValue(session, "INSTALLFOLDER"), $"{serviceName}.exe.config"); void setPropertyValue(XElement systemSettings, string settingName, string propertyName) { XElement setting = systemSettings.Elements("add").FirstOrDefault(element => string.Compare(element.Attributes("name").FirstOrDefault()?.Value, settingName, StringComparison.OrdinalIgnoreCase) == 0); string value = setting?.Attributes("value").FirstOrDefault()?.Value; if (!string.IsNullOrEmpty(value)) session[propertyName] = value; } if (File.Exists(configPath)) { XDocument config = XDocument.Load(configPath); XElement systemSettings = config.Descendants("systemSettings").FirstOrDefault(); if (systemSettings != null) { setPropertyValue(systemSettings, "CompanyName", "COMPANYNAME"); setPropertyValue(systemSettings, "CompanyAcronym", "COMPANYACRONYM"); setPropertyValue(systemSettings, "SystemName", "SYSTEMNAME"); } } logger.Log("End LoadExistingCompanyInfoAction"); return ActionResult.Success; } /// <summary> /// Custom action to write CompanyName, CompanyAcronym and SystemName settings to the configuration file of an installed service. /// </summary> /// <param name="session">Session object containing data from the installer.</param> /// <returns>Result of the custom action.</returns> [CustomAction] public static ActionResult CompanyInfoAction(Session session) { const string DefaultCompanyName = "Grid Protection Alliance"; const string DefaultCompanyAcronym = "GPA"; const string DefaultSystemName = " "; Logger logger = new Logger(session); logger.Log("Begin CompanyInfoAction"); string validAcronym(string propertyValue) { try { return Regex.Replace(propertyValue.Trim().ToUpperInvariant(), @"[^A-Z0-9\-!_\.@#\$]+", "_", RegexOptions.Compiled); } catch (Exception ex) { logger.Log(EventLogEntryType.Warning, ex); return propertyValue; } } string serviceName = GetPropertyValue(session, "SERVICENAME"); string configPath = Path.Combine(GetPropertyValue(session, "INSTALLDIR"), $"{serviceName}.exe.config"); string companyName = GetPropertyValue(session, "COMPANYNAME") ?? DefaultCompanyName; string companyAcronym = validAcronym(GetPropertyValue(session, "COMPANYACRONYM") ?? DefaultCompanyAcronym); string systemName = validAcronym(GetPropertyValue(session, "SYSTEMNAME") ?? DefaultSystemName); if (File.Exists(configPath)) { XDocument config = XDocument.Load(configPath); foreach (XElement systemSettings in config.Descendants("systemSettings")) { // Search for existing CompanyName settings and update their values foreach (XElement companyNameElement in systemSettings.Elements("add").Where(element => element.Attributes("name").Any(nameAttribute => string.Compare(nameAttribute.Value, "CompanyName", StringComparison.OrdinalIgnoreCase) == 0))) companyNameElement.Attributes("value").ToList().ForEach(valueAttribute => valueAttribute.Value = companyName); // Search for existing CompanyAcronym settings and update their values foreach (XElement companyAcronymElement in systemSettings.Elements("add").Where(element => element.Attributes("name").Any(nameAttribute => string.Compare(nameAttribute.Value, "CompanyAcronym", StringComparison.OrdinalIgnoreCase) == 0))) companyAcronymElement.Attributes("value").ToList().ForEach(valueAttribute => valueAttribute.Value = companyAcronym); // Search for existing SystemName settings and update their values foreach (XElement systemNameElement in systemSettings.Elements("add").Where(element => element.Attributes("name").Any(nameAttribute => string.Compare(nameAttribute.Value, "SystemName", StringComparison.OrdinalIgnoreCase) == 0))) systemNameElement.Attributes("value").ToList().ForEach(valueAttribute => valueAttribute.Value = systemName); // Add CompanyName setting if no such setting exists if (!systemSettings.Elements("add").Any(element => element.Attributes("name").Any(nameAttribute => string.Compare(nameAttribute.Value, "CompanyName", StringComparison.OrdinalIgnoreCase) == 0))) { systemSettings.Add(new XElement("add", new XAttribute("name", "CompanyName"), new XAttribute("value", companyName), new XAttribute("description", $"The name of the company who owns this instance of the {serviceName}."), new XAttribute("encrypted", "false") )); } // Add CompanyAcronym setting if no such setting exists if (!systemSettings.Elements("add").Any(element => element.Attributes("name").Any(nameAttribute => string.Compare(nameAttribute.Value, "CompanyAcronym", StringComparison.OrdinalIgnoreCase) == 0))) { systemSettings.Add(new XElement("add", new XAttribute("name", "CompanyAcronym"), new XAttribute("value", companyAcronym), new XAttribute("description", $"The acronym representing the company who owns this instance of the {serviceName}."), new XAttribute("encrypted", "false") )); } // Add SystemName setting if no such setting exists if (!systemSettings.Elements("add").Any(element => element.Attributes("name").Any(nameAttribute => string.Compare(nameAttribute.Value, "SystemName", StringComparison.OrdinalIgnoreCase) == 0))) { systemSettings.Add(new XElement("add", new XAttribute("name", "SystemName"), new XAttribute("value", systemName), new XAttribute("description", $"Name of system for this instance of the {serviceName} that will be prefixed to system level tags, when defined. Value should follow tag naming conventions, e.g., no spaces and all upper case."), new XAttribute("encrypted", "false") )); } } config.Save(configPath); } logger.Log("End CompanyInfoAction"); return ActionResult.Success; } /// <summary> /// Custom action to configure various failure settings of an installed service. /// </summary> /// <param name="session">Session object containing data from the installer.</param> /// <returns>Result of the custom action.</returns> [CustomAction] public static ActionResult ConfigureServiceAction(Session session) { Logger logger = new Logger(session); logger.Log("Begin ConfigureServiceAction"); UpdateServiceConfig(session, logger); logger.Log("End ConfigureServiceAction"); return ActionResult.Success; } /// <summary> /// Custom action to set up the group and necessary permissions for the service account. /// </summary> /// <param name="session">Session object containing data from the installer.</param> /// <returns>Result of the custom action.</returns> [CustomAction] public static ActionResult ServiceAccountAction(Session session) { Logger logger = new Logger(session); logger.Log("Begin ServiceAccountAction"); // Get properties from the installer session string serviceName = GetPropertyValue(session, "SERVICENAME"); string serviceAccount = GetPropertyValue(session, "SERVICEACCOUNT"); string servicePorts = GetPropertyValue(session, "HTTPSERVICEPORTS"); string serviceAdminsGroupName = $"{serviceName} Admins"; // Create service admins group try { logger.Log($"Creating {serviceAdminsGroupName} group..."); UserInfo.CreateLocalGroup(serviceAdminsGroupName, $"Members in this group have the necessary rights to administrate the {serviceName} service."); logger.Log($"Done creating {serviceAdminsGroupName} group..."); } catch (Exception ex) { logger.Log(InstallMessage.Error, $"Failed to create {serviceAdminsGroupName} group!"); logger.Log(EventLogEntryType.Error, ex); } void addServiceAccountToGroup(string groupName) { try { logger.Log($"Adding {serviceAccount} user to {groupName} group..."); UserInfo.AddUserToLocalGroup(groupName, serviceAccount); logger.Log($"Done adding {serviceAccount} user to {groupName} group."); } catch (InvalidOperationException ex) { // This exception is thrown when group doesn't exist, just treat this as a warning logger.Log(EventLogEntryType.Warning, ex); } catch (Exception ex) { logger.Log(InstallMessage.Error, $"Failed to add {serviceAccount} user to {groupName} group!"); logger.Log(EventLogEntryType.Error, ex); } } // Determine if we're dealing with the local system account bool isLocalSystem = serviceAccount == "LocalSystem"; // Don't attempt grant rights to the local system account because it already has them if (!isLocalSystem) { // Create service admins group and add service account to that group as well as the Performance Log Users group addServiceAccountToGroup(serviceAdminsGroupName); addServiceAccountToGroup("Performance Monitor Users"); addServiceAccountToGroup("Performance Log Users"); try { logger.Log($"Adding {serviceAccount} user right to logon as a service..."); AddPrivileges(serviceAccount, WindowsApi.SE_SERVICE_LOGON_NAME); logger.Log($"Done adding {serviceAccount} user right to logon as a service..."); } catch (Exception ex) { logger.Log(InstallMessage.Error, $"Failed to add {serviceAccount} user right to logon as a service!"); logger.Log(EventLogEntryType.Error, ex); } try { logger.Log($"Adding {serviceAccount} user right to change system time..."); AddPrivileges(serviceAccount, WindowsApi.SE_SYSTEMTIME_NAME); logger.Log($"Done adding {serviceAccount} user right to change system time..."); } catch (Exception ex) { // This right is only needed for updating system clock, so its treated as a warning logger.Log(EventLogEntryType.Warning, ex); } } // Attempt to grant rights to start and stop the service try { string accountSID = UserInfo.UserNameToSID(serviceAccount); string groupSID = UserInfo.GroupNameToSID(serviceAdminsGroupName); string acl; using (Process process = new Process()) { process.StartInfo.FileName = "sc"; process.StartInfo.Arguments = $"sdshow {serviceName}"; process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; process.Start(); acl = process.StandardOutput.ReadToEnd().Trim(); process.WaitForExit(); if (process.ExitCode != 0) throw new Exception($"Non-zero exit code ({process.ExitCode}) returned from 'sc sdshow {serviceName}'."); } int index = acl.IndexOf("S:", StringComparison.Ordinal); // Don't attempt grant rights to the local system account because it already has them if (!isLocalSystem && accountSID.StartsWith("S-") && !acl.Contains(accountSID)) acl = acl.Insert(index, $"(A;;RPWPCR;;;{accountSID})"); Lines 1–500 of 1286. Load more
//****************************************************************************************************** // CustomActions.cs - Gbtc // // Copyright © 2013, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 04/08/2013 - Stephen C. Wills // Generated original version of source code. Portions of code derived from Code Project article: // http://www.codeproject.com/Articles/6164/A-ServiceInstaller-Extension-That-Enables-Recovery // written by Neil Baliga // //****************************************************************************************************** using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Security.Principal; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Windows.Forms; using System.Xml.Linq; using System.Xml.XPath; using GSF.Data; using GSF.Identity; using GSF.Interop; using GSF.Security.Cryptography; using GSF.ServiceProcess; using Microsoft.Deployment.WindowsInstaller; namespace GSF.InstallerActions { /// <summary> /// Class that contains custom actions for a WiX installer. /// </summary> public class CustomActions { /// <summary> /// Custom action to attempt to authenticate the service account. /// </summary> /// <param name="session">Session object containing data from the installer.</param> /// <returns>Result of the custom action.</returns> [CustomAction] public static ActionResult AuthenticateServiceAccountAction(Session session) { Logger logger = new Logger(session); string serviceDomain = string.Empty; string serviceUser = string.Empty; logger.Log("Begin AuthenticateServiceAccountAction"); string serviceAccount = GetPropertyValue(session, "ServiceAccount"); string servicePassword = GetPropertyValue(session, "ServicePassword"); string[] splitServiceAccount = serviceAccount.Split('\\'); switch (splitServiceAccount.Length) { case 1: serviceDomain = UserInfo.CurrentUserID.Split('\\')[0]; serviceUser = splitServiceAccount[0]; break; case 2: serviceDomain = splitServiceAccount[0]; serviceUser = splitServiceAccount[1]; break; } bool isSystemAccount = serviceAccount.Equals("LocalSystem", StringComparison.OrdinalIgnoreCase) || serviceAccount.StartsWith(@"NT AUTHORITY\", StringComparison.OrdinalIgnoreCase) || serviceAccount.StartsWith(@"NT SERVICE\", StringComparison.OrdinalIgnoreCase); bool isManagedServiceAccount = serviceAccount.EndsWith("$", StringComparison.Ordinal); if (isSystemAccount) { session["SERVICEPASSWORD"] = string.Empty; session["SERVICEAUTHENTICATED"] = "yes"; } else if (isManagedServiceAccount) { using (UserInfo serviceAccountInfo = new UserInfo(serviceAccount)) { bool isManagedServiceAccountValid = serviceAccountInfo.Exists && !serviceAccountInfo.AccountIsDisabled && !serviceAccountInfo.AccountIsLockedOut && serviceAccountInfo.GetUserPropertyValue("msDS-HostServiceAccountBL").Split(',')[0]. Equals("CN=" + Environment.MachineName, StringComparison.CurrentCultureIgnoreCase); if (isManagedServiceAccountValid) { session["SERVICEPASSWORD"] = string.Empty; session["SERVICEAUTHENTICATED"] = "yes"; } else { session["SERVICEAUTHENTICATED"] = null; } } } else { IPrincipal servicePrincipal = UserInfo.AuthenticateUser(serviceDomain, serviceUser, servicePassword); session["SERVICEAUTHENTICATED"] = servicePrincipal?.Identity.IsAuthenticated == true ? "yes" : null; } logger.Log("End AuthenticateServiceAccountAction"); return ActionResult.Success; } /// <summary> /// Custom action to write to an XML file. /// </summary> /// <param name="session">Session object containing data from the installer.</param> /// <returns>Result of the custom action.</returns> [CustomAction] public static ActionResult XmlFileAction(Session session) { Logger logger = new Logger(session); logger.Log("Begin XmlFileAction"); string filePath = GetPropertyValue(session, "FilePath"); string mode = GetPropertyValue(session, "Mode").ToUpper(); string mappings = GetPropertyValue(session, "PropertyMappings"); if (File.Exists(filePath)) { // ReSharper disable PossibleMultipleEnumeration try { XDocument document = XDocument.Load(filePath); foreach (KeyValuePair<string, string> mapping in mappings.ParseKeyValuePairs()) { IEnumerable query = (IEnumerable)document.XPathEvaluate(mapping.Value); if (mode != "WRITE") { session[mapping.Key] = query.OfType<XElement>().Select(element => element.Value) .Concat(query.OfType<XAttribute>().Select(attribute => attribute.Value)) .FirstOrDefault(); } else { string value = GetPropertyValue(session, mapping.Key); query.OfType<XAttribute>().ToList().ForEach(attribute => { if (!string.IsNullOrEmpty(value)) attribute.Value = value; else attribute.Remove(); }); query.OfType<XElement>().ToList().ForEach(element => { if (!string.IsNullOrEmpty(value)) element.Value = value; else element.Remove(); }); document.Save(filePath); } } } catch (Exception ex) { string action = mode == "WRITE" ? "update" : "read"; string message = $"Failed to {action} XML file: {ex.Message}"; logger.Log(InstallMessage.Error, message); logger.Log(EventLogEntryType.Error, ex); logger.Log($"FilePath = {filePath}"); logger.Log($"PropertyMappings = {mappings}"); return ActionResult.Failure; } // ReSharper restore PossibleMultipleEnumeration } logger.Log("End XmlFileAction"); return ActionResult.Success; } [CustomAction] public static ActionResult ConnectionStringAction(Session session) { Logger logger = new Logger(session); logger.Log("Begin ConnectionStringAction"); string connectionString = GetPropertyValue(session, "ConnectionString"); string mappings = GetPropertyValue(session, "PropertyMappings"); Dictionary<string, string> settings = connectionString.ParseKeyValuePairs(); foreach (KeyValuePair<string, string> kvp in mappings.ParseKeyValuePairs()) { if (settings.TryGetValue(kvp.Value, out string setting)) session[kvp.Key] = setting; else session[kvp.Key] = null; } logger.Log("End ConnectionStringAction"); return ActionResult.Success; } /// <summary> /// Custom action to load any previously defined CompanyName, CompanyAcronym and SystemName settings from the configuration file of an installed service. /// </summary> /// <param name="session">Session object containing data from the installer.</param> /// <returns>Result of the custom action.</returns> [CustomAction] public static ActionResult LoadExistingCompanyInfoAction(Session session) { Logger logger = new Logger(session); logger.Log("Begin LoadExistingCompanyInfoAction"); string serviceName = GetPropertyValue(session, "SERVICENAME"); string configPath = Path.Combine(GetPropertyValue(session, "INSTALLFOLDER"), $"{serviceName}.exe.config"); void setPropertyValue(XElement systemSettings, string settingName, string propertyName) { XElement setting = systemSettings.Elements("add").FirstOrDefault(element => string.Compare(element.Attributes("name").FirstOrDefault()?.Value, settingName, StringComparison.OrdinalIgnoreCase) == 0); string value = setting?.Attributes("value").FirstOrDefault()?.Value; if (!string.IsNullOrEmpty(value)) session[propertyName] = value; } if (File.Exists(configPath)) { XDocument config = XDocument.Load(configPath); XElement systemSettings = config.Descendants("systemSettings").FirstOrDefault(); if (systemSettings != null) { setPropertyValue(systemSettings, "CompanyName", "COMPANYNAME"); setPropertyValue(systemSettings, "CompanyAcronym", "COMPANYACRONYM"); setPropertyValue(systemSettings, "SystemName", "SYSTEMNAME"); } } logger.Log("End LoadExistingCompanyInfoAction"); return ActionResult.Success; } /// <summary> /// Custom action to write CompanyName, CompanyAcronym and SystemName settings to the configuration file of an installed service. /// </summary> /// <param name="session">Session object containing data from the installer.</param> /// <returns>Result of the custom action.</returns> [CustomAction] public static ActionResult CompanyInfoAction(Session session) { const string DefaultCompanyName = "Grid Protection Alliance"; const string DefaultCompanyAcronym = "GPA"; const string DefaultSystemName = " "; Logger logger = new Logger(session); logger.Log("Begin CompanyInfoAction"); string validAcronym(string propertyValue) { try { return Regex.Replace(propertyValue.Trim().ToUpperInvariant(), @"[^A-Z0-9\-!_\.@#\$]+", "_", RegexOptions.Compiled); } catch (Exception ex) { logger.Log(EventLogEntryType.Warning, ex); return propertyValue; } } string serviceName = GetPropertyValue(session, "SERVICENAME"); string configPath = Path.Combine(GetPropertyValue(session, "INSTALLDIR"), $"{serviceName}.exe.config"); string companyName = GetPropertyValue(session, "COMPANYNAME") ?? DefaultCompanyName; string companyAcronym = validAcronym(GetPropertyValue(session, "COMPANYACRONYM") ?? DefaultCompanyAcronym); string systemName = validAcronym(GetPropertyValue(session, "SYSTEMNAME") ?? DefaultSystemName); if (File.Exists(configPath)) { XDocument config = XDocument.Load(configPath); foreach (XElement systemSettings in config.Descendants("systemSettings")) { // Search for existing CompanyName settings and update their values foreach (XElement companyNameElement in systemSettings.Elements("add").Where(element => element.Attributes("name").Any(nameAttribute => string.Compare(nameAttribute.Value, "CompanyName", StringComparison.OrdinalIgnoreCase) == 0))) companyNameElement.Attributes("value").ToList().ForEach(valueAttribute => valueAttribute.Value = companyName); // Search for existing CompanyAcronym settings and update their values foreach (XElement companyAcronymElement in systemSettings.Elements("add").Where(element => element.Attributes("name").Any(nameAttribute => string.Compare(nameAttribute.Value, "CompanyAcronym", StringComparison.OrdinalIgnoreCase) == 0))) companyAcronymElement.Attributes("value").ToList().ForEach(valueAttribute => valueAttribute.Value = companyAcronym); // Search for existing SystemName settings and update their values foreach (XElement systemNameElement in systemSettings.Elements("add").Where(element => element.Attributes("name").Any(nameAttribute => string.Compare(nameAttribute.Value, "SystemName", StringComparison.OrdinalIgnoreCase) == 0))) systemNameElement.Attributes("value").ToList().ForEach(valueAttribute => valueAttribute.Value = systemName); // Add CompanyName setting if no such setting exists if (!systemSettings.Elements("add").Any(element => element.Attributes("name").Any(nameAttribute => string.Compare(nameAttribute.Value, "CompanyName", StringComparison.OrdinalIgnoreCase) == 0))) { systemSettings.Add(new XElement("add", new XAttribute("name", "CompanyName"), new XAttribute("value", companyName), new XAttribute("description", $"The name of the company who owns this instance of the {serviceName}."), new XAttribute("encrypted", "false") )); } // Add CompanyAcronym setting if no such setting exists if (!systemSettings.Elements("add").Any(element => element.Attributes("name").Any(nameAttribute => string.Compare(nameAttribute.Value, "CompanyAcronym", StringComparison.OrdinalIgnoreCase) == 0))) { systemSettings.Add(new XElement("add", new XAttribute("name", "CompanyAcronym"), new XAttribute("value", companyAcronym), new XAttribute("description", $"The acronym representing the company who owns this instance of the {serviceName}."), new XAttribute("encrypted", "false") )); } // Add SystemName setting if no such setting exists if (!systemSettings.Elements("add").Any(element => element.Attributes("name").Any(nameAttribute => string.Compare(nameAttribute.Value, "SystemName", StringComparison.OrdinalIgnoreCase) == 0))) { systemSettings.Add(new XElement("add", new XAttribute("name", "SystemName"), new XAttribute("value", systemName), new XAttribute("description", $"Name of system for this instance of the {serviceName} that will be prefixed to system level tags, when defined. Value should follow tag naming conventions, e.g., no spaces and all upper case."), new XAttribute("encrypted", "false") )); } } config.Save(configPath); } logger.Log("End CompanyInfoAction"); return ActionResult.Success; } /// <summary> /// Custom action to configure various failure settings of an installed service. /// </summary> /// <param name="session">Session object containing data from the installer.</param> /// <returns>Result of the custom action.</returns> [CustomAction] public static ActionResult ConfigureServiceAction(Session session) { Logger logger = new Logger(session); logger.Log("Begin ConfigureServiceAction"); UpdateServiceConfig(session, logger); logger.Log("End ConfigureServiceAction"); return ActionResult.Success; } /// <summary> /// Custom action to set up the group and necessary permissions for the service account. /// </summary> /// <param name="session">Session object containing data from the installer.</param> /// <returns>Result of the custom action.</returns> [CustomAction] public static ActionResult ServiceAccountAction(Session session) { Logger logger = new Logger(session); logger.Log("Begin ServiceAccountAction"); // Get properties from the installer session string serviceName = GetPropertyValue(session, "SERVICENAME"); string serviceAccount = GetPropertyValue(session, "SERVICEACCOUNT"); string servicePorts = GetPropertyValue(session, "HTTPSERVICEPORTS"); string serviceAdminsGroupName = $"{serviceName} Admins"; // Create service admins group try { logger.Log($"Creating {serviceAdminsGroupName} group..."); UserInfo.CreateLocalGroup(serviceAdminsGroupName, $"Members in this group have the necessary rights to administrate the {serviceName} service."); logger.Log($"Done creating {serviceAdminsGroupName} group..."); } catch (Exception ex) { logger.Log(InstallMessage.Error, $"Failed to create {serviceAdminsGroupName} group!"); logger.Log(EventLogEntryType.Error, ex); } void addServiceAccountToGroup(string groupName) { try { logger.Log($"Adding {serviceAccount} user to {groupName} group..."); UserInfo.AddUserToLocalGroup(groupName, serviceAccount); logger.Log($"Done adding {serviceAccount} user to {groupName} group."); } catch (InvalidOperationException ex) { // This exception is thrown when group doesn't exist, just treat this as a warning logger.Log(EventLogEntryType.Warning, ex); } catch (Exception ex) { logger.Log(InstallMessage.Error, $"Failed to add {serviceAccount} user to {groupName} group!"); logger.Log(EventLogEntryType.Error, ex); } } // Determine if we're dealing with the local system account bool isLocalSystem = serviceAccount == "LocalSystem"; // Don't attempt grant rights to the local system account because it already has them if (!isLocalSystem) { // Create service admins group and add service account to that group as well as the Performance Log Users group addServiceAccountToGroup(serviceAdminsGroupName); addServiceAccountToGroup("Performance Monitor Users"); addServiceAccountToGroup("Performance Log Users"); try { logger.Log($"Adding {serviceAccount} user right to logon as a service..."); AddPrivileges(serviceAccount, WindowsApi.SE_SERVICE_LOGON_NAME); logger.Log($"Done adding {serviceAccount} user right to logon as a service..."); } catch (Exception ex) { logger.Log(InstallMessage.Error, $"Failed to add {serviceAccount} user right to logon as a service!"); logger.Log(EventLogEntryType.Error, ex); } try { logger.Log($"Adding {serviceAccount} user right to change system time..."); AddPrivileges(serviceAccount, WindowsApi.SE_SYSTEMTIME_NAME); logger.Log($"Done adding {serviceAccount} user right to change system time..."); } catch (Exception ex) { // This right is only needed for updating system clock, so its treated as a warning logger.Log(EventLogEntryType.Warning, ex); } } // Attempt to grant rights to start and stop the service try { string accountSID = UserInfo.UserNameToSID(serviceAccount); string groupSID = UserInfo.GroupNameToSID(serviceAdminsGroupName); string acl; using (Process process = new Process()) { process.StartInfo.FileName = "sc"; process.StartInfo.Arguments = $"sdshow {serviceName}"; process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; process.Start(); acl = process.StandardOutput.ReadToEnd().Trim(); process.WaitForExit(); if (process.ExitCode != 0) throw new Exception($"Non-zero exit code ({process.ExitCode}) returned from 'sc sdshow {serviceName}'."); } int index = acl.IndexOf("S:", StringComparison.Ordinal); // Don't attempt grant rights to the local system account because it already has them if (!isLocalSystem && accountSID.StartsWith("S-") && !acl.Contains(accountSID)) acl = acl.Insert(index, $"(A;;RPWPCR;;;{accountSID})");