Search code examples
vbscriptjscriptwsh

JScript: identifying whether double quotes are passed to a WSH script


There are situations when it is important to identify whether double quotes are passed as arguments to a WSH script. For example because they should be passed to another executable to be run.

The standard parsing functions/objects:

objArgs = WScript.Arguments;
for (i = 0; i < objArgs.length; i++)
{
   WScript.Echo(objArgs(i));
}

do not differentiate between:

cscript foo.js  "bar"

and

cscript foo.js  bar

Is it possible with some other approach?

Note: I also tried to sort of escape them with several combinations like:

cscript foo.js  '"bar"'

It seems that they are simply stripped away.


Solution

  • Following @Ekkehard.Horner suggestions:

    Solution

    // parseArgs.js     
    // Parsing jscript script arguments verbatim 
    
    var Shell =  new ActiveXObject("WScript.Shell"),
        wmi  = GetObject("winmgmts:{impersonationLevel=impersonate}!\\\\.\\root\\cimv2"),
        guid = (new ActiveXObject("Scriptlet.TypeLib")).GUID.substring(0,38),
        windir=Shell.ExpandEnvironmentStrings("%WinDir%"),
        winver="\"" + windir +  "\\System32\\winver.exe\" " + guid,
        pcol, pid, cmd;
    
    
    // Run winver.exe hidden and get this script ID as its ParentProcessId 
    winver=winver.replace(/\\/g, "\\\\");
    Shell.Run("winver " + guid, 0);
    pcol = new Enumerator (wmi.ExecQuery( 
      "SELECT * From Win32_Process WHERE CommandLine='"+ winver + "'",
      "WQL", 32));
    for (; !pcol.atEnd(); pcol.moveNext()){
      var prc = pcol.item();
      pid=prc.ParentProcessId;
      prc.Terminate;
    }
    
    
    // Get the command line for the found PID 
    pcol = new Enumerator (wmi.ExecQuery( 
      "SELECT * From Win32_Process WHERE ProcessID="+ pid,
      "WQL", 32));
    for (; !pcol.atEnd(); pcol.moveNext()){
      var prc = pcol.item();
      cmd =prc.CommandLine;
    }
    WScript.Echo(cmd);
    
    // Parse command line for arguments
    var ags,
        parseCmd=function(cmd){// WMI trims initial spaces
          var p = new Object(),
              re =/^"/.test(cmd) ? /"[^"]+" */ : /\S+\s*/;
          p.nxt=re.test(cmd) ? cmd.match(re)[0] : ""; // extract next token
          p.rst=cmd.replace(re, "")                 ; // remainder     
          return(p);
        }
    
    
    // Strip c/wscript path 
    ags=parseCmd(cmd).rst
    //WScript.Echo(ags);
    
    // Remove WSH "//xxx" options 
    ags=ags.replace(/\/\/\w+ +/g, "")   
    //WScript.Echo(ags);
    
    // Strip script name and get arguments
    ags=parseCmd(ags).rst
    WScript.Echo(ags);
    
    // Loop args and store as an array 
    var i=1, aags=[];
    while(ags != ""){
      var p =parseCmd(ags);
      ags=p.rst;
      aags.push(p.nxt.replace(/ +$/, ""));
      WScript.Echo(i, p.nxt);
      i++;
    }
    WScript.Echo(aags);
    

    Test

    Running parseArgs.js gives:

    > cscript //nologo parseArgs.js "hello" world
    cscript  //nologo parseArgs.js "hello" world
    "hello" world
    1 "hello"    
    2 world
    "hello",world
    

    The line:

    > parseArgs.js "hello" world
    

    gives similar results.

    Comments

    Do we need such a convoluted script? Short answer: no. Long: depends.

    In general, assuming you know the name of your script when it is run, you could query WMI for it.
    Anyway, when you deploy your script, you do not normally have control on the deploy directory. So, if there is another script running under the same name, you can't know for sure which one is yours.
    Another not so edge case is when there are two or more instances of your script running.

    The strategy here is to run some dummy standard Windows executable (winver.exe) hidden, passing to it a GUID. In this way, it is safe to identify winver.exe command line by the unique GUID and consequently your script as the parent of winver.exe.
    winver.exe does not require arguments, but does not protest if you pass some to it.