none
Installation von Systemdiensten - Ohne InstallUtil.exe RRS feed

  • Frage

  • Hallo,

    Ich habe einen einfachen Dienst geschrieben (nach Anleitung). Diesen würde ich nun gerne installieren und starten, allerdings ist das Problem, dass ich das ganze im Programmcode einer anderen Anwendung machen muss.

    Der Nutzer soll von der Installation des Dienstes nichts mitbekommen. Ich habe mich schon den ganzen Tag durch das Internet gegoogelt, aber bin zu keiner vernünftigen Antwort gekommen.

    In einem beitrag fand ich, man solle in seinem Dienst eine Klasse von Installer ableiten und entsprechend konfigurieren. Hab ich auch gemacht, allerdings hab ich keine Ahnung, wann mein Dienst jetzt installiert wird. Muss ich erst aus einer anderen Anwendung heraus diese Klasse aufrufen?

    Hier die Installer Klasse (Nach Anleitung abgeleitet):

    using System.ServiceProcess;
    using System.Configuration.Install;
    using System.ComponentModel;
    
    namespace ServiceTest
    {
        [RunInstaller(true)]
    
        class CTDInstaller : Installer
        {
            private ServiceProcessInstaller ServiceProcessInstaller1;
            private ServiceInstaller ServiceInstaller1;
    
            public CTDInstaller()
            {
                InitializeComponent();
            }
    
            private void InitializeComponent()
            {
                this.ServiceInstaller1 = new ServiceInstaller();
                this.ServiceProcessInstaller1 = new ServiceProcessInstaller();
                this.ServiceProcessInstaller1.Username = null;
                this.ServiceProcessInstaller1.Password = null;
                this.ServiceInstaller1.Description = "Ein simpler Test Dienst";
                this.ServiceInstaller1.DisplayName = "TestDienst";
                this.ServiceInstaller1.ServiceName = "TestDienst";
                this.Installers.Add(ServiceInstaller1);
                this.Installers.Add(ServiceProcessInstaller1);
            }
    
        }
    }
    

    Wie gesagt, befindet sich diese Klasse im selben Namespace wie der Dienst und auch im selben Projekt, muss also in der gleichen .exe vorhanden sein.

    Hoffe, jemand kann mir helfen.


    Chrissoftan Freeware - Live is Binary

    Freitag, 16. März 2012 16:35

