Starten No-Touch Bereitstellungsanwendungen mit Befehlszeilenargumenten

 

Chris Sells
Microsoft Corporation

23. Mai 2003

Zusammenfassung: Chris Sells gibt einige benutzerdefinierten Code frei, den Sie auf client- und serverseitiger Seite verwenden können, um eine Bereitstellung ohne Toucheingabe Windows Forms Anwendungen zu erstellen. (11 gedruckte Seiten)

Laden Sie die winforms05152003.msi Beispieldatei herunter.

Eine der Fragen, die mir am häufigsten gestellt werden, sobald Leute mit der Verwendung von NTD-Anwendungen (No-Touch Deployment) beginnen, d. h. Windows Forms Anwendungen, die mit einer URL gestartet werden können, ist, wie sie Befehlszeilenargumente an sie übergeben können. Anscheinend möchten Die Benutzer Links auf ihren Webseiten mit verschiedenen Startoptionen bereitstellen oder URLs mit Befehlszeilenargumenten erstellen, die auf der Sitzung des aktuellen Benutzers basieren. Für ein Beispiel für letzteres sehen Sie sich Abbildung 1 an.

Abbildung 1. Eine Webseite mit einem Link zu einer NTD-Anwendung

Abbildung 1 zeigt eine Webseite, die für die Person, die darauf surft, eindeutig personalisiert wurde. Der Link auf der Webseite ist zu einer NTD-Anwendung, die bei der ersten Ausführung wie Abbildung 2 aussieht.

Abbildung 2. Anmeldeformular für die NTD-Anwendung

Beachten Sie, dass diese Anwendung mit dem Namen und der ID des Benutzers vorab aufgefüllt wird. Die Art und Weise, wie ich dies getan habe, besteht darin, Argumente mit einer URL zu übergeben, die wie folgt formatiert ist:

https://localhost/vacaplan/vacaplan.exe?uid=csells&uname=Chris%20Sells

Beim Starten einer verwalteten Anwendung von der lokalen Festplatte sind Befehlszeilenparameter aus dem an Main übergebenen Zeichenfolgenarray verfügbar:

static void Main(string[] args) {
  // Get command line args
  string uid = "";
  string uname = "";
  foreach( string arg in args ) {
    string[] pair = arg.Split('=');
    switch( pair[0].ToLower() ) {
      case "uid":
        uid = pair[1];
        break;

      case "uname":
        uname = pair[1];
        break;
    }
  }
  ...
}

Leider ist die Unterstützung für das Abrufen von Befehlszeilenargumenten aus der Start-URL neu für microsoft .NET Framework 1.1 und fast nicht dokumentiert. Da die Start-URL zum Erstellen des Pfads zu .config Datei verwendet wird, erfordert die vollständige Unterstützung von Befehlszeilenargumenten auch auf der Serverseite ausgeführten Code.

Hinweis Es gibt eine Problemumgehung, um das Abrufen von Befehlszeilenargumenten aus der Start-URL zu aktivieren, die auch in .NET 1.0 funktioniert, und dies wird weiter unten in diesem Artikel behandelt.

Client-Size Unterstützung für NTD-Argumente

Um die Argumente aus der Start-URL zu ziehen, muss zuerst über die NTD-Anwendung auf die Start-URL zugegriffen werden. Hierzu stellt .NET 1.1 die APP_LAUNCH_URL Datenvariablen aus der Anwendungsdomäne bereit:

// Only works for .NET 1.1+
AppDomain domain = AppDomain.CurrentDomain;
object obj = domain.GetData("APP_LAUNCH_URL");
string appLaunchUrl = (obj != null ? obj.ToString() : "");
MessageBox.Show(appLaunchUrl);

Die zum Starten der NTD-Anwendung verwendete URL, einschließlich Argumenten, wird vollständig von APP_LAUNCH_URL bereitgestellt. Leider ist APP_LAUNCH_URL in .NET 1.0 nicht verfügbar. Da der Pfad zur .config Datei einer NTD-Anwendung jedoch nur die URL (einschließlich Argumenten) mit ".config" am Ende ist, können Sie das Äquivalent des APP_LAUNCH_URL in .NET 1.0 abrufen. Beispiel: Starten einer Anwendung aus:

http://foo/foo.exe?foo=bar@quux

Gibt den folgenden .config Dateipfad ab:

http://foo/foo.exe?foo=bar@quux.config

