Tag Archives: services

QuickMon Version 2.3 and Installers

I’ve now updated the QuickMon installers to include some ‘new’ functionality that allows you to choose to install the Windows service as part of the MSI installation.

There are still a few possible quirks – like what happens when you choose not to install the service initially (and do it manually afterwards) or when uninstalling the MSI (without removing the service manually) and then installing a new version ‘and then ‘choosing to install the service. In that case you get an error message that the service is already installed (duh as if you don’t know), the MSI Installer fails and rolls back completely. The solution (one of them) is to simply install the MSI and not choose to install/register the service in Service Manager.

Installers

I’ve learned a whole bunch of new ‘things’ about the standard VS installer during all of this. Mostly how limited the build-in Installer technology is but also some tweaks you can do to still (despite) get something good out of it.

Here is a few tips I came across:

1. When you add a ‘Checkboxes’ custom dialog to the ‘User Interface’ of the installer and want to use the checkbox value as a condition in one of the Custom Action it must look like this e.g: ‘CHKINSTALLSERVICE=1’  (without the quotes). I tried all kind of weird combinations like =”True”, =”1″ etc. that didn’t work…

2. You can have the MSI Installer call the ‘Installer’ classes housed inside your service project (and automatically install it) by adding a ‘Custom Action’ – ‘Install’ action. The default for it is to look for any installer classes inside the assembly. Similarly you can add the same for ‘Uninstall’ but this requires that the service has actually been installed (which is a problem for me since the user can choose not to install it at all)

3. Adding the “-install” and “-uninstall” parameters is nice and all but please remember to run it under an Administrator account (elevated) otherwise it simply fails with an “Access is denied” error. If you understand why then it seems obvious but for people that do not know about this it could be a pain (the error message does not tell you why ‘Access is denied…’)

4. The whole ‘backwards compatibility ID generation thing still baffles me. Upgraded projects (from VS2008 to VS2010) does not show it and the default on VS2010 is false. If not set it seems each time you install a newer version any copy of your shortcuts (like pinned to the taskbar) becomes ‘invalid. This is really a big pain (yes, it is probably easy enough to just unpin/pin again to fix it…)

5. The whole ‘Target platform’ thing is a mess – especially when the solution is under source control. Why do you have to check out the whole project just to change the Target platform setting to rebuild the MSI installer? Why is there not an option to build ‘both’ x86 and x64 at the same time (separate directories or something)?

6. Yes, using a better Installer tool like Wix will probably help… still need to learn how to use it. Anyone have some good tutorials somewhere? 🙂

 

Creating a self installing Windows Service

Being in the business of creating several Windows Services myself (EventScavenger, Event Collector, QuickMon) I started thinking about how to make the whole install and set up process a bit easier and eliminating the manual steps to install a Windows Service.

So what if you can simply run the service ‘exe’ with a “-install” or “-uninstall” parameter and let it do the work for you? Additionally, what if it it can even prompt you for start up type and user account details during the process – all of this without even using installutil…?

Well after some research, testing, trail and error, losing some more hair and eventually finding a way to make it work I now have a way to do all this. Nice thing is it can be used together with an MSI installer as well or even an external app or script.

ProjectInstaller

The trick is to create a custom ProjectInstaller class (not use the wizards that comes with VS to add installers). I’ve added some properties to this class to specify things like Service name, display name, start up type, account type, user name and password.

[RunInstaller(true)]
public class ProjectInstaller : Installer
{

private ServiceProcessInstaller processInstaller;
private System.ServiceProcess.ServiceInstaller serviceInstaller;

#region Added properties
public string ServiceName
{
get { return serviceInstaller.ServiceName; }
set { serviceInstaller.ServiceName = value; }
}
public string DisplayName
{
get { return serviceInstaller.DisplayName; }
set { serviceInstaller.DisplayName = value; }
}
public string Description
{
get { return serviceInstaller.Description; }
set { serviceInstaller.Description = value; }
}
public ServiceStartMode StartType
{
get { return serviceInstaller.StartType; }
set { serviceInstaller.StartType = value; }
}
public ServiceAccount Account
{
get { return processInstaller.Account; }
set { processInstaller.Account = value; }
}
public string ServiceUsername
{
get { return processInstaller.Username; }
set { processInstaller.Username = value; }
}
public string ServicePassword
{
get { return processInstaller.Password; }
set { processInstaller.Password = value; }
}
#endregion

public ProjectInstaller()
{

processInstaller = new ServiceProcessInstaller();
serviceInstaller = new System.ServiceProcess.ServiceInstaller();

Installers.AddRange(new Installer[] {
processInstaller,
serviceInstaller});

}

protected override void OnBeforeInstall(System.Collections.IDictionary savedState)
{

SetContextParameter(“name”, ServiceName);
SetContextParameter(“display”, DisplayName);
SetContextParameter(“desc”, Description);
SetContextParameter(“start”, StartType.ToString());
SetContextParameter(“account”, Account.ToString());

if (Account == ServiceAccount.User)
{

SetContextParameter(“user”, ServiceUsername);
SetContextParameter(“password”, ServicePassword);

}
base.OnBeforeInstall(savedState);

}

public void SetContextParameter(string key, string value)
{

if (!this.Context.Parameters.ContainsKey(key))

this.Context.Parameters.Add(key, value);

else

this.Context.Parameters[key] = value;

}

}

