ReportWeb Trigger

Auch wenn der Taskplaner "gut" arbeitet und die meisten Ergebnisse nicht "Realtime" sind, dann ist es dennoch wünschenswert, einen Report auch von der Ferne zu starten. Natürlich könnte man mit einem Art "Remote Command" oder auch den Taskplaner aus der Ferne solche Aktionen starten. Aber schicker ist natürlich die Aktion über einen Browser auszulösen. Vielleicht sogar direkt aus der Statusübersicht.

Die Möglichkeit einen Report per Trigger starten zu lassen erlaubt es, die regelmäßige Generierung seltener durchführen zu lassen. Durch die Anzeige der letzten Laufzeit kann auch abgeschätzt werden, wann die aktuellen Daten vorliegen.

Ich habe mir verschiedene Optionen betrachtet.

  • Auslösung per Browser
    Natürlich wäre es am schönsten, wenn auf jeder Reportseite ein „jetzt ausführen“ angeboten wird. Da ist noch einfach. Per URL kann ja auch dem Webserver z.B. ASP.NET Code gestartet werden
  • Keine PowerShell in ASP.NET
    Es verbietet sich aber, direkt die Aktionen in ASP.NET zu machen. Zum einen geht das so etwas mal nicht. Zudem wäre die Frage der Berechtigungen zu klären. Weiterhin sind Timeouts zu beachten, in die lang laufende Skripte schon kommen können. Ich kenne natürlich auch Programme wie PowerShell.ASP (http://www.PowerShellinside.com/PowerShell/asp/) aber wenn ein Quota-Report 10 Minuten arbeitet, werden Sie sicher nicht solange vor ihrem Browser warten.
  • DoS Schutz
    Ich möchte einen Schutz gegen Überlastung haben, nicht dass jemand einen Report ganz oft antriggert. So darf der Report nicht mehrfach gestartet werden und auch die Anzahl der parallel ausgeführten Reports muss beschränkt bleiben
  • Auditing
    Es sollte nachvollziehbar sein, wer hat wann welchen Report „getriggert“ hat

Nach einigem Hin und Her haben ich mich für folgenden Weg entschieden.

  • IIS/ASPX erzeugt Eventlog-Eintrag
    Eine einfache ASPX-Seite übernimmt den Reportnamen aus dem Parameter der URL und schreibt einen Eintrag ins Eventlog. Damit ist schon mal die Nachvollziehbarkeit protokolliert.
  • Taskmanager wird durch Eventlog angestoßen
    Es ist unter Windows 2008 sehr einfach möglich, den Taskmanager durch einen Eintrag im Eventlog eine Aktion auslösen zu lassen.
  • Trigger-Skript legt Trigger-Datei an
    Natürlich könnte ich das Hauptprogramm von ReportWeb aufrufen und nach den Events suchen und den Report starten lassen. Nur würde dann während des Reports kein weiterer Event mehr verarbeitet. Daher startet der Taskplaner beim Eintreffen eines Event ein eigenes PowerShell Skript (Reportweb-trigger.ps1), welche im Eventlog nach der letzten Meldung von ReportWeb sucht und den Reportnamen ermittelt. Das Skript startet aber nicht selbst nun die Reports, sondern legt einfach nur eine kleine „TRIGGER“-Datei an und beendet sich sofort wieder.
  • Der Controller sieht beim nächsten Lauf diesen Trigger und startet den Report, sobald es die Ressourcen zulassen.

Der Weg über das das Eventlog und den Taskmanager hat einige handfeste Vorteile:

  • Protokollierung
    Jeder getriggerte Aktion erscheint im Eventlog, da basierend auf dieser Information überhaupt eine Aktion ausgelöst wird. Quasi nebenbei ist damit auch sichergestellt, dass all diese Aktionen protokolliert und später nachvollziehbar sind.
  • Berechtigungen
    Ein ASP.NET Task des IIS muss nur einen Eintrag ins Application-Log schreiben aber weder PowerShell starten noch kann es den IIS umfangreich stören. Die eigentliche Ausführung erfolgt durch das PowerShell-Skript, welches vom Taskplaner in einem eigenen Kontext gestartet wird. Eine perfekte Trennung von Zuständigkeiten und Berechtigungen. Der Code mit privilegierten Rechten ist sehr weit vom IIS "entfernt". Zudem erspare ich mir damit eine Konfigurationseinstellung zum Pfad der Trigger-Datei in der ASPX-Seite.
  • Throttling
    Der Taskmanager kann das Trigger-Skript sehr oft aufrufen aber die TRIGGER-Datei wird nur angelegt, wenn es keine RUN-Datei und noch keine TRIGGER-Datei gibt. So wird ein Report im Gegensatz zu einer ASP/ASP.NET Lösung nur maximal einmal parallel gestartet.

Einen kleinen Nachteil gibt es aber auch: Die Kommunikation ist "asynchron" und es gibt kein Feedback, d.h. die Webseite kann nur ein OK melden, dass der Eintrag im Eventlog gelandet ist aber keine Bestätigung oder gar Anzeige des Fortschritts.

ASP.NET-Seite "\trigger\default.aspx"

Ich habe eine kleine ASP.NET-Seite quasi mit dem Sharepoint Designer ganz ohne Visual Studio geschrieben, die zum einen den Namen des Report über die URL annimmt. Diese kann z.B. von der Statusseite direkt "anklickbar" gemacht werden. Ein zweiter Weg ist die Angabe des gewünschten Reports als Formular. In beiden Fällen schreibt das ASP.NET-Skript den gewünschten Eventlog-Eintrag. Hier der relevante Code für die Steuerung per URL-Parameter:

<%@ Page Language="c#" AutoEventWireup="true" %>
<%@ Import namespace="System.Diagnostics" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
 
<% 
EventLog ev = new EventLog("Application");
ev.Source = "ReportWeb";
string report = Request.QueryString["report"]; 
if (report != null){
    try {
        report += Environment.NewLine + "User:" + Request.ServerVariables["AUTH_User"];
        report += Environment.NewLine + "ClientIP:" + Request.ServerVariables["REMOTE_ADDR"];
        report += Environment.NewLine + "UserAgent:" + Request.ServerVariables["HTTP_User_AGENT"];
        report += Environment.NewLine + "Query:" + Request.ServerVariables["QUERY_STRING"];
        ev.WriteEntry(report);
        Response.Write("<p>Report:"+Request.QueryString["report"]              +" triggered</p>");
    }
    catch (Exception b) {
        Response.Write("<p>Error:" + b.Message + "</p>");
    }
    ev = null;
}
%>

Als Ergebnis landet dann im Application Eventlog der folgende Eintrag:

Die Event-Quelle und ID kann natürlich im Skript einfach angepasst werden. Eventuell müssen Sie die Quelle aber erst eintragen. ASP.NET versucht das auch alleine aber wenn die Berechtigungen beschränkt sind, müssen Sie einmalig als Administrator folgenden Befehl in einer PowerShell als Administrator ausführen:

[System.Diagnostics.EventLog]::CreateEventSource("ReportWeb", "Application")

Damit wird "ReportWeb" als gültige Quelle angelegt.

By default, components that are run from an ASP page are created under the IUSR_COMPUTERNAME account. This account is a member of the Guests group, and the security privileges that are needed to write the application event log changed in Windows 2000. To resolve this problem, you must modify a registry key.

  • 301309 How To Log Events from Active Server Pages
  • 251264 App.Logevent does not work in components that you call from ASP on Windows 2000
  • 329291 PRB: "Requested Registry Access Is Not Allowed" Error Message When ASP.NET Application Tries to Write New EventSource in the EventLog

reportweb-trigger.ps1

Ein eigenes Skript kümmert sich nur darum, vom Taskplaner gestartet zu werden, aus dem Eventlog den Reportnamen zu extrahieren und eine "TRIGGER"-Datei anzulegen. Diese sucht den letzten Eintrag im Eventlog und generiert daraus eine TRIGGER-Datei.

write-host "Trigger-report: Get Report from last eventlog entry"
$reportname = (Get-EventLog -LogName application -Source "ReportWeb" -Newest 1).message.Split("`n`r")[0]
write-host ("Trigger-report: Trigger Report Name:" + $reportname)

Ich gehe davon aus, dass nicht sehr viel Administratoren gleichzeitig einen Report "antriggern". Daher beschränke ich mich darauf, einfach den letzten Eintrag zu lesen. Ich weiß, dass der Start dieses Skripts durch den Taskplaner ein paar Sekunden dauert und daher ein Trigger mehrerer Reports in zu kurzer Zeit dazu führen kann, dass einige eben nicht ausgeführt werden. Aus Vereinfachungsgründen ist mir das aber lieber als erst wieder zu speichern, wie weit das Eventlog vorher gelesen wurde um dann dort wieder aufzusetzen.

Taskplaner einrichten

Nun ist es an der Zeit dem Taskplaner so einzurichten, dass er das PowerShell-Script mit dem Parameter "-Eventlog" startet, wenn der oben konfigurierte Event ausgelöst wird:

Action: Start a program
Program/script: PowerShell.exe
Add arguments: -Command "C:\Temp.ps1"
                Skript liest Eventlog aus und startet letzte Anforderung

Bilder für die Einrichtung als "Triggered Task" fehlen noch.

Weiter Links