Da die Anwendungsdomäne Zugriff auf den .config Dateipfad bietet, können Sie diesen und eine kleine Zeichenfolgenbearbeitung verwenden, um Zugriff auf die NTD-Start-URL für .NET 1.0 und .NET 1.1 zu erhalten:

// Only works for .NET 1.1+
AppDomain domain = AppDomain.CurrentDomain;
object obj = domain.GetData("APP_LAUNCH_URL");
string appLaunchUrl = (obj != null ? obj.ToString() : "");

// Fall back for .NET 1.0
if( appLaunchUrl == string.Empty ) {
  const string ext = ".config";
  string configFile = domain.SetupInformation.ConfigurationFile;
  appLaunchUrl =
    configFile.Substring(0, configFile.Length - ext.Length);
}

In beiden Fällen ermöglichen sowohl die Standard-Intranet- als auch die Internetberechtigungen für .NET 1.x den Zugriff auf Befehlszeilenargumentinformationen von teilweise vertrauenswürdigen Clients. Sobald Sie die vollständige URL erhalten haben, kann sie decodiert und auf die Argumente analysiert werden.

Sicherheitsüberlegungen

Bevor ich mich mit den Argumenten auseinandersetze, muss ich mich kurz über die Sicherheitsauswirkungen der Behandlung von Befehlszeilenargumenten in einer NTD-Anwendung informieren. Wenn Argumente an eine exe übergeben werden, die bereits auf dem Computer installiert ist, erfolgt dies vom Benutzer über die Shell oder die Befehlszeilenkonsole, sodass Sie darauf vertrauen können, dass die Dinge wahrscheinlich in Ordnung sind. Im Web werden Benutzer jedoch ständig dazu verleiten, Anlagen in E-Mails zu öffnen oder im Browser auf Links zu klicken, die nicht in ihrem interesse sind. Stellen Sie sich beispielsweise HotMail als NTD vor, die die folgenden Argumente zulässt:

http://hotmail.com/edit.exe?uid=csells&forward=doctor@evil.com&ui=no

In diesem Fall übergeben wir Argumente, die unsere Intensität angeben, um unsere E-Mail ohne Bestätigungsbenutzeroberfläche an eine andere Person weiterzuleiten. Wenn dieser Link auf einer Webseite eingebettet und mit "Kostenlose Schokolade!" gekennzeichnet wurde, können Sie ehrlich sagen, dass Sie nicht versucht wären, ihn selbst zu klicken? Stellen Sie sicher, dass Sie die Möglichkeiten ihrer Befehlszeilenargumentkombinationen sorgfältig mit dem Blick auf Übeltäter auswerten, bevor Sie es in die wilde und wollige Welt des Webs entfesseln.

Decodieren und Analysieren der URL

Sobald Sie zufrieden sind, dass Sie die Sicherheitsüberlegungen gut im Griff haben, müssen Sie die Argumente aus der URL selbst analysieren. Manchmal müssen Sie Sonderzeichen in eine URL codieren, um sie zu übergeben, so als ob Sie sie an einen serverseitigen Code übergeben würden. Um beispielsweise den Leerraum im uname Argument zu codieren, muss der ASCII-Wert des Leerzeichens mit der Syntax %dd codiert werden:

https://localhost/vacaplan/vacaplan.exe?uid=csells&uname=Chris%20Sells

Serverseitig stellt System.Web nicht eine, sondern zwei Klassen bereit, die wissen, wie eine URL decodiert wird (HttpServerUtility und HttpUtility). Leider ist es NTD-Clients aus der Intranet- oder Internetzone untersagt, die UrlDecode-Methode aus beiden Klassen zu verwenden. Glücklicherweise ergibt ein wenig kreatives Reverse-Engineering der .NET Framework Klassenbibliothek einen UrlDecode, den Sie verwenden können (und wird mit der Quelle dieses Artikels bereitgestellt). Durch das Decodieren der URL werden Dinge wie %20 mit den tatsächlichen Zeichen erweitert:

https://localhost/vacaplan/vacaplan.exe?uid=csells&uname=Chris Sells

Sobald eine URL decodiert wurde, können Sie die eigentlichen Argumente herausziehen, indem Sie das Fragezeichen als Marker für den Argumentteil der URL und den Ampersand als Marker zwischen den Argumenten verwenden. Während System.Web dazu eine Klasse bereitstellt (HttpValueCollection), wird sie intern verwendet, um die Abfragezeichenfolgenanalyse zu implementieren, und ist selbst nicht verfügbar. Glücklicherweise ist es nicht schwer, diese Zeichenfolgenanalysefunktion für unsere Verwendung reverse-zu entwickeln, und die Argumente in Schlüssel-Wert-Paare aufzuteilen, und das Beispiel stellt auch den Code dafür bereit.

