Search code examples
jscriptwshhta

Getting a response from a wsh script


In my HTA app there are some actions which are executing some heavily time-consuming tasks on a server. For example an action uses an old ActiveX component to read some fileproperties (like Subject and Comments) of files in a particular folder (0 - ~200 files in a folder).

Originally this was done by setting an interval, and reading the fileproperties file by file. The slowing down of the app was acceptable when connected to the server using fast local connections. But now, as remote working has significantly increased, and the remote connections are magnitudes slower than the intranet connections, the interval is not suitable for the task anymore.

To make the app faster during the filepropety search, I outsourced the code to a wsh job. The results are stored in a file, which of existence an interval (5 secs) is observing. However, some users are still experiencing remarkable slow-down of the app, even when polling the file existence with the said interval of 5 secs.

Now I wanted to know, if there is an event or some other internal mechanism, which I could use to detect when the wsh script has done its job? And if possible, even perhaps there's a way to send the results directly from the wsh job to HTA, without using the intermediate temporal file at all?

Here's some simplified code for the actual task performed in the wsh file and HTA app. HTA has the HTML5 DTD and it's running in Edge-mode using IE11. ui is an utility library, the referred propertynames are hopefully describing the usage accurate enough.

WSF:

<package>
<job id="getFileProps">
<script language="JScript">
    (function () {
        var topRoot = WScript.Arguments(0),                           // The starting folder <String>
            fso = WScript.CreateObject('Scripting.FileSystemObject'), // Filesystem object <ActiveXObject>
            fileProps = readProps(topRoot),                           // Fileprops, the result <Array>
            file;                                                     // The result file to read in HTA <FileObject>

        function readProps (root) {
            var fileProperties = [];
            // A bunch of code reading the fileproperties on a disk
            return fileProperties;
        }

        file = fso.openTextFile(topRoot + '\\$fileprops$.txt', 2, true);
        file.Write(fileProps.join(',');
        file.Close();
        WScript.Quit();
    )());
</script>
</job>
</package>

JSCRIPT:

ui.winShell.Exec('WScript //Job:getFileProps ' + '"' + ui.appRoot + '"');
function fpCheck () {
        var file, fileProps;
        try {
                file = ui.fileIO.OpenTextFile('$fileprops$.txt', 1, false);
        } catch (err) {
                file && file.Close();
                setTimeout(fpCheck, 5000);
                return;
        }
        // Write the fileprops to the view
}

If someone is interested in the fileproperty reader, its name is DSOFile.OleDocumentProperties. It originates somewhere to the deep internet archives, but I can't recall where I've loaded it from.


Solution

  • Eventually what you need is Inter-Process Communication.

    Neither HTA or WSH do offer something built-in for IPC.

    So, I'm going share a hacky solution that I rarely use.

    The trick is to use a shared object in the two ends like a client-server scenario.

    We're going to use and Internet Explorer object as an IPC server, the methods we will use are GetProperty and PutProperty.

    Since Internet Explorer windows can be enumerated by using Shell objects's Windows() method, it's possible to access the object created in HTA from another application.

    Examine and create the following two files, then start test.hta and click Start WSF button.

    test.hta:

    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <meta http-equiv="X-UA-Compatible" content="IE=Edge">
            <title>IPC</title>
            <script>
                sharedObject = {
                    sendData: function(data){
                        // blocking here is not a good idea 
                        // so we process the data within another callback
                        // leaving the method as soon as possible
                        var receivedData = data;
                        setTimeout(function(){alert("Data received: " + receivedData)}, 50);
                    }
                };
                
                shareName = "ipcShare";
                serverId = createIPCServer(shareName);
                
                function createIPCServer(shareName){
                    var ie = new ActiveXObject("InternetExplorer.Application");
                    ie.PutProperty(shareName, sharedObject);
                    window.onbeforeunload = function(){ ie.Quit(); };                
                    return ie.HWND; // window handle
                }
                
                function starWSFJob() {
                    alert("please wait about 5 seconds...");
                    var wshShell = new ActiveXObject("Wscript.Shell");
                    wshShell.Run('wscript.exe test.wsf //Job:TestIPC /serverId:"' + serverId + '" /shareName:"' + shareName + '"');
                }
            </script>
        </head>
        <body>
            <button onclick="starWSFJob()">Start WSF</button>
        </body>
    </html>
    

    test.wsf:

    <package>
        <job id="TestIPC">
            <script language="JScript">
                function getSharedObject(serverId, shareName){
                    var shellApp = new ActiveXObject("Shell.Application");
                    var windows = new Enumerator(shellApp.Windows());
                    for (;!windows.atEnd();windows.moveNext()){
                        var window = windows.item();
                        if(window.HWND == serverId) {
                            return window.GetProperty(shareName);
                        }
                    }
                }
                
                function App() {
                    var serverId = Number(WScript.Arguments.Named("serverId"));
                    var shareName = WScript.Arguments.Named("shareName");
                    var sharedObject = getSharedObject(serverId, shareName);
                    if(!sharedObject){
                        WScript.Echo("shared object not found.");
                        return 1;
                    }
                    
                    // simulate long running job
                    WScript.Sleep(5000);
                    
                    // send data
                    sharedObject.sendData("Hello from WSF Job!");
                    
                    return 0;
                }
    
                WScript.Quit(App());
            </script>
        </job>
    </package>