ERCC (http://eriedel.info)

Alternatives to VBScript

Using COM objects with JScript and PowerShell

This article covers using the COM components I developed, VTool and DynaLib, with JScript and PowerShell. These components (COM servers) equip Windows scripts with additional options, in particular by providing dialogs for user interaction (VTool) or a method for calling DLL/API functions (DynaLib). This allows for more versatile and sophisticated script applications.
As explained in the respective descriptions (VTool, DynaLib) and the documentation included, the design of both components is primarily geared towards the interwork with the Windows Script Host (WSH) and VBScript. However, the components can also be used with JScript and PowerShell.

JScript

JScript is a variant of JavaScript and is available as a standard scripting language in Windows. It can be used —also alongside VBScript— in HTML pages including HTAs as well as in the Windows Script Host (WSH). I will only discuss the latter usage here.

JScript can use the WSH objects, as well as other registered COM objects that support the automation interface. The objects from VTool and DynaLib can therefore also be used in principle.

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

or

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

JScript can also process object events:

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;
}

However, VTool and DynaLib were designed to work with VBScript and are based on its language properties. The differences to JScript can of course have an impact on the use of the objects. In the simplest case, there are minor deviations in the processing of data types, as the following examples show.

VTool.Aux, FormatTimeInterval method: The parameter is formally a signed integer (Long), but in VBScript also the higher unsigned (positive) value can be passed as a hexadecimal number. This is not possible in JScript.

VBScript:

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

JScript:

obj = new ActiveXObject("VTool.Aux");
str = obj.FormatTimeInterval(0x80000000);   // script error (overflow)
str = obj.FormatTimeInterval(-2147483648);  // ok (corresponds to 0x80000000)

VTool.Aux, StrToInt64 method: The returned integer is represented by VBScript as a currency value. With JScript, however, there is a conversion to a floating point number and a rounding at the same time.

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

So if you expect certain results like those you know from VBScript, you should check your JScript code and be prepared for deviations.

The language differences also mean that certain method parameters which are optional for VBScript cannot be omitted in JScript. For example:

obj = new ActiveXObject("VTool.Noise");
obj.Ring();             // okay (both opt. parameters omitted)
obj.Ring(, 1000);       // syntax error
obj.Ring(2000);         // okay (2nd opt. parameter omitted)

Unfortunately, due to the properties of JScript, there are also major difficulties. This affects the array data type and passing ByRef parameters to functions.

JScript arrays differ from the array type that COM objects and VBScript use (so-called SAFEARRAY, also called VBArray in the JScript documentation). Therefore the following method call does not work:

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

The method internally aborts operation because the first argument (and also the second) does not represent a valid array type. 1  

It is possible to convert the two array types respectively. The JScript method toArray() creates a JScript array from the VBArray type. Unfortunately, there is no native reverse translation feature. However, such a conversion can be carried out using a makeshift procedure.
A common method for this uses the Scripting.Dictionary object, a standard WSH component:

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);

A similar result can be achieved with VBScript code, which can be used alongside JScript in a WSF file: 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>

The two functions differ in the processing of sparse arrays, as can be created in JScript. The conversion using Scripting.Dictionary delivers a similar VBArray, possibly with empty elements. For a completely empty array, the value of dimensions is 1 and that of ubound is −1.
The VBScript function, on the other hand, removes empty elements from the array. It might therefore also return an undimensioned array (dimensions = 0), which can lead to an error when accessed. This must be taken into account during the evaluation. For example:

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

So while the array problems can still be solved, another characteristic of JScript is unfortunately more serious: Although some data types are formally passed as function parameters "by reference", this is not done in such a way that the function can change the parameter value. This affects object methods that return a value via a Variant parameter. The call is possible without any problems, but the return value is lost.

The following code demonstrates this using the example of a string conversion to uppercase letters (which in JScript would of course be carried out using the toUpperCase method):

obj = new ActiveXObject("VTool.Aux");
val = obj.StrConv("abc", 1);    // result as a function value
WScript.Echo(val);          // = ABC
val = "abc";
obj.StrConv2(val, 1);           // result in ByRef parameter
WScript.Echo(val);          // = abc
WScript.Quit();

The second method cannot return the string alteration.

For this reason, some VTool functions cannot be used with JScript. However, as of version 3.6, alternative methods exist that also support calling via JScript.

The DynaLib.Caller component is affected by both JScript peculiarities. The CallFunc method takes the arguments for an API function in an array, in which also changes are returned. As already said, this return does not happen with JScript.

API functions that do not return values via parameters (pointers) can be called after an array conversion:

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

(Only the function name for the conversion is listed here. Of course, the code needs to be supplemented with the function itself, as shown above.)

The usability of DynaLib for JScript is therefore restricted accordingly. But as of version 1.3, an additional method CallFunc2 is available, which returns argument changes via a property and is suitable for clients such as JScript:

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 lacks a native function for generating a string of specified length. The example shows one way to accomplish this.)

Another limitation is the smaller range of data types in JScript. The following data types are available: signed 32-bit integer (corresponds to VBScript's Long), 64-bit floating point number (corresponds to VBScript's Double), String (Unicode) and some object types, including JScript's Array. There are also the special data types "null" and "undefined". However, this should be sufficient for a large number of API functions.

Besides, DynaLib, like VTool, can also be used with JScript by calling object methods via VBScript and combining all statements in WSF files. This principle was already demonstrated above using the example of array conversion.

PowerShell

PowerShell (PS) is a command line interpreter based on the .NET framework. It includes a powerful scripting language that is independent of WSH. 3   By using the .NET classes, PowerShell has a wide range of functions. The standard Windows dialogs are available, GUI dialogs can also be defined as required, 4   and a variety of system information and file operations are available. The need for supplementation with additional functional components is therefore relatively low.
However, some applications may be a bit difficult or complicated to implement with PowerShell. In such cases, support through "ready-made solutions" may be desirable. The use of COM add-ins such as VTool or DynaLib may then be considered.

PowerShell is capable of using such components. And although the mentioned objects were primarily designed for WSH/VBScript, their use with PowerShell is largely possible without any difficulties, apart from event processing.

The language differences naturally lead to some deviations or necessary adjustments. For example, omitting optional arguments from methods can cause syntax errors depending on their position. In this case, the default values must be passed explicitly.
For some Variant parameters that return data, the use of a wrapper object is required (see examples).

The following examples show some applications of the VTool and DynaLib objects.
The MsgBox function used in the code is representative of an arbitrary instruction for text output. The appendix lists ways to define such a function. 5  

Drive Selection (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'
}

Using DynaLib to call API functions is also possible with PowerShell.
When passing value parameters, pay attention to the array nesting described in the documentation:

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

In the next example, as with many API functions, a data buffer (string) of a fixed size is required to be passed:

$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



Remarks:

1 In this case, an alternative is available, the Selection() method, which uses a string and a numeric parameter:

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 The WSF (Windows Script File) file type represents an executable script file that is not language specific. It is a simple text file in XML format that can contain both JScript and VBScript code as well as several script applications ("jobs"). See the information in the WSH documentation.

3 To run PowerShell scripts, see the instructions in this article.

4 For example, see the links on GUI dialogs (Input box, List box) in this article.

5 There are numerous ways to output text with PowerShell. For a GUI-oriented application, displaying a MessageBox window is certainly desirable. Below are some examples of defining a simple MsgBox function.

(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/en/info/com-js-ps.html


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


link to info overview