views:

146

answers:

4

We have built a Windows Service that is running on client's machines, which occasionally downloads a newer version of itself, and then performs a self-update: installs a new service, starts it, stops the old one, and eventually deletes it. The service cannot stop itself directly and do other stuff, so it spins another executable, which does some of the work. getting this right is tricky, and this is particularly bad when the newer service is built using a newer .Net Framework (such as the recent switch from .Net 2.0 to .net 4.0). The problem is that a .Net 2.0 library cannot operate on a .Net 4.0 service.

Now, one approach would be to have the older version of the service spin a helper program which comes with the newer version, but ... what if the behavior of it HAS to change in a breaking way? I feel safer about not mixing up the versions, even if some things usually stay the same - helps to keep the design complexity down.

Now, there appears to be a .Net version-neutral way of operating on Windows Services: the sc.exe tool: http://support.microsoft.com/kb/251192

I wonder if it is indeed the silver bullet that I was seeking. Now, because I will be calling this guy programmatically, and checking for errors, then I might as well use an API for this. Ideally I would have one native C++ project, which compiles to a native exe, which interfaces with SC.exe. Is this possible? If not, then how can I go about locating sc.exe on different computers? They could be 32-bit or 64-bit, and running any version of Windows starting with Win XP SP2/3.

Let me know if you have questions about my question, or some ingenious idea, or an answer to the exact question that I posed here.


Edit: If I try to install a 4.0 service using 2.0 code, I get the same error as if I do:

> C:\Windows\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe MyService4.exe

Microsoft (R) .NET Framework Installation utility Version 2.0.50727.4927 Copyright (c) Microsoft Corporation. All rights reserved.

Exception occurred while initializing the installation: System.BadImageFormatException: Could not load file or assembly 'file:///[path]\MyService4.exe' or one of its dependencies. This assembly is built by a runtime newer than the currently loaded runtime and cannot be loaded..

+1  A: 

I'm not sure what you mean by this:

The problem is that a .Net 2.0 library cannot operate on a .Net 4.0 service.

The ServiceController class is capable of controlling any service, even native ones. I haven't tried it, but I believe that the ServiceInstaller class could be used to (un)install any service as well.

If ServiceController and ServiceInstaller don't meet your needs, I'd recommend wrapping the native API directly instead of sc.exe. The MSDN docs should get you started; you'll be needing the Service Control Manager functions. The sc.exe program is just a thin command-line wrapper around the SCM API.

Stephen Cleary
"If ServiceController and ServiceInstaller don't meet your needs" - you are right, they do not. The `ServiceInstaller` library that I am using is equivalent to using the installutil.exe that comes with a particular .net Framework. If you try using the 2.0 tool to install a 4.0 service, then you will get an error/exception. I will provide details in my original question. Please do elaborate on other sections of your answer. I have never messed with Windows API, so I am not sure what SCM API is, for instance. I ran out of up-votes today :)
Hamish Grubijan
@Stephen, I put up a bounty on this question, by the way. I usually have follow-up questions - the goal is not always to have a single answer, but to help me explore and evaluate an option. More work than usual, but that is what bounty points are for.
Hamish Grubijan
@Hamish: The MSDN docs link in my answer takes you to the SCM API part of MSDN that discusses controlling services (including installing/uninstalling them). SCM API = "Service Control Manager"; sc.exe = "Service Control"; `ServiceController` = Service Control. sc.exe and the SCM API are functionally equivalent, but `ServiceController` does not include all of their functionality. Hence I recommend using p/Invoke to wrap the SCM API.
Stephen Cleary
+1  A: 

As part of the update package include an executable with a name that doesn't change between versions, e.g. Setup.exe :-)

This tool then does all of the setup stuff when launched as a new process by the update logic. It should be compiled using the same version of the Framework as the rest of the package to avoid CLR versioning issues.

The biggest issue I've encountered with the existing ServiceInstaller class is the lack of support for newer service features such as a description and failure/recovery actions. To get around that I've tweaked Narendra (Neil) Baliga's code from CodeProject and use that ServiceInstallerEx class instead. His code includes the required p-invoke definitions to call the same Windows APIs that SC.exe does.

Somewhat related: if your update requirements are similar to those I've used before, I'd recommend installing to side-by-side folders (whose names include the version/release number), and keep the last 2 or 3 to allow for rollback. Pass the path of the current version to the setup tool so it knows the current state of play.

devstuff
Damn, sounds like I need some reading to do. So ... are there any advantages to building this `Setup.exe` in .Net at all? I know, it manages the memory for you, and has safer exceptions, etc. but I am probably looking at 200 lines of C++ code max. Because ServiceInstaller ends up throwing (inner) Win32Exceptions when something goes wrong, it seems to me that it is nothing but the wrapper to a Win32 API, which also lets me do less things that a C++ app would. Also, why does side-by-side issue come up?
Hamish Grubijan
Finally, we do not want to give the end users too much power - the end product is is not an app that costs 29.99 and can be run by anyone. The service downloads stuff which is specialized, and is licensed for a good amount of money. End users are usually not IT-savvy. Just in case I do not think we want to have severals versions of the same service installed. A reboot can start those. We are willing to take a risk that a service would not be running at all at some point (due to an exception). In that case having a good log entry is better than a clever recovery scheme, also easier(test,maint).
Hamish Grubijan
Sure, a simple C++ app can do it. I mentioned a C# solution since your question is tagged `.NET`. The side-by-side installion avoids overwriting a working installation if a setup error occurs. Only 1 service is actually referenced by the SCM, just different executable paths.
devstuff
For recovery I normally setup my services to just restart on failure after a minute or 2. My services normally exit for hard-to-recover scenarios such as network dropouts, etc.
devstuff
+1  A: 

sc.exe is actually always at the same path: %windir%\System32\sc.exe (x64 uses System32 for 64-bit binaries :-). Don't know how your current setup looks like, but if you have, or can switch to MSI - it can run custom install action which can be your new .exe with a config file either going for SCM API or just launching sc.exe 2-3 times to do stop, config, start. Technically you could even use .cmd as a custom install action in MSI but it may look a bit ugly (user seeing cmd window coming up).

If you want to go for SCM API and you know your C++, that would definitely be the best way to go - no layers in-between to mess something up. Also, once you have separate binary just for install tasks you are not bound by .NET's embedded installation procedure - you can reach for the same NT API from C# and C++. The key is that the binary that's doing admin tasks to stop, start, config etc. the service has to be different from the binary for the service itself - that's .NET's problem - lumping up.

Since you are doing a major switch, if your prior install isn't under MSI then a new MSI won't be able to handle everything on it's own, so you may want to literally drop a .cmd file to just stop and clean up the old service by using sc.exe, just to start clean, and then start new install binary - whatever it is. MSI would stop and unistall window service but only if it was previously installed by MSI. It's kind of sticky that way :-)

ZXX
+1  A: 

Your native C++ app can call the functions documented here: http://msdn.microsoft.com/en-us/library/ms685942(v=VS.85).aspx (the SCM API that others have mentioned)

You'll want to do something along the lines of

OpenSCManager
OpenService (or CreateService)
ChangeServiceConfig
CloseServiceHandle
Bob