Antworten

  • Hallo,

    Deine erste Anforderung - dass die Installation nicht über InstallUtil.exe erfolgen sollte - ist relativ leicht erfüllt: Man kann einen eigenen Installer von TransactedInstaller ableiten. In der Dokumentation zu TransactedInstaller findet man auch ein recht einfaches Beispiel, das Du in deinem Sinne abändern kannst. Dazu mußt Du den dort verwendeten AssemblyInstaller durch eine Instanz von ServiceInstaller ersetzen. Probier's einfach aus.

    Die zweite Anforderung (dass sich der Dienst in der selben Assembly befinden soll wie die Hauptanwendung) stellt einige Herausforderungen in den Weg, von denen ich hier nur das wichtigste erwähne: Wie Du weißt, sind Anwendungen meist interaktiv, Dienste jedoch nicht. Das heißt, dass deine Anwendung zwei verschiedene Einstiegspunkte haben muss, um beide Funktionen zu erfüllen: einen für die Ausführung als Windows Anwendung (Main -> Application.Run(mainForm)), und einen zweiten für die Ausführung als Dienst (Main -> ServiceBase.Run(ServicesToRun)). Die Fallunterscheidung könnte über Startparameter erfolgen, aber man schlittert auch da möglicherweise in neue Probleme hinein. Probleme, die man sich ersparen könnte, wenn man den Dienst in eine eigene Bibliothek auslagern würde (unter Beibehaltung des Namensraums natürlich).

    Gruß
    Marcel

    • Als Antwort markiert Chrissoftan Sonntag, 18. März 2012 09:17
    • Tag als Antwort aufgehoben Chrissoftan Sonntag, 18. März 2012 12:50
    • Als Antwort markiert Chrissoftan Montag, 19. März 2012 07:20
    Samstag, 17. März 2012 19:43
    Moderator
  • Hallo,

    Dir fehlt u.a. der ServiceProcessInstaller und Installationsrechte (wahrscheinlich hast Du mit gefiltertem Token getestet). Nun gut. Dann werde ich meine oben kurz skizzierten Ideen doch noch hier zu Code konkretisieren müssen:

    using System;
    using System.Collections;
    using System.Configuration.Install;
    using System.Security.Principal;
    using System.ServiceProcess;
    using System.Windows.Forms;
    using Microsoft.Win32;
    namespace SvcInstallDemo
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
            private void buttonRunInstaller_Click(object sender, EventArgs e)
            {
                using (TransactedInstaller transactedInstaller = new TransactedInstaller())
                {
                    using (ServiceProcessInstaller serviceProcessInstaller = new ServiceProcessInstaller())
                    {
                        ServiceInstaller myServiceInstaller;
                        InstallContext myInstallContext;
                        serviceProcessInstaller.Account = ServiceAccount.User;
                        serviceProcessInstaller.Username = WindowsIdentity.GetCurrent().Name;
                        serviceProcessInstaller.Password = textBoxPassword.Text;
                        try
                        {
                            buttonRunInstaller.Enabled = false;
                            groupBoxOptions.Enabled = false;
                            var serviceName = "BeepService";
                            var serviceDescription = "Hier steht die Beschreibung";
                            var serviceDisplayName = "Beep-Dienst";
                            myServiceInstaller = new ServiceInstaller {
                                Description = serviceDescription, 
                                DisplayName = serviceDisplayName,
                                ServiceName = serviceName,
                                StartType = ServiceStartMode.Manual
                            };
                            serviceProcessInstaller.Installers.Add(myServiceInstaller);
                            transactedInstaller.Installers.Add(serviceProcessInstaller);
                            myInstallContext = new InstallContext();
                            var imagePath = System.Reflection.Assembly.GetExecutingAssembly().Location;
                            myInstallContext.Parameters.Add("assemblypath", imagePath);
                            transactedInstaller.Context = myInstallContext;
                            if (radioButtonInstall.Checked)
                            {
                                transactedInstaller.Install(new Hashtable());
                                string serviceKeyPath = String.Format(@"SYSTEM\CurrentControlSet\Services\{0}", serviceName);
                                RegistryKey serviceKey = Registry.LocalMachine.OpenSubKey(serviceKeyPath, true);
                                if (serviceKey != null) serviceKey.SetValue("ImagePath", imagePath + " -svc");
                            }
                            else
                                if (radioButtonUninstall.Checked)
                                    transactedInstaller.Uninstall(null);
                        }
                        catch (Exception ex)
                        {
                            var message = ex.InnerException == null ? ex.Message : String.Format("{0}\n\nDetails: {1}", ex.Message, ex.InnerException.Message);
                            MessageBox.Show(message, "Ausnahmefehler");
                        }
                        finally
                        {
                            buttonRunInstaller.Enabled = true;
                            groupBoxOptions.Enabled = true;
                        }
                    }
                }  
            }
        }
    }


    Was man oben sieht, ist eine Windows Forms-Anwendung mit einer Passwort-TextBox, einer Optionen-GroupBox (installieren/deinstallieren) und einem Button zum Starten der gewählten Installer-Aktion. Die Program.cs wurde etwas abgeändert, damit die Datei wahlweise als normale WF-Anwendung oder als Windows Dienst gestartet werden kann:

    using System; using System.Windows.Forms; using System.ServiceProcess; namespace SvcInstallDemo { static class Program { [STAThread] static void Main(string[] args) { if (args.Length == 0) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } else { if(args[0].Contains("svc")) ServiceBase.Run(new BeepService()); } } } }

    Das Installieren/Deinstallieren auf einem Window 7-Rechner lief problemlos durch (natürlich mußt du die Anwendung als Administrator ausführen).

    Gruß
    Marcel

    • Als Antwort markiert Chrissoftan Montag, 19. März 2012 07:20
    Sonntag, 18. März 2012 20:45
    Moderator

