Resource icon

[WSH] File Dialog Klassen für VBScript bzw. JScript Stand-Alone Scripte

Programmiersprache(n)
VBS, JS
Betriebssystem(e)
Windows
Windows hat mit PowerShell eine Scriptsprache an Bord, mit der grundsätzlich alles möglich ist, was mit .NET machbar ist. Im Einzelfall kann es von Nachteil sein, dass PowerShell Scripte immer im Konsolefenster laufen. Hingegen werden VBS und JS in der Regel standardmäßig mit wscript.exe als Script Host ausgeführt und laufen somit ohne Fenster. Allerding sind die Möglichkeiten dieser Sprachen sehr beschränkt. Selbst einfache Dialoge, wie für das Öffnen einer Datei, sind nicht vorhanden. Wäre also schön wenn es ein Interface zwischen PowerShell und dem Windows Script Host (WSH) geben würde.
Mit ein paar Tricks ist das tatsächlich möglich. Die File Dialog Klassen, um die es hier geht, sind eigentlich nur Praxisbeispiele. Das Prinzip lässt sich auch für andere Aufgaben übertragen. Aber erst einmal die Scripts und im Nachgang die Erklärung ...

VBScript (FileDialog.vbs):
Visual Basic:
Option Explicit

Class FileDlg
  Public strFile__
  Private strId, objShellWindow, objFSO, objWSHShell

  Public Function GetPath(ByRef strStartDir_in, ByVal arrFilters_in, ByRef strTitle_in)
    Dim i
    GetPath=vbNullString
    For i=0 To UBound(arrFilters_in) Step 2 : arrFilters_in(i)=arrFilters_in(i)&" ("&CStr(arrFilters_in(i + 1))&")" : Next
    objWSHShell.Run _
      "powershell.exe -nop -ex Bypass -c ""Add-Type -an System.Windows.Forms;"& _
      "$FileDialog=New-Object System.Windows.Forms.OpenFileDialog -Property @{"& _
        "InitialDirectory='"&objFSO.GetAbsolutePathName(CStr(strStartDir_in))&"';"& _
        "Filter='"&Join(arrFilters_in,"|")&"';"& _
        "Title='"&CStr(strTitle_in)&"';};"& _
      "$null=$FileDialog.ShowDialog();"& _
      MakePSInterfaceCode("$VbsInstance")& _
      "$VbsInstance.strFile__=[string]$FileDialog.FileNames;""",0,True
    GetPath=strFile__
    strFile__=vbNullString
    If Not objFSO.FileExists(GetPath) Then GetPath=vbNullString
  End Function

  Private Function MakePSInterfaceCode(ByRef strPsVarName_in)
    MakePSInterfaceCode=CStr(strPsVarName_in)&"=((New-Object -com Shell.Application).Windows()|?{$_.getProperty('"&strId&"')}|select -First 1).getProperty('"&strId&"');"
  End Function

  Private Function CreateShellInterface()
    Set CreateShellInterface=CreateObject("InternetExplorer.Application")
    CreateShellInterface.putProperty strId,Me
  End Function

  Private Sub DestroyShellInterface()
    objShellWindow.Quit
    Set objShellWindow=Nothing
  End Sub

  Private Sub Class_Initialize()
    strFile__=vbNullString
    strId=Left(CreateObject("Scriptlet.TypeLib").Guid,38)
    Set objFSO=CreateObject("Scripting.FileSystemObject")
    Set objWSHShell=CreateObject("WScript.Shell")
    Set objShellWindow=CreateShellInterface()
  End Sub

  Private Sub Class_Terminate()
    DestroyShellInterface
  End Sub
End Class

'******************************************************************************'

Dim objWSHShell, objFileDlg, file, dir, filters, title
Set objWSHShell=CreateObject("WScript.Shell")
Set objFileDlg=New FileDlg
dir=objWSHShell.CurrentDirectory
filters=Array("Textdateien", "*.txt;*.log", "Alle Dateien", "*.*")
title="Auswahl"

file=objFileDlg.GetPath(dir, filters, title)
If file<>vbNullString Then
  objWSHShell.Popup file, 0, "Ausgewählt:", vbInformation Or vbSystemModal Or &h00200000&
Else
  objWSHShell.Popup "Keine Datei ausgewählt.", 0, "Fehler:", vbCritical Or vbSystemModal Or &h00200000&
End If

JScript (FileDialog.js):
Javascript:
var FileDlg=function() {
  this.strFile__=null;
  var me=this,strId=WScript.CreateObject("Scriptlet.TypeLib").Guid.slice(0,38),objFSO=WScript.CreateObject("Scripting.FileSystemObject"),objWSHShell=WScript.CreateObject("WScript.Shell"),objShellWindow=null;

  this.GetPath=function(strStartDir_in, arrFilters_in, strTitle_in) {
    var ret=null;
    objShellWindow=CreateShellInterface();
    for (var i=0; i < arrFilters_in.length; i+=2) arrFilters_in[i]+=" ("+arrFilters_in[i+1]+")";
    objWSHShell.Run(
      "powershell.exe -nop -ex Bypass -c \"Add-Type -an System.Windows.Forms;"+
      "$FileDialog=New-Object System.Windows.Forms.OpenFileDialog -Property @{"+
        "InitialDirectory='"+objFSO.GetAbsolutePathName(strStartDir_in)+"';"+
        "Filter='"+arrFilters_in.join("|")+"';"+
        "Title='"+strTitle_in+"';};"+
      "$null=$FileDialog.ShowDialog();"+
      MakePSInterfaceCode("$JsInstance")+
      "$JsInstance.strFile__=[string]$FileDialog.FileNames;\"",0,true);
    ret=me.strFile__;
    me.strFile__=null;
    if (!objFSO.FileExists(ret)) ret=null;
    DestroyShellInterface();
    return ret;
  };

  function MakePSInterfaceCode(strPSVarName_in) {
    return ""+strPSVarName_in+"=((New-Object -com Shell.Application).Windows()|?{$_.getProperty('"+strId+"')}|select -First 1).getProperty('"+strId+"');";
  }

  function CreateShellInterface() {
    var shellInterface=WScript.CreateObject("InternetExplorer.Application");
    shellInterface.putProperty(strId,me);
    return shellInterface;
  }

  function DestroyShellInterface() {
    objShellWindow.Quit();
    objShellWindow=null;
  }
};

