0

I have a windows service that I would like to be automatically and silently updated. I started using wyBuild to implement this, but have had some issues with it, and decided to try to build my own. I've written a standalone exe that can be called to do the update procedure: checks for a new zip file with the update, downloads it, unzips, stop the windows service, copy files from the zip, then restart the service. This exe works fine when I run it from the commandline and wasn't really difficult to write.

However, now I would like the service (the same one being updated) to shell out to the updater exe to update itself. I first tried Process.Start:

var proc = Process.Start(pathToUpdaterExe);
proc.WaitForExit(60000);

This called the updater, but when the updater stops the service, the process is killed and the update stops. I did some searching and it sounds like the solution is to use a separate AppDomain. This is what I have now:

Evidence baseEvidence = AppDomain.CurrentDomain.Evidence;
Evidence objEvidence = new System.Security.Policy.Evidence(baseEvidence);
AppDomainSetup setup = new AppDomainSetup();
var updateDomain = AppDomain.CreateDomain("updateDomain", objEvidence, setup);
updateDomain.ExecuteAssembly(updater);
AppDomain.Unload(updateDomain);

However, now I get the error System.IO.IOException: "The process cannot access the file 'C:\Program Files (x86)\Company\Service\Service.dll' because it is being used by another process" when attempting to copy over the new Service.dll

Again, I've stopped the service at this point. I've confirmed this with logging. I can't imagine what would have Service.dll still locked, so I added code to check to see what is locking it:

 public static IEnumerable<Process> GetProcessesLocking(string filePath)
    {
        var result = new List<Process>();

        result.Clear();
        var processes = Process.GetProcesses();
        foreach (Process proc in processes)
        {
            try
            {
                if (proc.HasExited) continue;
                foreach (ProcessModule module in proc.Modules)
                {
                    if ((module.FileName.ToLower().CompareTo(filePath.ToLower()) == 0))
                    {
                        result.Add(proc);
                        break;
                    }
                }
            }
            catch (Exception ex)
            {
                Log(ex.ToString());
                Log("There was an error checking " + proc.ProcessName );
            }
        }
        return result;
    }

However this code indicates that nothing has a lock on the dll (result is empty and nothing is logged indicating an error).

I suspect I'm running afoul of some UAC issue that is the real cause of the IOException. The windows service runs as LocalSystem. All that to ask: How should I be running the update exe from the windows service such that it has rights to copy files in c:\Program Files?

Update

As the comments and answer suggest, Process.Start can work, but there is some nuance. You have to start cmd.exe and use it to start the updater. I also found I could not use a full path for the updater exe and that I needed to set UseShellExecute=false. This is my final working code that launches the updater from the .NET service:

 var cmd = "/c start updater.exe";     
 var startInfo = new ProcessStartInfo("cmd.exe");
 startInfo.Arguments = cmd;
 startInfo.WorkingDirectory = AssemblyDirectory;
 startInfo.UseShellExecute = false;
 var proc = Process.Start(startInfo);
Daniel
  • 3,021
  • 5
  • 35
  • 50
  • Completely off the top of my head, but why not just have the service create a scheduled task to start in 5 seconds time? That way the process that initiates the update and the actual processing of the update are completely disconnected – Brendan Green Sep 02 '15 at 22:28
  • When this happens, can look up the DLL name ("find handle") in Process Explorer to see who still has a handle open to the DLL? Or record such a session in Process Monitor. Just find the real culprit. You don't know whether starting 5 seconds later will be more successful. – Thomas Weller Sep 02 '15 at 22:37
  • 1
    Your 1st solution could work. Read http://stackoverflow.com/questions/1035213/process-start-and-the-process-tree to see how to keep the updater alive. – Richard Schneider Sep 02 '15 at 22:39

1 Answers1

1

I did this exact thing - using a simpler (some might say kludgy) approach. The service:

  1. Produces a batch command,
  2. Downloads the new executables to a staging location,
  3. Starts a process: cmd.exe which, in turn, runs the batch script w/o waiting for it to complete, and then
  4. Immediately terminates itself.

The batch command:

  1. Pings 127.0.0.1 five times,
  2. Copies the executables to the final location and,
  3. Restarts the service.

Works like clockwork. The ping is a reliable 5 second delay - lets the service shutdown before copying the files.

Edit:

Just for completeness - I realized that by batch cmd is pinging 127.0.0.1 not 128.0.0.1 and so I edited this answer to reflect that. I suppose either works - but 128.0.0.1 pings timeout, where 127.0.0.1 resolves to "me". Since I'm only using it as a poor-man's delay, it serves the purpose either way.

Clay
  • 4,999
  • 1
  • 28
  • 45
  • It looks like using Process.Start with cmd.exe is key here. Process.Start("cmd.exe", "/c updater.exe") does what I want. My process is a bit different - instead of a batch it uses a .net console app, and the console app does the check for updates, stopping/starting the service, and applying update. There is some other nuance I ran into - I will update my question with the code I'm using. – Daniel Sep 03 '15 at 16:30
  • Excellent - glad that worked for you. Getting it cleanly out from your process makes it much easier. – Clay Sep 03 '15 at 18:37