Alle Antworten

  • Hallo,

    Deine erste Anforderung - dass die Installation nicht über InstallUtil.exe erfolgen sollte - ist relativ leicht erfüllt: Man kann einen eigenen Installer von TransactedInstaller ableiten. In der Dokumentation zu TransactedInstaller findet man auch ein recht einfaches Beispiel, das Du in deinem Sinne abändern kannst. Dazu mußt Du den dort verwendeten AssemblyInstaller durch eine Instanz von ServiceInstaller ersetzen. Probier's einfach aus.

    Die zweite Anforderung (dass sich der Dienst in der selben Assembly befinden soll wie die Hauptanwendung) stellt einige Herausforderungen in den Weg, von denen ich hier nur das wichtigste erwähne: Wie Du weißt, sind Anwendungen meist interaktiv, Dienste jedoch nicht. Das heißt, dass deine Anwendung zwei verschiedene Einstiegspunkte haben muss, um beide Funktionen zu erfüllen: einen für die Ausführung als Windows Anwendung (Main -> Application.Run(mainForm)), und einen zweiten für die Ausführung als Dienst (Main -> ServiceBase.Run(ServicesToRun)). Die Fallunterscheidung könnte über Startparameter erfolgen, aber man schlittert auch da möglicherweise in neue Probleme hinein. Probleme, die man sich ersparen könnte, wenn man den Dienst in eine eigene Bibliothek auslagern würde (unter Beibehaltung des Namensraums natürlich).

    Gruß
    Marcel

    • Als Antwort markiert Chrissoftan Sonntag, 18. März 2012 09:17
    • Tag als Antwort aufgehoben Chrissoftan Sonntag, 18. März 2012 12:50
    • Als Antwort markiert Chrissoftan Montag, 19. März 2012 07:20
    Samstag, 17. März 2012 19:43
    Moderator
  • Erstmal vielen Dank für diese Antwort. Habs auch probiert, und das Beispiel etwas abgeändert und dann ausprobiert. Es klappt an sich, meiner meinung nach ganz gut, allerdings wird mein dienst nicht installiert und im Install.log steht als begründung folgendes:

    System.Security.SecurityException: Die Quelle wurde nicht gefunden, aber einige oder alle Ereignisprotokolle konnten nicht durchsucht werden. Protokolle, auf die kein Zugriff möglich war: Security.

    woran kann das liegen? Hier nochmal der abgeänderte Code des Beispiels

    ArrayList myOptions = new ArrayList();
                String myOption;
                bool toUnInstall = false;
                bool toPrintHelp = false;
                TransactedInstaller myTransactedInstaller = new TransactedInstaller();
                ServiceInstaller myAssemblyInstaller;
                InstallContext myInstallContext;
    
                try
                {                
                            // Determine whether the assembly file exists.
                    if (!File.Exists("ServiceTest.exe"))
                            {
                                // If assembly file doesn't exist then print error.
                                Console.WriteLine("\nError : {0} - Assembly file doesn't exist.",
                                   "ServiceTest.exe");
                                return;
                            }
    
                            // Create a instance of 'AssemblyInstaller' that installs the given assembly.
                            myAssemblyInstaller =
                               new ServiceInstaller();
                            myAssemblyInstaller.ServiceName = "ChrissoftanTestDienst";
                            myAssemblyInstaller.StartType = ServiceStartMode.Automatic;
                    
                                
                            // Add the instance of 'AssemblyInstaller' to the 'TransactedInstaller'.  
                            myTransactedInstaller.Installers.Add(myAssemblyInstaller);
                    
                                        
                    // Create a instance of 'InstallContext' with the options specified.
                    myInstallContext =
                       new InstallContext("Install.log",
                       (string[])myOptions.ToArray(typeof(string)));
                    myTransactedInstaller.Context = myInstallContext;
                   
    
                    // Install or Uninstall an assembly depending on the option provided.
                    if (!toUnInstall)
                        myTransactedInstaller.Install(new Hashtable());
                    else
                        myTransactedInstaller.Uninstall(null);
                }
                catch (Exception ex)
                {
                   Console.WriteLine("\nException raised : "+ ex.Message);
                }  
    Den Dienst in eine eigene Bibliothek auslagern klingt ganz gut. Im moment ist es aber noch eine .exe datei, da die Vorlage für einen Dienst automatisch eine exe erstellt, soviel ich weis. Hab den Code für den Installer jetzt auch mal in eine externe Anwendung gepackt.

    Chrissoftan Freeware - Live is Binary

    Sonntag, 18. März 2012 09:58
  • Hallo,

    Dir fehlt u.a. der ServiceProcessInstaller und Installationsrechte (wahrscheinlich hast Du mit gefiltertem Token getestet). Nun gut. Dann werde ich meine oben kurz skizzierten Ideen doch noch hier zu Code konkretisieren müssen:

    using System;
    using System.Collections;
    using System.Configuration.Install;
    using System.Security.Principal;
    using System.ServiceProcess;
    using System.Windows.Forms;
    using Microsoft.Win32;
    namespace SvcInstallDemo
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
            private void buttonRunInstaller_Click(object sender, EventArgs e)
            {
                using (TransactedInstaller transactedInstaller = new TransactedInstaller())
                {
                    using (ServiceProcessInstaller serviceProcessInstaller = new ServiceProcessInstaller())
                    {
                        ServiceInstaller myServiceInstaller;
                        InstallContext myInstallContext;
                        serviceProcessInstaller.Account = ServiceAccount.User;
                        serviceProcessInstaller.Username = WindowsIdentity.GetCurrent().Name;
                        serviceProcessInstaller.Password = textBoxPassword.Text;
                        try
                        {
                            buttonRunInstaller.Enabled = false;
                            groupBoxOptions.Enabled = false;
                            var serviceName = "BeepService";
                            var serviceDescription = "Hier steht die Beschreibung";
                            var serviceDisplayName = "Beep-Dienst";
                            myServiceInstaller = new ServiceInstaller {
                                Description = serviceDescription, 
                                DisplayName = serviceDisplayName,
                                ServiceName = serviceName,
                                StartType = ServiceStartMode.Manual
                            };
                            serviceProcessInstaller.Installers.Add(myServiceInstaller);
                            transactedInstaller.Installers.Add(serviceProcessInstaller);
                            myInstallContext = new InstallContext();
                            var imagePath = System.Reflection.Assembly.GetExecutingAssembly().Location;
                            myInstallContext.Parameters.Add("assemblypath", imagePath);
                            transactedInstaller.Context = myInstallContext;
                            if (radioButtonInstall.Checked)
                            {
                                transactedInstaller.Install(new Hashtable());
                                string serviceKeyPath = String.Format(@"SYSTEM\CurrentControlSet\Services\{0}", serviceName);
                                RegistryKey serviceKey = Registry.LocalMachine.OpenSubKey(serviceKeyPath, true);
                                if (serviceKey != null) serviceKey.SetValue("ImagePath", imagePath + " -svc");
                            }
                            else
                                if (radioButtonUninstall.Checked)
                                    transactedInstaller.Uninstall(null);
                        }
                        catch (Exception ex)
                        {
                            var message = ex.InnerException == null ? ex.Message : String.Format("{0}\n\nDetails: {1}", ex.Message, ex.InnerException.Message);
                            MessageBox.Show(message, "Ausnahmefehler");
                        }
                        finally
                        {
                            buttonRunInstaller.Enabled = true;
                            groupBoxOptions.Enabled = true;
                        }
                    }
                }  
            }
        }
    }


    Was man oben sieht, ist eine Windows Forms-Anwendung mit einer Passwort-TextBox, einer Optionen-GroupBox (installieren/deinstallieren) und einem Button zum Starten der gewählten Installer-Aktion. Die Program.cs wurde etwas abgeändert, damit die Datei wahlweise als normale WF-Anwendung oder als Windows Dienst gestartet werden kann:

    using System; using System.Windows.Forms; using System.ServiceProcess; namespace SvcInstallDemo { static class Program { [STAThread] static void Main(string[] args) { if (args.Length == 0) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } else { if(args[0].Contains("svc")) ServiceBase.Run(new BeepService()); } } } }

    Das Installieren/Deinstallieren auf einem Window 7-Rechner lief problemlos durch (natürlich mußt du die Anwendung als Administrator ausführen).

    Gruß
    Marcel

    • Als Antwort markiert Chrissoftan Montag, 19. März 2012 07:20
    Sonntag, 18. März 2012 20:45
    Moderator