cool hit counter [C#] Share Win32API based service operation class (solve ManagedInstallerClass.InstallHelper can't install with parameters)_Intefrankly

[C#] Share Win32API based service operation class (solve ManagedInstallerClass.InstallHelper can't install with parameters)


Notes. The services here are Windows Services

------------------201508250915 update------------------

I just learned that the TransactedInstaller class is supported with parameter installation service, thanks to ape friendKOFIP for pointers and codes, please see the response for details.

------------------201506182056 original text------------------

There are probably a few common ways to install a service on the market.

  • Call external tools such as sc.exe, Installutil.exe, etc. with the Process class for installation. Highly discouraged, knowing that creating a process is expensive and relying on external tools is detrimental to the robustness of the program
  • Use the TransactedInstaller and AssemblyInstaller installation classes for installation. Not recommended, why not use a better method when they all use the hosting method
  • Install with ManagedInstallerClass.InstallHelper. This one I think is the preferred method of the managed methods, and in fact it is a wrapper around the two installation classes mentioned above. Also, Installutil.exe uses this method

Previously I had been using the InstallHelper method, but recently I ran into a problem when I needed to install a service, namely the program file (exe) that hosts the service, but also a desktop program with a user interface, which decides whether to run as a service or as a desktop program by passing in parameters to the main method (no discussion here about why not split the service and desktop program into two exes. Also for more information on how to make an exe a service and a desktop application, see the garden Articles by other apes(or I'll write one if I have the time), which requires that when installing the service, you give the image file path with parameters, but InstallHelper doesn't support parameters, and if you barely bring them, it either reports that the file doesn't exist or that the path can't have illegal characters. Looking at the source code for InstallHelper, I see that it will put the entire path and parameters into a pair of double quotes, so that when passed to a lower-level install method, the underlying method will treat the string as a path, which naturally is not a legal path. But I want to use the sc.exe, it is a million reluctant, I am a pursuit of code dick, okay. So after a good look at the InstallHelper implementation, the general set-up is this.

The AssemblyInstaller will be responsible for creating all instances of the classes in the assembly marked with the RunInstallerAttribute feature and adding them to a collection of installers, and finally the TransactedInstaller will execute these installers one by one, which is actually each installer instance executing its own Install method. For the service (ServiceBase class), when you add the installer with VS, a class called ProjectInstaller is automatically generated, which is labeled with the RunInstallerAttribute feature. The ProjectInstaller class itself will carry two instances of installer, belonging to the ServiceProcessInstaller and ServiceInstaller classes, and ProjectInstaller will add these two instances to the above installer collection when it gets executed, so the final execution will be the Install method of these two instances. Where the ServiceProcessInstaller's Install doesn't really perform anything, it just carries some information (like the account to run the service) for the ServiceInstaller's Install to fetch, it's the ServiceInstaller's Install method that actually performs the install service thing.

-- However, the above paragraph is not very useful, and children who don't understand it will have to read the source code and nudge themselves to figure it out. Just remember one thing, the installation of the service is the Install method of System.ServiceProcess.ServiceInstaller works, a bunch of XXXInstaller above are just floating around. And it is the system API CreateService that is called internally by ServiceInstaller.Install to perform the installation of the service. So in the end, going back to its roots, CreateService is the real deal, but of course what it is internally is unknown. Off-topic, an exe in multiple services, ServiceProcessInstaller shall be only one, while ServiceInstaller is each service corresponds to a, look at the name to know that the former is related to the service bearing process, we are in an exe, of course, to share the process settings, the latter is to store the settings of each service, naturally, to correspond to one by one.

Back to the point, after figuring out that InstallHelper ultimately calls CreateService, it's straightforward to see if the latter supports installation with parameters, and the answer is obviously yes (the API Documentation is here ), so I wrote an API-based operation class and the problem was solved.

ServiceController class already has a complete implementation, so I'm in a hurry and have no time to build a wheel, so I'll talk about it later.

Programme source code.

There is quite a lot of code, if you just implement Install will be very little, which is mainly brought about by messing with Uninstall, because before uninstall you have to consider stopping first, and stopping you have to consider stopping the slave service first, so the loop is intertwined, and the API is wrapped one after another, and it becomes like this.

Note: Only the installation of own process services is supported, not shared process services. That is, only the case where only one service is hosted in an exe is supported, and the case where multiple services share an exe is not supported. This is specified by the dwServiceType parameter of CreateService, and Install has been written dead to the SERVICE_WIN32_OWN_PROCESS constant, which is the own-process class service.

using System;
using System.ComponentModel;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;

namespace AhDung
{
     #region Exception Definition

    /// <summary>
     /// Service does not have exceptions
    /// </summary>
    public class ServiceNotExistException : ApplicationException
    {
        public ServiceNotExistException() : base("Service does not exist!") { }

        public ServiceNotExistException(string message) : base(message) { }
    }

    #endregion

     #region Enumeration Definition

    /// <summary>
     /// Service start type
    /// </summary>
    public enum ServiceStartType
    {
        Boot,
        System,
        Auto,
        Manual,
        Disabled
    }

    /// <summary>
     /// Service operation account
    /// </summary>
    public enum ServiceAccount
    {
        LocalSystem,
        LocalService,
        NetworkService,
    }

    #endregion

    /// <summary>
     /// Windows Service Helper Class
    /// </summary>
    public static class ServiceHelper
    {
         #region public method

        /// <summary>
         /// Installation services
        /// </summary>
        /// <param name="serviceName"> service name</param>
        /// <param name="displayName"> Friendly Name</param>
        /// <param name="binaryFilePath"> Image file path, Available with parameters</param>
        /// <param name="description"> Service Description</param>
        /// <param name="startType"> Start Type</param>
        /// <param name="account"> Activate account</param>
        /// <param name="dependencies"> dependent service</param>
        public static void Install(string serviceName, string displayName, string binaryFilePath, string description, ServiceStartType startType, ServiceAccount account = ServiceAccount.LocalSystem, string[] dependencies = null)
        {
            IntPtr scm = OpenSCManager();

            IntPtr service = IntPtr.Zero;
            try
            {
                service = Win32Class.CreateService(scm, serviceName, displayName, Win32Class.SERVICE_ALL_ACCESS, Win32Class.SERVICE_WIN32_OWN_PROCESS, startType, Win32Class.SERVICE_ERROR_NORMAL, binaryFilePath, null, IntPtr.Zero, ProcessDependencies(dependencies), GetServiceAccountName(account), null);

                if (service == IntPtr.Zero)
                {
                    if (Marshal.GetLastWin32Error() == 0x431)//ERROR_SERVICE_EXISTS
                    { throw new ApplicationException(" Service already exists!"); }

                    throw new ApplicationException(" Service installation failed!");
                }

                 // Set the service description
                Win32Class.SERVICE_DESCRIPTION sd = new Win32Class.SERVICE_DESCRIPTION();
                try
                {
                    sd.description = Marshal.StringToHGlobalUni(description);
                    Win32Class.ChangeServiceConfig2(service, 1, ref sd);
                }
                finally
                {
                    Marshal.FreeHGlobal(sd.description); // liberate (a prisoner)
                }
            }
            finally
            {
                if (service != IntPtr.Zero)
                {
                    Win32Class.CloseServiceHandle(service);
                }
                Win32Class.CloseServiceHandle(scm);
            }
        }

        /// <summary>
         /// Uninstallation of services
        /// </summary>
        /// <param name="serviceName"> service name</param>
        public static void Uninstall(string serviceName)
        {
            IntPtr scmHandle = IntPtr.Zero;
            IntPtr service = IntPtr.Zero;

            try
            {
                service = OpenService(serviceName, out scmHandle);

                 StopService(service);  // Stop service. Inside will recursively stop the slave service

                if (!Win32Class.DeleteService(service) && Marshal.GetLastWin32Error() != 0x430) // Ignore services marked for deletion。ERROR_SERVICE_MARKED_FOR_DELETE
                {
                    throw new ApplicationException(" Failed to delete service!");
                }
            }
             catch (ServiceNotExistException) { } // ignore that the service doesn't exist
            finally
            {
                if (service != IntPtr.Zero)
                {
                    Win32Class.CloseServiceHandle(service);
                     Win32Class.CloseServiceHandle(scmHandle);// put if inside because if the service fails to open, the SCM is already released in OpenService
                }
            }
        }

        #endregion

         #region Auxiliary methods

        /// <summary>
         /// Convert account enumerations to valid parameters
        /// </summary>
        private static string GetServiceAccountName(ServiceAccount account)
        {
            if (account == ServiceAccount.LocalService)
            {
                return @"NT AUTHORITYLocalService";
            }
            if (account == ServiceAccount.NetworkService)
            {
                return @"NT AUTHORITYNetworkService";
            }
            return null;
        }

        /// <summary>
         /// Processing of dependency service parameters
        /// </summary>
        private static string ProcessDependencies(string[] dependencies)
        {
            if (dependencies == null || dependencies.Length == 0)
            {
                return null;
            }

            StringBuilder sb = new StringBuilder();
            foreach (string s in dependencies)
            {
                sb.Append(s).Append('');
            }
            sb.Append('');

            return sb.ToString();
        }

        #endregion

#region API Wrapper Methods

        /// <summary>
         /// Open Service Manager
        /// </summary>
        private static IntPtr OpenSCManager()
        {
            IntPtr scm = Win32Class.OpenSCManager(null, null, Win32Class.SC_MANAGER_ALL_ACCESS);

            if (scm == IntPtr.Zero)
            {
                throw new ApplicationException(" Failed to open Service Manager!");
            }

            return scm;
        }

        /// <summary>
         /// Open the service
        /// </summary>
        /// <param name="serviceName"> Service Name</param>
        /// <param name="scmHandle"> Service Manager Handle。 caller liberate (a prisoner)</param>
        private static IntPtr OpenService(string serviceName, out IntPtr scmHandle)
        {
            scmHandle = OpenSCManager();

            IntPtr service = Win32Class.OpenService(scmHandle, serviceName, Win32Class.SERVICE_ALL_ACCESS);

            if (service == IntPtr.Zero)
            {
                int errCode = Marshal.GetLastWin32Error();

                Win32Class.CloseServiceHandle(scmHandle); // closeSCM

                if (errCode == 0x424) //ERROR_SERVICE_DOES_NOT_EXIST
                {
                    throw new ServiceNotExistException();
                }

                throw new Win32Exception();
            }

            return service;
        }

        /// <summary>
         /// Stopping the service
        /// </summary>
        private static void StopService(IntPtr service)
        {
            ServiceState currState = GetServiceStatus(service);

            if (currState == ServiceState.Stopped)
            {
                return;
            }

            if (currState != ServiceState.StopPending)
            {
                 // Recursive stopping of slave services
                string[] childSvs = EnumDependentServices(service, EnumServiceState.Active);
                if (childSvs.Length != 0)
                {
                    IntPtr scm = OpenSCManager();
                    try
                    {
                        foreach (string childSv in childSvs)
                        {
                            StopService(Win32Class.OpenService(scm, childSv, Win32Class.SERVICE_STOP));
                        }
                    }
                    finally
                    {
                        Win32Class.CloseServiceHandle(scm);
                    }
                }

                Win32Class.SERVICE_STATUS status = new Win32Class.SERVICE_STATUS();
                 Win32Class.ControlService(service, Win32Class.SERVICE_CONTROL_STOP, ref status);  // Send a stop command
            }

            if (!WaitForStatus(service, ServiceState.Stopped, new TimeSpan(0, 0, 30)))
            {
                throw new ApplicationException(" Failure to stop service!");
            }
        }

        /// <summary>
         /// Traversing slave services
        /// </summary>
        /// <param name="serviceHandle"></param>
        /// <param name="state"> selective traversal( activities、 inactive、 full)</param>
        private static string[] EnumDependentServices(IntPtr serviceHandle, EnumServiceState state)
        {
             int bytesNeeded = 0;  // holds the size of space for slave services, returned by the API
             int numEnumerated = 0;  // number of slave services, returned by the API

             // first try to get it with empty structure, if it succeeds it means the slave service is empty, otherwise get the above two values
            if (Win32Class.EnumDependentServices(serviceHandle, state, IntPtr.Zero, 0, ref bytesNeeded, ref numEnumerated))
            {
                return new string[0];
            }
             if (Marshal.GetLastWin32Error() != 0xEA) //Throw exception only if the error value is not undersized (ERROR_MORE_DATA)
            {
                throw new Win32Exception();
            }

             // Create pointer in unmanaged area
            IntPtr structsStart = Marshal.AllocHGlobal(new IntPtr(bytesNeeded));
            try
            {
                 // Stuff the structure group holding the slave services at the above pointer, each slave is a structure
                if (!Win32Class.EnumDependentServices(serviceHandle, state, structsStart, bytesNeeded, ref bytesNeeded, ref numEnumerated))
                {
                    throw new Win32Exception();
                }

                string[] dependentServices = new string[numEnumerated];
int sizeOfStruct = Marshal.SizeOf(typeof(Win32Class.ENUM_SERVICE_STATUS));  // Size of each structure
                long structsStartAsInt64 = structsStart.ToInt64();
                for (int i = 0; i < numEnumerated; i++)
                {
                    Win32Class.ENUM_SERVICE_STATUS structure = new Win32Class.ENUM_SERVICE_STATUS();
                     IntPtr ptr = new IntPtr(structsStartAsInt64 + i * sizeOfStruct);  // Derive each structure start pointer based on start pointer, structure order and structure size
                     Marshal.PtrToStructure(ptr, structure);  // Get the structure based on the pointer
                     dependentServices[i] = structure.serviceName;  // get the service name from the structure
                }

                return dependentServices;
            }
            finally
            {
                Marshal.FreeHGlobal(structsStart);
            }
        }

        /// <summary>
         /// Get service status
        /// </summary>
        private static ServiceState GetServiceStatus(IntPtr service)
        {
            Win32Class.SERVICE_STATUS status = new Win32Class.SERVICE_STATUS();

            if (!Win32Class.QueryServiceStatus(service, ref status))
            {
                throw new ApplicationException(" Error getting service status!");
            }

            return status.currentState;
        }

        /// <summary>
         /// Waiting for service to reach target state
        /// </summary>
        private static bool WaitForStatus(IntPtr serviceHandle, ServiceState desiredStatus, TimeSpan timeout)
        {
            DateTime startTime = DateTime.Now;

            while (GetServiceStatus(serviceHandle) != desiredStatus)
            {
                if (DateTime.Now - startTime > timeout) { return false; }

                Thread.Sleep(200);
            }

            return true;
        }

        #endregion

         #region nested classes

        /// <summary>
         /// Win32 API related
        /// </summary>
        private static class Win32Class
        {
             #region constant definition

            /// <summary>
             /// Open Service Manager Permissions requested when: full
            /// </summary>
            public const int SC_MANAGER_ALL_ACCESS = 0xF003F;

            /// <summary>
             /// Service type: own process type service
            /// </summary>
            public const int SERVICE_WIN32_OWN_PROCESS = 0x10;

            /// <summary>
             /// Open the service Permissions requested when: full
            /// </summary>
            public const int SERVICE_ALL_ACCESS = 0xF01FF;

            /// <summary>
             /// Open the service Permissions requested when: stop
            /// </summary>
            public const int SERVICE_STOP = 0x20;

            /// <summary>
             /// Service operation flag: Stop
            /// </summary>
            public const int SERVICE_CONTROL_STOP = 0x1;

            /// <summary>
             /// Service error behavior flag
            /// </summary>
            public const int SERVICE_ERROR_NORMAL = 0x1;

            #endregion

             #region API required class and structure definitions

            /// <summary>
             /// Service state structure
            /// </summary>
            [StructLayout(LayoutKind.Sequential)]
            public struct SERVICE_STATUS
            {
                public int serviceType;
                public ServiceState currentState;
                public int controlsAccepted;
                public int win32ExitCode;
                public int serviceSpecificExitCode;
                public int checkPoint;
                public int waitHint;
            }

            /// <summary>
             /// Service description structure
            /// </summary>
            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
            public struct SERVICE_DESCRIPTION
            {
                public IntPtr description;
            }

            /// <summary>
             /// Service state structure. The traversal API will use the
            /// </summary>
            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
            public class ENUM_SERVICE_STATUS
            {
                public string serviceName;
                public string displayName;
                public int serviceType;
                public int currentState;
                public int controlsAccepted;
                public int win32ExitCode;
                public int serviceSpecificExitCode;
                public int checkPoint;
                public int waitHint;
            }

            #endregion

             #region API Definition

            [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
            public static extern bool ChangeServiceConfig2(IntPtr serviceHandle, uint infoLevel, ref SERVICE_DESCRIPTION serviceDesc);

            [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
            public static extern IntPtr OpenSCManager(string machineName, string databaseName, int dwDesiredAccess);

            [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            public static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, int dwDesiredAccess);

            [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
            public static extern IntPtr CreateService(IntPtr hSCManager, string lpServiceName, string lpDisplayName, int dwDesiredAccess, int dwServiceType, ServiceStartType dwStartType, int dwErrorControl, string lpBinaryPathName, string lpLoadOrderGroup, IntPtr lpdwTagId, string lpDependencies, string lpServiceStartName, string lpPassword);

            [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
            public static extern bool CloseServiceHandle(IntPtr handle);

            [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
            public static extern bool QueryServiceStatus(IntPtr hService, ref SERVICE_STATUS lpServiceStatus);

            [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
            public static extern bool DeleteService(IntPtr serviceHandle);

            [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
            public static extern bool ControlService(IntPtr hService, int dwControl, ref SERVICE_STATUS lpServiceStatus);

            [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
            public static extern bool EnumDependentServices(IntPtr serviceHandle, EnumServiceState serviceState, IntPtr bufferOfENUM_SERVICE_STATUS, int bufSize, ref int bytesNeeded, ref int numEnumerated);

            #endregion
        }

        #endregion

        /// <summary>
         /// Service status enumeration. For traversing the slave service API
        /// </summary>
        private enum EnumServiceState
        {
            Active = 1,
            //InActive = 2,
            //All = 3
        }

        /// <summary>
         /// Service status
        /// </summary>
        private enum ServiceState
        {
            Stopped = 1,
            //StartPending = 2,
            StopPending = 3,
            //Running = 4,
            //ContinuePending = 5,
            //PausePending = 6,
            //Paused = 7
        }
    }
}

Example of use.

Since it's a direct API install, it's the same as having bypassed a bunch of logic for the managed methods, so No longer need to add installers for services in VS (i.e., the VS auto-generated ProjectInstaller class and the ServiceProcessInstaller and ServiceInstaller it carries can be omitted.) It's useless to add them. It is straightforward to pass in the individual information as follows.

 //Installation
ServiceHelper.Install(
    "test",                                //  service name
    "Test Sv",                             //  Display Name
    @"""C: newly built  file (paper)	est.exe"" /s",      //  image path, Available with parameters, If the path has spaces, Need to give path( No parameters) enclose in double quotation marks
    " Description Description Description",                         //  Service Description
     ServiceStartType.Auto, // Start type
     ServiceAccount.LocalService, // running account, optional, default is LocalSystem, i.e. Supreme account
    new[] { "AudioSrv", "WebClient" }      //  dependent service, To fill in the service name, If not, it isnull or an empty array, selectable
    );

 //Uninstall
ServiceHelper.Uninstall("test");

A final illustration of the various concepts of service through a diagram.

-Bunbi-


Recommended>>
1、Siemens s7200 programming software installation tutorial
2、Exhub Shanghai Roadshow Registration Open
3、Q1 2018 Blockchain Industry Overview
4、Blockchain Principal 11 Things You Must Know About Investing in Blockchain Projects
5、The Chinese AI market is so fat that Google still cant let go without stopping

    已推荐到看一看 和朋友分享想法
    最多200字,当前共 发送

    已发送

    朋友将在看一看看到

    确定
    分享你的想法...
    取消

    分享想法到看一看

    确定
    最多200字,当前共

    发送中

    网络异常,请稍后重试

    微信扫一扫
    关注该公众号