Jak debugować usługi systemu Windows w programie Visual Studio?

85

Czy można debugować usługi systemu Windows w programie Visual Studio?

Użyłem kodu takiego jak

System.Diagnostics.Debugger.Break();

ale powoduje błąd w kodzie, taki jak:

Wystąpiły dwa błędy zdarzenia: eventID 4096 VsJITDebugger i „Usługa nie odpowiedziała na żądanie uruchomienia lub sterowania w odpowiednim czasie”.

PawanS
źródło

Odpowiedzi:

124

Użyj następującego kodu w OnStartmetodzie serwisowej :

System.Diagnostics.Debugger.Launch();

Wybierz opcję programu Visual Studio z wyskakującego komunikatu.

Uwaga: Aby używać go tylko w trybie debugowania, #if DEBUGmożna użyć dyrektywy kompilatora w następujący sposób. Zapobiegnie to przypadkowemu lub debugowaniu w trybie wydania na serwerze produkcyjnym.

#if DEBUG
    System.Diagnostics.Debugger.Launch();
#endif
Chirag
źródło
9
Pamiętaj, aby uruchomić VS jako Administrator. Wtedy będzie dostępny na liście.
Michael
1
Czy ktoś może wyjaśnić, co oznacza komunikat wyskakujący? Kiedy / jak to się pojawia?
Mike
@Mike, zainstaluj i uruchom usługę, pojawi się.
Harshit
@Mike, to okno dialogowe systemu Windows, które pojawia się na interaktywnym (zalogowanym) pulpicie i pyta, czy chcesz wybrać aplikację do debugowania procesu. Jeśli wybierzesz VS, uruchomi debugger i dołączy do procesu
Chris Johnson
63

Możesz także spróbować tego.

  1. Utwórz usługę Windows, zainstaluj i uruchom…. Oznacza to, że w systemie muszą być uruchomione usługi Windows.
  2. Gdy usługa jest uruchomiona, przejdź do menu Debugowanie , kliknij Dołącz proces (lub proces w starym programie Visual Studio)
  3. Znajdź uruchomioną usługę, a następnie upewnij się, że zaznaczono opcję Pokaż proces dla wszystkich użytkowników i Pokaż procesy we wszystkich sesjach , a jeśli nie, wybierz ją.

wprowadź opis obrazu tutaj

  1. Kliknij przycisk Dołącz
  2. Kliknij OK
  3. Kliknij Zamknij
  4. Ustaw punkt przerwania w pożądanej lokalizacji i poczekaj na wykonanie. Debuguje automatycznie za każdym razem, gdy kod osiągnie ten punkt.
  5. Pamiętaj, umieść punkt przerwania w osiągalnym miejscu , jeśli jest onStart (), a następnie zatrzymaj i uruchom usługę ponownie

(Po wielu googlach znalazłem to w artykule „Jak debugować usługi Windows w programie Visual Studio”).

