Archiv der Kategorie: IIS

IIS Applications regelmäßig aufrufen und vor dem Shutdown/Restart bewahren


Wenn eine Webseite im IIS nicht regelmäßig aufgerufen wird, dann stoppt der IIS die Seite und die Seite muss beim nächsten aufrufen wieder neu gestartet werden. Leider kann der Neustart einer Webseite unter Umständen recht lang dauern, denn hier kommt es darauf an wie aufwendig z.B. der Application Start ist und ob auch der Applicationpool neugestartet werden muss.

Ab dem IIS 8.0 bietet Microsoft zwar die Möglichkeit das man einem Applicationpool mit der Erweiterung “Anwendungsinitialisierung” für den IIS so konfigurieren kann das der Applicationpool automatisch startet . Für die Webseiten ist mir dies bisher leider nicht gelungen. Das IIS Modul steht auch für den IIS 7.0/7.5 zur Verfügung.

image

In den erweiterten Applicationpool Einstellungen kann man dann den Startmodus des Applicationpools festlegen.

image

Auf Anwendungsebene gibt es dann in den Erweiterten Einstellungen die Option “Vorabladen aktiviert”.

image

Leider scheint es beim Vorabladen immer noch Probleme zu geben, vor allem wenn man bei seinen Webseiten Windows-Authentifizierung für den Login verwendet, scheint diese Variante zumindest mit IIS 7.X nicht zu funktionieren.

Eine alternative Lösung ist z.B. eine Konsolenanwendung die auf dem Server ausgeführt wird, auf dem die Webseiten im gehostet werden. Die Konsolenanwendung  wird in regelmäßigen Abständen aufgerufen und durchsucht den lokalen IIS nach allen Webseiten und ruft diese auf.

static void Main(string[] args)
{
    //Diese DLL einbinden für den ServerManager:
    //c:\Windows\System32\inetsrv\Microsoft.Web.Administration.dll 

    //Servermanager verwenden um auf den lokalen IIS zuzugreifen
    ServerManager manager = new ServerManager();
    List<string> pages = new List<string>();

    // Die Auflistung aller Seiten auf dem Server ermitteln
    string sitedisplay = string.Empty;
    foreach (Site site in manager.Sites)
    {
        sitedisplay = sitedisplay + site.Name + ": ID= " + site.Id + "\n";
        string bindingdisplay = string.Empty;
        string port = string.Empty;
        string protokoll = "http";
        string host = "localhost";
        string appPath = string.Empty;

        foreach (Microsoft.Web.Administration.Binding binding in site.Bindings)
        {
            if (binding.EndPoint != null && (binding.Protocol == "http" || binding.Protocol == "https"))
            {
                bindingdisplay = "  Binding:";
                bindingdisplay += "\n   Host: " + binding.Host;
                bindingdisplay += "\n   Port: " + binding.EndPoint.Port;
                bindingdisplay += "\n   Protocol: " + binding.Protocol + "\n\n";

                port = binding.EndPoint.Port.ToString();
                protokoll = binding.Protocol;
                host = string.IsNullOrEmpty(binding.Host) ? "localhost" : binding.Host;
            }
        }

        foreach (Application app in site.Applications)
        {
            appPath = app.Path;
            //Alle Seiten URLs ermitteln
            var url = string.Format("{0}://{1}:{2}{3}", protokoll, host, port, appPath);
            pages.Add(url);
            Console.WriteLine(url);
        }

        sitedisplay = "\n" + sitedisplay + bindingdisplay;
    }

    Console.WriteLine(sitedisplay);
    
    //Callback Funktion für SSL-Zugriffe festlegen.
    ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback(AcceptAllCertifications);

    //Alle Seiten die wir ermittelt haben mit einem WebRequest starten.
    //dieser WebRequest verwendet die Anmeldeinformationen der Konsolenanwendung.
    foreach (string page in pages)
    {
        Stopwatch stopwatch = new Stopwatch();
        try
        {
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(page);
            request.Method = "GET";
            request.UseDefaultCredentials = false;
            request.PreAuthenticate = true;
            request.Credentials = CredentialCache.DefaultCredentials;
            Console.WriteLine("Page: {0} | Starting...", page);
            stopwatch.Start();
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            Console.WriteLine("Page: {0} | Status: {1}", page, response.StatusCode);
        }
        catch (Exception exception)
        {
            Console.WriteLine("Page: {0} | Status: {1} | Message: {2}", page, "Error", exception.Message);
        }
        finally
        {
            stopwatch.Stop();
            Console.WriteLine("Page: {0} | Startdauer: {1} ms \n\n", page, stopwatch.ElapsedMilliseconds);
        }
    }

    Console.WriteLine("DONE!");
    Console.ReadLine();
}

