Archiv für Kategorie Entwicklung

mod_auth_sspi: Patch für SSPIUsernameFormat eingereicht

Hat etwas gedauert (*räusper*) bis ich den Patch für mod_auth_sspi veröffentlicht habe. Günter ist noch nicht dazu gekommen, den Patch ins CVS zu mergen, deshalb habe ich das gepatchte Modul inkl. Patches für die Sourcen bei sourceforge.net hochgeladen.

Der SSPIUsernameFormat-Patch steht nun also offiziell zum Download bereit.

, ,

Keine Kommentare

How-To: Module des Apache Webservers unter Windows mit Visual Studio kompilieren und debuggen

Aus aktuellem Anlass musste ich mal wieder ein Apache-Modul gerade biegen. Diesmal war es mod_auth_ldap. mod_auth_ldap sollte als Modul zur Authentifizierung und Autorisierung von LDAP-Benutzern (Active Directory, eDirectory, OpenLDAP etc.) vielen Leuten ein Begriff sein.

Diese Anleitung zeigt in wenigen Schritten, wie man aus den Sourcen des Apache Webserver 2.0.63 ein Modul patcht, kompiliert und debuggt. Voraussetzung für die Kompilierung ist ein Visual Studio Express bzw. Visual C++. Ich verwende auf meinem Rechner ein (relativ altes) Visual Studio 2005 Professional.

Auf meinem Arbeitsrechner schaut es so aus, dass ich unter c:\ckl\dev\srv\web\apache\2.0.63 ($DIR_BIN) die installierte und kompilierte Version des Apache Webservers habe und unter c:\ckl\dev\projects\app\apache\2.0.63 ($DIR_SRC) die zugehörigen Sourcen liegen.

Zuerst müssen von apache.org die aktuellen Sourcen für Windows heruntergeladen und auf der Festplatte ($DIR_SRC) entpackt werden. Wenn man zusätzlich noch durch die Sourcen des Apache Webservers debuggen will, werden weiterhin die Symbols benötigt. Diese müssen in das Hauptverzeichnis des kompilierten Apache Webservers ($DIR_BIN) entpackt werden.

Die Datei $DIR_SRC\Apache.dsw enthält alle Teilprojekte des Webservers und muss mit Visual Studio geöffnet werden. Falls beim Öffnen die Frage nach einer Konvertierung der Daten kommt, kann/muss dies mit Ja beantwortet werden.

Ausgehend davon, dass wir nur mod_auth_ldap patchen wollen, muss nun im Solutions Explorer das Teilprojekt mod_auth_ldap ausgewählt und die jeweiligen Änderungen in die mod_auth_ldap.c eingetragen werden. Mit einem Rechtsklick auf mod_auth_ldap > Build wird nun das Modul erstellt. Die .so-Datei lässt sich jetzt unter $DIR_SRC\modules\experimental\[Debug|Release]\mod_auth_ldap.so ($MOD_AL) finden. Weiterhin ist die $DIR_SRC\modules\experimental\Debug\mod_auth_ldap.pdb ($DEBUG_AL) wichtig.

Der Apache muss nun als Dienst beendet werden (net stop apache2), danach muss $MOD_AL und $DEBUG_AL nach $DIR_BIN\modules kopiert (vorher Sicherung des Originals erstellen!) und Apache auf der Kommandozeile ($DIR_BIN\bin\apache.exe) gestartet werden. Dies ist nötig, damit Visual Studio sich an den Apache-Prozess hängen kann. Wird der Apache als Dienst ausgeführt, ist dies nicht ohne Weiteres möglich.

Sobald der Webserver läuft, kann im Visual Studio unter Debug > Attach to Process die Apache-Prozesse ausgewählt werden. Beim Hit eines Breakpoints in der mod_auth_ldap.c stoppt der Apache die Ausführung.

Hier die Hinweise im Überblick:

  • $DIR_SRC\Apache.dsw als Projekt öffnen und nicht $DIR_SRC\modules\experimental\mod_auth_ldap.dsw, da man bei letzterem zu viele Einstellungen ändern muss.
  • Apache.exe als normalen Prozess und nicht als Dienst starten, da sich sonst der Debugger nicht nutzen lässt.
  • Neben der .so-Datei muss auch die zugehörige .pdb-Datei in $DIR_BIN\modules kopiert werden.