PawanS
źródło
2
Nie widzę tej opcji w 5. aktualizacji VS2013 :(
sandeep talabathula
1
Ale musisz uruchomić Vs-2017 jako administrator
chozha rajan
1
Próbowałem tego.
Działało
22

Powinieneś oddzielić cały kod, który będzie robił rzeczy z projektu usługi, do osobnego projektu, a następnie stworzyć aplikację testową, którą możesz normalnie uruchamiać i debugować.

Projekt usługi byłby tylko powłoką potrzebną do zaimplementowania części usługowej.

Lasse V. Karlsen
źródło
OOW !! ctrl + C, a następnie ctrl + V, mam na myśli nowy projekt. Tak, tylko ja to robię. Czy nie jest możliwe dołączenie żadnego procesu do debugowania lub innej opcji zamiast oddzielnego projektu?
PawanS,
1
Oczywiście jest to możliwe, ale znacznie łatwiej jest opracować usługę systemu Windows, jeśli część usługi zostanie wyjęta podczas opracowywania.
Lasse V. Karlsen
hmmm ... to dobry sposób, ale po prostu podwaja pracę. Myślałem, że może istnieć jakikolwiek inny sposób.
PawanS,
9
Nie widzę, jak mogłoby to „podwoić” pracę. Jasne, doda to trochę narzutu przy tworzeniu dodatkowego projektu i oddzieleniu kodu w usłudze do trzeciego projektu, ale poza tym nie zrobiłbyś kopii kodu, mógłbyś go przenieść, i odwołaj się do tego projektu.
Lasse V. Karlsen
3
^ + 1. To podwaja pracę związaną z zarządzaniem usługą, która jest prawie zerowa pod względem czasu programowania, a robisz to tylko raz. Debugowanie usługi jest BOLESNE - raczej spraw, aby uruchamiało się podwójnie w wierszu poleceń. Sprawdź google pod kątem predefiniowanych klas opakowujących, które to umożliwiają (używają one refleksji, aby symulować start / stop bez klasy usługi). godzina pracy, tony oszczędności, strata netto: ujemna - zyskujesz czas.
TomTom
14

Albo zgodnie z sugestią Lasse V. Karlsena, albo skonfiguruj pętlę w usłudze, która będzie czekać na podłączenie debuggera. Najprostsze jest to

while (!Debugger.IsAttached)
{
    Thread.Sleep(1000);
}

... continue with code

W ten sposób możesz uruchomić usługę i w programie Visual Studio wybrać opcję „Dołącz do procesu ...” i dołączyć do usługi, która następnie wznowi normalne wykonywanie.

Pauli Østerø
źródło
gdzie powinienem umieścić powyższy kod ... iw trakcie załączania otrzymuję usługę o nazwie disable
PawanS
3
użyliśmy również takiego kodu if (Environment.UserInteractive) { InteractiveRun(args); } else { Service instance = new Service(); ServiceBase[] servicesToRun = new ServiceBase[] { instance }; ServiceBase.Run(servicesToRun); }
Kirill Kovalenko
ten kod powinien być jak najwcześniejszy, zanim będzie można uruchomić kod, który chcesz debugować.
Pauli Østerø
@Pawan: In Start/ OnStart()Chyba
abatishchev
@Kirill: Użyj tyld, aby wyróżnić kod w komentarzach, np.foo(bar)
abatishchev
7

Biorąc pod uwagę, że ServiceBase.OnStartjest to protectedwidoczne, poszedłem na ścieżkę refleksji, aby uzyskać debugowanie.

private static void Main(string[] args)
{
    var serviceBases = new ServiceBase[] {new Service() /* ... */ };

#if DEBUG
    if (Environment.UserInteractive)
    {
        const BindingFlags bindingFlags =
            BindingFlags.Instance | BindingFlags.NonPublic;

        foreach (var serviceBase in serviceBases)
        {
            var serviceType = serviceBase.GetType();
            var methodInfo = serviceType.GetMethod("OnStart", bindingFlags);

            new Thread(service => methodInfo.Invoke(service, new object[] {args})).Start(serviceBase);
        }

        return;
    }
#endif

    ServiceBase.Run(serviceBases);
}

Zauważ, że Threaddomyślnie jest to wątek pierwszego planu. returning od Mainnatomiast gwinty faux-usługowe działają nie zakończy proces.

ta.speot.is
źródło
Ponieważ OnStart ma powrócić szybko, nie powinieneś tego robić w innym wątku. Jeśli jednak usługa nie uruchomi innego wątku, proces zostanie natychmiast zakończony.
Matt Connolly,
@MattConnolly Na tym drugim: jeśli to konieczne, zmieniam powyższy kod, aby uruchomić wątek pierwszego planu, który śpi na zawsze (przed normalnym przetwarzaniem).
ta.speot.jest
To powinna być prawdziwa odpowiedź. Działa pięknie!
lentyai
4

Artykuł firmy Microsoft wyjaśnia, jak debugować usługę systemu Windows w tym miejscu i jaką część można przegapić, jeśli debuguje ją przez dołączenie do procesu.

Poniżej znajduje się mój działający kod. Postępowałem zgodnie z podejściem sugerowanym przez firmę Microsoft.

Dodaj ten kod do program.cs:

static void Main(string[] args)
{
    // 'If' block will execute when launched through Visual Studio
    if (Environment.UserInteractive)
    {
        ServiceMonitor serviceRequest = new ServiceMonitor();
        serviceRequest.TestOnStartAndOnStop(args);
    }
    else // This block will execute when code is compiled as a Windows application
    {
        ServiceBase[] ServicesToRun;
        ServicesToRun = new ServiceBase[]
        {
            new ServiceMonitor()
        };
        ServiceBase.Run(ServicesToRun);
    }
}

Dodaj ten kod do klasy ServiceMonitor.

internal void TestOnStartAndOnStop(string[] args)
{
    this.OnStart(args);
    Console.ReadLine();
    this.OnStop();
}

Teraz przejdź do Właściwości projektu , wybierz zakładkę „Aplikacja” i wybierz Typ wyjścia jako „Aplikacja konsolowa” podczas debugowania lub „Aplikacja Windows” po zakończeniu debugowania, przekompiluj i zainstaluj usługę.

Tutaj wprowadź opis obrazu

kumar chandraketu
źródło
1
czy istnieje sposób ustawienia danych wyjściowych na aplikację konsolową w debugowaniu i aplikacji okienkowej w wersji?
kofifus
3

Możesz stworzyć aplikację konsolową. Używam tej mainfunkcji:

    static void Main(string[] args)
    {
        ImportFileService ws = new ImportFileService();
        ws.OnStart(args);
        while (true)
        {
            ConsoleKeyInfo key = System.Console.ReadKey();
            if (key.Key == ConsoleKey.Escape)
                break;
        }
        ws.OnStop();
    }

Moja ImportFileServiceklasa jest dokładnie taka sama, jak w aplikacji mojej usługi Windows, z wyjątkiem inheritant ( ServiceBase).

kerrubin
źródło
czy to w tym samym projekcie, czy ja inny projekt dla tej aplikacji konsolowej
PawanS
To 2 różne projekty z podobnymi klasami. W moim przypadku jest to prosta usługa, w której tylko klasa ImportFileService jest zduplikowana. Kiedy chcę tworzyć / testować, używam aplikacji console, a następnie kopiuję / wklejam. Jak powiedział Lasse V. Karlsen, jest to program do debugowania, cała logika (biznes) znajduje się w trzecim projekcie.
kerrubin
Czy OnStart nie jest chroniony?
Joe Phillips
Tak, jest. Dlatego powiedziałem „oprócz dziedziczenia ( ServiceBase)”. Debugowanie jest łatwiejsze w aplikacji konsolowej, ale rozumiem, czy to nie przekonuje wszystkich.
kerrubin
3

Używam świetnego pakietu Nuget o nazwie ServiceProcess.Helpers.

I cytuję...

Ułatwia debugowanie usług systemu Windows, tworząc interfejs odtwarzania / zatrzymywania / wstrzymywania podczas pracy z podłączonym debugerem, ale także umożliwia zainstalowanie i uruchomienie usługi w środowisku serwera Windows.

Wszystko to za pomocą jednej linii kodu.

http://windowsservicehelper.codeplex.com/

Po zainstalowaniu i podłączeniu wszystko, co musisz zrobić, to ustawić projekt usługi systemu Windows jako projekt startowy i kliknąć przycisk Start w debugerze.

Christophe Chang
źródło
Dzięki za udostępnienie! To było zdecydowanie najprostsze rozwiązanie!
Wellington Zanelli
2

Możesz także wypróbować metodę System.Diagnostics.Debugger.Launch () . Pomaga w przeniesieniu wskaźnika debugera do określonej lokalizacji, a następnie można debugować kod.

Przed tym krokiem zainstaluj plik service.exe za pomocą wiersza poleceń wiersza poleceń programu Visual Studio - installutil projectservice.exe

Następnie uruchom usługę z Panelu sterowania -> Narzędzia administracyjne -> Zarządzanie komputerem -> Usługa i aplikacja -> Usługi -> Nazwa usługi

Abhishek Srivastava
źródło
2

Właśnie dodałem ten kod do mojej klasy usług, więc mogłem pośrednio wywołać OnStart, podobnie jak w przypadku OnStop.

    public void MyOnStart(string[] args)
    {
        OnStart(args);
    }
RichardHowells
źródło
2

Używam /Consoleparametru w projekcie Visual Studio DebugStart OptionsCommand line arguments :

public static class Program
{
    [STAThread]
    public static void Main(string[] args)
    {
         var runMode = args.Contains(@"/Console")
             ? WindowsService.RunMode.Console
             : WindowsService.RunMode.WindowsService;
         new WinodwsService().Run(runMode);
    }
}


public class WindowsService : ServiceBase
{
    public enum RunMode
    {
        Console,
        WindowsService
    }

    public void Run(RunMode runMode)
    {
        if (runMode.Equals(RunMode.Console))
        {
            this.StartService();
            Console.WriteLine("Press <ENTER> to stop service...");
            Console.ReadLine();

            this.StopService();
            Console.WriteLine("Press <ENTER> to exit.");
            Console.ReadLine();
        }
        else if (runMode.Equals(RunMode.WindowsService))
        {
            ServiceBase.Run(new[] { this });
        }
    }

    protected override void OnStart(string[] args)
    {
        StartService(args);
    }

    protected override void OnStop()
    {
        StopService();
    }

    /// <summary>
    /// Logic to Start Service
    /// Public accessibility for running as a console application in Visual Studio debugging experience
    /// </summary>
    public virtual void StartService(params string[] args){ ... }

    /// <summary>
    /// Logic to Stop Service
    /// Public accessibility for running as a console application in Visual Studio debugging experience
    /// </summary>
    public virtual void StopService() {....}
}
Sean M.
źródło
2

Znalazłem to pytanie, ale myślę, że brakuje jasnej i prostej odpowiedzi.

Nie chcę dołączać debugera do procesu, ale nadal chcę mieć możliwość wywoływania usługi OnStarti OnStopmetod. Chcę również, aby działał jako aplikacja konsolowa, abym mógł logować informacje z NLog do konsoli.

Znalazłem te genialne przewodniki, które to robią:

Zacznij od zmiany projektów Output typena Console Application.

Tutaj wprowadź opis obrazu

Zmień swój Program.cswygląd, aby wyglądał tak:

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    static void Main()
    {
        // Startup as service.
        ServiceBase[] ServicesToRun;
        ServicesToRun = new ServiceBase[]
        {
            new Service1()
        };

        if (Environment.UserInteractive)
        {
            RunInteractive(ServicesToRun);
        }
        else
        {
            ServiceBase.Run(ServicesToRun);
        }
    }
}

