Home > Installshield, Installshield Change Requests, Software Deployment > Suppress FilesInUse dialog on uninstall for services

Suppress FilesInUse dialog on uninstall for services

MSI 4.0 (Vista+) introduced the Restart Manager (RM) that evaluates file locks during the InstallValidate step in the execute sequence.

There’s a chicken and egg problem here.  It evaluates file locks and displays the FilesInUse dialog to the user, and THEN runs our custom action to do the /unregister type custom actions on the files to stop running services. It’s not possible to run our /unregister CA before InstallValidate because the condition Remove=”ALL” isn’t set yet, so there is no way to know when we’re uninstalling.

I initially thought this was because we’re not following ‘best practices’ by having the msi handle registering services with the ServiceControl table, but there are a TON of posts on the Internet complaining that even with the ServiceControl table, it’s still showing the FilesInUse dialog. http://www.packagedeploy.com/forum/platformsdk-msi/stop-services-before-installvalidate-52/

There’s some talk of RM being buggy:
http://community.flexerasoftware.com…d.php?p=399798
http://community.flexerasoftware.com…d.php?p=415694
http://www.packagedeploy.com/forum/p…llvalidate-52/

http://social.msdn.microsoft.com/forums/en-US/windowscompatibility/thread/21cf9eca-8e45-4e5d-8cf1-a3f88bed4051/

What’s more is that our service relies on assemblies in the GAC and according to the Installshield documentation, that’s a no-go with the ServiceControl table:

“Services that rely on the presence of an assembly in the Global Assembly Cache (GAC) cannot be installed or started using the ServiceInstall and ServiceControl tables. If you need to start a service that depends on an assembly in the GAC, you must use a custom action sequenced after the InstallFinalize action or a commit custom action. For information about installing assemblies to the GAC see Installation of Assemblies to the Global Assembly Cache.”

Qoute from Installshield forums:

“..it seems that InstallShield detects the “conflict” during InstallValidate (without taking the scheduled StopServices action into account), but does not perform the actual stop until StopServices. Furthermore, InstallShield won’t allow the StopServices action to be scheduled earlier than InstallValidate. The most reasonable solution I have been able to think of so far is to write my own custom action to stop the service..”-

http://community.softsummit.com/showthread.php?p=453933

So that’s just what I attempted to do.  I wrote a managed CA to manually stop our services before reaching the InstallValidate step.  Because of the chicken/egg problem mentioned above, I had to schedule this during the UI Sequence at the only location possible:  After MainteneceWelcome (This is when the _ISMaintenance=”Remove” is set.)
Yes, that means this only gets run during attended install and will not run on silent installs.  But that’s not a problem because RM forces close by default for /qn installs:

“When a silent install is performed, Windows Installer will use restart manager to detect the processes that need to be shutdown to mitigate the reboot and will request restart manager to automatically shutdown these processes.”

http://blogs.msdn.com/b/hemchans/archive/2006/09/15/756728.aspx

In testing, the UI Sequence CA to stop our running services worked in that it suppressed the FilesInUse dialog, but not reliably.

I wrote the following C# as a DTF custom action.  (DTF rocks,.. here’s a primer if you need one: http://blog.deploymentengineering.com/2008/05/deployment-tools-foundation-dtf-custom.html)

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Deployment.WindowsInstaller;
using System.Windows.Forms;
using Microsoft.Win32;
using System.IO;
using System.ServiceProcess;
using System.Threading;

namespace ManagedCustomActions
{
    public class CustomActions
    {
        [CustomAction]
        public static ActionResult STOP_ALL_SERVICES(Session session)
        {
            // Attempt to stop all services.
            // Originally written to supress FilesInUse dialog on uninstall

            string[] serviceNames = new string[] {"ServiceName1",
                                         "ServiceName2",
                                         "ServiceName3"};

            foreach (string serviceName in serviceNames)
            {
                try
                {
                    if (ServiceExists(serviceName))
                    {
                        // notify user of current action
                        string message = "Attempting to stop service: " + serviceName;
                        if (session != null) { session.Log(message); }
                        else { MessageBox.Show(message); }

                        // attempt to stop the service
                        ServiceController service = new ServiceController(serviceName);
                        if (service.CanStop) { service.Stop(); }
                        // wait up to X sec for service to stop
                        TimeSpan timeout = TimeSpan.FromSeconds(60);
                        service.WaitForStatus(ServiceControllerStatus.Stopped, timeout);
                        service.Dispose();
                    }
                }
                catch
                {
                 return ActionResult.Failure;
                }
            }
            // above code removed FileInUse dialog, but installer was still showing "reboot" prompt.  Adding timeout was the only solution.
            Thread.Sleep(20000);

            return ActionResult.Success;

        }}}