/******************************************************************************/

var objWSHShell=WScript.CreateObject("WScript.Shell"),
    objFileDlg=new FileDlg,
    file=null,
    dir=objWSHShell.CurrentDirectory,
    filters=["Textdateien", "*.txt;*.log", "Alle Dateien", "*.*"],
    title="Auswahl";

file=objFileDlg.GetPath(dir, filters, title);
if (file) {
  objWSHShell.Popup(file, 0, "Ausgewählt:", 0x00000040 | 0x00001000 | 0x00200000);
} else {
  objWSHShell.Popup("Keine Datei ausgewählt.", 0, "Fehler:", 0x00000010 | 0x00001000 | 0x00200000);
}

Benutzung

Kopiere die FileDlg Klasse in dein Script und lege eine neue Instanz an. Die Klasse hat nur eine öffentliche Methode, GetPath, mit 3 Parametern:
- Dem ersten Parameter wird das Startverzeichnis als String übergeben, in dem das Dialogfenster öffnet.
- Das zweite Argument ist ein String-Array. Jeweils zwei aufeinanderfolgende Arrayelemente gehören als Paar zusammen. Das erste beinhaltet die Beschreibung des zu suchenden Dateityps, das zweite den Dateityp als Suchpattern, wobei mehrere Dateitypen durch Semikolon getrennt angegeben werden können. Das erste Paar wird standardmäßig im Dialog angenommen. Falls weitere Paare definiert wurden, können diese später im Drop Down ausgewählt werden.
- Dem dritten Parameter wird der Fenstertitel des Dialogs übergeben.

Die GetPath Methode gibt entweder den Pfad der selektierten Datei zurück, oder vbNullString (VBS) / null (JS) wenn der Dialog ohne Auswahl abgebrochen wurde.


Funktionsweise

Es geht uns um zwei Dinge:

1. Kein Programmfenster
Das ist einfach. VBS und JS haben keines. Powershell rufen wir mit der Run Methode eines WScript.Shell Objektes auf, der wir 0 für "hidden" als zweites Argument übergeben. Auch das ist also kein Problem.

2. Interface zwischen WSH und PS
Hier wird es schwierig. Eine Datei um Daten von einem Prozess schreiben und vom anderen lesen zu lassen, ginge quasi immer, ist aber hässlich. Named und Anonymous Pipes sind ähnlich geartet, lassen sich aber von einem WSH Script nicht erzeugen. Was wir hier tun, ist aber noch smarter. Wir lassen den Wert einer Variablen des WSH Scriptobjekts direkt von PowerShell schreiben. Dafür muss aber eine Referenz auf das Scriptobjekt an PowerShell übergeben werden. Das geht nicht ohne Weiteres. Wir bedienen uns eines alten Tricks. Shell Windows kann eine Eigenschaft zugewiesen werden. Diese Eigenschaft hat einen Name und einen Wert. Der Name sollte eineindeutig sein. Um das zu gewährleisten wird ein GUID String erzeugt. Der Wert kann jeden beliebigen Typ haben. In unserem Fall ist es die Referenz auf das Scriptobjekt. Genauer, die Referenz auf die Klasse. Als Shell Window, also als Träger für diese Informationen, erzeugen wir ein nicht sichtbares InternetExplorer.Application Objekt und weisen die Eigenschaft per putProperty Methode zu. Das PowerShellscript iteriert nun über alle Shell Windows (in der Regel existieren nicht mehr als eine Handvoll) und sucht das, welches die Eigenschaft mit der GUID als Name beinhaltet. Wurde es gefunden, wird die Referenz auf die WSH Klasse einer PowerShellvariablen zugewiesen. Mit dieser Variable kann nun auf alle public Member der Klasse (also auch auf deren Membervariablen) zugegriffen werden. Genau das passiert in unserem Beispiel. Die Variable strFile__ bekommt im PowerShell Script den Pfad der im Dialog selektierten Datei zugewiesen. Nach Beenden des PowerShell Scripts ist dieser Wert im WSH Script abgreifbar. Nun müssen wir uns nur noch um das Shell Window kümmern. Das läuft in einem iexplore.exe Prozess. Das Freigeben der Objektvariablen durch den Garbage Collector terminiert leider nicht gleichzeitig den Prozess. Vorher muss die Quit Methode zu diesem Zweck aufgerufen werden. Im VBS haben wir den Luxus einer vernünftigen Klassenimplementierung und können das dem Destruktor erledigen lassen. JScript Klassen kennen leider keinen Destruktor, somit erzeugen und zerstören wir das Shell Window bei jedem Aufruf der GetPath Methode in JS um keine Geisterprozesse zu hinterlassen.
Autor
German
First release
Last update
Bewertung
0,00 Stern(e) 0 Bewertungen
Oben