Następnie dodaj następującą metodę, aby zezwolić na działanie usług w trybie interaktywnym.

static void RunInteractive(ServiceBase[] servicesToRun)
{
    Console.WriteLine("Services running in interactive mode.");
    Console.WriteLine();

    MethodInfo onStartMethod = typeof(ServiceBase).GetMethod("OnStart",
        BindingFlags.Instance | BindingFlags.NonPublic);
    foreach (ServiceBase service in servicesToRun)
    {
        Console.Write("Starting {0}...", service.ServiceName);
        onStartMethod.Invoke(service, new object[] { new string[] { } });
        Console.Write("Started");
    }

    Console.WriteLine();
    Console.WriteLine();
    Console.WriteLine(
        "Press any key to stop the services and end the process...");
    Console.ReadKey();
    Console.WriteLine();

    MethodInfo onStopMethod = typeof(ServiceBase).GetMethod("OnStop",
        BindingFlags.Instance | BindingFlags.NonPublic);
    foreach (ServiceBase service in servicesToRun)
    {
        Console.Write("Stopping {0}...", service.ServiceName);
        onStopMethod.Invoke(service, null);
        Console.WriteLine("Stopped");
    }

    Console.WriteLine("All services stopped.");
    // Keep the console alive for a second to allow the user to see the message.
    Thread.Sleep(1000);
}
Ogglas
źródło
Świetny kod! Proste, skuteczne. +1. Ale równie łatwo stworzyłem tę aplikację Forms. Naprawdę nienawidzę aplikacji konsolowych. Ponadto można łatwo zaimplementować przycisk Formularze dla każdego zdarzenia usługi.
Roland
1

