views:

758

answers:

2

I would lke to programmatically enable and start the Net.Tcp Port Sharing Service in C#. I can easily start the service using the ServiceController class. However, how do I enable the service, which is disabled by default?

I found one recommendation online to set the following registry key to 2 as follows, which supposedly sets the service startup type to Automatic:

string path = "SYSTEM\\CurrentControlSet\\Services\\" + serviceName;
using (RegistryKey key = Registry.LocalMachine.OpenSubKey(path, true)) {
    key.SetValue("Start", 2);
}

I tried this and while it did appear to change the startup type to Automatic, there must be more to it, as the service would not now start up (programmatically or manually). I had to reset the startup type manually through the services.msc to reset things, so that the service could be enabled and started up again.

Has anyone solved this?

+7  A: 

There is more than one way to do this, depending on how "pure" of a solution you want. Here are some options. Note that all of these solutions require administrative rights and must run in an elevated process.

Use the command prompt via C#

This will involve shelling out to sc.exe and changing the startup type of the service via command line arguments. This is similar to the solution you mention above except there is no registry hacking required.

namespace Sample
{
    using System;
    using System.Diagnostics;
    using System.Globalization;

    internal class ServiceSample
    {
        private static bool ChangeStartupType(string serviceName, string startupType)
        {
            string arguments = string.Format(
                CultureInfo.InvariantCulture,
                "config {0} start= {1}",
                serviceName,
                startupType);
            using (Process sc = Process.Start("sc.exe", arguments))
            {
                sc.WaitForExit();
                return sc.ExitCode == 0;
            }
        }

        private static void Main()
        {
            ServiceSample.ChangeStartupType("NetTcpPortSharing", "auto");
        }
    }
}

Use WMI

This requires an assembly reference for System.Management.dll. Here we will use WMI functionality to ChangeStartMode for the service.

namespace Sample
{
    using System;
    using System.Globalization;
    using System.Management;

    internal class ServiceSample
    {
        private static bool ChangeStartupType(string serviceName, string startupType)
        {
            const string MethodName = "ChangeStartMode";
            ManagementPath path = new ManagementPath();
            path.Server = ".";
            path.NamespacePath = @"root\CIMV2";
            path.RelativePath = string.Format(
                CultureInfo.InvariantCulture,
                "Win32_Service.Name='{0}'",
                serviceName);
            using (ManagementObject serviceObject = new ManagementObject(path))
            {
                ManagementBaseObject inputParameters = serviceObject.GetMethodParameters(MethodName);
                inputParameters["startmode"] = startupType;
                ManagementBaseObject outputParameters = serviceObject.InvokeMethod(MethodName, inputParameters, null);
                return (uint)outputParameters.Properties["ReturnValue"].Value == 0;
            }
        }

        private static void Main()
        {
            ServiceSample.ChangeStartupType("NetTcpPortSharing", "Automatic");
        }
    }
}

Use P/Invoke to Win32 APIs

To some people, this is the most "pure" method, although it's more tricky to get right. Basically you'll want to call ChangeServiceConfig from .NET. However, this requires that you first call OpenService for the specified service and that requires calling OpenSCManager beforehand (and don't forget to CloseServiceHandle when you're done!).

Note: This code is for demonstration purposes only. It does not contain any error handling and may leak resources. A proper implementation should use SafeHandle types to ensure proper cleanup and should add appropriate error checking.

namespace Sample
{
    using System;
    using System.ComponentModel;
    using System.Runtime.InteropServices;

    internal class ServiceSample
    {
        private const uint SC_MANAGER_CONNECT = 0x1;
        private const uint SERVICE_CHANGE_CONFIG = 0x2;
        private const uint STANDARD_RIGHTS_WRITE = 0x20000;

        private const uint SERVICE_NO_CHANGE = 0xFFFFFFFF;

        private const uint SERVICE_AUTO_START = 0x2;
        private const uint SERVICE_DEMAND_START = 0x3;
        private const uint SERVICE_DISABLED = 0x4;

        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern IntPtr OpenSCManager(string lpMachineName, string lpDatabaseName, uint dwDesiredAccess);

        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);

        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern bool ChangeServiceConfig(IntPtr hService, uint dwServiceType, uint dwStartType, uint dwErrorControl, string lpBinaryPathName, string lpLoadOrderGroup, IntPtr lpdwTagId, string lpDependencies, string lpServiceStartName, string lpPassword, string lpDisplayName);

        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern bool CloseServiceHandle(IntPtr hSCObject);

        private static bool ChangeStartupType(string serviceName, uint startType)
        {
            IntPtr scManager = ServiceSample.OpenSCManager(null, null, ServiceSample.SC_MANAGER_CONNECT);
            IntPtr service = ServiceSample.OpenService(scManager, serviceName, ServiceSample.SERVICE_CHANGE_CONFIG | ServiceSample.STANDARD_RIGHTS_WRITE);
            bool succeeded = ServiceSample.ChangeServiceConfig(service, ServiceSample.SERVICE_NO_CHANGE, startType, ServiceSample.SERVICE_NO_CHANGE, null, null, IntPtr.Zero, null, null, null, null);
            ServiceSample.CloseServiceHandle(service);
            ServiceSample.CloseServiceHandle(scManager);

            return succeeded;
        }

        private static void Main()
        {
            ServiceSample.ChangeStartupType("NetTcpPortSharing", ServiceSample.SERVICE_AUTO_START);
        }
    }
}
bobbymcr
This looks promising! I see I have both sc.exe and System.Management.dll available on my development machine. Are either or both of these options available on a standard Windows Install (XP, Vista, Windows 7, Server 2003, 2008 etc.) without Visal Studio? Do these options require a Resource Kit or SDK to be installed on the machine?
Elan
`sc.exe` is standard on every Windows system at least since Windows 2000. `System.Management.dll` is part of .NET and will be present as long as .NET is (at least since .NET 1.1).
bobbymcr
I am going with the WMI option for now - it worked like a charm! Thanks very much!
Elan
A: 

There is a straightforward answer using ServiceController

System.ServiceProcess.ServiceController netTcpPortSharingService = new System.ServiceProcess.ServiceController("NetTcpPortSharing");

if (netTcpPortSharingService != null)
            {
                if(netTcpPortSharingService.Status!=ServiceControllerStatus.Running && netTcpPortSharingService.Status!=ServiceControllerStatus.StartPending)
                {
                    netTcpPortSharingService.Start();
                }
            }
Wayne Lo
Well, I just realize if the startType is disable, the above code will not work.
Wayne Lo