Abstrakte Befehlszeilenargumente

Im Beispiel werden sowohl die URL-Decodierung als auch die Abfragezeichenfolgenanalyse in der WebCommandLineHelper-Klasse gebündelt:

static void Main(string[] argsFromMain) {
  // Get command line args (either from Main or from launching URL)
  string uid = "";
  string uname = "";
  string[]
    args = WebCommandLineHelper.GetCommandLineArgs(argsFromMain);
  foreach( string arg in args ) {...}
  ...
}

Die statische GetCommandLineArgs-Methode der WebCommandLineHelper-Klasse verwendet als Eingabeparameter die Argumente, die von Main abgerufen wurden. In der GetCommandLineArgs-Methode wird zuerst überprüft, ob die Anwendung über eine URL gestartet wird oder nicht:

static public bool LaunchedFromUrl {
  get {
    try {
      // Check if we have a site
      string  url = (string)AppDomain.CurrentDomain.GetData("APPBASE");
      System.Security.Policy.Site.CreateFromUrl(url);
      return true;
    }
    catch( ArgumentException ) {
      return false;
    }
  }
}

Die Appbase einer .NET-Assembly ist der Ort, an dem sie sich befindet. beispielsweise ein Spot auf der Festplatte oder eine URL. Wenn eine Website aus der Appbase extrahiert werden kann, wurde die Anwendung über eine URL gestartet. Wenn die Anwendung nicht über eine URL gestartet wurde, gibt GetCommandLineArgs nur die von Main übergebenen Argumente zurück:

static public string[] GetCommandLineArgs(string[] argsFromMain) {
  if( !LaunchedFromUrl ) return argsFromMain;
  ... // Decode and parse URL for arguments
}

Auf diese Weise kümmert sich die Anwendung nicht wirklich darum, wo die Argumente herkommen. Entscheidend sind nur die Argumente selbst. Der WebCommandLineHelper vereinheitlicht die beiden Quellen von Argumenten zu Ihrer Bequemlichkeit.

Server-Side Unterstützung für NTD-Argumente

Leider reicht die Clientseite nicht aus, um Befehlszeilenargumente für NTD-Anwendungen zu pullen. Da die URL, einschließlich Argumenten, verwendet wird, um den Pfad zur .config-Datei zu erzeugen, benötigen Sie beim Bereitstellen einer .config-Datei serverseitigen Code, um Anforderungen für .config Dateien wie folgt zu übersetzen:

http://foo/foo.exe?foo=bar@quux.config

In Anforderungen wie diese, mit entfernten Argumenten:

 
http://foo/foo.exe.config

Wenn IIS foo.exe?blah.config sieht, ordnet es dies einer Anforderung für eine .exe-Datei zu, nicht einer .config-Datei. Das bedeutet, dass verschiedene Teile von .NET, die nach .config Dateien suchen, z. B. der Assemblyauflösungsprozess, der benutzerdefinierte Konfigurationsleser und Webdienste, alle die .exe Bits erhalten, wenn sie die .config Bits anfordern. Während bei den vorherigen beiden Stapeln ein Fehler auftritt, als ob die .config Datei fehlt, löst der .NET-Webdienststapel eine Ausnahme aus, wenn er die .exe Bits erhält, auch wenn es überhaupt keine .config Datei gibt, die bereitgestellt werden kann. Natürlich ist dies nicht das, was wir suchen.

ASP.NET .EXE Dateien verarbeiten lassen

Das Bereitstellen der entsprechenden .config-Datei in Anwesenheit von URL-Argumenten ist ein mehrstufiger Prozess. Schritt 1 besteht darin, Anforderungen für .exe Dateien an ASP.NET zu übergeben, damit Sie benutzerdefinierten Code an Anforderungen für .exe Dateien anschließen können. Standardmäßig sind alle Webanwendungen so konfiguriert, dass .exe Dateien direkt verteilt werden. Um die .exe-Erweiterung ASP.NET zuzuordnen, verwenden Sie das IIS-Konfigurationstool, um die Konfiguration Ihrer Webanwendung anzupassen, wie in Abbildung 3 dargestellt.