Niestety, jeśli próbujesz debugować coś na samym początku operacji usługi Windows, „dołączanie” do uruchomionego procesu nie zadziała. Próbowałem użyć Debugger.Break () w ramach procedury OnStart, ale w przypadku 64-bitowej, skompilowanej aplikacji Visual Studio 2010, polecenie break po prostu zgłasza następujący błąd:

System error 1067 has occurred.

W tym momencie musisz ustawić w rejestrze opcję „Wykonywanie pliku obrazu” dla pliku wykonywalnego. Konfiguracja zajmuje pięć minut i działa bardzo dobrze. Oto artykuł firmy Microsoft zawierający szczegóły:

Instrukcje: automatyczne uruchamianie debugera

Brian
źródło
1

Wypróbuj własny wiersz polecenia zdarzenia po kompilacji programu Visual Studio .

Spróbuj dodać to w post-build:

@echo off
sc query "ServiceName" > nul
if errorlevel 1060 goto install
goto stop

:delete
echo delete
sc delete "ServiceName" > nul
echo %errorlevel%
goto install

:install
echo install
sc create "ServiceName" displayname= "Service Display Name" binpath= "$(TargetPath)" start= auto > nul
echo %errorlevel%
goto start

:start
echo start
sc start "ServiceName" > nul
echo %errorlevel%
goto end