/// <summary>
/// Callback Funktion für SSL Aufrufe immer mit True bestätigen auch wenn das Zertifikat evtl. nicht valide ist. Für z.B. selbstsignierte Zertifikate im IIS
/// </summary>
public static bool AcceptAllCertifications(object sender, System.Security.Cryptography.X509Certificates.X509Certificate certification, 
                                           System.Security.Cryptography.X509Certificates.X509Chain chain, System.Net.Security.SslPolicyErrors sslPolicyErrors)
{
    return true;
}

Wichtig ist das der Benutzer der die Konsolenanwendung ausführt als lokaler Administrator eingerichtet ist, denn die Anwendung greift auf den lokalen IIS zu. Außerdem muss der Benutzer als Domainuser eingetragen sein, wenn die Anwendung auch auf Webseiten zugreifen soll für die Windows-Authentifizierung eingerichtet ist.

Der Quellcode steht auch unter Codeplex zur Verfügung.

SSL einstellen für IIS 7 mit C# und dem ServerManager


Wie ich bereits gezeigt habe gibt es die unterschiedlichsten Wege um für eine Webseite SSL einzustellen. Ich habe das ganze bereits hier gezeigt, in dem ich SSL manuell eingestellt habe und hier in dem ich SSL mit Hilfe von C# und DirectoryEntry gesetzt habe. Jetzt kommt noch das setzten von SSL mit Hilfe des ServerManagers hinzu, was aber nur für den IIS größer der Version 7 geht. Auch hier gehe ich nur auf Details ein die das Einstellen von SSL beinhalten.

Dazu muss gesagt werden das jede Webseite eine ID besitzt im IIS, welche in der ID-Spalte der Webseiten Auflistung des IIS zu finden ist.

image

In unserem Falle handelt es sich um die Webseite mit der ID = 1.

Das Folgende Beispiel Zeigt wie man die passende Webseite findet und ein Binding für ein passendes SSL Zertifikat setzt für Port 443. Hier wird nicht geprüft ob bereits ein Binding existiert, ….

 ServerManager iisManager = new ServerManager();
 Site baseWebsite = null;

 foreach (Site eSite in iisManager.Sites)
 {
     //Es handelt sich um die Basiswebseite diese hat immer die 
     //ID 1 (Sei denn sie wurde evtl. vom Admin gelöscht und neu angelegt)
     if (eSite.Id == 1)
     {
         baseWebsite = eSite;
         break;
     }
 }
 
 baseWebsite.Bindings.Add("*:443:",
         CertificateFunctions.GetCertificateByHashString("61a669c54ab30b5e72209ff88b3c0785df927228").GetCertHash(),
         CertificateFunctions.GetLocalCertificateStore().Name);
 iisManager.CommitChanges();

So einfach kann der SSL Port für eine Webseite mit dem ServerManager angelegt werden. Wenn Sie dann den IIS öffnen dann sehen Sie wie oben im Screenshot bereits zu sehen ist auch den Port 443 mit https in den Bindungen für Ihre Standard Webseite wieder.

So öffnen Sie den lokalen Zertifikatsstore um an den Storename zu kommen für das Zertifikat.

/// 
/// Gibt den Zertiofikat Store zurück der benutzt wird.
/// 
public static X509Store GetLocalCertificateStore()
{
    X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
    store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite);
    return store;
}

Das passende Zertifikat anhand des übergebenen Hashes ermitteln.

/// 
/// Sucht aus dem lokalen Zertifikaten das mit dem übergebenen
/// Hash heraus und gibt das passende Zertifikat zurück
/// 
/// der Hashstring nach dem gesucht werden soll in den Zertifikaten
/// das passende X509Certificate2 | wenns nicht gefunden wird NULL
public static X509Certificate2 GetCertificateByHashString(string hashString)
{
    //Öffnen des lokalen Zertifikatstores
    X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
    store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite);

    //Alle lokalen Zertifikate durchgehen.
    foreach (X509Certificate2 x509Certificate2 in store.Certificates)
    {
        //Wenn das Zertifikat mit dem Hashcode gefunden wurde das Zertifikat zurückgeben.
        if (hashString.ToLower() == x509Certificate2.GetCertHashString().ToLower())
        {
            return x509Certificate2;
        }
    }

    //Das Zertifikat konnte nicht gefunden werden.
    return null;
}

SSL einstellen für IIS mit C# und DirectoryEntry


Es gibt die unterschiedlichsten Wege um für eine Webseite SSL einzustellen, wie .z.B. das manuelle Hinzufügen einer https Verbindung mit dem IIS-Manager hier.