Abbildung 3. Zuordnen .exe Dateien zu ASP.NET in IIS

Behandeln von .EXE Dateien

Sobald ASP.NET .exe Dateien verarbeitet, können Sie einen HTTP-Handler hinzufügen. So können Sie ASP.NET benutzerdefinierten Code schreiben, um eine Anforderung zu behandeln. Ein Handler ist einfach eine Implementierung von IHttpHandler:

public class ConfigFileHandler : IHttpHandler {
    // Just the .exe part in the file system
    string path = context.Request.PhysicalPath;

    // The entire request URL, include args and .config
    string url = context.Request.RawUrl;

    // If someone's asking for a .config, strip the arguments
    string ext = ".config";
    if( url.ToLower().EndsWith(ext) ) {
      context.Response.WriteFile(path + ext);
    }
    // If someone's asking for the .exe, send it
    else {
      context.Response.ContentType = "application/octet-stream";
      context.Response.WriteFile(path);
    }
  }

  public bool IsReusable {
    get { return true; }
  }
}

Die IHttpHandler-Schnittstelle verfügt nur über eine einzige interessante Methode– ProcessRequest. Diese ProcessRequest-Methode verwendet einen Kontext, den ein Handler erhält, z. B. den physischen Pfad zu der Datei, die vom Client angefordert wird, und die Roh-URL derselben Anforderung und überprüft, ob die URL mit ".config" endet. Wenn dies der Fall ist, erstellen wir einen physischen Pfad der angeforderten .exe-Datei (denken Sie daran, dass IIS und ASP.NET die .exe-Datei als die angeforderte Datei betrachten, nicht die .config-Datei), am Ende ".config." anheften und die entsprechende .config-Datei bereitstellen.

Da wir die Zuordnung für .exe Dateien in ASP.NET geändert haben, ist dieser Handler für .exe Dateien und .exe?blah.config Dateien verantwortlich. Wenn jemand die .exe-Datei anfordert, legen wir den ENTSPRECHENDEN MIME-Typ fest und stellen diese Datei her.

Wenn die angeforderte Datei fehlt, wird in beiden Fällen ein Fehler 404 angezeigt, mit dem .NET umgehen kann.

Zuordnen .EXE Dateien zum Handler

Nachdem Sie .EXE Dateien ASP.NET zugeordnet und einen .NET-Handler implementiert haben, um .exe- und .exe?blah.config-Dateien zu verarbeiten, müssen Sie ASP.NET dennoch darüber informieren, welcher Handler für .exe Dateien verwendet werden soll. Dies geschieht in den web.config-Dateien für Ihre ASP.NET Anwendung:

<configuration>
  <system.web>
    <httpHandlers>
      <!-- map .exe and .exe?blah.config files to our handler -->
      <add verb="*" path="*.exe"
           type="Genghis.Web.ConfigFileHandler, ConfigHandler" />
    </httpHandlers>
 </system.web>
</configuration>

Wenn ASP.NET dieses <Add-Element> im Abschnitt httpHandlers> sieht, werden alle HTTP-Adressen für Pfade, die <auf .exe enden, der ConfigHandler-Assembly und der Genghis.Web.ConfigFileHandler-Klasse (der zuvor gezeigten IHttpHandler-Implementierung) zugeordnet. ASP.NET suchen nach der web.config-Datei im Stammverzeichnis der Webanwendung und nach der Assembly im Verzeichnis bin im Stammverzeichnis der Webanwendung. Achten Sie also darauf, die Dateien an der richtigen Stelle zu platzieren.

Zulassen, dass IIS und ASP.NET .config Dateien verteilen

Nach dem Routing von Anforderungen für .exe Dateien von IIS an ASP.NET, schreiben sie den Handler, um .config Dateien zurück zu übergeben, und nach dem Weiterleiten ASP.NET Anforderungen für .exe Dateien an diesen Handler bleiben zwei weitere Konfigurationsschritte. Der erste Grund ist, dass IIS nicht zulässt, dass .config Dateien aus der Tür gehen, es sei denn, der anonyme Zugriff ist aktiviert. Sie müssen die Option Anonymer Zugriff im Dialogfeld Authentifizierungsmethoden der Verzeichnissicherheit Ihrer IIS-Webanwendung aktivieren, wie in Abbildung 4 dargestellt.

Abbildung 4. Aktivieren des anonymen Zugriffs auf eine Webanwendung in IIS, damit .config Dateien bereitgestellt werden können

