ERCC (http://eriedel.info)

Alternativen zu VBScript

COM-Objekte mit JScript und PowerShell nutzen

In diesem Artikel wird die Verwendung der von mir entwickelten COM-Komponenten VTool und DynaLib mit JScript und PowerShell behandelt. Diese Komponenten (COM-Server) dienen dazu, Windows-Scripte mit zusätzlichen Möglichkeiten auszustatten, insb. indem Dialoge zur Benutzerinteraktion (VTool) oder eine Methode zum Aufruf von DLL-/API-Funktionen (DynaLib) verfügbar gemacht werden. Dies erlaubt vielseitigere und anspruchsvollere Script-Anwendungen.
Wie in den jeweiligen Beschreibungen (VTool, DynaLib) und den enthaltenen Dokumentationen erklärt, ist die Gestaltung beider Komponenten in erster Linie am Zusammenspiel mit dem Windows Script Host (WSH) und VBScript ausgerichtet. Ihre Nutzung ist jedoch auch mit JScript sowie PowerShell möglich.

JScript

JScript ist eine JavaScript-Variante und in Windows standardmäßig als Scriptsprache vorhanden. Es kann – auch neben VBScript – in HTML-Seiten einschließlich HTAs wie auch im Windows Script Host (WSH) verwendet werden. Ich behandle hier nur den zuletzt genannten Einsatz.

JScript kann die WSH-Objekte nutzen, ebenso andere registrierte COM-Objekte, die die Automationsschnittstelle unterstützen. Auch die Objekte von VTool und DynaLib können somit grundsätzlich verwendet werden.

obj = new ActiveXObject("VTool.DriveList");
val = obj.Selection("D:", "Please choose a drive");
WScript.Echo(val);

oder

obj = new ActiveXObject("VTool.FileSelection");
if (obj.Dialog(0))
    WScript.Echo("File: ", obj.Directory + obj.FileName);
WScript.Quit();

JScript kann auch Objekt-Events verarbeiten:

var obj, triggered = false;
obj = WScript.CreateObject("VTool.Clock", "Timer_");
obj.SetTimer(3000);
WScript.Sleep(5000);
if (!triggered)
    WScript.Echo("No timer event occurred.");
WScript.Quit();

function Timer_Buzz() {
    WScript.Echo("Alarm, alarm! :-)");
    triggered = true;
}

VTool und DynaLib wurden allerdings primär für die Nutzung mit VBScript entworfen und orientieren sich an dessen Spracheigenschaften. Daher können die Unterschiede zu JScript Auswirkungen bei der Verwendung der Objekte haben. Im einfachsten Fall ergeben sich kleinere Abweichungen bei der Verarbeitung von Datentypen, wie die folgenden Beispiele zeigen.

VTool.Aux, Methode FormatTimeInterval: Der Parameter ist formal eine vorzeichenbehaftete Ganzzahl (Long), jedoch kann in VBScript auch der höhere vorzeichenlose (positive) Wert als Hexadezimalzahl übergeben werden. Das ist in JScript so nicht möglich.

VBScript:

Set obj = CreateObject("VTool.Aux")
str = obj.FormatTimeInterval(&h80000000)

JScript:

obj = new ActiveXObject("VTool.Aux");
str = obj.FormatTimeInterval(0x80000000);   // Scriptfehler (Überlauf)
str = obj.FormatTimeInterval(-2147483648);  // ok (entspricht 0x80000000)

VTool.Aux, Methode StrToInt64: Die zurückgegebene Ganzzahl wird von VBScript als Currency-Wert dargestellt. Bei JScript erfolgt hingegen eine Konvertierung in eine Gleitkommazahl und zugleich eine Rundung.

VBScript:

Set obj = CreateObject("VTool.Aux")
MsgBox obj.StrToInt64("0x123456789ABCDEF")      ' 8198552921648,6895

JScript:

obj = new ActiveXObject("VTool.Aux");
WScript.Echo(obj.StrToInt64("0x123456789ABCDEF"));  // 8198552921648,69

Wenn Sie also bestimmte Ergebnisse erwarten, wie Sie sie von VBScript kennen, sollten Ihren JScript-Code daraufhin überprüfen bzw. auf Abweichungen vorbereitet sein.

Die Sprachunterschiede führen auch dazu, dass in JScript bestimmte Methodenparameter, die für VBScript optional sind, nicht ausgelassen werden können. Zum Beispiel:

obj = new ActiveXObject("VTool.Noise");
obj.Ring();             // okay (beide opt. Parameter weggelassen)
obj.Ring(, 1000);       // Syntaxfehler
obj.Ring(2000);         // okay (2. opt. Parameter ausgelassen)

Zum Teil ergeben sich aufgrund der Eigenschaften von JScript leider auch größere Schwierigkeiten. Dies betrifft den Datentyp Array und die Übergabe von ByRef-Parametern an Funktionen.

JScript-Arrays unterscheiden sich vom Array-Typ, den COM-Objekte und VBScript verwenden (sog. SAFEARRAY, in der JScript-Doku auch VBArray genannt). Daher funktioniert der folgende Methodenaufruf nicht:

obj = new ActiveXObject("VTool.CheckList");
items = new Array("Africa", "America", "Antarctica", "Asia", "Australia");
val = obj.SelectionA(items, 0, "Continents with A");
WScript.Echo(val);

Die Methode bricht den Ablauf intern ab, weil das erste Argument (und ebenso das zweite) keinen gültigen Array-Typ darstellt. 1  

Es ist möglich, die beiden Array-Typen jeweils zu konvertieren. Die JScript-Methode toArray() erzeugt aus dem Typ VBArray ein JScript-Array. Leider gibt es keine native Funktion für die umgekehrte Übersetzung. Man kann eine solche Konvertierung aber mit einem Behelfsverfahren durchführen.
Eine gängige Methode benutzt hierfür das Objekt Scripting.Dictionary, eine standardmäßige WSH-Komponente:

function toVBArray(arr) {
    var wsDict = new ActiveXObject("Scripting.Dictionary");
    for (var i = 0; i < arr.length; i++)
        wsDict.Add(i, arr[i]);
    return wsDict.Items();
}

arr = new Array("abc", "def", "ghi");
vbarr = toVBArray(arr);

Ein ähnliches Ergebnis lässt sich mit VBScript-Code erzielen, dessen Verwendung mit JScript in einer WSF-Datei möglich ist: 2  

<job>
<script language="VBScript">
Function toVBArray(ByRef jsa)
    Dim e, i, vba()
    i = 0
    For Each e In jsa
        ReDim Preserve vba(i)
        vba(i) = e
        i = i + 1
    Next
    toVBArray = vba
End Function
</script>
<script language="JScript">
    var items, ndx, obj, val;
    obj = new ActiveXObject("VTool.CheckList");
    items = toVBArray(Array("Africa", "America", "Asia", "Australia"));
    ndx = toVBArray(Array([0]));
    val = obj.SelectionA(items, ndx, "Continents with A");
    WScript.Echo(val);
</script>
</job>

Die beiden Funktionen unterscheiden sich in der Verarbeitung von lückenhaften (»sparse«) Arrays, wie sie in JScript angelegt werden können. Die Konvertierung mittels Scripting.Dictionary liefert ein gleichartiges VBArray, gegebenenfalls mit leeren Elementen. Bei einem gänzlich leeren Array beträgt der Wert von dimensions 1 und der von ubound −1.
Die VBScript-Funktion hingegen entfernt leere Elemente aus dem Array. Sie könnte daher auch ein undimensioniertes Array zurückgeben (dimensions = 0), was beim Zugriff zu einem Fehler führen kann. Dies ist bei der Auswertung zu berücksichtigen. Zum Beispiel:

if (vbarr.dimensions() > 0) {
    WScript.Echo(vbarr.ubound());
}

Lassen sich die Array-Probleme also noch lösen, so ist eine andere Eigenschaft von JScript leider schwerwiegender: Obwohl einige Datentypen als Funktionsparameter formal »by reference« übergeben werden, geschieht dies nicht in der Weise, dass die Funktion den Parameterwert ändern kann. Davon sind solche Objektmethoden betroffen, die über einen Variant-Parameter einen Wert zurückgeben. Der Aufruf ist problemlos möglich, aber der Rückgabewert geht verloren.

Der folgende Code demonstriert dies am Beispiel einer String-Umwandlung in Großbuchstaben (die man in JScript freilich mit der toUpperCase-Methode durchführen würde):

obj = new ActiveXObject("VTool.Aux");
val = obj.StrConv("abc", 1);    // Ergebnis als Funktionswert
WScript.Echo(val);          // = ABC
val = "abc";
obj.StrConv2(val, 1);           // Ergebnis im ByRef-Parameter
WScript.Echo(val);          // = abc
WScript.Quit();

Die zweite Methode kann die Stringänderung nicht zurückgeben.

Aus diesem Grund sind einige VTool-Funktionen mit JScript nicht benutzbar. Ab Version 3.6 existieren jedoch alternative Methoden, die auch den Aufruf mit JScript unterstützen.

Die Komponente DynaLib.Caller ist von beiden JScript-Eigenheiten betroffen. Die Methode CallFunc übernimmt die Argumente für eine API-Funktion in einem Array und gibt darin auch Änderungen zurück. Diese Rückgabe findet bei JScript wie gesagt nicht statt.

API-Funktionen, die keine Werte über Parameter (Pointer) zurückgeben, lassen sich nach einer Array-Konvertierung aufrufen:

obj = new ActiveXObject("DynaLib.Caller");
arg1 = toVBArray(Array([500]));
arg2 = toVBArray(Array([2000]));
args = toVBArray(Array(arg1, arg2));
obj.CallFunc("Kernel32", "Beep", args);

(Es wird hier und im folgenden Beispiel lediglich der Funktionsname zur Konvertierung aufgeführt. Der Code ist natürlich noch, wie oben gezeigt, um die Funktion selbst zu ergänzen.)

Die Verwendbarkeit dieser DynaLib-Methode ist für JScript also entsprechend eingeschränkt. Ab der Version 1.3 ist eine zusätzliche Methode CallFunc2 vorhanden, die Argumentänderungen über eine Eigenschaft zurückgibt und für Clients wie JScript geeignet ist:

function getBuffer(size) {
    var arr = new Array(size);
    for (var i = 0; i < arr.length; i++) {
        arr[i] = "\0";
    }
    return arr.join("");
}

obj = new ActiveXObject("DynaLib.Caller");
size = 1024;
arg1 = getBuffer(size);
arg2 = toVBArray(Array([size]));
args = toVBArray(Array(arg1, arg2));
val = obj.CallFunc2("Kernel32", "GetSystemWow64DirectoryW", args);
res = new VBArray(obj.ParameterArray);
WScript.Echo(val, " / ", res.getItem(0));

(JScript fehlt eine native Funktion zur Erzeugung eines Strings mit vorgegebener Länge. Das Beispiel zeigt eine Möglichkeit, dies zu bewerkstelligen.)

Eine Einschränkung anderer Art ergibt sich aus dem geringeren Umfang an Datentypen in JScript. Verfügbar sind folgende Datentypen: vorzeichenbehaftete 32-Bit-Ganzzahl (entspricht VBScripts Long), 64-Bit-Gleitkommazahl (entspricht VBScripts Double), String (Unicode) und einige Objekttypen, einschließlich JScripts Array. Hinzu kommen noch die speziellen Datentypen »null« und »undefined«. Für eine große Zahl von API-Funktionen dürfte dies jedoch ausreichend sein.

Im Übrigen kann DynaLib ebenso wie VTool auch in der Weise mit JScript genutzt werden, dass Objektmethoden über VBScript aufgerufen und alle Anweisungen in WSF-Dateien kombiniert werden. Dieses Prinzip wurde oben bereits am Beispiel der Array-Konvertierung demonstriert.

PowerShell

PowerShell (PS) ist ein Kommandozeileninterpreter basierend auf dem .NET-Framework. Es beinhaltet eine leistungsfähige Scriptsprache, die unabhängig vom WSH ist. 3   Durch die Nutzung der .NET-Klassen verfügt PowerShell über einen großen Funktionsumfang. So sind die Windows-Standarddialoge verfügbar, GUI-Dialoge können auch nach Bedarf definiert werden, 4   und es ist eine Vielzahl an Systeminformationen und Dateioperationen vorhanden. Der Bedarf an einer Ergänzung durch zusätzliche Funktionskomponenten ist daher relativ gering.
Manche Anwendungen sind mit PowerShell aber vielleicht etwas schwierig oder umständlich zu realisieren. In solchen Fällen mag die Unterstützung durch »fertige Lösungen« wünschenswert sein. Eventuell kommt dann der Einsatz von COM-Add-Ins wie VTool oder DynaLib in Betracht.

PowerShell kann solche Komponenten verwenden. Und obwohl die genannten Objekte in erster Linie für WSH/VBScript entworfen wurden, ist ihre Nutzung mit PowerShell weitgehend ohne Schwierigkeiten möglich, abgesehen von der Verarbeitung von Ereignissen (Events).

Die sprachlichen Unterschiede führen natürlich zu einigen Abweichungen oder Anpassungen. Beispielsweise kann das Weglassen optionaler Argumente von Methoden abhängig von ihrer Position Syntaxfehler hervorrufen. In diesem Fall müssen die Default-Werte explizit übergeben werden.
Bei manchen Variant-Parametern, die Daten zurückliefern, ist der Einsatz eines Wrapper-Objekts erforderlich (s. Beispiele).

Die folgenden Beispiele zeigen einige Anwendungen der Objekte von VTool und DynaLib.
Die im Code eingesetzte MsgBox-Funktion steht exemplarisch für eine beliebige Anweisung zur Textausgabe. Im Anhang sind Möglichkeiten zur Definition einer solchen Funktion aufgeführt. 5  

Laufwerksauswahl (VTool)

$obj = New-Object -ComObject VTool.DriveList
$val = $obj.Selection('D:', 'Drive Selection')
MsgBox $val 'Selected Drive'

CheckList (VTool)

$obj = New-Object -ComObject VTool.CheckList
$items = @("Africa", "America", "Antarctica", "Asia", "Australia")
$checked = @(1, 0, 0, 1)
$val = $obj.SelectionA([ref]$items, [ref]$checked, 'Continent Selection')
$msg = ""
for ($i = 0; $i -lt $items.length; $i++) {
    if ($val -band [math]::Pow(2,$i)) {
        $msg += $items[$i] + "`n"
    }
}
MsgBox $msg 'Your Selection'

Multi-InputBox (VTool)

$obj = New-Object -ComObject VTool.MInputBox
$obj.Title = "Some Basic Data"
$obj.InputNumber = 2
$obj.InpLabel = 'Your Name', 'Your Age'
$obj.InpTextStyle = 0, 3
$obj.InpText = 'John Doe', '66'
$arr = @(1)
$val = New-Object Runtime.InteropServices.VariantWrapper($arr)
$n = $obj.InputM([ref]$val)
if ($n) {
    MsgBox $val
}
else {
    MsgBox 'No input'
}

Auch die Verwendung von DynaLib zum Aufruf von API-Funktionen ist mit PowerShell möglich.
Bei der Übergabe von Wertparametern ist auf die in der Dokumentation beschriebene Array-Verschachtelung zu achten:

$obj = New-Object -ComObject DynaLib.Caller
$args = @(@(500), @(2000))
$val = $obj.CallFunc("Kernel32", "Beep", [ref]$args)

Im nächsten Beispiel wird wie bei vielen API-Funktionen die Übergabe eines Datenpuffers (String) in festgelegter Größe verlangt:

$obj = New-Object -ComObject DynaLib.Caller
$cch = 1024
$a1 = "`0" * $cch
$a2 = @($cch)
$args = ($a1, $a2)
$val = $obj.CallFunc("Kernel32", "GetSystemWow64DirectoryW", [ref]$args)
if ($val) {
    if ($val -gt $cch) {
        $str = "Buffer too small."
    }
    else {
        $str = $args[0].SubString(0, $val)
    }
}
else {
    $str = "n/a"
}
MsgBox $str



Anmerkungen:

1 In diesem Fall steht mit der Methode Selection() eine Alternative zur Verfügung, die einen String- und einen numerischen Parameter verwendet:

obj = new ActiveXObject("VTool.CheckList");
items = "Africa\r\nAmerica\r\nAntarctica\r\nAsia\r\nAustralia";
val = obj.Selection(items, 0, "Continents with A");
WScript.Echo(val);

2 Der Dateityp WSF (Windows Script File) stellt eine ausführbare Scriptdatei dar, die nicht sprachspezifisch ist. Es handelt sich dabei um eine einfache Textdatei im XML-Format, die sowohl JScript- als auch VBScript-Code sowie mehrere Script-Anwendungen (»Jobs«) enthalten kann. Siehe hierzu die Informationen in der WSH-Doku.

3 Zur Ausführung von PowerShell-Scripten siehe die Hinweise in diesem Artikel.

4 Siehe beispielsweise die Links zu GUI-Dialogen (Input box, List box) in diesem Artikel.

5 Es gibt vielerlei Möglichkeiten, mit PowerShell einen Text auszugeben. Bei einer GUI-orientierten Anwendung ist sicher die Anzeige eines MessageBox-Fensters nützlich. Nachfolgend einige Beispiele für die Definition einer einfachen MsgBox-Funktion.

(1)

function MsgBox ($msg, $title) {
    $wsh = New-Object -ComObject WScript.Shell
    $wsh.Popup($msg, 0, $title)
}

(2)

Add-Type -AssemblyName System.Windows.Forms

function MsgBox ([string]$msg, [string]$title) {
    [System.Windows.Forms.MessageBox]::Show($msg, $title)
}

(3)

Add-Type -AssemblyName PresentationFramework

function MsgBox ([string]$msg, [string]$title) {
    [System.Windows.MessageBox]::Show($msg, $title)
}



http://eriedel.info/info/div/com-js-ps.html


ERCC (http://eriedel.info)  2024   © Erhard Riedel Computer Consulting (ERCC)

 

Link zur ERCC-Hauptseite   Link zur Info-Übersicht