Как служба Windows может определить свое имя службы?

Я посмотрел и не смог найти простой вопрос:

Как служба Windows может определить имя службы, для которой она была запущена?

Я знаю, что установка может взломать реестр и добавить аргумент командной строки, но по логике кажется, что это не нужно, отсюда и этот вопрос.

Я надеюсь запустить несколько копий одного двоичного файла более аккуратно, чем взломать реестр.

Редактировать:

Это написано на C#. Точка входа My apps Main () делает разные вещи, в зависимости от аргументов командной строки:

  • Установите или удалите службу. Командная строка может предоставлять имя службы, отличное от заданного по умолчанию, и может изменять количество рабочих потоков.
  • Запустить как исполняемый файл командной строки (для отладки),
  • Запускать как "Служба Windows". Здесь он создает экземпляр моего класса ServiceBase -derived, затем вызывает System.ServiceProcess.ServiceBase.Run(instance);

В настоящее время шаг установки добавляет имя службы и число потоков в ImagePath в реестре, чтобы приложение могло определить его ServiceName.

8 ответов

Решение

От: https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=387024

Вот решение WMI. Переопределение ServiceBase.ServiceMainCallback() также может работать, но мне кажется, что это работает...

    protected String GetServiceName()
    {
        // Calling System.ServiceProcess.ServiceBase::ServiceNamea allways returns
        // an empty string,
        // see https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=387024

        // So we have to do some more work to find out our service name, this only works if
        // the process contains a single service, if there are more than one services hosted
        // in the process you will have to do something else

        int processId = System.Diagnostics.Process.GetCurrentProcess().Id;
        String query = "SELECT * FROM Win32_Service where ProcessId = " + processId;
        System.Management.ManagementObjectSearcher searcher =
            new System.Management.ManagementObjectSearcher(query);

        foreach (System.Management.ManagementObject queryObj in searcher.Get()) {
            return queryObj["Name"].ToString();
        }

        throw new Exception("Can not get the ServiceName");
    } 

Свойство ServiceBase.ServiceName дает имя службы во время компиляции. Если при установке службы вы указали другое имя, атрибут ServiceName не даст правильное имя. Поэтому мне пришлось использовать приведенный ниже код, чтобы получить название службы для моей службы.

Это альтернатива (без использования LINQ) методу NVRAM:

/**
 * Returns the service name of currently running windows service.
 */
static String getServiceName()
{
    ServiceController[] scServices;
    scServices = ServiceController.GetServices();

    // Display the list of services currently running on this computer.
    int my_pid = System.Diagnostics.Process.GetCurrentProcess().Id;

    foreach (ServiceController scTemp in scServices)
    {
        // Write the service name and the display name
        // for each running service.

        // Query WMI for additional information about this service.
        // Display the start name (LocalSytem, etc) and the service
        // description.
        ManagementObject wmiService;
        wmiService = new ManagementObject("Win32_Service.Name='" + scTemp.ServiceName + "'");
        wmiService.Get();

        int id = Convert.ToInt32(wmiService["ProcessId"]);
        if (id == my_pid)
        {
            return scTemp.ServiceName;
#if IS_CONSOLE
            Console.WriteLine();
            Console.WriteLine("  Service :        {0}", scTemp.ServiceName);
            Console.WriteLine("    Display name:    {0}", scTemp.DisplayName);

            Console.WriteLine("    Start name:      {0}", wmiService["StartName"]);
            Console.WriteLine("    Description:     {0}", wmiService["Description"]);

            Console.WriteLine("    Found.......");
#endif
        }
    }
    return "NotFound";
}

Я неправильно пытался получить имя службы Windows в качестве первой строки в main() без предварительного вызова ServiceBase.Run(). Мы должны зарегистрировать наш исполняемый файл как сервис, используя ServiceBase.Run(), прежде чем получить его имя.

Ссылка: http://msdn.microsoft.com/en-us/library/hde9d63a.aspx

Короткая версия с Linq

  int processId = System.Diagnostics.Process.GetCurrentProcess().Id;
  ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_Service where ProcessId = " + processId);
  ManagementObjectCollection collection = searcher.Get();
  var serviceName = (string)collection.Cast<ManagementBaseObject>().First()["Name"];

Что не так с этим.ServiceName, если вы находитесь внутри service.cs?

то есть:

protected override void OnStart(string[] args)
    {
        Logger.Info($"{this.ServiceName} started on {Environment.MachineName}...");  
    }

У меня была проблема типа "курица и яйцо", когда мне нужно было узнать местоположение службы до завершения Service.Run() (служба могла быть частью установки клиента или сервера, установщик назвал их соответствующим образом, и мне нужно было определить, какая служба включена запускать)

Я полагался на реестр, чтобы получить мне имя.

public String IdentifySelfFromRegistry()
{
    String executionPath = Assembly.GetEntryAssembly().Location;
    Microsoft.Win32.RegistryKey services = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
            @"SYSTEM\CurrentControlSet\services");
    if (services != null)
    {
        foreach(String subkey in services.GetSubKeyNames())
        {
            if (executionPath.Equals(ServicePathFromServiceKey(services.OpenSubKey(subkey))))
                return subkey;
        }
    }
    return String.Empty;
}

protected static String ServicePathFromServiceKey(Microsoft.Win32.RegistryKey serviceKey)
{
    if (serviceKey != null)
    {
        String exec = serviceKey.GetValue(ServicePathEntry) as String;
        if (exec != null)
            return exec.Trim('\"');
    }
    return String.Empty;
}

Точка входа ServiceMain(), которую должен реализовать каждый исполняемый файл службы, получает ServiceName в качестве первого входного аргумента.

Если вы пишете свой сервис с использованием.NET, точка входа ServiceMain() будет реализована.NET для вас. Имя ServiceName назначается при установке службы с использованием свойства ServiceProcess.ServiceBase.ServiceName. Если вы пытаетесь настроить службу.NET для поддержки динамических значений ServiceName, я понятия не имею, как получить доступ к фактическому ServiceName во время выполнения.

В поисках лучшего решения я попробовал это:

string serviceName = "myDynamicServiceName";
string serviceBin = "path\\to\\Service.exe";
string configFile = "path\\to\\myConfig.xml";
string credentials = "obj= .\\mytestuser password= test";

string scCommand = string.Format( "sc create {0} start= auto binPath= \"\\\"{1}\\\" -ini={2} -sn={3}\" type= own{4}", serviceName, serviceBin, configFile , serviceName  ,credentials );

Я передал имя сервера и файл конфигурации в binpath. Служба была установлена ​​с помощью SC.exe (я не использую installutil!)

На сервисе вы можете получить Commandline-Arguments

protected override void OnStart(string[] args){
    string binpath = new System.IO.FileInfo(System.Reflection.Assembly.GetAssembly(this.GetType()).Location).DirectoryName + "\\";
    System.IO.StreamWriter sw = new System.IO.StreamWriter( binpath + "test.log");

    sw.WriteLine( binpath );

    string[] cmdArgs = System.Environment.GetCommandLineArgs();
    foreach (string item in cmdArgs) {
        sw.WriteLine(item);
    }

    sw.Flush();
    sw.Dispose();
    sw = null;
}
      public static bool IsServiceInstalled(string serviceName)
{
  // get list of Windows services
  ServiceController[] services = ServiceController.GetServices();

  // try to find service name
  foreach (ServiceController service in services)
  {
    if (service.ServiceName == serviceName)
      return true;
  }
  return false;
}
Другие вопросы по тегам