Exec.js

/* globals Wsh: false */

(function () {
  // Shorthands
  var CD = Wsh.Constants;
  var util = Wsh.Util;
  var sh = Wsh.Shell;
  var shApp = Wsh.ShellApplication;

  var objAdd = Object.assign;
  var insp = util.inspect;
  var isArray = util.isArray;
  var isNumber = util.isNumber;
  var isPureNumber = util.isPureNumber;
  var isString = util.isString;
  var isSolidArray = util.isSolidArray;
  var isSolidString = util.isSolidString;
  var includes = util.includes;
  var obtain = util.obtainPropVal;

  var os = Wsh.OS;

  /** @constant {string} */
  var MODULE_TITLE = 'WshOS/Exec.js';

  var throwErrNonStr = function (functionName, typeErrVal) {
    util.throwTypeError('string', MODULE_TITLE, functionName, typeErrVal);
  };

  // os.surroundCmdArg {{{
  /**
   * Surrounds a file path and argument with double quotes.
   *
   * @example
   * var os = Wsh.OS; // Shorthand
   *
   * os.surroundCmdArg('C:\\Windows\\System32\\notepad.exe'); // No space
   * // Returns: 'C:\\Windows\\System32\\notepad.exe'
   *
   * os.surroundCmdArg('C:\\Program Files'); // Has a space
   * // Returns: '"C:\\Program Files"'
   *
   * os.surroundCmdArg('"C:\\Program Files (x86)\\Windows NT"'); // Already quoted
   * // Returns: '"C:\\Program Files (x86)\\Windows NT"'
   *
   * os.surroundCmdArg('D:\\2011-01-01家族で初詣'); // Non-ASCII chars
   * // Returns: '"D:\\2011-01-01家族で初詣"'
   *
   * os.surroundCmdArg('D:\\Music\\R&B'); // Ampersand
   * // Returns: '"D:\\Music\\R&B"'
   *
   * os.surroundCmdArg('abcd1234'); // Returns: 'abcd1234'
   * os.surroundCmdArg('abcd 1234'); // Returns: '"abcd 1234"
   * os.surroundCmdArg('あいうえお'); // Returns: '"あいうえお"'
   *
   * // A command control character
   * os.surroundCmdArg('|'); // Returns: '|'
   * os.surroundCmdArg('>'); // Returns: '>'
   *
   * // Inner quoted
   * os.surroundCmdArg('-p"My p@ss wo^d"'); // Returns: '-p"My p@ss wo^d"'
   * os.surroundCmdArg('1> "C:\\logs.txt"'); // Returns: '"1> "C:\\logs.txt""'
   * os.surroundCmdArg('1>"C:\\logs.txt"'); // Returns: '1>"C:\\logs.txt"'
   * @function surroundCmdArg
   * @memberof Wsh.OS
   * @param {string} str - The path to surround.
   * @returns {string} - The surrounded path.
   */
  os.surroundCmdArg = function (str) {
    if (!isString(str)) throwErrNonStr('os.surroundCmdArg', str);

    if (str === '') return '';

    // Already quoted
    if (/^".*"$/.test(str)) return str;

    if (util.isASCII(str)) {
      if (!includes(str, ' ')) return str;

      // @Note CMD treats the ,=; as an argument delimiter
      // if (!/["&<>^|,=;]/.test(str)) return str;
    }

    // Inner quoted
    // Ex1. Stdout: 1>"D:\\My data\\logs.txt"
    // Ex2. 7-Zip password option: -p"My p@ss wo^d", @"D:\excluded files.txt"
    // @TODO This completion is low level ...
    if (/^[A-Za-z0-9_@>/=-]+(".+")?[A-Za-z0-9_@&/=-]*$/.test(str)) return str;

    return '"' + str + '"';
  };
  var _srrPath = os.surroundCmdArg;
  // }}}

  // os.escapeForCmd {{{
  /**
   * Escapes the string of argument in CMD.exe. Note {@link http://thinca.hatenablog.com/entry/20100210/1265813598|Ref1} {@link http://output.jsbin.com/anitaz/11|Ref2}
   *
   * @example
   * var os = Wsh.OS; // Shorthand
   *
   * os.escapeForCmd('tag=R&B'); // Returns: 'tag=R^&B'
   *
   * os.escapeForCmd('>');
   * // Returns: '>' // Not escaped. It's a redirect character.
   *
   * os.escapeForCmd('/RegExp="^(A|The) $"');
   * // Returns: '"/RegExp=\\"^^(A^|The) $\\""'
   *
   * os.escapeForCmd('<%^[yyyy|yy]-MM-DD%>');
   * // Returns: '^<%^^[yyyy^|yy]-MM-DD%^>'
   *
   * os.escapeForCmd(300);
   * // Returns: '300'
   *
   * os.escapeForCmd('C:\\Program Files');
   * // Returns: 'C:\\Program Files');
   *
   * os.escapeForCmd('C:\\Windows\\System32\\notepad.exe');
   * // Returns: 'C:\\Windows\\System32\\notepad.exe'
   *
   * os.escapeForCmd('"C:\\Program Files (x86)\\Windows NT"');
   * // Returns: '\\"C:\\Program Files (x86)\\Windows NT\\"'
   * @function escapeForCmd
   * @memberof Wsh.OS
   * @param {string} str - The string to convert.
   * @returns {string} - The string escaped for Command-Prompt.
   */
  os.escapeForCmd = function (str) {
    if (!isNumber(str) && !isString(str)) throwErrNonStr('os.escapeForCmd', str);

    if (isNumber(str)) return String(str);
    if (str === '') return str;

    // Stdout
    if (/^(1|2)?>{1,2}(&(1|2))?$/.test(str)) return str;

    // Pipe
    if (str === '<' || str === '|') return str;

    return str.replace(/(["])/g, '\\$1').replace(/([&<>^|])/g, '^$1');
  };
  var _escapeForCmd = os.escapeForCmd;
  // }}}

  // os.joinCmdArgs {{{
  /**
   * Escapes and joines the Array of arguments for Command-Prompt.
   *
   * @example
   * Wsh.OS.joinCmdArgs([
   *   'C:\\Program Files (x86)\\Hoge\\foo.exe',
   *   '1>&2', // Redirect
   *   'C:\\Logs\\err.log',
   *   '|', //Pipe
   *   'tag=R&B',
   *   '/RegExp="^(A|The) $"',
   *   '<%^[yyyy|yy]-MM-DD%>'
   * ]);
   * // Returns: '"C:\\Program Files (x86)\\Hoge\\foo.exe" 1>&2 C:\\Logs\\err.log | tag=R^&B "/RegExp=\\"^^(A^|The) $\\"" ^<%^^[yyyy^|yy]-MM-DD%^>'
   *
   * // @NOTE If you include standard output, divide the array elements
   * Wsh.OS.joinCmdArgs(['ping.exe', 'localhost', '>', 'C:\\My Logs\\stdout.log]);
   * // Or, do not put space in between the dest and quoted it
   * Wsh.OS.joinCmdArgs(['ping.exe', 'localhost', '1>"C:\\My Logs\\stdout.log"]);
   * @function joinCmdArgs
   * @memberof Wsh.OS
   * @param {string[]|string} args - The arguments to convert. If args is String, return this string value.
   * @param {object} [options] - Optional parameters.
   * @param {boolean} [options.escapes=true] - Escapes arguments for CMD.
   * @returns {string} - The string of escaped and joined.
   */
  os.joinCmdArgs = function (args, options) {
    if (isSolidString(args)) return String(args);
    if (!isSolidArray(args)) return '';

    var escapes = obtain(options, 'escapes', true);

    var argsStr = args.reduce(function (acc, arg) {
      var str = escapes ? _escapeForCmd(arg) : arg;

      return acc + ' ' + os.surroundCmdArg(str);
    }, '');

    return argsStr.trim();
  };
  var _joinCmdArgs = os.joinCmdArgs;
  // }}}

  // os.convToCmdlineStr {{{
  /**
   * @typedef {object} typeConvToCommandOptions
   * @property {boolean} [shell=false] - Wraps with CMD.EXE
   * @property {boolean} [closes=true] - /C (Close?) or /K (Keep?)
   * @property {boolean} [escapes=true] - Escapes the arguments.
   */

  /**
   * Converts the command and arguments to a command line string.
   *
   * @example
   * var os = Wsh.OS;
   *
   * os.convToCmdlineStr('net', ['use', '/delete']);
   * // Returns: 'net user /delete'
   *
   * os.convToCmdlineStr('net', ['use', '/delete'], { shell: true });
   * // Returns: 'C:\Windows\System32\cmd.exe /S /C"net user /delete"'
   *
   * os.convToCmdlineStr('net', 'use /delete', { shell: true, closes: false });
   * // Returns: 'C:\Windows\System32\cmd.exe /S /K"net user /delete"'
   *
   * // The 2nd argument: Array vs String
   * // Array is escaped
   * os.convToCmdlineStr('D:\\My Apps\\app.exe',
   *   ['/RegExp="^(A|The) $"', '-f', 'C:\\My Data\\img.doc']);
   * // Returns: '"D:\\My Apps\\app.exe" "/RegExp=\\"^^(A^|The) $\\"" -f "C:\\My Data\\img.doc"'
   *
   * // String is not escaped
   * os.convToCmdlineStr('D:\\My Apps\\app.exe',
   *   '/RegExp="^(A|The) $" -f C:\\My Data\\img.doc');
   * // Returns: '/RegExp="^(A|The) $" -f C:\\My Data\\img.doc'
   * @function convToCmdlineStr
   * @memberof Wsh.OS
   * @param {string} cmdStr - The executable file path or The command of Command-Prompt.
   * @param {(string[]|string)} [args] - The arguments to format.
   * @param {typeConvToCommandOptions} [options] - Optional parameters.
   * @returns {string} - The converted command.
   */
  os.convToCmdlineStr = function (cmdStr, args, options) {
    var FN = 'os.convToCmdlineStr';
    if (!isSolidString(cmdStr)) throwErrNonStr(FN, cmdStr);

    var command = _srrPath(cmdStr);

    var argsStr;
    if (isArray(args)) {
      argsStr = _joinCmdArgs(args, options);
    } else if (isString(args)) {
      argsStr = args;
    } else {
      argsStr = '';
      // throw new Error('Error [ERR_INVALID_ARG_TYPE]\n'
      //   + '  at ' + FN + ' (' + MODULE_TITLE + ')\n'
      //   + '  args: ' + insp(args));
    }

    if (argsStr !== '') command += ' ' + argsStr;

    var shell = obtain(options, 'shell', false);
    var closes = obtain(options, 'closes', true);
    /**
     * /C or /K <cmd.exe op1 op2...>: cmdを実行(/Kは実行後終了しない
     * /S: /C or /K 後の""で括られた中身をコマンド全体とみなす。
     * "のエスケープに""とする必要がなくなる
     * "以外の特殊文字は^でエスケープする必要がある
     */
    if (shell) {
      if (closes) {
        command = _srrPath(os.exefiles.cmd) + ' /S /C"' + command + '"';
      } else {
        command = _srrPath(os.exefiles.cmd) + ' /S /K"' + command + '"';
      }
    }

    return command;
  }; // }}}

  // _shRun {{{
  /**
   * @typedef {typeOsExecOptions} typeShRunOptions
   * @property {(number|string)} [winStyle="activeDef"] - See {@link https://tuckn.net/docs/WshUtil/Wsh.Constants.windowStyles.html|Wsh.Constants.windowStyles}.
   * @property {boolean} [waits=true] - See {@link https://tuckn.net/docs/WshUtil/Wsh.Constants.waits.html|Wsh.Constants.waits}.
   */

  /**
   * @private
   * @function _shRun
   * @memberof Wsh.OS
   * @param {string} command - The command and arguments.
   * @param {typeShRunOptions} [options] - Optional parameters.
   * @returns {number|string} - Return a number except when options.isDryRun is true.
   */
  function _shRun (command, options) {
    var FN = '_shRun';
    if (!isSolidString(command)) throwErrNonStr(FN, command);

    var isDryRun = obtain(options, 'isDryRun', false);
    if (isDryRun) return 'dry-run [' + FN + ']: ' + command;

    var winStyle = obtain(options, 'winStyle', 'activeDef');
    var winStyleCode = isPureNumber(winStyle)
      ? winStyle : CD.windowStyles[winStyle];
    var waits = obtain(options, 'waits', CD.waits.yes);

    try {
      /**
       * @function Run
       * @memberof Wsh.Shell
       * @param {string} strCommand
       * @param {number} intWindowStyle
       * @param {boolean} bWaitOnReturn
       * @returns {number}
       */
      return sh.Run(command, winStyleCode, waits);
    } catch (e) {
      throw new Error(insp(e) + '\n'
        + '  at ' + FN + ' (' + MODULE_TITLE + ')\n'
        + '  command: ' + command);
    }
  } // }}}

  // os.shRun {{{
  /**
   * Asynchronously runs the application with WScript.Shell.Run. {@link https://docs.microsoft.com/ja-jp/previous-versions/windows/scripting/cc364421%28v%3dmsdn.10%29|WSH Run method}
   *
   * @example
   * var os = Wsh.OS;
   * var fso = Wsh.FileSystemObject;
   *
   * // Ex1. CMD-command
   * // Throws a Error. `mkdir` is the CMD command
   * os.shRun('mkdir', 'D:\\Temp1');
   *
   * // With the options
   * os.shRun('mkdir', 'D:\\Temp1', { shell: true, winStyle: 'hidden' });
   * // Returns: Always 0
   *
   * while (!fso.FolderExists('D:\\Temp1')) { // Waiting the created
   *   WScript.Sleep(300);
   * }
   * fso.FolderExists('D:\\Temp1'); // Returns: true
   *
   * // Ex2. Exe-file
   * os.shRun('notepad.exe', ['D:\\Test2.txt'], { winStyle: 'activeMax' });
   * // Returns: Always 0
   *
   * // Ex.3 Dry Run
   * var cmd = os.shRun('mkdir', 'D:\\Temp3', { shell: true, isDryRun: true });
   * console.log(cmd);
   * // Outputs:
   * // dry-run [_shRun]: C:\Windows\System32\cmd.exe /S /C"mkdir D:\Temp3"
   * fso.FolderExists('D:\\Temp3'); // Returns: false
   * @function run
   * @memberof Wsh.OS
   * @param {string} cmdStr - The executable file path or The command of Command-Prompt.
   * @param {(string[]|string)} [args] - The arguments.
   * @param {typeShRunOptions} [options] - Optional parameters.
   * @returns {number|string} - Returns 0 except when options.isDryRun is true.
   */
  os.shRun = function (cmdStr, args, options) {
    // var FN = 'os.shRun';

    var command = os.convToCmdlineStr(cmdStr, args, options);

    return _shRun(command, objAdd({}, options, { waits: false }));
  }; // }}}

  // os.shRunSync {{{
  /**
   * Synchronize of {@link Wsh.OS.shRun}
   *
   * @example
   * var os = Wsh.OS;
   * var fso = Wsh.FileSystemObject;
   *
   * // Ex1. CMD-command
   * // Throws a Error. `mkdir` is the CMD command
   * os.shRunSync('mkdir', 'D:\\Temp1');
   *
   * // With the options
   * var retVal1 = os.shRunSync('mkdir', 'D:\\Temp1', {
   *   shell: true, winStyle: 'hidden'
   * });
   *
   * console.log(retVal1); // Outputs the number of the result code
   * fso.FolderExists('D:\\Temp1'); // Returns: true
   *
   * // Ex2. Exe-file
   * var retVal2 = os.shRunSync('notepad.exe', ['D:\\Test2.txt'], {
   *   winStyle: 'activeMax'
   * });
   *
   * // Waits until the notepad process is finished
   *
   * console.log(retVal2); // Outputs the number of the result code
   *
   * // Ex.3 Dry Run
   * var cmd = os.shRunSync('mkdir', 'D:\\Temp3', { shell: true, isDryRun: true });
   * console.log(cmd);
   * // Outputs:
   * // dry-run [_shRun]: C:\Windows\System32\cmd.exe /S /C"mkdir D:\Temp3"
   * fso.FolderExists('D:\\Temp3'); // Returns: false
   * @function runSync
   * @memberof Wsh.OS
   * @param {string} cmdStr - The executable file path or The command of Command-Prompt.
   * @param {(string[]|string)} [args] - The arguments.
   * @param {object} [options] - Optional parameters. See [typeConvToCommandOptions]{@link https://tuckn.net/docs/WshOS/global.html#typeConvToCommandOptions}  and [typeShRunOptions]{@link https://tuckn.net/docs/WshOS/docs/global.html#typeShRunOptions}.
   * @returns {number|string} - Returns code from the app except when options.isDryRun is true.
   */
  os.shRunSync = function (cmdStr, args, options) {
    // var FN = 'os.shRunSync';

    var command = os.convToCmdlineStr(cmdStr, args, options);
    return _shRun(command, objAdd({}, options, { waits: true }));
  }; // }}}

  /**
   * @typedef {typeConvToCommandOptions} typeOsExecOptions
   * @property {boolean} [isDryRun=false] - No execute, returns the string of command.
   * @see In addition to the above, [typeConvToCommandOptions]{@link https://tuckn.net/docs/WshOS/global.html#typeConvToCommandOptions} can also be specified.
   */

  // os.shExec {{{
  /**
   * The object returning from Wsh.OS.shExec ({@link https://msdn.microsoft.com/ja-jp/library/cc364375.aspx|WshScriptExec object}). When `options.shell: true` is specified, exitCode may not be accurate because this value is returned by CMD.
   *
   * @typedef {object} typeExecObject
   * @property {number} ExitCode
   * @property {number} ProcessID
   * @property {number} Status
   * @property {object} StdOut
   * @property {object} StdIn
   * @property {object} StdErr
   * @property {function} Terminate
   */

  /**
   * Asynchronously executes the command with WScript.Shell.Exec. Note that a DOS window is always displayed. {@link https://msdn.microsoft.com/ja-jp/library/cc364375.aspx|WshScriptExec object}
   *
   * @example
   * var os = Wsh.OS;
   *
   * // Ex.1 CMD-command
   * // Throws a Error. `mkdir` is the CMD command
   * os.shExec('mkdir', 'D:\\Temp');
   *
   * // With the `shell` option
   * var oExec1 = os.shExec('mkdir', 'D:\\Temp1', { shell: true });
   * console.log(oExec1.ExitCode); // 0 (always)
   *
   * while (oExec1.Status == 0) WScript.Sleep(300); // Waiting the finished
   * console.log(oExec1.Status); // 1 (It means finished)
   * fso.FolderExists('D:\\Temp1'); // Returns: true
   *
   * // Ex.2 Exe-file
   * var oExec2 = os.shExec('ping.exe', ['127.0.0.1']);
   * console.log(oExec2.ExitCode); // 0 (always)
   *
   * while (oExec2.Status == 0) WScript.Sleep(300); // Waiting the finished
   * console.log(oExec2.Status); // 1 (It means finished)
   *
   * var result = oExec2.StdOut.ReadAll();
   * console.log(result); // Outputs the result of ping 127.0.0.1
   *
   * // Ex.3 Dry Run
   * var cmd = os.shExec('mkdir', 'D:\\Temp3', { shell: true, isDryRun: true });
   * console.log(cmd);
   * // Outputs:
   * // dry-run [os.shExec]: C:\Windows\System32\cmd.exe /S /C"mkdir D:\Temp3"
   * fso.FolderExists('D:\\Temp3'); // Returns: false
   * @function exec
   * @memberof Wsh.OS
   * @param {string} cmdStr - The executable file path or The command of Command-Prompt.
   * @param {(string[]|string)} [args] - The arguments.
   * @param {typeOsExecOptions} [options] - Optional parameters.
   * @returns {typeExecObject|string} - If options.isDryRun is true, returns string.
   */
  os.shExec = function (cmdStr, args, options) {
    var FN = 'os.shExec';
    if (!isSolidString(cmdStr)) throwErrNonStr(FN, cmdStr);

    var command = os.convToCmdlineStr(cmdStr, args, options);
    if (!isSolidString(command)) throwErrNonStr(FN, command);

    var isDryRun = obtain(options, 'isDryRun', false);
    if (isDryRun) return 'dry-run [' + FN + ']: ' + command;

    try {
      /**
       * @function Exec
       * @memberof Wsh.Shell
       * @param {string} strCommand
       * @returns {typeExecObject}
       */
      return sh.Exec(command);
    } catch (e) {
      throw new Error(insp(e) + '\n'
        + '  at ' + FN + ' (' + MODULE_TITLE + ')\n'
        + '  command: ' + command);
    }
  }; // }}}

  // os.shExecSync {{{
  /**
   *  The object returning from Wsh.OS.shExecSync. When `options.shell: true` is specified, exitCode may not be accurate because this value is returned by CMD.
   *
   * @typedef {object} typeExecSyncReturn
   * @property {number} exitCode - There are applications that return 1 (NG) even if the processing is successful.
   * @property {string} command - The executed command line
   * @property {string} stdout
   * @property {string} stderr
   * @property {boolean} error
   */

  /**
   * Synchronize of {@link Wsh.OS.shExec}
   *
   * @example
   * var os = Wsh.OS;
   *
   * // Ex1. CMD-command
   * // Throws a Error. `mkdir` is the CMD command
   * os.shExecSync('mkdir', 'D:\\Temp');
   *
   * // With the `shell` option
   * var retObj1 = os.shExecSync('mkdir', 'D:\\Temp', { shell: true });
   * console.dir(retObj1);
   * // Outputs: {
   * //   exitCode: 0,
   * //   stdout: "",
   * //   stderr: "",
   * //   error: false };
   *
   * // Ex2. Exe-file
   * var retObj2 = os.shExecSync('ping.exe', ['127.0.0.1']);
   * console.dir(retObj2);
   * // Outputs: {
   * //   exitCode: 0,
   * //   stdout: <The result of ping 127.0.0.1>,
   * //   stderr: "",
   * //   error: false };
   *
   * // Ex.3 Dry Run
   * var cmd = os.shExecSync('ping.exe', '127.0.0.1', { isDryRun: true });
   * console.log(cmd);
   * // Outputs:
   * // dry-run [os.shExecSync]: C:\Windows\System32\cmd.exe /S /C"C:\Windows\System32\PING.EXE 127.0.0.1"
   * @function execSync
   * @memberof Wsh.OS
   * @param {string} cmdStr - The executable file path or The command of Command-Prompt.
   * @param {(string[]|string)} [args] - The arguments.
   * @param {typeOsExecOptions} [options] - Optional parameters.
   * @returns {typeExecSyncReturn|string} - If options.isDryRun is true, returns string.
   */
  os.shExecSync = function (cmdStr, args, options) {
    var FN = 'os.shExecSync';
    if (!isSolidString(cmdStr)) throwErrNonStr(FN, cmdStr);

    var command = os.convToCmdlineStr(cmdStr, args, options);
    if (!isSolidString(command)) throwErrNonStr(FN, command);

    var isDryRun = obtain(options, 'isDryRun', false);
    if (isDryRun) return 'dry-run [' + FN + ']: ' + command;

    var oExec;
    var stdout = '';
    var stderr = '';
    var readSize = 4096;

    /** The problem of Exec Stream. {@link https://social.msdn.microsoft.com/Forums/ja-JP/da5a0366-6446-49f0-b4e2-adfbd4314aac/vba12391wsh12434exec123912345526045261781239832066201023090635469?forum=vbajp|Microsoft Forums}, {@link https://srad.jp/~IR.0-4/journal/572274/|Ref} */
    try {
      oExec = sh.Exec(command);

      // @note .Status -> 0:Processing, 1:Finished
      while (oExec.Status === 0) {
        stdout += oExec.StdOut.Read(readSize);
        stderr += oExec.StdErr.Read(readSize);
        WScript.Sleep(300);
      }
      // Suck out the remnants
      while (!oExec.StdOut.AtEndOfStream) {
        stdout += oExec.StdOut.Read(readSize);
      }
      while (!oExec.StdOut.AtEndOfStream) {
        stdout += oExec.StdErr.Read(readSize);
      }
    } catch (e) {
      throw new Error(insp(e) + '\n'
        + '  at ' + FN + ' (' + MODULE_TITLE + ')\n'
        + '  command: ' + command);
    }

    return {
      command: command,
      exitCode: oExec.ExitCode,
      stdout: stdout,
      stderr: stderr,
      error: isSolidString(stderr)
    };
  }; // }}}

  var _isAdmin = undefined;

  // os.isAdmin {{{
  /**
   * Checks if this process is running as Administrator authority.
   *
   * @example
   * Wsh.OS.isAdmin(); // Returns: false
   * @function isAdmin
   * @memberof Wsh.OS
   * @returns {boolean} - true -> running as Administrator authority.
   */
  os.isAdmin = function () {
    if (_isAdmin !== undefined) return _isAdmin;

    try {
      /** `net session` worked on from Windows XP to 10 */
      var iRetVal = os.shRunSync(os.exefiles.net, ['session'], {
        winStyle: 'hidden'
      });
      _isAdmin = iRetVal === CD.runs.ok;
    } catch (e) {
      _isAdmin = false;
    }

    return _isAdmin;
  }; // }}}

  // os.runAsAdmin {{{
  /**
   * Asynchronously runs the application as administrator authority with WScript.Shell.ShellExecute. {@link https://docs.microsoft.com/en-us/windows/win32/shell/shell-shellexecute|WSH ShellExecute method}
   *
   * @example
   * var os = Wsh.OS;
   *
   * // Ex1. CMD-command
   * // Throws a Error. `mklink` is the CMD command
   * os.runAsAdmin('mklink', 'D:\\Temp-Symlink D:\\Temp');
   *
   * // With the `shell` option
   * os.runAsAdmin('mklink', 'D:\\Temp-Symlink D:\\Temp', {
   *   shell: true
   * });
   *
   * // Ex2. Exe-file
   * os.runAsAdmin('D:\\MyApp\\Everything.exe', ['-instance', 'MySet']);
   * @function runAsAdmin
   * @memberof Wsh.OS
   * @param {string} cmdStr - The executable file path or The command of Command-Prompt.
   * @param {(string[]|string)} [args] - The arguments.
   * @param {typeShRunOptions} [options] - Optional parameters.
   * @returns {void|string} - Returns undefined except when options.isDryRu is =true.
   */
  os.runAsAdmin = function (cmdStr, args, options) {
    if (os.isAdmin()) return os.shRun(cmdStr, args, options);

    var FN = 'os.runAsAdmin';
    if (!isSolidString(cmdStr)) throwErrNonStr(FN, cmdStr);

    var exePath = cmdStr;

    var argsStr;
    if (isArray(args)) {
      argsStr = _joinCmdArgs(args, options);
    } else if (isString(args)) {
      argsStr = args;
    } else {
      argsStr = '';
    }

    var shell = obtain(options, 'shell', false);
    if (shell) {
      exePath = os.exefiles.cmd;
      argsStr = '/S /C"' + _srrPath(cmdStr) + ' ' + argsStr + '"';
    }

    var isDryRun = obtain(options, 'isDryRun', false);
    if (isDryRun) return 'dry-run [' + FN + ']: ' + exePath + ' ' + argsStr;

    var winStyle = obtain(options, 'winStyle', 'activeDef');
    var winStyleCode = isPureNumber(winStyle) ? winStyle : CD.windowStyles[winStyle];

    try {
      /**
       * Performs the specified operation on a specified file. It's asyncronize. {@link https://docs.microsoft.com/en-us/windows/win32/shell/shell-shellexecute|Microsoft Docs}
       * @function ShellExecute
       * @memberof Wsh.ShellApplication
       * @param {string} sFile
       * @param {string} [vArguments]
       * @param {string} [vDirectory]
       * @param {string} [vOperation=open] - {@link https://docs.microsoft.com/en-us/windows/win32/shell/context|Microsoft Docs}
       * @param {number} [vShow]
       * @returns {void}
       */
      return shApp.ShellExecute(
        exePath,
        argsStr,
        'open',
        'runas',
        winStyleCode
      );
    } catch (e) {
      // @FIXME Can not catch the error in admin process
      throw new Error(insp(e) + '\n'
        + '  at _execFileAsAdmin (' + MODULE_TITLE + ')\n'
        + '  file: ' + insp(exePath) + '\n  argsStr: ' + insp(argsStr));
    }
  }; // }}}
})();

// vim:set foldmethod=marker commentstring=//%s :