Calling the Installer/uninstaller

In order to get the service executable to handle the -“install” or “-uninstall” parameters I modified the Main method in Program.cs to accept a string parameter array and then call the appropriate method if either of those two parameters are passed.

Example code:

static class Program
{

static void Main(string[] args)
{

if (args.Length > 0)
{

if (args[0].ToUpper() == “-INSTALL”)
{

InstallService();
return;

}
else if (args[0].ToUpper() == “-UNINSTALL”)
{

UnInstallService();
return;

}

}
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[] { new SelfInstallService() };
ServiceBase.Run(ServicesToRun);

}

private static bool InstallService()
{

bool success = false;
try
{

string exeFullPath = System.Reflection.Assembly.GetExecutingAssembly().Location;
string workingPath = System.IO.Path.GetDirectoryName(exeFullPath);
string logPath = System.IO.Path.Combine(workingPath, “Install.log”);
ServiceStartMode startmode = ServiceStartMode.Automatic;
ServiceAccount account = ServiceAccount.LocalService;
string username = “”;
string password = “”;

InstallerForm installerForm = new InstallerForm();
installerForm.StartType = ServiceStartMode.Automatic;
installerForm.AccountType = ServiceAccount.User;
if (installerForm.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{

startmode = installerForm.StartType;
account = installerForm.AccountType;
if (installerForm.AccountType == ServiceAccount.User)
{

username = installerForm.UserName;
password = installerForm.Password;

}

}

Hashtable savedState = new Hashtable();
ProjectInstaller myProjectInstaller = new ProjectInstaller();
InstallContext myInstallContext = new InstallContext(logPath, new string[] { });
myProjectInstaller.Context = myInstallContext;
myProjectInstaller.ServiceName = “SelfInstallService”;
myProjectInstaller.DisplayName = “Self Install Service”;
myProjectInstaller.Description = “Self Install Service test”;
myProjectInstaller.StartType = startmode;
myProjectInstaller.Account = account;
if (account == ServiceAccount.User)
{

myProjectInstaller.ServiceUsername = username;
myProjectInstaller.ServicePassword = password;

}
myProjectInstaller.Context.Parameters[“AssemblyPath”] = exeFullPath;

myProjectInstaller.Install(savedState);
success = true;

}
catch (Exception ex)
{

System.Windows.Forms.MessageBox.Show(ex.Message, “Install service”, System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error);

}
return success;

}

private static bool UnInstallService()
{

bool success = false;
try
{

string exeFullPath = System.Reflection.Assembly.GetExecutingAssembly().Location;
string workingPath = System.IO.Path.GetDirectoryName(exeFullPath);
string logPath = System.IO.Path.Combine(workingPath, “Install.log”);

ServiceInstaller myServiceInstaller = new ServiceInstaller();
InstallContext Context = new InstallContext(logPath, null);
myServiceInstaller.Context = Context;
myServiceInstaller.ServiceName = “SelfInstallService”;
myServiceInstaller.Uninstall(null);
success = true;

}
catch (Exception ex)
{

System.Windows.Forms.MessageBox.Show(ex.Message, “Uninstall service”, System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error);

}
return success;

}

}

You might notice I use a custom form ‘InstallerForm’ here which I’m not showing the code for. All it is is a simple Windows Form that allows you to enter or choose some settings that this code is using. I’ll upload some code example a bit later.

Now, you can simple go to the command line (or create a batch file) that calls the service exe with either the “-install” or “-uninstall” parameters.

Update: I’ve added some sample code here. There have been some improvements since the article. It turns out the overridden method OnBeforeInstall is not necessary at all.

In a new blog post I’ve enhanced the functionality to include multi instance functionality. See this.

EventScavenger 4.2

I recently had the ‘pleasure’ of having my EventScavenger tool being used to track the security event logs of a couple of domain controllers for a ‘full’ enterprise company. It may be temporary for us to help a specific issue but in the process I had to re-look at how the collector service (the heart of EventScavenger) performs under really high stress. In case you don’t fully appreciate it, with full auditing for successful and failed events these event logs full up within an hour or so – and they have been set to something like 100MB+! Events literally flood into the logs at (sometimes) 10000 plus events in a single minute.

Now, I’ve never had any performance problems with the collectors until now. I’ve had a single collector running on a plain old pc gathering around 50 logs or more running 24×7 for 4-5 years. In all those cases each individual event log never have more than 20MB of data in them and almost never full of data that gets overwritten within an hour or so. The problem with the domain controller event logs are the sheer volume of events written ‘all the time’ plus the collector is running on a separate pc with the sql database on a remote system that is shared with dozen of other systems. The end result is that events retrieved from the logs cannot (1) be read quickly enough and (2) written quickly enough to the database. The first problem is not something I can do anything about now – other than having a collector installed on each of the domain controllers. This is not feasible as it won’t be allowed and they are Windows Core machines anyway.

To address the second issue I did some research into how to submit batches of records to the database since doing it one by one just isn’t fast enough. I know about the new table variable in newer versions of sql server but unfortunately the server that was provided is only sql 2005 which does not support that. Fortunately it does support the xml data type even as a parameter. I found an article ‘Sending Multiple Rows to Database for Modification‘ on codeproject that specifically deals with sql 2005 that describes 2 ways to accomplish this: using a delimited string or the xml data type. The delimited option won’t work for me as it is limited to about 4000 characters and the amount of data I deal with is orders of magnitude more than that. The xml data type allows for up to 2GB in a single shot!

I did quite a few performance tests and there is a real increase in number of records that can be processed using batches – despite the possible overhead of first converting the rows to xml and then sql server converting it back to a table (in memory). Interestingly, increasing the batch size beyond a certain point (1000 as I tested) does not increase the overall throughput. It more or less stay linear. The problem with that is you increase the chance that a single failure caused by one row can make the whole batch fail. So the best is to have a batch size that is big enough to warrant the benefit but not too big so it can cause failures.

Example code

A basic example of the sql xml handling:

Declare @xml  XML
SET @xml = N’
<rs>
<r cn=”machineName” l=”logName” etc />

</rs>’
SELECT
T.Row.value(‘@cn’, ‘VARCHAR(255)’) MachineName,
T.Row.value(‘@l’, ‘varchar(255)’) LogName
FROM   @xml.nodes(‘/rs/r’) AS T(Row)

To generate the xml on client side (simplified code):

System.IO.MemoryStream outstream = new System.IO.MemoryStream();
using (System.Xml.XmlTextWriter wrt = new System.Xml.XmlTextWriter(outstream, Encoding.UTF8))
{

wrt.Formatting = System.Xml.Formatting.Indented;
wrt.Indentation = 1;
wrt.IndentChar = ‘ ‘;
wrt.WriteStartDocument();
wrt.WriteStartElement(“rs”);

foreach (EventLogEntry ele in eventEntries)
{

wrt.WriteStartElement(“r”);
wrt.WriteAttributeString(“cn”, machine);
wrt.WriteAttributeString(“l”, logName);


wrt.WriteEndElement(); //r

}

wrt.WriteEndElement(); //rs
wrt.WriteEndDocument();
wrt.Flush();

outstream.Position = 0;
InsertEventLogEntries(outstream);

}

public InsertEventLogEntries(System.IO.Stream xml)
{

string sql = “InsertEventLogEntries”;
System.Data.SqlTypes.SqlXml sxl = new System.Data.SqlTypes.SqlXml(xml);
SqlParameter[] parms = new SqlParameter[] {
new SqlParameter(“@xml”, SqlDbType.Xml) { Value = sxl}
};
using (SqlCommand cmnd = new SqlCommand(sql, insertConn))
{
cmnd.CommandType = CommandType.StoredProcedure;
cmnd.CommandTimeout = CommandTimeout;
cmnd.Parameters.AddRange(parms);
try
{

cmnd.ExecuteNoQuery();

}
catch { throw; }

}

So with this change it is possible to handle larger sets of inserts into the database. Now only if there was a way to make reading an event log faster…

Scheduled tasks and restarting services

I recently had the ‘pleasure’ to have some of my maintenance scripts that restart some ‘Windows Services‘ stopped working since ‘other’ people installed stuff that creates new dependencies. The problem is that the normal commands like ‘net stop’ or ‘sc start’ are not aware of dependent services and simply fail to perform their task if the dependent service is still running. Additionally, the built in task engine for Windows does not really have much in the line of useful logging and error reporting. Most of the time you have to ‘guess’ what went wrong 😉

.Net to the rescue

Fortunately I knew from past experience that the .Net framework’s ServiceController class has the Stop and Start methods that do take into account the dependencies and also try to stop or start them as needed. So all you need is to create what is essentially a wrapper class to call these methods. Additionally you have full access to errors and can log them in any way you like. As a side bonus it might be useful to have this tool also handle batches of requests since you usually want to stop and start a bunch of related services at the same time.

To keep things simple the tool simple accepts one parameter that – a file name for the file that contain a list of the action ‘service name’s. e.g.

stop .\Winmgmt
start .\Winmgmt

Then it is a simple matter of reading the file line for line using File.ReadAllLines() method and using the first part of the line to see if the service must be stopped or started. I like things that are simple and ‘just works’.

The code also makes provision that it can stop/start services on other machines – provided the account it runs under has the right privileges. The rest of the code are there just for error handling and logging.

The code

static void Main(string[] args)
{

…initial stuff

string[] lines = File.ReadAllLines(args[0]);
foreach (string line in lines)
{

ProcessLine(line);
System.Threading.Thread.Sleep(new TimeSpan(0,0, waitBetweenServiceChangeSec));

}

}

private static void ProcessLine(string line)
{

string action = “”;
string machineName = “.”;
string serviceName = “”;
if (line.Split(‘ ‘).Length > 1) //make sure there is an ‘action’ and service name
{

action = line.Split(‘ ‘)[0].Trim().ToLower();
serviceName = line.Substring(line.IndexOf(‘ ‘)).Trim();
if (serviceName.IndexOf(‘\\’) > -1)
{

machineName = serviceName.Substring(0, serviceName.IndexOf(‘\\’));
serviceName = serviceName.Substring(serviceName.IndexOf(‘\\’) + 1);

}

if (action == “start”)

StartService(serviceName, machineName);

else if (action == “stop”)

StopService(serviceName, machineName);

}

}

private static void StopService(string serviceName, string machineName)
{

ServiceController sc;
ServiceControllerStatus resultingStatus = ServiceControllerStatus.Stopped;
try
{

LogProgress(string.Format(“Stopping the service {0}\\{1}”, machineName, serviceName), EventLogEntryType.Information, 2);
if (machineName == “.”)

sc = new ServiceController(serviceName);

else

sc = new ServiceController(serviceName, machineName);

if (sc.CanStop && sc.Status != ServiceControllerStatus.Stopped)
{

sc.Stop();
sc.WaitForStatus(resultingStatus, new TimeSpan(0, 0, waitTimeSec));
sc.Refresh();
if (sc.Status != resultingStatus)
{

LogProgress(string.Format(“The service {0}\\{1} did not stop properly in the time allowed!”, machineName, serviceName), EventLogEntryType.Warning, 2);

}
#if DEBUG
LogProgress(string.Format(“The service {0}\\{1} stopped sucessfully”, machineName, serviceName), EventLogEntryType.Information, 2);
#endif

}

}
catch (Exception ex)
{

LogProgress(string.Format(“Error stopping service {0}\\{1}: {2}”, machineName, serviceName, ex.Message), EventLogEntryType.Error, 2);

}

}

private static void LogProgress(string message, EventLogEntryType elet, int eventId)
{

Console.WriteLine(message);
EventLog.WriteEntry(eventLogSource, message, elet, eventId);

}

I’m not showing the StartService method as it is basically the same as the StopService method (with one or two extra ‘if’s).

Alternatives

I considered powershell (for 0.834 seconds like Data would have said) but it is only really useful if you run them manually since you have to ‘sign’ them and jump through a million hoops to get it working in a scheduled ‘hands-off’ way. Yes I know this was done to make it more ‘secure’ and avoid malicious stuff but it also makes it useless to solve a simple problem. With the amount of trouble you have to go through to get it working you might as well create a proper app like I did here to solve the issue.

Find a copy here.