views:

55

answers:

2

I currently maintain an internal application in .Net. The application uses IPC to maintain one running session at a time; if another session attempt to open (Someone clicks the icon again, some opens up a saved result file), the second session communicates this to the first session and then terminates, with the first session then launching the requested action.

I currently do this using the the System.ServiceModel namespace, like so:

namespace EventLogViewer.IPCServer { //Provides the common framework for processes to communicate to oneanother.

[ServiceContract(Namespace = "http://ELV.domain")]
interface IELVLauncher
{
    [OperationContract]
    bool LaunchTabs(String[] fileNames);
}
public delegate void TabLauncher(String[] fileNames);
class ELVLauncherServer : IDisposable
{
    ServiceHost ipcService;
    public ELVLauncherServer()
    {
        Uri baseAddress = new Uri("Http://localhost:45600/elvlauncher/service");
        String address = "net.pipe://localhost/elvlauncher/tablauncher";

        ipcService = new ServiceHost(typeof(ELVLauncher), baseAddress);

        NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
        ipcService.AddServiceEndpoint(typeof(IELVLauncher), binding, address);

        ServiceMetadataBehavior behavior = new ServiceMetadataBehavior();
        behavior.HttpGetEnabled = true;
        behavior.HttpGetUrl = new Uri("http://localhost:45601/elvlauncher");
        ipcService.Description.Behaviors.Add(behavior);
        Utilities.WriteMemoryDebugStatement(new DebugStatement(DebugStatement.StatementType.INFO, "Registering IPC Service"));
        ipcService.Open();

    }

    #region IDisposable Members

    public void Dispose()
    {
        ipcService.Close();
    }

    #endregion
}
public class ELVLauncher : IELVLauncher
{
    #region IELVLauncher Members

    public bool LaunchTabs(string[] fileNames)
    {
        try
        {
            Utilities.WriteMemoryDebugStatement(new DebugStatement(DebugStatement.StatementType.INFO, String.Format("IPC request received, files: {0}", String.Join(", ", fileNames))));
            Program.mainWindow.backgroundListener_OnLaunchRequestReceived(fileNames);
        }
        catch (Exception exception)
        {
            Utilities.WriteMemoryDebugStatement(new DebugStatement(exception));
        }
        return (true);
    }

    #endregion

}

This code was originally developed under Windows XP, as like most corporations we did not move to Vista. Since we all run as local admins, this code ran without a problem.

However, we are transitioning to Win 7 now, and this code produces an exception on startup because it cannot register the IPC endpoint. More information is here: http://mark.michaelis.net/Blog/WindowsCommunicationFoundationWithWindowsVistaAndUAC.aspx

The workaround given is to up the app to requiring admin privledges. This doesn't work for two reasons:

  1. This app does not need admin privledges otherwise
  2. We use ClickOnce for deployment of our internal tools and ClickOnce does not support anything other than running as the process launcher.

So at this point I need to find an IPC solution which does not require admin privledges and lets me achieve the original goal: detecting an already running instance of the code and telling it what to do, otherwise launching itself. Any suggestions on what to do (within the .Net framework, no 3rd party solutions please) would be much appreciated.

+2  A: 

That is an incredibly difficult (and as you have discovered, problematic way) to ensure a single session of your app. You should just refactor/redo the feature to use a mutex.

Here is another example. And the requisite wiki article about mutexes.

EDIT

To get around the communication bit you could generate a temp file with your own special prefix and or extension in OS-provided temp folder which the single app would poll (filtering for your special prefix and/or extension) to see whether more requests were made. See:

System.IO.Path.GetTempFileName 

and

System.IO.Path.GetTempPath
Paul Sasik
bonus: A Mutex can be created without Admin privileges.
Cheeso
@Cheeso: exactly. i guess i should be explicit about that in the answer.
Paul Sasik
Thanks, but this only solves half the problem. The second process has to tell the first what it was going to do (launch, load a saved report). Mutexes do not allow for that.
Dan
@Dan: How about putting launch requests into an identifiable temp location that can be polled by the app at some interval to see if such a request was made?
Paul Sasik
@Paul: I'm not a huge fan of filesystem polling, but if nobody has another solution for a memory-based approach, then I guess that's what I'll have to do.
Dan
@Dan: Me neither. But in this case i think it'll be an ok solution. i'm guessing that you will see at most two dozen dozen such files in a day of app sessions on a machine. If it was much higher-rate communication (several messages per second) then i'd say figure out the security issues with IPC.
Paul Sasik
@Dan: By your rep count i was gonna say "welcome to SO." But i see you've been a member for 10+ months. You should come back more often, to ask and to contribute. This site is an incredibly good resource and dialog like ours above makes it better, IMO.
Paul Sasik
How do programs like Firefox, etc., do it? You click the icon, and it opens a new window using the current process; you click an HTML file, and it opens it in a new tab or new window, still in the same process. In fact, a large amount of application software does exactly that... do they all implement their own unique methods of doing so, or is there some API call that can redirect events like that from a new process to the old one, so that the new one isn't formed? ...Or maybe I've misunderstood the question, and there are more difficulties to keeping a single session than a single process.
JAB
A filesystem file is not what you want. What I do in my apps is use WM_COPYDATA. The flow is: app starts up, checks mutex. If mutex exists, send a message to it. If no mutex, create it. The "listen for message" is implicit via a custom WndProc(). See code here: http://boycook.wordpress.com/2008/07/29/c-win32-messaging-with-sendmessage-and-wm_copydata/
Cheeso
+1  A: 

Definately use a named mutex to limit the application to a single instance, then you can use NamedPipeServerStream and NamedPipeClientStream to do your IPC which would not require admin rights.

Chris Taylor
Thanks, this looks like the route I'll go.
Dan