:stop
echo stop
sc stop "ServiceName" > nul
echo %errorlevel%
goto delete

:end

W przypadku błędu kompilacji z takim komunikatem Error 1 The command "@echo off sc query "ServiceName" > nul, Ctrl+ Cnastępnie Ctrl+ Vkomunikat o błędzie do Notatnika i spójrz na ostatnie zdanie wiadomości.

To mogłoby mówić exited with code x. Poszukaj kodu w typowym błędzie tutaj i zobacz, jak go rozwiązać.

1072 -- Marked for deletion → Close all applications that maybe using the service including services.msc and Windows event log.
1058 -- Can't be started because disabled or has no enabled associated devices → just delete it.
1060 -- Doesn't exist → just delete it.
1062 -- Has not been started → just delete it.
1053 -- Didn't respond to start or control → see event log (if logged to event log). It may be the service itself throwing an exception.
1056 -- Service is already running → stop the service, and then delete.

Więcej o kodach błędów tutaj .

A jeśli błąd kompilacji z takim komunikatem,

Error    11    Could not copy "obj\x86\Debug\ServiceName.exe" to "bin\Debug\ServiceName.exe". Exceeded retry count of 10. Failed.    ServiceName
Error    12    Unable to copy file "obj\x86\Debug\ServiceName.exe" to "bin\Debug\ServiceName.exe". The process cannot access the file 'bin\Debug\ServiceName.exe' because it is being used by another process.    ServiceName

otwórz cmd, a następnie spróbuj najpierw go zabić taskkill /fi "services eq ServiceName" /f

Jeśli wszystko jest w porządku, F5powinno wystarczyć do debugowania.

asakura89
źródło
0