Und während ASP.NET uns ordnungsgemäß .exe Dateien und .exe?args.config-Dateien verteilt, dürfen wir .config Dateien standardmäßig nicht verteilen. Anders ausgedrückt: Von den folgenden vier URL-Typen verteilen wir nur die ersten drei mit der aktuellen Konfiguration:

1. http://foo/foo.exe?foo=bar@quux
2. http://foo/foo.exe?foo=bar@quux.config
3. http://foo/foo.exe
4. http://foo/foo.exe.config

Die ersten drei URL-Typen werden in unserem Handler angezeigt. Der vierte URL-Typ ist wichtig, wenn Sie Ihre NTD-Anwendung ohne URL-Argumente starten, aber trotzdem die .config-Datei benötigen. Der Grund dafür, dass ASP.NET standardmäßig keine *.config-Dateien ausgibt, liegt darin, dass Sie möglicherweise alle Arten vertraulicher Informationen in Ihrer web.config-Datei speichern. Fügen Sie die folgenden Einträge hinzu, damit *.config Dateien gelöscht werden, aber trotzdem Ihre web.config Dateien geschützt bleiben:

<configuration>
  <system.web>
    <httpHandlers>
      <!-- map .exe and .exe?blah.config files to our handler -->
      <add verb="*" path="*.exe"
           type="Genghis.Web.ConfigFileHandler, ConfigHandler" />
      
      <!-- allow .config files but disable web.config files -->
      <remove verb="*" path="*.config" />
      <add verb="*" path="web.config"
           type="System.Web.HttpForbiddenHandler"/>
    </httpHandlers>
 </system.web>
</configuration>

In diesem Fall verfügt ASP.NET über .config Dateien, die dem HttpForbiddenHandler zugeordnet sind, wodurch sichergestellt wird, dass sie nicht an den Client zurückgegeben werden. Wir entfernen diese Handlerzuordnung, sodass .config Dateien zulässig sind, fügen sie dann aber für nur web.config Dateien wieder hinzu, damit diese geschützt bleiben.

Wo sind wir

Es ist wahrscheinlich ziemlich klar, dass der .NET Framework 1.1 zwar Zugriff auf die Start-URL einer NTD-Anwendung bietet, aber keine wirklich integrierte Unterstützung für die Behandlung von URL-Argumenten beim Starten von NTD-Anwendungen vorhanden ist. Mit etwas kreativem Programmieren können Sie dies jedoch trotzdem tun:

  • Verwenden Sie auf dem Client APP_LAUNCH_URL oder ConfigurationFile, um die Start-URL abzurufen, zu decodieren und nach Bedarf zu analysieren.
  • Wenn Sie ASP.NET auf dem Server verwenden:
    1. Konfigurieren Sie IIS, um Anforderungen für .exe Dateien an ASP.NET weiterzuleiten.
    2. Implementieren Sie einen Handler, um .exe- und .exeblah.config dateien zu verarbeiten.
    3. Ordnen Sie den Handler .exe Dateien für Ihre ASP.NET-Anwendung zu.
    4. Konfigurieren Sie IIS für anonymen Zugriff, damit .config Dateien bereitgestellt werden können.
    5. Entfernen Sie die ASP.NET Handlerzuordnung, die alle .config-Dateien verbietet.
    6. Fügen Sie den ASP.NET-Handler hinzu, um web.config Dateien zu verbieten.

Obwohl Argumente nicht so gründlich unterstützt werden, wie wir möchten, ist .NET flexibel genug, damit Sie argumente auf Client- und Serverseite sowieso absolut an NTD-Anwendungen übergeben können.

Referenzen

Hinweis Ein Teil dieses Materials stammt aus dem kommenden Addison-Wesley Titel Windows Forms Programming in C# von Chris Sells (0321116208).

Chris Sells ist Content Strategist für MSDN Online, der sich derzeit auf Longhorn, das nächste Betriebssystem von Microsoft, konzentriert. Er hat mehrere Bücher geschrieben, darunter Mastering Visual Studio .NET und Windows Forms für C#-Programmierer. In seiner Freizeit veranstaltet Chris verschiedene Konferenzen, leitet das Genghis-Quellprojekt, spielt mit Rotor und macht im Allgemeinen einen Schädling von sich in der Blogsphäre. Weitere Informationen zu Chris und seinen verschiedenen Projekten finden Sie unter http://www.sellsbrothers.com.