Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

The Anti-Service, Persistence sans System.ServiceModel

0.00/5 (No votes)
9 Sep 2010 1  
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 and Timers
  • 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 sets CanClose 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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here