W OnStartmetodzie wykonaj następujące czynności.

protected override void OnStart(string[] args)
{
    try
    {
        RequestAdditionalTime(600000);
        System.Diagnostics.Debugger.Launch(); // Put breakpoint here.

        .... Your code
    }
    catch (Exception ex)
    {
        .... Your exception code
    }
}

Następnie uruchom wiersz polecenia jako administrator i wpisz:

c:\> sc create test-xyzService binPath= <ProjectPath>\bin\debug\service.exe type= own start= demand

Powyższa linia utworzy test-xyzService na liście usług.

Aby uruchomić usługę, pojawi się monit o dołączenie do debiutu w programie Visual Studio, czy nie.

c:\> sc start text-xyzService

Aby zatrzymać usługę:

c:\> sc stop test-xyzService

Aby usunąć lub odinstalować:

c:\> sc delete text-xyzService
user3942119
źródło
0

Debugowanie usługi systemu Windows za pośrednictwem protokołu HTTP (testowane z wersją VS 2015 Update 3 i .Net FW 4.6)

Po pierwsze, musisz utworzyć projekt konsoli w swoim rozwiązaniu VS (Dodaj -> Nowy projekt -> Aplikacja konsolowa).

W nowym projekcie utwórz klasę „ConsoleHost” z tym kodem:

class ConsoleHost : IDisposable
{
    public static Uri BaseAddress = new Uri(http://localhost:8161/MyService/mex);
    private ServiceHost host;

    public void Start(Uri baseAddress)
    {
        if (host != null) return;

        host = new ServiceHost(typeof(MyService), baseAddress ?? BaseAddress);

        //binding
        var binding = new BasicHttpBinding()
        {
            Name = "MyService",
            MessageEncoding = WSMessageEncoding.Text,
            TextEncoding = Encoding.UTF8,
            MaxBufferPoolSize = 2147483647,
            MaxBufferSize = 2147483647,
            MaxReceivedMessageSize = 2147483647
        };

        host.Description.Endpoints.Clear();
        host.AddServiceEndpoint(typeof(IMyService), binding, baseAddress ?? BaseAddress);

        // Enable metadata publishing.
        var smb = new ServiceMetadataBehavior
        {
            HttpGetEnabled = true,
            MetadataExporter = { PolicyVersion = PolicyVersion.Policy15 },
        };

        host.Description.Behaviors.Add(smb);

        var defaultBehaviour = host.Description.Behaviors.OfType<ServiceDebugBehavior>().FirstOrDefault();
        if (defaultBehaviour != null)
        {
            defaultBehaviour.IncludeExceptionDetailInFaults = true;
        }

        host.Open();
    }

    public void Stop()
    {
        if (host == null)
            return;

        host.Close();
        host = null;
    }

    public void Dispose()
    {
        this.Stop();
    }
}

A to jest kod klasy Program.cs:

public static class Program
{
    [STAThread]
    public static void Main(string[] args)
    {
        var baseAddress = new Uri(http://localhost:8161/MyService);
        var host = new ConsoleHost();
        host.Start(null);
        Console.WriteLine("The service is ready at {0}", baseAddress);
        Console.WriteLine("Press <Enter> to stop the service.");
        Console.ReadLine();
        host.Stop();
    }
}

Konfiguracje, takie jak connectionstrings, należy skopiować do pliku App.config projektu konsoli.

Aby uruchomić konsolę, kliknij prawym przyciskiem myszy projekt konsoli i kliknij Debuguj -> Uruchom nową instancję.

mggSoft
źródło
0

Po prostu dodaj konstruktora do swojej klasy usług (jeśli jeszcze go nie masz). Poniżej możesz sprawdzić przykład dla Visual Basic .net.

Public Sub New()
   OnStart(Nothing) 
End Sub

Następnie kliknij prawym przyciskiem myszy projekt i wybierz „ Debuguj -> Uruchom nową instancję ”.

Pedro Martín
źródło