, , , ,

Keine Kommentare

Web Application goes Desktop

Heute habe ich bei Golem gelesen, dass Titanium den Appcelerator entwickelt hat. An sich nichts Neues, schließlich werden jeden Tag zu hauf’ neue Applikationen veröffentlicht.
Das Interessante ist, dass ich erst beim Lesen des Artikels die Tragweite von RIAs für die Zukunft erfasst habe.
Kurz zum Appcelerator: Das Framework stellt ein Browser-Umgebung – bisher nur für MacOS X und Windows, Linux folgt später – bereit. Dieses  erlaubt es den Entwicklern, ihre Anwendung mehr oder weniger nativ auf dem Desktop laufen zu lassen. Dabei wird in JavaScript + XHTML entwickelt. Appcelerator bietet einige eigene JavaScript-Namespaces an, die unter anderem das Abspielen von Sounds (MP3, WAV) oder das Speichern von relationalen Daten erlauben.

Appcelerator reiht sich in die Reihe der RIA-Frameworks ein. Silverlight von Microsoft und Flex von Adobe sind sicherlich wesentlich bekannter – haben allerdings den Nachteil, dass sie eben nicht JavaScript und XHTMl für die Entwicklung nutzen. Ich persönlich zähle mittlerweile auch das GWT als RIA-Framwork.

Wann kann aber ein RIA-Framework erfolgreich sein? Meiner Meinung nach ist es wichtig, dass der Entwickler -also ich- sich nicht in eine neue Technologie umständlich einarbeiten muss, sondern stattdessen bestehende Technologien benutzt werden. Eindeutig ein Plus für Appcelerator (JavaScript, CSS + XHTML) und GWT (Java + CSS).

Personen, die bereits mit WPF und XAML entwickelt haben, werden sicherlich Silverlight den Vorzug geben.
Aber nicht nur die Um- oder Eingewöhnung in das neue Environment sind wichtig, sondern auch die Features. Meine Wunschliste sieht folgendermaßen aus:

  • Abspielen von Medien – sowohl Video als auch Audio
  • Zugriff auf das Tray-Icon
  • Speicherung von Benutzerdaten in einer relationalen Datenbank (bspw. SQLite)
  • Asynchrone Verbindungen
  • JSON und XML
  • SOAP inkl. WSDL-Security
  • Netzwerkzugriff auf Layer >= 4 um beispielsweise POP3 oder IMAP zu benutzen, Gameserver anzufragen. Hiermit liessen sich etwa die Spielergebnsse eine Counter-Strike-Matches automatisch in das Blog eintragen
  • Plattformunabhängigkeit (Windows, Windows Mobile, MacOS X, Linux, Symbian)

Für mich besonders wichtig ist die Unterstützung von SOAP/Security. Denn damit können RIAs vernünftig an Service-orientierten Architekturen angebunden werden und Sessions lassen sich darüber realisieren.

Für mich steht eines fest: Im Laufe der nächsten zwei Jahre werden sich RIA-Frameworks vor allem in Business-Anwendungen durchsetzen können. Denn – und das habe ich bei der Xtopia oft genug gehört – : “Die User-Experience ist wichtig – vielleicht sogar wichtiger als die Funktionalität”.

Keine Kommentare

UML2 to Hibernate für Lau

Über http://www.mda4eclipse.com/2007/05/acceleo-20-free-module-for-uml2-to.html bin ich darüber gestolpert, dass Accelo in den nächsten Monaten sein UML2Hibernate-Plugin für Eclipse neben der kommerziellen Lizenz ebenfalls unter einer Open Source-Lizenz freigeben wird.

Keine Kommentare

Blog-Empfehlung: Ralph Westphal

Der Blog von Ralph Westphal behandelt Themen rund um Software Architektur – definitiv lesenswert!

2 Kommentare

API für Google Mobile Maps

Nach einigem Suchen bin ich endlich fündig geworden. Neil Young hat die Pakete mit Wireshark analysiert und heraus kam folgender C#-Code:

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using System.Diagnostics;

/*
 * Sample code to obtain geo codes from a cell info
 * "GSM/UMTS" setting revealed by smuraro, thanks!
 */

/* (c) "Neil Young" (neil.young@freenet.de)
 *
 * This script/program is provided "as is".
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * GNU General Public License, see <http://www.gnu.org/licenses/>.
 */

namespace GMM {
    class Program {
        static byte[] PostData(int MCC, int MNC, int LAC, int CID, bool shortCID) {
            /* The shortCID parameter follows heuristic experiences:
             * Sometimes UMTS CIDs are build up from the original GSM CID (lower 4 hex digits)
             * and the RNC-ID left shifted into the upper 4 digits.
             */
            byte[] pd = new byte[] {
                0x00, 0x0e,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00,
                0x00, 0x00,
                0x00, 0x00,

                0x1b,
                0x00, 0x00, 0x00, 0x00, // Offset 0x11
                0x00, 0x00, 0x00, 0x00, // Offset 0x15
                0x00, 0x00, 0x00, 0x00, // Offset 0x19
                0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, // Offset 0x1f
                0x00, 0x00, 0x00, 0x00, // Offset 0x23
                0x00, 0x00, 0x00, 0x00, // Offset 0x27
                0x00, 0x00, 0x00, 0x00, // Offset 0x2b
                0xff, 0xff, 0xff, 0xff,
                0x00, 0x00, 0x00, 0x00
            };

            bool isUMTSCell = ((Int64)CID > 65535);

            if (isUMTSCell)
                Console.WriteLine("UMTS CID. {0}", shortCID ? "Using short CID to resolve." : "");
            else
                Console.WriteLine("GSM CID given.");

            if (shortCID)
                CID &= 0xFFFF;      /* Attempt to resolve the cell using the GSM CID part */

            if ((Int64)CID > 65536) /* GSM: 4 hex digits, UTMS: 6 hex digits */
                pd[0x1c] = 5;
            else
                pd[0x1c] = 3;

            pd[0x11] = (byte)((MNC >> 24) & 0xFF);
            pd[0x12] = (byte)((MNC >> 16) & 0xFF);
            pd[0x13] = (byte)((MNC >> 8) & 0xFF);
            pd[0x14] = (byte)((MNC >> 0) & 0xFF);

            pd[0x15] = (byte)((MCC >> 24) & 0xFF);
            pd[0x16] = (byte)((MCC >> 16) & 0xFF);
            pd[0x17] = (byte)((MCC >> 8) & 0xFF);
            pd[0x18] = (byte)((MCC >> 0) & 0xFF);

            pd[0x27] = (byte)((MNC >> 24) & 0xFF);
            pd[0x28] = (byte)((MNC >> 16) & 0xFF);
            pd[0x29] = (byte)((MNC >> 8) & 0xFF);
            pd[0x2a] = (byte)((MNC >> 0) & 0xFF);

            pd[0x2b] = (byte)((MCC >> 24) & 0xFF);
            pd[0x2c] = (byte)((MCC >> 16) & 0xFF);
            pd[0x2d] = (byte)((MCC >> 8) & 0xFF);
            pd[0x2e] = (byte)((MCC >> 0) & 0xFF);

            pd[0x1f] = (byte)((CID >> 24) & 0xFF);
            pd[0x20] = (byte)((CID >> 16) & 0xFF);
            pd[0x21] = (byte)((CID >> 8) & 0xFF);
            pd[0x22] = (byte)((CID >> 0) & 0xFF);

            pd[0x23] = (byte)((LAC >> 24) & 0xFF);
            pd[0x24] = (byte)((LAC >> 16) & 0xFF);
            pd[0x25] = (byte)((LAC >> 8) & 0xFF);
            pd[0x26] = (byte)((LAC >> 0) & 0xFF);

            return pd;
        }

        static void Main(string[] args) {

            if (args.Length < 4) {
                Console.WriteLine("Usage: gmm MCC MNC LAC CID [\"shortcid\"]");
                return;
            }
            string shortCID = "";   /* Default, no change at all */
            if (args.Length == 5)
                 shortCID = args[4].ToLower();

            try {
                String url = "http://www.google.com/glm/mmap";
                HttpWebRequest req = (HttpWebRequest)WebRequest.Create(new Uri(url));
                req.Method = "POST";

                int MCC = Convert.ToInt32(args[0]);
                int MNC = Convert.ToInt32(args[1]);
                int LAC = Convert.ToInt32(args[2]);
                int CID = Convert.ToInt32(args[3]);
                byte[] pd = PostData(MCC, MNC, LAC, CID, shortCID == "shortcid");

                req.ContentLength = pd.Length;
                req.ContentType = "application/binary";
                Stream outputStream = req.GetRequestStream();
                outputStream.Write(pd, 0, pd.Length);
                outputStream.Close();

                HttpWebResponse res = (HttpWebResponse)req.GetResponse();
                byte[] ps = new byte[res.ContentLength];
                int totalBytesRead = 0;
                while (totalBytesRead < ps.Length) {
                    totalBytesRead += res.GetResponseStream().Read(ps, totalBytesRead, ps.Length - totalBytesRead);
                }

                if (res.StatusCode == HttpStatusCode.OK) {
                    short opcode1 = (short)(ps[0] << 8 | ps[1]);
                    byte opcode2 = ps[2];
                    System.Diagnostics.Debug.Assert(opcode1 == 0x0e);
                    System.Diagnostics.Debug.Assert(opcode2 == 0x1b);
                    int ret_code = (int)((ps[3] << 24) | (ps[4] << 16) | (ps[5] << 8) | (ps[6]));

                    if (ret_code == 0) {
                        double lat = ((double)((ps[7] << 24) | (ps[8] << 16) | (ps[9] << 8) | (ps[10]))) / 1000000;
                        double lon = ((double)((ps[11] << 24) | (ps[12] << 16) | (ps[13] << 8) | (ps[14]))) / 1000000;
                        Console.WriteLine("Latitude: {0}, Longitude: {1}", lat, lon);

                        Process p = new Process();
                        p.StartInfo.FileName = "iexplore";

                        Console.WriteLine("\nClose map window to exit\n");

                        p.StartInfo.Arguments = String.Format(
                            "http://maps.google.de/maps?f=q&hl=de&q={0},{1}&ie=UTF8&z=15",
                            lat.ToString().Replace(',','.'), lon.ToString().Replace(',','.'));
                        p.Start();
                        p.WaitForExit();
                    }
                    else
                        Console.WriteLine("Error {0}", ret_code);
                }
                else
                    Console.WriteLine("HTTP Status {0} {1}", res.StatusCode, res.StatusDescription);
            }
            catch (Exception) {
                throw;
            }
        }
    }
}

Sehr gute Arbeit!
Die Beschreibung des Hexdumps gibt es unter http://maps.alphadex.de/datafiles/fct0e1b117823ccc1a.txt, der Blog von Neil ist unter http://foreverneilyoung.blogspot.com/ zu finden.

Weitere Infos: Anleitung zum Schreiben einer eigenen Anwendung mit GMM-API in Java
Google Maps Mobile API in Python.

Keine Kommentare

Alternative zu Selenium

Unter http://www.froglogic.com/pg?id=Products&category=squish&sub=editions&subsub=web gibt es eine Alternative zu Selenium, die sich Squish nennt. Sie testet auch Webseiten, Tests lassen sich u.a. im Firefox 3 erstellen.

Keine Kommentare

Arbeiten bei gefühlten 45 Grad

Sommerzeit bedeutet nicht immer Ferienzeit – für mich hieß es in den letzten Tagen: Fehler in unserer IT-Infrastruktur fixen, bis die Tastatur glimmt. Unter anderem konnte die Administrationsoberfläche des McAfee ePO 3.61 nicht mehr aufgerufen werden. Überraschenderweise stellte ich nach einiger Zeit fest, dass unsere MSSQL MSDE-Instanz, auf der das ePO liegt, alle Benutzer aus der Datenbank gedroppt hat. Ich konnte mich weder mit Domänen-Administrator, noch mit sa einloggen. Wie das Problem zustande gekommen ist, ist mir ein Rätsel – aber jetzt läuft wieder alles und ich habe gleich auf das ePO 4.0 geupgradet.
Nebenbei etabliert sich langsam aber sicher Trac als ein gutes Bugtracking-System. Unsere komplette Infrastruktur wird über das Trac-Wiki dokumentiert, alle aufgetretenen Fehler kommen inkl. Lösung in den Bugtracker. Unsere Scripte für Backup, Tools u.s.w. liegen in einem zentralen SVN-Repository, das an das Trac angebunden ist.
Sicherlich ist dieser Lösungsansatz nicht unbedingt ITIL-konform, aber er funktioniert und das ganz gut.
Vor zwei Tagen ist in mir auch langsam die Idee des Deployens unserer Anwendungen gereift. Da alle bestehenden Deployment-Tools nicht direkt unseren Anforderungen entsprechen, habe ich beschlossen, etwas eigenes zu bauen.
Unser Deployment-Client läuft als Java-Tool auf den jeweiligen Deploy-Server. Sobald von einem zentralen Server über REST ein Deployment-Prozess getriggert wird, lädt sich das Tool das jeweils zu deployende Artefakt aus unserem Artefakt-Repository und führt die Installation aus. Alle Artefakte sind als ZIP-Datei verfügbar und besitzen eine deploy.xml-Datei. Diese wiederum benutzt Ant-Tasks.
Florian kümmert sich um den Client, damit er auch mal in den Geschmack einer zu programmierenden Servlet-Anwendung unter Java kommt (eingesetzt wird Java 1.5, Jetty und Ant) ;)

Unser momentaner Praktikant hat in den letzten Tagen von mir die Aufgabe bekommen, sich mit dem Zend Framework, CakePHP, Doctrine und Propel auseinander zu setzen. Für den Serverteil unseres Deyploment-Tools, den ich entwickle, habe ich mich für das Zend Framework und Doctrine entschieden.
Was mir besonders beim Zend Framework fehlt, ist die Möglichkeit des Scaffoldings. Zwar existiert ein Proposal, das ist aber weder ausgereift, noch für den Einsatz mit Doctrine gedacht. Im Internet existiert bis dato auch keine vernünftige Zend_Doctrine_Scaffolding-Klasse.
Deshalb habe ich mich heute Nachmittag daran gesetzt, und eine rudimentäre Klasse zusammenzuschustern. Die Ergebnisse lassen sich sicherlich hier in ein paar Tagen bestaunen.

Die Scaffolding-Klasse werde ich übrigens auch für meinen neuen Blog einsetzen. Sobald die Tage wieder etwas länger und dunkler werden, werde ich die komplette Seite auf das Zend Framework und ebenfalls Doctrine umstellen. Unter anderem ist für die neue Seite Google Maps-Integration, Post-Deployment über XML-RPC an andere Blogs, eine neue Bildergalerie, eventuell Anbindung an das Active Directory der Firma und ein bissel AJAX geplant. Das Design soll weiter bestehen bleiben und natürlich bleiben auch alle Einträge erhalten.

Keine Kommentare

ServiceConsoleControl

Gestern habe ich ein alt-bekanntes Problem gelöst. Des öfteren muss ich über die Konsole Windows-Dienste neu starten. Da “net restart” nicht existiert, muss man auf

net stop $SERVICE && net start $SERVICE

zurückgreifen.
Außerdem haben wir in unseren Backup-Scripts einige Dienste, die Abhängigkeiten zu anderen Diensten haben (z.B. McAffe EPO, das von MSSQL abhängt). Hier wäre der pragmatische Lösungsansatz

net stop "McAfee Alert Manager" >> %log%
net stop "McAfee ePolicy Orchestrator 3.6.1 Application Server" >> %log%
net stop "McAfee ePolicy Orchestrator 3.6.1 Server" >> %log%
net stop "McAfee ePolicy Orchestrator 3.6.1 Event Parser" >> %log%
net stop "McAfee Outbreak Manager" >> %log%
net stop "McAfee Log Service" >> %log%
net stop "McAfee SpamKiller" >> %log%
net stop "SQLAgent$EPOSERVER" >> %log%
net stop "MSSQL$EPOSERVER" >> %log%

REM Am Ende alles wieder starten

Viel einfacher wäre ein einfaches

net stop MSSQL$EPOSERVER

wodurch alle von diesem Dienst abhängigen Dienste ebenfalls beendet werden.

Soweit so gut, ich habe mich gestern also hingesetzt und das ganze in C# heruntergetippert. scc (angelehnt an sc) unterstützt die Befehle “start, stop und restart”. Der Code dafür lautet:

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceProcess;
using System.Threading;

namespace ServiceConsoleControl
{
    /// <summary>
    /// Main entry class
    /// </summary>
    class Program
    {
        private static String NAME = "scc";

        static void Main(string[] args)
        {
            if (args.Length != 2)
            {
                ShowUsageAndExit(null);
            }

            try
            {
                Command cmd = Command.Factory(args[0]);
                cmd.SetServiceName(args[1]);
                cmd.Execute();
            }
            catch (Exception e)
            {
                ShowUsageAndExit("Error: " + e.Message);
            }
        }
        /// <summary>
        /// Shows the usage of this program and exits with code 0
        /// </summary>
        /// <param name="_line">additional line to print before usage is shown</param>
        public static void ShowUsageAndExit(String _line)
        {
            if (_line != null)
            {
                Program.LogLine(_line);
            }

            Program.LogLine(Program.NAME + " starts, stops and restarts services with all dependencies to other services");
            Program.LogLine("Usage: " + Program.NAME + " [" + Command.CMD_START + ", " + Command.CMD_STOP + ", " + Command.CMD_RESTART + "] [Service]");
            Environment.Exit(0);
        }

        /// <summary>
        /// Write string to console
        /// </summary>
        /// <param name="_msg">string to write</param>
        public static void LogLine(String _msg)
        {
            Console.WriteLine(_msg);
        }
    }

    /// <summary>
    /// Interface for executable service commands
    /// </summary>
    interface IServiceCommand
    {
        /// <summary>
        /// Execute the command
        /// </summary>
        void Execute();

        /// <summary>
        /// Sets name of service
        /// </summary>
        /// <param name="_name">name of service</param>
        void SetServiceName(String _name);

        /// <summary>
        /// Retrieves name of service
        /// </summary>
        /// <returns>name of service</returns>
        string GetServiceName();
    }

    /// <summary>
    /// Abstract class for commands, implements IServiceCommand
    /// </summary>
    abstract class Command : IServiceCommand
    {
        private String _name;

        protected ServiceController sc = new ServiceController();

        public static String CMD_STOP = "stop";

        public static String CMD_START = "start";

        public static String CMD_RESTART = "restart";

        /// <summary>
        /// Factory method for command.
        /// Given command will be lowered.
        /// If the command is not supported, an exception will be thrown
        /// </summary>
        /// <param name="_cmd">Command to execute - this can be one of this.CMD_*</param>
        /// <returns>Instance of Command</returns>
        public static Command Factory(String _cmd)
        {
            _cmd = _cmd.ToLower();

            if (_cmd.Equals(CMD_RESTART))
            {
                return new RestartCommand();
            }
            else if (_cmd.Equals(CMD_START))
            {
                return new StartCommand();
            }
            else if (_cmd.Equals(CMD_STOP))
            {
                return new StopCommand();
            }

            throw new Exception("Unsupported command: " + _cmd);
        }

        /// <summary>
        /// Sets service name, required by IServiceCommand
        /// </summary>
        /// <param name="_serviceName">Service name to set</param>
        public void SetServiceName(String _serviceName)
        {
            _name = _serviceName;
            sc.ServiceName = _serviceName;
        }

        /// <summary>
        /// Gets service name, required by IServiceCommand
        /// </summary>
        /// <returns>Service name</returns>
        public String GetServiceName()
        {
            return _name;
        }

        /// <summary>
        /// Abstract execute command which is required by IServiceCommand
        /// </summary>
        abstract public void Execute();
    }

    /// <summary>
    /// Abstract class for recursive commands, can be used for solving dependencies between services
    /// </summary>
    abstract class RecursiveCommand : Command
    {
        /// <summary>
        /// Depth of dependency
        /// </summary>
        private int _depth = 0;

        /// <summary>
        /// Stringbuilder with prefixing spaces
        /// </summary>
        private StringBuilder sb;

        /// <summary>
        /// Timespan to wait for service
        /// </summary>
        protected TimeSpan tsWait = new System.TimeSpan(10000000000000);

        /// <summary>
        /// Abstract method which retrieves the recursive command
        /// </summary>
        /// <returns>RecursiveCommand</returns>
        abstract protected RecursiveCommand GetCommand();

        /// <summary>
        /// Depth value
        /// </summary>
        public int Depth
        {
            set { _depth = value; }
            get { return _depth; }
        }

        /// <summary>
        /// Return depth as spaces
        /// </summary>
        /// <returns>String with depth as spaces</returns>
        protected String GetSpaces()
        {
            if (sb == null)
            {
                sb = new StringBuilder();

                for (int i = 0; i < _depth; i++)
                {
                    sb.Append("  ");
                }
            }

            return sb.ToString();
        }

        /// <summary>
        /// Executes GetCommand() on all given ServiceController[]
        /// </summary>
        /// <param name="scs">Array of ServiceController on which the GetCommand() will be executed</param>
        protected void ExecuteCommandOnServiceControllers(ServiceController[] _scs)
        {
            Program.LogLine(GetSpaces() + "Service dependencies: " + _scs.Length);

            for (int i = 0, m = _scs.Length; i < m; i++)
            {
                RecursiveCommand rc = this.GetCommand();
                rc.Depth = this.Depth + 1;
                rc.SetServiceName(_scs[i].ServiceName);
                rc.Execute();
            }
        }

        /// <summary>
        /// Returns true if service is of ServiceType Win32ShareProcess or Win32OwnProcess. Other types cannot be killed
        /// </summary>
        protected bool IsKillable
        {
            get
            {
                if (sc.ServiceType.Equals(ServiceType.Win32ShareProcess) || sc.ServiceType.Equals(ServiceType.Win32OwnProcess))
                {
                    return true;
                }

                Program.LogLine(GetSpaces() + this.DisplayName + " is of type " + sc.ServiceType.ToString() + ". These services can not be processed.");
                return false;
            }
        }

        /// <summary>
        /// Returns display name
        /// </summary>
        public String DisplayName
        {
            get
            {
                return "Service " + sc.DisplayName + " (" + sc.ServiceName + ")";
            }

        }
    }

    /// <summary>
    /// Command for restarting a service
    /// </summary>
    class RestartCommand : Command
    {
        /// <summary>
        /// Stops the service and then restarts
        /// </summary>
        public override void Execute()
        {
            IServiceCommand stopCmd = new StopCommand();
            IServiceCommand startCmd = new StartCommand();
            stopCmd.SetServiceName(this.GetServiceName());
            startCmd.SetServiceName(this.GetServiceName());

            stopCmd.Execute();
            startCmd.Execute();
        }

        public override String ToString()
        {
            return CMD_RESTART;
        }
    }

    /// <summary>
    /// Command for starting a service with all its dependencies
    /// </summary>
    class StartCommand : RecursiveCommand
    {
        public override String ToString()
        {
            return CMD_START;
        }

        /// <summary>
        /// Retrieve a new instance of myself
        /// </summary>
        /// <returns></returns>
        protected override RecursiveCommand GetCommand()
        {
            return new StartCommand();
        }

        /// <summary>
        /// Starts a service. The service could only be started if it is Win32[Own|Share]Process. KernelDriver or other types of services are not allowed.
        /// At first all services will be started which this service depends on. At second the service itself will be started.
        /// After that all services are started which depends on this service.
        /// </summary>
        public override void Execute()
        {
            if (this.IsKillable)
            {
                if (sc.Status.Equals(ServiceControllerStatus.Stopped))
                {
                    Program.LogLine(GetSpaces() + "Starting " + this.DisplayName);
                    Program.LogLine(GetSpaces() + "Starting all services that " + this.DisplayName + " depends on ...");
                    ExecuteCommandOnServiceControllers(sc.ServicesDependedOn);
                    sc.Start();
                    sc.WaitForStatus(ServiceControllerStatus.Running, tsWait);
                    Program.LogLine(GetSpaces() + "Service is now in status " + sc.Status.ToString());
                    Program.LogLine(GetSpaces() + "Starting all services that depends on " + this.DisplayName + " ... ");
                    ExecuteCommandOnServiceControllers(sc.DependentServices);
                }
                else
                {
                    Program.LogLine(GetSpaces() + this.DisplayName + " has status " + sc.Status.ToString() + " - nothing to do");
                }
            }
        }
    }