Dann kann man noch für den IIS 6 mit Hilfe von C# und DirectoryEntry SSL setzten. Im Folgenden erläutere ich welche Einstellungen im DirectoryEntry gesetzt werden müssen. Da ich leider keine “genaue” Anleitung gefunden habe, sondern alles mit Hilfe von Probieren und einem guten Metabase Explorer aus dem Resource Toolkit für den IIS 6 “herausgefunden” habe, kann es gut sein das es auch andere und evtl. einfachere Wege gibt das ganze einzustellen, wenn ja würde ich mich über einen Kommentar sehr freuen.

Als erstes müssen wir uns einen passenden DirectoryEntry heraussuchen für den wir SSL setzten wollen. Da man SSL für eine komplette Site setzt, suchen wir uns in meinem Beispiel die “Default Web Site” heraus und setzten hier den Standard SSL Port 443.

Ich setzte voraus das man im ISS bereits ein SSL Zertifikat angelegt hat, wie z.B. hier am Anfang erläutert wird, das Zertifikat mit dem Namen “squadwuschelscert”.

Eine Liste der installierten Zertifikate auf dem lokalen Rechner erhält man z.B. mit der folgenden Funktion:

/// <summary>
/// Gibt eine Liste mit den installierten Zertifikaten zurück
/// </summary>
public static SortedList<string, string> GetInstalledCertifcates()
{
    //SortedList(HashAlsString,CertificateFileName)
    SortedList<string, string> certificates = new SortedList<string, string>();
    bool edoCertIsInstalled = false;

    //Den lokalen Zertifikatsstore auf dem Rechner öffnen.
    X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
    store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite);

    //alle Zertifikate auf dem lokalen Rechner durchgehen.
    foreach (X509Certificate2 eCertificate in store.Certificates)
    {
        //Die Zertifikate unserer Liste hinzufügen, der Hash wird als Key verwendet, 
        //da dieser im Regelfall nur einmal im lokalen Store installiert sein sollte.
        if (!certificates.ContainsKey(eCertificate.GetCertHashString()))
        {
            if (eCertificate.FriendlyName.Trim().Length == 0)
            {
                certificates.Add(eCertificate.GetCertHashString(), eCertificate.SubjectName.Name);
            }
            else
            {
                certificates.Add(eCertificate.GetCertHashString(), eCertificate.FriendlyName);
            }
        }
    }

    return certificates;
}

Wir benötigen den passenden Zertifikatshash, den wir dann im DirectoryEntry eintragen (das Zertifikat muss auf dem Lokalen System installiert sein zu dem der Hash gehört!).

Der Normale HashString der vom Store für die einzelnen Zertifikate zurück gegeben wird, kann nicht im DirectoryEntry hinzugefügt werden, daher muss dieser “modifiziert” werden mit der folgenden Funktion, wird ein Hash der z.B: “61a669c54ab30b5e72209ff88b3c0785df927228” aussieht an die Funktion übergeben.

/// <summary>
/// Der normale HashString, der vom Zertifikat zurück gegeben wird, kann so nicht verwendet werden um ihn
/// dem directoryEntry hinzufügen zu können. Dieser wird hier bearbeitet und e
/// </summary>
/// <param name="certHashString">der HashString der in ein object array umgewandelt werden soll.</param>
public static object[] ConvertCertHashString(string certHashString)
{
    //Der String vom Certifikathash muss speziell "aufgearbeitet" werden, damit dieser unserem SSLCertHash im IIS hinzugefügt werden kann.
    string newHash = string.Empty;
    string tmp = string.Empty;
    for (int i = 1; i <= certHashString.Length; i++)
    {
        //BeispielString der erzeugt wird: "a7 1f db 98 92 7e 71 c0 7b 1e f7 0d 74 1d c4 5a 39 a8 22 f5"
        //damit eine Split methode ausgeführt werden kann.
        tmp += certHashString[i - 1];

        if (i % 2 == 0)
        {
            tmp += ' ';
            newHash += tmp.ToLower();
            tmp = string.Empty;
        }
    }

    //Damit der Eintrag dann dem DirectoryEntry hinzugefügt werden kann wird ein Object Array benötigt.
    string[] newValueSplit = newHash.Trim().Split(new Char[] { ' ' });
    object[] newObj = new object[newValueSplit.Length];
    newValueSplit.CopyTo(newObj, 0);

    return newObj;
}

Die Funktion gibt dann ein Object Array zurück, welches dem DirectoryEntry erfolgreich hinzugefügt werden kann.

Mit dem folgenden Code Ausschnitt suchen wir unsere Website heraus und setzten die DirectoryEntries. Wir müssen hier nur zwei Einträge setzten. Dazu gehört “SSLCertHash” und “SecureBindings”.

