The Anti-Service, Persistence sans System.ServiceModel





5.00/5 (2 votes)
When you want an application to trigger on an event (timer, system, file, etc.), but you don't want the overhead of the service manager

Introduction
I was asked to create a small reminder application for an organization I belong to. Ever hour it would prompt with a quote. My "work" answer for something like this would be to create a Windows service, make it self installing, easy peazy. But I wanted to be adventurous, because this is mostly U/I and I didn't want to mess with the service user account, how could this be done without a service? This article outlines this adventure...
Background
The basic components to this project are:
- Windows (WinForms in this case, but WPF will work too),
OnClose
andTimers
- Sockets/Listeners for forcing single instance and "killing" the application
- InnoSetup Script
Using the Code
- Create a new Windows project (New Project... Windows)
- Create a timer. Drag and drop a timer object, set the parameters, create a timer handler. Your project may not be on a timer, that's ok, if an event triggers the window to show/hide, that's all you want
- Create a "Force Close" mechanism, I created a
checkbox
called "CanClose
" - Override
FormClosing
private void TheMessage_FormClosing(object sender, FormClosingEventArgs e) { e.Cancel = !CanClose.Checked; this.Hide(); }
- Create a separate
ForceClose()
method which setsCanClose
and calls "Close
"public void ForceClose() { CanClose.Checked = true; Close(); }
- Sockets and threading. This is where it gets interesting. So I wanted the behavior that the application should be single instance. There are several good ways to do that, the mutex here on CodeProject is great. But, I also needed it to install/uninstall cleanly, which means it needs to force itself closed. To do that, I used sockets listener/client on a dedicated port. It sounds counter intuitive, but I created the client first (is that the chicken or the egg?!)
public void Main(string[] args){ ... bool killStream = args.Length > 0 && args[0] == "-kill"; try { using (TcpClient clnt = new TcpClient()) { clnt.Connect("localhost", 7231); if (clnt.Connected) { clnt.GetStream().Write(new byte[] { (byte)(killStream ? 0 : 1) }, 0, 1); // 0 - kill or 1 - show clnt.Close(); return; } } } // I really hate catching an exception as logic, but there's no other way catch (SocketException) { } // nothing listening, not already running if (killStream) // if it's a kill... don't start return;
Finally the listener...
TcpListener lstn = new TcpListener (System.Net.IPAddress.Parse("127.0.0.1"), 7231); lstn.Start(); // So, we start a long loop listening for incoming messages while (true) { using (TcpClient clnt = lstn.AcceptTcpClient()) { byte[] buff = new byte[1]; if (1 == clnt.GetStream().Read(buff, 0, 1)) { switch (buff[0]) { case 0: // Kill request msgWindow.ForceClose(); return; case 1: // Show request msgWindow.Show(); continue; } } } }
- And finally, you need an InnoSetup Script that will invoke this before attempting to re-install/uninstall, that looks like this...
[Files] Source: "{Your Project}\bin\Release\AntiService.exe"; DestDir: "{app}"; Flags: ignoreversion; BeforeInstall: StopLBApp ... [Code] procedure StopLBApp(); var FileName : String; ResultCode: Integer; begin Log('Asking any existing processes to stop now'); FileName := ExpandConstant('{app}\AntiService.exe'); if FileExists(FileName) then begin if not ShellExec('', FileName, '-kill', '', SW_SHOWNORMAL, ewNoWait, ResultCode) then MsgBox('DeinitializeSetup:' #13#13 'Execution of ''' + FileName + ''' failed. ' + SysErrorMessage(ResultCode) + '.', mbError, MB_OK); end; end;
Summary
So what you have is an application that once it's started, will stay running, and will attempt to show a window every time it's launched or on some event. Timer in my case. I hope you have enjoyed!
History
- 9th September, 2010: Initial post