    /// <summary>
    /// Command for stopping a service with all its dependencies
    /// </summary>
    class StopCommand : RecursiveCommand
    {
        public override String ToString()
        {
            return CMD_STOP;
        }

        /// <summary>
        /// Returns an instance of myself
        /// </summary>
        /// <returns>New instance of StopCommand</returns>
        protected override RecursiveCommand GetCommand()
        {
            return new StopCommand();
        }

        /// <summary>
        /// Executes the command. The service could only be stopped if it is Win32[Own|Share]Process. KernelDriver or other types of services are not allowed.
        /// At first all services will be stopped that depends on this service. Then this service is stopped.
        /// </summary>
        public override void Execute()
        {
            if (this.IsKillable)
            {
                if (sc.Status.Equals(ServiceControllerStatus.Running))
                {
                    Program.LogLine(GetSpaces() + "Stopping " + this.DisplayName);
                    Program.LogLine(GetSpaces() + "Stopping all services that depends on " + this.DisplayName + " ...");
                    ExecuteCommandOnServiceControllers(sc.DependentServices);
                    sc.Stop();
                    sc.WaitForStatus(ServiceControllerStatus.Stopped, tsWait);
                    Program.LogLine(GetSpaces() + "Service is now in status " + sc.Status.ToString());
                }
                else
                {
                    Program.LogLine(GetSpaces() + this.DisplayName + " has status " + sc.Status.ToString() + " - nothing to do");
                }
            }
        }
    }
}

Keine Kommentare

Fixe Idee: Wie man die Firefox-Einstellungen per Gruppenrichtlinie verteilt

Christoph und ich hatten uns heute Mittag über die Erstellung von MSI-Paketen unterhalten – ich hatte vor einigen Tagen unsere Fonts per MSI und Active Directory deployt.
Nun kamen wir wieder einmal auf das Thema “Deployment von Firefox” und Setzen der Einstellungen.

Mein erster Gedanke war, ein eigenes XPCOM-Modul zu schreiben. XPCOM ist wie DCOM auch ein Komponentenmodell und wird von der Mozilla-Truppe voran getrieben. Die XPCOM-Module werden in C++ entwickelt und sind so plattformunabhängig.
Das fiktive XPCOM-Modul würde nun die Daten aus der Registry laden und dementsprechend alle Einstellungen des Firefoxes setzen.

So weit, so gut. Es geht aber auch einfacher: Nach einer kurzen Recherche habe ich heraus gefunden, dass Firefox ab der Version 1.5 das XPCOM-Interface nsIWindowRegKey bereitstellt. Unter http://developer.mozilla.org/en/docs/Accessing_the_Windows_Registry_Using_XPCOM findet sich dazu ein ausführlicher Artikel.

Über das Interface nsIPrefService lassen sich Einstellungen des Firefoxes lesen und schreiben, siehe dazu auch http://developer.mozilla.org/en/docs/nsIPrefService.

Das weitere Vorgehen ist eigentlich trivial. Anstatt eines in C++ geschriebenes XPCOM-Modul wird einfach eine Firefox-Extension in JavaScript geschrieben.
Diese lädt beim Start alle Einstellungen aus z.B. HKCU\Software\Mozilla\Firefox, überprüft die Typen (DWORD, Boolean, String) und setzt über nsIPrefService die Einstellungen.

Damit die Einstellungen über das AD verteilt werden können, ist etwas Handarbeit nötig: eine neue .adm-Datei hält die definierten Einstellungen vor. Mit dieser Vorlage lassen sich alle Einstellungen ganz einfach per Active Directory verteilen.

Mal sehen, ob einer aus unserer Truppe dafür irgendwann mal Zeit hat.

Keine Kommentare