The FilesInUse dialog went away, but was replaced with a prompt directing the user to restart after the install.  The only way to suppress this new dialog  was to add a 10 second Thread.Sleep().  This is not reliable!  (I’d also like to add that I added a File.Move() method and I was able to move the file just fine, so there were no file locks, even though RM is reporting one)

If I knew what RM was checking to determine file locks, I could at least make my code check for the same thing and only then give control back to the installer.

Exhausted, I went back to the less than optimal solution of completely disabling RM.  Rather than simply adding it as a property to have it globally turned off by default, I set the property at run time where we know it doesn’t work for us; uninstall:


The following custom action supresses the FilesInUse dialog only on UI uninstalls.

Property-Set custom action (type 51):

Install UI Sequence: After MaintananceWelcome

Install UI Condition (_IsMaintenance=”Remove”)

Property Name: MSIRESTARTMANAGERCONTROLProperty Value: Disable

http://msdn.microsoft.com/en-us/library/Aa370377

Keep in mind, this was for a service.  If you have an application that won’t close, it’s most likely that the Restart Manager API calls need to be added to the source code of the application.  You can find information on that at Codeproject.com and AdvancedInstaller.com.

Additionally, here are the guidelines as provided by Microsoft for a service: http://msdn.microsoft.com/library/aa373653.aspx

Advertisements
  1. jpwilliams990
    March 16, 2011 at 1:54 pm

    Luckily I (currently) don’t have to deal with installing services. I’ve had a similar issue with a past job though (using windows server). We went about it pretty different. Maybe I’ll recall my notes and blog about it one day. I like the solution you came up with though.

  2. Ferdi
    March 17, 2011 at 8:15 pm

    Nice article, perhaps you should make your conditions component-based, we’re doing this usually because of our underlaying merge modules.

    I’m also using service (unmanaged) CAs for stopping managed services, so I can give you the advice to kill the service process after the stop, because there still can be something in the threadpool after the service stop.

    Regarding best practices, we should not change anything on the machine during UI sequence? How do you handle Rollback in your installer? I think you’re leaving the transactional idea of installation if you manipulate services during UI with fire-and-forget actions.

    These days I was very deep in this matter, perhaps you can tell me why a (standard action or custom action, doesnt matter) (managed) service stop and afterwards uninstall leads to a “disabled” startup type, but without deletion of the service from the list, during repair (and upgrade, and so on)? #
    Normally, workflow of installutil: uninstall stops, disables and deletes the service, but with a service stop before it makes this undesired behaviour.

    I removed the service stop, and placed my process kill after un-installutil. Now everything works fine 😉 but I would really like to get explained from you maestro why this happens, I don’t see the error source

    • Nick Skitch
      April 1, 2011 at 5:26 am

      Hi Ferdi! Thanks for taking the time to share.

      >perhaps you should make your conditions component-based
      Yes, what I provided was for the blog post, but I had added component based conditions.

      >I can give you the advice to kill the service process after the stop
      I was able to move() the file with no problem, so there was no lock on the data on disk. However, I can point out that this seemed to be specific to our managed services (C#) and not our unmanaged (C++) services. The C++ services stopped and exited with no problems and were not displayed on the FilesInUse dialog. What you say is probably on the right track as it pertains to thread-pools, and is likely the cause. Maybe something relating to garbage collection. Someone should investigate this.

      >Regarding best practices, we should not change anything….
      Yes. But ‘best practices’ can only be adhered to when the software works as it was designed. If it does not, as it does not in this case, then we are forced to resort to hacks of sorts. 😦 I don’t want my blog to be about philosophical arguments, I want to find solutions. Let’s be honest, a software architect can only design for the outcomes they can think of, if there’s an exception they didn’t plan for then it’s no longer possible to follow these ‘best practices’.

      Regarding how I handle Rollbacks? Yes, I use differed context for system changes with rollbacks. You’ll see that the solution I used at the end was a simple property change (non system change) so it does follow ‘best practices’.

      >perhaps you can tell me why a (standard action or custom action, doesnt matter) (managed) service stop and afterwards uninstall leads to a “disabled” startup type

      Alas, I’m just a guy. I do not have answers to the mysteries of the universe. um, 42? Sounds like your not using the ServiceInstall table? As a test, can you do an “SC DELETE “? Beyond that, I’m useless. The IS forums are very helpful though.

      Again, thanks for taking the time to share, Ferdi!

  3. Ryan B
    June 10, 2011 at 8:37 pm

    FYI: the links regarding a potentially buggy RM are broken…

    • Nick Skitch
      June 10, 2011 at 9:08 pm

      Thanks Ryan, I’ve fixed the broken links you pointed out.

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: