54 Stimmen

Bin ich als Dienst tätig?

Ich schreibe gerade einen kleinen Bootstrap-Code für einen Dienst, der in der Konsole ausgeführt werden kann. Es läuft im Wesentlichen darauf hinaus, die OnStart()-Methode aufzurufen, anstatt die ServiceBase zu verwenden, um den Dienst zu starten und zu stoppen (weil die Anwendung nicht ausgeführt wird, wenn sie nicht als Dienst installiert ist und die Fehlersuche zu einem Albtraum wird).

Im Moment verwende ich Debugger.IsAttached, um festzustellen, ob ich ServiceBase.Run oder [service].OnStart verwenden sollte, aber ich weiß, dass ist nicht die beste Idee, weil einige Male Endbenutzer den Dienst in einer Konsole ausführen möchten (um die Ausgabe usw. in Echtzeit zu sehen).

Haben Sie eine Idee, wie ich feststellen kann, ob der Windows-Dienstcontroller "mich" gestartet hat oder ob der Benutzer "mich" in der Konsole gestartet hat? Anscheinend Umgebung.IsUserInteractive ist nicht die Antwort. Ich dachte an die Verwendung von Befehlszeilen-Args, aber das scheint "schmutzig".

Ich könnte immer sehen, über eine Try-Catch-Anweisung um ServiceBase.Run, aber das scheint schmutzig. Bearbeiten: Try-Catch funktioniert nicht.

Ich habe eine Lösung: Ich stelle sie hier für alle anderen interessierten Stacker auf:

    public void Run()
    {
        if (Debugger.IsAttached || Environment.GetCommandLineArgs().Contains<string>("-console"))
        {
            RunAllServices();
        }
        else
        {
            try
            {
                string temp = Console.Title;
                ServiceBase.Run((ServiceBase[])ComponentsToRun);
            }
            catch
            {
                RunAllServices();
            }
        }
    } // void Run

    private void RunAllServices()
    {
        foreach (ConsoleService component in ComponentsToRun)
        {
            component.Start();
        }
        WaitForCTRLC();
        foreach (ConsoleService component in ComponentsToRun)
        {
            component.Stop();
        }
    }

EDIT: Es gab eine andere Frage auf StackOverflow, wo der Kerl Probleme mit dem Environment.CurrentDirectory hatte, das " C:\Windows\System32 "Sieht so aus, als könnte das die Antwort sein. Ich werde das heute testen.

0 Stimmen

Vielen Dank, dass Sie Ihre Lösung hinzugefügt haben, das sollte eine nützliche Referenz sein.

2 Stimmen

Nicht, dass IsUserInteractive no für Konsolenanwendungen false zurückgeben, wie es in dem Link, den Sie oben angegeben haben, angegeben wurde - zumindest nicht generell. Ich benutze es für diesen Zweck und hatte nie irgendwelche Probleme damit.

0 Stimmen

10voto

Rolf Kristensen Punkte 14300

Ich habe den ProjectInstaller so modifiziert, dass er den Befehlszeilenparameter /service anfügt, wenn er als Dienst installiert wird:

static class Program
{
    static void Main(string[] args)
    {
        if (Array.Exists(args, delegate(string arg) { return arg == "/install"; }))
        {
            System.Configuration.Install.TransactedInstaller ti = null;
            ti = new System.Configuration.Install.TransactedInstaller();
            ti.Installers.Add(new ProjectInstaller());
            ti.Context = new System.Configuration.Install.InstallContext("", null);
            string path = System.Reflection.Assembly.GetExecutingAssembly().Location;
            ti.Context.Parameters["assemblypath"] = path;
            ti.Install(new System.Collections.Hashtable());
            return;
        }

        if (Array.Exists(args, delegate(string arg) { return arg == "/uninstall"; }))
        {
            System.Configuration.Install.TransactedInstaller ti = null;
            ti = new System.Configuration.Install.TransactedInstaller();
            ti.Installers.Add(new ProjectInstaller());
            ti.Context = new System.Configuration.Install.InstallContext("", null);
            string path = System.Reflection.Assembly.GetExecutingAssembly().Location;
            ti.Context.Parameters["assemblypath"] = path;
            ti.Uninstall(null);
            return;
        }

        if (Array.Exists(args, delegate(string arg) { return arg == "/service"; }))
        {
            ServiceBase[] ServicesToRun;

            ServicesToRun = new ServiceBase[] { new MyService() };
            ServiceBase.Run(ServicesToRun);
        }
        else
        {
            Console.ReadKey();
        }
    }
}

Die Datei ProjectInstaller.cs wird dann so geändert, dass ein OnBeforeInstall() und OnBeforeUninstall() überschrieben wird

[RunInstaller(true)]
public partial class ProjectInstaller : Installer
{
    public ProjectInstaller()
    {
        InitializeComponent();
    }

    protected virtual string AppendPathParameter(string path, string parameter)
    {
        if (path.Length > 0 && path[0] != '"')
        {
            path = "\"" + path + "\"";
        }
        path += " " + parameter;
        return path;
    }

    protected override void OnBeforeInstall(System.Collections.IDictionary savedState)
    {
        Context.Parameters["assemblypath"] = AppendPathParameter(Context.Parameters["assemblypath"], "/service");
        base.OnBeforeInstall(savedState);
    }

    protected override void OnBeforeUninstall(System.Collections.IDictionary savedState)
    {
        Context.Parameters["assemblypath"] = AppendPathParameter(Context.Parameters["assemblypath"], "/service");
        base.OnBeforeUninstall(savedState);
    }
}

1 Stimmen

Das obige Beispiel behandelt die Anführungszeichen nicht richtig, hier eine bessere Lösung stackoverflow.com/questions/4862580/

0 Stimmen

Verbesserte Behandlung von Anführungszeichen um den Pfad

4voto

shockwave121 Punkte 139

Dieser Thread ist schon sehr alt, aber ich dachte, ich würde meine Lösung hier veröffentlichen. Ganz einfach, um diese Art von Situation zu handhaben, habe ich einen "Service-Kabelbaum" gebaut, der sowohl in der Konsole als auch in den Windows-Dienstfällen verwendet wird. Wie oben, die meisten der Logik ist in einer separaten Bibliothek enthalten, aber dies ist mehr für die Prüfung und "Linkability".

Der beigefügte Code stellt keineswegs den "bestmöglichen" Weg zur Lösung dieses Problems dar, sondern nur meinen eigenen Ansatz. Hier wird das Service-Harness von der Konsolenanwendung aufgerufen, wenn sie sich im "Konsolenmodus" befindet, und von der "Start Service"-Logik der gleichen Anwendung, wenn sie als Dienst ausgeführt wird. Auf diese Weise können Sie nun Folgendes aufrufen

ServiceHost.Instance.RunningAsAService (Boolesch)

an einer beliebigen Stelle in Ihrem Code, um zu prüfen, ob die Anwendung als Dienst oder einfach als Konsole läuft.

Hier ist der Code:

public class ServiceHost
{
    private static Logger log = LogManager.GetLogger(typeof(ServiceHost).Name);

    private static ServiceHost mInstance = null;
    private static object mSyncRoot = new object();

    #region Singleton and Static Properties

    public static ServiceHost Instance
    {
        get
        {
            if (mInstance == null)
            {
                lock (mSyncRoot)
                {
                    if (mInstance == null)
                    {
                        mInstance = new ServiceHost();
                    }
                }
            }

            return (mInstance);
        }
    }

    public static Logger Log
    {
        get { return log; }
    }

    public static void Close()
    {
        lock (mSyncRoot)
        {
            if (mInstance.mEngine != null)
                mInstance.mEngine.Dispose();
        }
    }

    #endregion

    private ReconciliationEngine mEngine;
    private ServiceBase windowsServiceHost;
    private UnhandledExceptionEventHandler threadExceptionHanlder = new UnhandledExceptionEventHandler(ThreadExceptionHandler);

    public bool HostHealthy { get; private set; }
    public bool RunningAsService {get; private set;}

    private ServiceHost()
    {
        HostHealthy = false;
        RunningAsService = false;
        AppDomain.CurrentDomain.UnhandledException += threadExceptionHandler;

        try
        {
            mEngine = new ReconciliationEngine();
            HostHealthy = true;
        }
        catch (Exception ex)
        {
            log.FatalException("Could not initialize components.", ex);
        }
    }

    public void StartService()
    {
        if (!HostHealthy)
            throw new ApplicationException("Did not initialize components.");

        try
        {
            mEngine.Start();
        }
        catch (Exception ex)
        {
            log.FatalException("Could not start service components.", ex);
            HostHealthy = false;
        }
    }

    public void StartService(ServiceBase serviceHost)
    {
        if (!HostHealthy)
            throw new ApplicationException("Did not initialize components.");

        if (serviceHost == null)
            throw new ArgumentNullException("serviceHost");

        windowsServiceHost = serviceHost;
        RunningAsService = true;

        try
        {
            mEngine.Start();
        }
        catch (Exception ex)
        {
            log.FatalException("Could not start service components.", ex);
            HostHealthy = false;
        }
    }

    public void RestartService()
    {
        if (!HostHealthy)
            throw new ApplicationException("Did not initialize components.");         

        try
        {
            log.Info("Stopping service components...");
            mEngine.Stop();
            mEngine.Dispose();

            log.Info("Starting service components...");
            mEngine = new ReconciliationEngine();
            mEngine.Start();
        }
        catch (Exception ex)
        {
            log.FatalException("Could not restart components.", ex);
            HostHealthy = false;
        }
    }

    public void StopService()
    {
        try
        {
            if (mEngine != null)
                mEngine.Stop();
        }
        catch (Exception ex)
        {
            log.FatalException("Error stopping components.", ex);
            HostHealthy = false;
        }
        finally
        {
            if (windowsServiceHost != null)
                windowsServiceHost.Stop();

            if (RunningAsService)
            {
                AppDomain.CurrentDomain.UnhandledException -= threadExceptionHanlder;
            }
        }
    }

    private void HandleExceptionBasedOnExecution(object ex)
    {
        if (RunningAsService)
        {
            windowsServiceHost.Stop();
        }
        else
        {
            throw (Exception)ex;
        }
    }

    protected static void ThreadExceptionHandler(object sender, UnhandledExceptionEventArgs e)
    {
        log.FatalException("Unexpected error occurred. System is shutting down.", (Exception)e.ExceptionObject);
        ServiceHost.Instance.HandleExceptionBasedOnExecution((Exception)e.ExceptionObject);
    }
}

Alles, was Sie hier tun müssen, ist, diese ominös aussehende ReconcilationEngine Referenz mit der Methode, die Ihre Logik untergräbt. Verwenden Sie dann in Ihrer Anwendung die ServiceHost.Instance.Start() et ServiceHost.Instance.Stop() Methoden, egal ob Sie im Konsolenmodus oder als Dienst arbeiten.

3voto

mdb Punkte 50584

Der einzige Weg, den ich gefunden habe, um dies zu erreichen, ist zu überprüfen, ob eine Konsole an den Prozess in erster Linie angehängt ist, durch den Zugriff auf eine beliebige Console-Objekteigenschaft (z. B. Titel) innerhalb eines Try/Catch-Blocks.

Wenn der Dienst vom SCM gestartet wird, gibt es keine Konsole, und der Zugriff auf die Eigenschaft führt zu einem System.IO.IOError.

Da sich dies jedoch ein wenig zu sehr nach einem implementierungsspezifischen Detail anfühlt (was, wenn der SCM auf einigen Plattformen oder eines Tages beschließt, den von ihm gestarteten Prozessen eine Konsole zur Verfügung zu stellen?), verwende ich in Produktionsanwendungen immer einen Befehlszeilenschalter (-console)...

3voto

Vielleicht sollte geprüft werden, ob der übergeordnete Prozess C:\Windows\system32\services.exe.

1voto

Ben Voigt Punkte 268424

Hier ist eine Übersetzung der Antwort von chksr auf .NET, und die Vermeidung des Fehlers, der interaktive Dienste nicht erkennt:

using System.Security.Principal;

var wi = WindowsIdentity.GetCurrent();
var wp = new WindowsPrincipal(wi);
var serviceSid = new SecurityIdentifier(WellKnownSidType.ServiceSid, null);
var localSystemSid = new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null);
var interactiveSid = new SecurityIdentifier(WellKnownSidType.InteractiveSid, null);
// maybe check LocalServiceSid, and NetworkServiceSid also

bool isServiceRunningAsUser = wp.IsInRole(serviceSid);
bool isSystem = wp.IsInRole(localSystemSid);
bool isInteractive = wp.IsInRole(interactiveSid);

bool isAnyService = isServiceRunningAsUser || isSystem || !isInteractive;

0 Stimmen

Ein Prozess kann als LocalSystem laufen, auch wenn er keinen Dienst hat: psexec -s MyApp.exe . In diesem Fall ist sie standardmäßig auch nicht interaktiv.

0 Stimmen

@marsh-wiggle: IMO, wenn ein Dienst einen Teil seiner Arbeit außerhalb des Prozesses erledigt, sind die erzeugten Kindprozesse immer noch "Teil des Dienstes". Aber es ist wahr, sie würden nicht aufrufen wollen StartServiceCtrlDispatcher wenn sie nicht der Root-Prozess des Dienstes sind.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X