Der “SSLCertHash” Eintrag wird benötigt, damit der IIS weiß welches Zertifikat verwendet werden soll und mit Hilfe von “SecureBindings” wird der Port angegeben für den SSL Zugang. Wenn wir diese Werte dem DirectoryEntry hinzugefügt haben, dann werden automatisch noch weitere Werte angelegt, was man sehr gut in einem Metabase Explorer sehen kann wie z.B. “SLLCertStoreName”.

//Die Basiswebseite (ROOT) ermitteln.
DirectoryEntry w3svc = new DirectoryEntry("IIS://localhost/W3SVC/1");

//Der Hash kann immer wieder neu gesetzt werden, 
//d.h. wenn jmd ein anderes Zertifikat möchte muss man das alte erst entfernen
PropertyValueCollection propValues = w3svc.Properties["SSLCertHash"];
propValues.Clear();
w3svc.CommitChanges();

//Einen passenden SSL Cert Hash heraussuchen
propValues.Add(CertificateFunctions.ConvertCertHashString("61a669c54ab30b5e72209ff88b3c0785df927228"));
w3svc.CommitChanges();

//Wenn noch keine Bindings für SSL gemacht wurden, wird hier ein Standard Binding für SSL angelegt, sonst
//wird das bereits gesetzte Binding verwendet, 
//um keine Änderungen an der bestehenden Basiswebseite zu machen die nicht notwendig sind.
if (w3svc.Properties["SecureBindings"].Count == 0)
{
    propValues = w3svc.Properties["SecureBindings"];
    //Den neuen Ports für SSL - Wenn dieser Eintrag hinzugefügt wird, werden automatisch weitere Einträge vom 
    //System erstellt wie SLLCertStoreName = "MY" muss nicht von uns erstellt werden.
    //Gut zu sehen mit einem Metabase explorer
    propValues.Add("*:443:");
    w3svc.CommitChanges();
}

Wenn man jetzt die Bindings der Default Web Site anschaut, ist dort auch ein Binding für SSL und dem Port 443 angegeben.

Das ganze geht auch noch einfacher, aber nur für den IIS größer Version 7 mit dem ServerManager.

SSL für den IIS 7.5 und eine Webseite konfigurieren


Wenn man auf seiner Webseite z.B. einen Sicheren Login anbieten möchte, muss man dafür im IIS für diese Webseite die Verschlüsselung aktivieren und dann kann der Nutzer mit Hilfe von “https” eine Sichere Verbindung aufbauen.

Als erstes benötigt man ein SSL Zertifikat, welches man selbst erstellen kann, direkt im IIS-Manager. Wenn man das Zertifikat selbst erstellt, dann wird der Nutzer eine Sicherheitswarnung im Browser erhalten, das es sich um ein unsicheres Zertifikat handelt. Was für unser Beispiel aber erst einmal egal ist.

Wenn man keine Warnung erhalten möchte weil es sich um die offizielle Firmenhomepage o.ä. handelt, dann muss man sich z.B. ein Zertifikat bei VerySign oder einem ähnlichen Dienstanbieter kaufen.

Als erstes starten wir den IIS-Manager und wenn wir unseren Rechner auswählen, finden wir dann rechts unter IIS die Option “Serverzertifikate”.

image

Wenn wir die “Serverzertifikate” öffnen, haben wir die Möglichkeit unser Zertifikat von z.B. VerySign zu importieren oder wir können ein “Selbstsigniertes Zertifikat erstellen….”

image

Wenn wir das Zertifikat erstellt haben, sehen wir es ebenfalls unter unseren Serverzertifikaten

image

Jetzt muss nur noch für unsere Webseite der SSL Port und Zertifikat eingestellt werden. Dafür werden die “Bindungen…” für unsere Webseite bearbeitet. Dafür einfach z.B. die “Default Web Site” auswählen und dann rechts auf “Bindungen….” klicken.

image

Wenn bisher nur die Standard Bindung enthalten ist, kann das ganze z.B. folgendermaßen aussehen. Hier einfach auf “Hinzufügen” gehen.

image

Im folgenden Fenster dann bei Typ “https” auswählen und unter “SSL-Zertifikat” unser Zertifikat auswählen. Der Standard Port bei SSL ist 443, dann muss dieser nicht zusätzlich mit in der URL angegeben werden, wenn auf die Seite mit Hilfe von “https” zugegriffen wird.

image

Unsere “Seitenbindungen” sehen dann folgendermaßen aus

image

wenn man die Webseite jetzt lokal aufruft, dann sieht das im Firefox bei mir z.B. folgendermaßen aus mit “https://localhost” bringt mir der Firefox eine Warnung, das der Verbindung nicht vertraut werden kann, da ich das Zertifikat selbst erstellt habe.

image

hier einfach das “Ich kenne das Risiko” anklicken und das Zertifikat zu meiner Liste der “vertrauenswürdigen” Zertifikate hinzufügen und unsere Seite wird über “https” angezeigt.

image