ZLIB.js

/* globals Wsh: false */

(function () {
  if (Wsh && Wsh.ZLIB) return;

  /**
   * This module provides helper WSH (Windows Script Host) functions that handle archiver apps (7-Zip and RAR).
   *
   * @namespace ZLIB
   * @memberof Wsh
   * @requires {@link https://github.com/tuckn/WshChildProcess|tuckn/WshChildProcess}
   */
  Wsh.ZLIB = {};

  // Shorthands
  var util = Wsh.Util;
  var CD = Wsh.Constants;
  var path = Wsh.Path;
  var os = Wsh.OS;
  var fs = Wsh.FileSystem;
  var fse = Wsh.FileSystemExtra;
  var child_process = Wsh.ChildProcess;

  var objAdd = Object.assign;
  var obtain = util.obtainPropVal;
  var includes = util.includes;
  var isEmpty = util.isEmpty;
  var isTrueLike = util.isTrueLike;
  var isSolidArray = util.isSolidArray;
  var isSolidString = util.isSolidString;
  var isPureNumber = util.isPureNumber;
  var isSameStr = util.isSameMeaning;
  var startsWith = util.startsWith;
  var insp = util.inspect;
  var parseDate = util.createDateString;
  var srrd = os.surroundCmdArg;
  var exec = child_process.exec;
  var execFileSync = child_process.execFileSync;

  var zlib = Wsh.ZLIB;

  /** @constant {string} */
  var MODULE_TITLE = 'WshZLIB/ZLIB.js';

  var DEF_DIR_7ZIP = 'C:\\Program Files\\7-Zip';
  var EXENAME_7Z = '7z.exe';
  var EXENAME_7ZFM = '7zFM.exe';

  /**
   * @name DEF_7Z_EXE
   * @constant {string}
   */
  var DEF_7Z_EXE = path.join(DEF_DIR_7ZIP, EXENAME_7Z);

  /**
   * @name DEF_7ZFM_EXE
   * @constant {string}
   */
  var DEF_7ZFM_EXE = path.join(DEF_DIR_7ZIP, EXENAME_7ZFM);

  /**
   * @name DEF_DIR_WINRAR
   * @constant {string}
   */
  var DEF_DIR_WINRAR = 'C:\\Program Files\\WinRAR';

  /** @constant {string} */
  var EXENAME_WINRAR = 'WinRar.exe';

  /** @constant {string} */
  var EXENAME_RAR = 'Rar.exe';

  var throwErrInvalidValue = function (functionName, argName, typeErrVal) {
    util.throwValueError(argName, MODULE_TITLE, functionName, typeErrVal);
  };

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

  /**
   * console logging
   *
   * @private
   * @function _log
   * @param {boolean} sw - Shows a log
   * @param {string} text - The logging text
   * @returns {void}
   */
  var _log = function (sw, text) {
    if (sw) console.log(text);
  };

  /**
   * @typedef {object} typeDeflateResult
   * @property {string} command - A executable command line
   * @property {string} archivedPath - An archived file path
   * @property {boolean} error - Error or not.
   * @property {string} stdout - Std Output.
   * @property {string} stderr - Std Error.
   */

  // zlib._createTmpListFile {{{
  /**
   * Creates a temporary list file.
   *
   * @function _createTmpListFile
   * @memberof Wsh.ZLIB
   * @param {string[]|string} paths - The file paths.
   * @param {object} [options] - Optional parameters.
   * @param {string} [options.encoding='utf8'] - The character encoding.
   * @param {string} [options.eol='\n'] - The character of EOL (end of line).
   * @returns {string} - The created temporary file path.
   */
  zlib._createTmpListFile = function (paths, options) {
    var FN = 'zlib._createTmpListFile';

    if (!isSolidArray(paths) && !isSolidString(paths)) {
      throwErrInvalidValue(FN, 'paths', paths);
    }

    // Setting excluding filepaths (-x)
    var encoding = obtain(options, 'encoding', 'utf8');
    var eol = obtain(options, 'eol', '\n');
    var writeData;

    if (isSolidArray(paths)) {
      writeData = paths.reduce(function (acc, p) {
        return acc + p + eol;
      }, '');
    } else {
      writeData = paths + eol;
    }

    return fs.writeTmpFileSync(writeData, { encoding: encoding });
  }; // }}}

  // zlib._makeDestArchivePath {{{
  /**
   * Makes An archive file path.
   *
   * @function _makeDestArchivePath
   * @memberof Wsh.ZLIB
   * @param {string} ext - The archive file extension (ex: ".zip")
   * @param {string[]|string} paths - The archiving file paths.
   * @param {string} [dest] - A destination path.
   * @returns {string} - The created archive file path.
   */
  zlib._makeDestArchivePath = function (ext, paths, dest) {
    var FN = 'zlib._makeDestArchivePath';

    if (!isSolidArray(paths) && !isSolidString(paths)) {
      throwErrInvalidValue(FN, 'paths', paths);
    }

    var destPath = dest;
    var srcPath1 = isSolidArray(paths) ? paths[0] : paths;

    if (isEmpty(destPath)) {
      if (fs.existsSync(srcPath1) && fs.statSync(srcPath1).isDirectory()) {
        destPath = srcPath1 + ext;
      } else if (/\\\*$/.test(srcPath1)) {
        destPath = path.dirname(srcPath1).replace(/\\\*$/, '') + ext;
      } else {
        destPath = srcPath1.replace(/\*/g, 'xxx') + ext;
      }
    } else if (fs.existsSync(destPath) && fs.statSync(destPath).isDirectory()) {
      // When specified the dest and it's a existing directory,
      if (fs.existsSync(srcPath1) && fs.statSync(srcPath1).isDirectory()) {
        destPath = path.join(destPath, path.basename(srcPath1) + ext);
      } else {
        destPath = path.join(
          destPath,
          path.basename(srcPath1).replace(/\*/g, 'xxx') + ext
        );
      }
    }

    return destPath;
  }; // }}}

  // 7-Zip Command Line User's Guide
  /**
   * [7-Zip Command Line Version User's Guide](https://sevenzip.osdn.jp/chm/cmdline/index.htm)
7-Zip (a) 22.00 (x64) : Copyright (c) 1999-2022 Igor Pavlov : 2022-06-15

Usage: 7za <command> [<switches>...] <archive_name> [<file_names>...] [@listfile]

<Commands>
  a : Add files to archive
  b : Benchmark
  d : Delete files from archive
  e : Extract files from archive (without using directory names)
  h : Calculate hash values for files
  i : Show information about supported formats
  l : List contents of archive
  rn : Rename files in archive
  t : Test integrity of archive
  u : Update files to archive
  x : eXtract files with full paths

<Switches>
  -- : Stop switches and @listfile parsing
  -ai[r[-|0]]{@listfile|!wildcard} : Include archives
  -ax[r[-|0]]{@listfile|!wildcard} : eXclude archives
  -ao{a|s|t|u} : set Overwrite mode
  -an : disable archive_name field
  -bb[0-3] : set output log level
  -bd : disable progress indicator
  -bs{o|e|p}{0|1|2} : set output stream for output/error/progress line
  -bt : show execution time statistics
  -i[r[-|0]]{@listfile|!wildcard} : Include filenames
  -m{Parameters} : set compression Method
    -mmt[N] : set number of CPU threads
    -mx[N] : set compression level: -mx1 (fastest) ... -mx9 (ultra)
  -o{Directory} : set Output directory
  -p{Password} : set Password
  -r[-|0] : Recurse subdirectories for name search
  -sa{a|e|s} : set Archive name mode
  -scc{UTF-8|WIN|DOS} : set charset for for console input/output
  -scs{UTF-8|UTF-16LE|UTF-16BE|WIN|DOS|{id}} : set charset for list files
  -scrc[CRC32|CRC64|SHA1|SHA256|*] : set hash function for x, e, h commands
  -sdel : delete files after compression
  -seml[.] : send archive by email
  -sfx[{name}] : Create SFX archive
  -si[{name}] : read data from stdin
  -slp : set Large Pages mode
  -slt : show technical information for l (List) command
  -snh : store hard links as links
  -snl : store symbolic links as links
  -sni : store NT security information
  -sns[-] : store NTFS alternate streams
  -so : write data to stdout
  -spd : disable wildcard matching for file names
  -spe : eliminate duplication of root folder for extract command
  -spf : use fully qualified file paths
  -ssc[-] : set sensitive case mode
  -sse : stop archive creating, if it can't open some input file
  -ssp : do not change Last Access Time of source files while archiving
  -ssw : compress shared files
  -stl : set archive timestamp from the most recently modified file
  -stm{HexMask} : set CPU thread affinity mask (hexadecimal number)
  -stx{Type} : exclude archive type
  -t{Type} : Set type of archive
  -u[-][p#][q#][r#][x#][y#][z#][!newArchiveName] : Update options
  -v{Size}[b|k|m|g] : Create volumes
  -w[{path}] : assign Work directory. Empty path means a temporary directory
  -x[r[-|0]]{@listfile|!wildcard} : eXclude filenames
  -y : assume Yes on all queries
  */

  // zlib.deflateSync {{{
  /**
   * @typedef {object} typeDeflateZipOption
   * @property {string} [exe7z=DEF_7ZIP_EXE] - A custom .exe path of 7-ZIP.
   * @property {string} [workingDir] - Working directory
   * @property {boolean} [updateMode='sync'] - A method of overwriting an existing dest Zip file. "sync" (default) or "add"
   * @property {boolean} [includesSubDir] - How to interpret the specified path (src, dest, excludingFiles). default: recurse subdirectories only when using wildcard. true: recurse subdirectories. false: disable recurse subdirectories.
   * @property {string[]|string} [excludingFiles] - You should specify relative paths with a wildcard. Cannot establish absolute paths.
   * @property {number|string} [compressLv=5] Level of compression. 1,3,5,7,9 or Fastest, Fast, Normal, Maximum, Ultra
   * @property {string} [password] - Specifies password. File names will not be encrypted in Zip archive.
   * @property {string} [dateCode] - If specify "yyyy-MM-dd" to Zipfile name is <name>_yyyy-MM-dd.zip
   * @property {boolean} [savesTmpList=false] - Does not remove temporary file list.
   * @property {boolean} [outputsLog=false] - Output console logs.
   * @property {boolean} [isDryRun=false] - No execute, returns the string of command.
   */

  /**
   * Compresses and encrypts files into ZIP with 7-Zip.
   *
   * @example
   * var zlib = Wsh.ZLIB; // Shorthand
   * var path = Wsh.Path;
   *
   * var exe7z = path.join(__dirname, '.\\bin\\7-Zip\\7z.exe');
   *
   * // Zipping a directory
   * var rtn = zlib.deflateSync('C:\\My Data', 'D:\\Backup.zip', {
   *   exe7z: exe7z
   * });
   *
   * console.dir(rtn);
   * // Outputs:
   * // { command: "C:\My script\bin\7-Zip\7z.exe" u -tzip -ssw -r0 "D:\\Backup.zip" @"C:\Users\<Your Name>\AppData\Local\Temp\fs-writeTmpFileSync_rad3CD32.tmp"",
   * //   exitCode: 0,
   * //   stdout: "
   * // 7-Zip 22.00 (x64) : Copyright (c) 1999-2022 Igor Pavlov : 2022-06-15
   * // ...
   * // ..
   * // Everything is Ok
   * // ",
   * //   stderr: "",
   * //   error: false,
   * //   archivedPath: "D:\\Backup.zip" }
   *
   * // With many options
   * var rtn = zlib.deflateSync('C:\\My Data\\*.txt', 'D:\\Backup.zip', {
   *   dateCode: 'yyyyMMdd-HHmmss',
   *   compressLv: 9,
   *   password: 'This is mY&p@ss ^_<',
   *   excludingFiles: ['*SJIS*'],
   *   includesSubDir: false,
   *   exe7z: exe7z
   * });
   *
   * console.dir(rtn);
   * // Outputs:
   * // { command: "C:\My script\bin\7-Zip\7z.exe" u -tzip -ssw -r- -xr-@"C:\Users\<Your Name>\AppData\Local\Temp\fs-writeTmpFileSync_radD1C8B.tmp" -mx9 -p"This is mY&p@ss ^_<" -mem=AES256 "D:\\Backup_20220722-100513.zip" @"C:\Users\<Your Name>\AppData\Local\Temp\fs-writeTmpFileSync_radA1BD8.tmp"",
   * //   exitCode: 0,
   * //   stdout: "
   * // 7-Zip 22.00 (x64) : Copyright (c) 1999-2022 Igor Pavlov : 2022-06-15
   * // ...
   * // ..
   * // Files read from disk: 5
   * // Archive size: 2291 bytes (3 KiB)
   * // Everything is Ok
   * // ",
   * //   stderr: "",
   * //   error: false,
   * //   archivedPath: "D:\\Backup_20220722-100513.zip" }
   * @function deflateSync
   * @memberof Wsh.ZLIB
   * @param {string[]|string} paths - The compressed file paths. If a directory is specified, all of them are compressed, including sub directories. If you use a wildcard to specify the paths, you can use the R option to control the files contained in the sub directories.
   * @param {string} [dest] - The filepath or directory of destination ZIP.
   * @param {typeDeflateZipOption} [options] - Optional parameters.
   * @returns {typeDeflateResult|string} - See typeDeflateResult. If options.isDryRun is true, returns string.
   */
  zlib.deflateSync = function (paths, dest, options) {
    var FN = 'zlib.deflateSync';

    if (!isSolidArray(paths) && !isSolidString(paths)) {
      throwErrInvalidValue(FN, 'paths', paths);
    }

    var outputsLog = obtain(options, 'outputsLog', false);
    var isDryRun = obtain(options, 'isDryRun', false);

    // Setting the arguments
    var args =[];

    // _log(outputsLog, 'a: Add files to archive');
    _log(outputsLog, 'u : Update files to archive');
    _log(outputsLog, '-tzip: Set ZIP type of archive');
    _log(outputsLog, '-ssw: Compress shared(locked) files');
    // _log(outputsLog, '-snh : store hard links as links');
    // _log(outputsLog, '-snl : store symbolic links as links');

    // args.push('u', '-tzip', '-ssw', '-snh', '-snl');
    args.push('u', '-tzip', '-ssw');

    // Assign the working directory (-w[{path}])
    var workingDir = obtain(options, 'workingDir', null);
    if (isSolidString(workingDir)) {
      _log(outputsLog, '-w"' + workingDir + '": Assign the working directory');
      args.push('-w"' + workingDir + '"');
    }

    // Setting the updating method and adding the dest path to args.
    var updateMode = obtain(options, 'updateMode', 'SYNC');
    if (fs.existsSync(destZip)) {
      if (isSameStr(updateMode, 'SYNC')) {
        args.push('-up0q0r2x1y1z1w2');
      }
    }

    /*
     * Setting Recurse switch
     * -r 	Enable recurse subdirectories.
     *   ex: "C:\hoge\foo.exe" -> Targets all foo.exe in all sub folders.
     * -r- 	Disable recurse subdirectories. This option is default for all commands.
     *   ex1: "C:\hoge\foo.exe" -> Targets only "C:\hoge\foo.exe"
     *   ex2: "C:\hoge\*foo.exe" -> Targets only "C:\hoge\foo.exe"
     * -r0 	Enable recurse subdirectories only for wildcard names.
     *   ex1: "C:\hoge\*foo.exe" -> Targets all foo.exe in all sub subdirectories.
     *   ex2: "C:\hoge\*\foo.exe" -> Targets foo.exe in subdirectories. Exclude "C:\hoge\foo.exe" and files in sub-sub-directories ex. "C:\hoge\AAA\BBB\foo.exe".
     */
    var includesSubDir = obtain(options, 'includesSubDir', null);
    var rc = '';

    if (includesSubDir === true) {
      _log(outputsLog, '-r: Enable recurse subdirectories.');
      args.push('-r');
      rc = 'r';
    } else if (includesSubDir === false) {
      _log(outputsLog, '-r: Disable recurse subdirectories.');
      args.push('-r-');
      rc = 'r-';
    } else {
      _log(
        outputsLog,
        '-r0: Enable recurse subdirectories only for wildcard names.'
      );
      args.push('-r0');
      rc = 'r0';
    }

    // Setting excluding filepaths (-x)
    var excludingFiles = obtain(options, 'excludingFiles', null);
    var excludeListFile;

    if (!isEmpty(excludingFiles)) {
      excludeListFile = zlib._createTmpListFile(excludingFiles);
      args.push('-x' + rc + '@"' + excludeListFile + '"');

      _log(outputsLog, '-x: Set excluding filepaths ' + insp(excludingFiles));
      _log(outputsLog, 'Excluding list file: ' + excludeListFile);
    }


    // Setting the level of compression (-mx1(fastest) ... -mx9(ultra)')
    var compressLv = obtain(options, 'compressLv', null);
    if (isPureNumber(compressLv) || isSolidString(compressLv)) {
      var lv = compressLv.toString().toUpperCase().trim();
      var mxN;

      if (lv === 'FASTEST' || lv === '1') {
        mxN = '-mx1';
      } else if (lv === 'FAST' || lv === '3') {
        mxN = '-mx3';
      } else if (lv === 'MAXIMUM' || lv === '7') {
        mxN = '-mx7';
      } else if (lv === 'ULTRA' || lv === '9') {
        mxN = '-mx9';
      } else {
        mxN = '-mx' + lv;
      }

      _log(outputsLog, mxN + ': Set compression level (-mx1(fastest) ... -mx9(ultra)');
      args.push(mxN);
    }

    // Setting a zip password (-p{Password})
    // @note File names will be not encrypted in Zip archive.
    // Encode an archive header option, -mhe=on can be used by .7z only!
    var password = obtain(options, 'password', null);
    if (isSolidString(password)) {
      _log(outputsLog, '-p"****": Set the password (-mem=AES256)');
      args.push('-p"' + password + '"', '-mem=AES256');
    }

    // Setting the destination ZIP file path
    var destZip = zlib._makeDestArchivePath('.zip', paths, dest);

    // Appends the current date string to an archive name.
    var dateCode = obtain(options, 'dateCode', null);
    if (isSolidString(dateCode)) {
      destZip = destZip.replace(/(\.zip)?$/i, '_' + parseDate(dateCode) + '$1');
    }

    var destZipDir = path.dirname(destZip);
    if (!fs.existsSync(destZipDir) && !isDryRun) fse.ensureDirSync(destZipDir);

    // Adding the dest Zip path and source paths.
    args.push(destZip);

    // Setting source file paths
    var srcListFile = zlib._createTmpListFile(paths);
    args.push('@"' + srcListFile + '"');

    _log(outputsLog, 'Set compressed filepaths ' + insp(paths));
    _log(outputsLog, 'Compressed list file: ' + excludeListFile);

    // Executing
    // Setting the .exe path
    var exe7z = obtain(options, 'exe7z', DEF_7Z_EXE);
    var op = objAdd({ isDryRun: isDryRun }, options);

    _log(outputsLog, 'exe path: ' + exe7z);
    _log(outputsLog, 'arguments: ' + insp(args));
    _log(outputsLog, 'options: ' + insp(op));

    var rtn = execFileSync(exe7z, args, op);
    rtn.archivedPath = destZip; // provisional

    // Remove lists
    var savesTmpList = obtain(options, 'savesTmpList', false);
    if (!savesTmpList) {
      if (excludeListFile) fse.removeSync(excludeListFile);
      fse.removeSync(srcListFile);
    }

    _log(outputsLog, insp(rtn));
    if (isDryRun) return rtn; // rtn is {string}

    // Setting the true archived file path
    if (rtn.stdout && includes(rtn.stdout, 'Creating archive: ')) {
      rtn.archivedPath = rtn.stdout.match(/Creating archive: (.+)\r\n/)[1];
    }

    // Exit Code Handling
    // 0: No error
    if (rtn.exitCode === 0) {
      _log(outputsLog, 'No error');
      rtn.error = false;
      return rtn;
    }

    // 1: Warning (Non fatal error(s)).
    //   For example, one or more files were locked by some other application,
    //   so they were not compressed.
    if (rtn.exitCode === 1) {
      _log(outputsLog, 'Warning. Non fatal error(s).');
      rtn.error = false;
      return rtn;
    }

    // 2: Fatal Error
    if (rtn.exitCode === 2) {
      throw new Error(
        '[ERROR] 7-Zip: Fatal Error\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 7: Command line error
    if (rtn.exitCode === 7) {
      throw new Error(
        '[ERROR] 7-Zip: Command line error\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 8: Not enough memory for operation
    if (rtn.exitCode === 8) {
      throw new Error(
        '[ERROR] 7-Zip: Not enough memory for operation\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 255: User stopped the process
    if (rtn.exitCode === 255) {
      throw new Error(
        '[ERROR] 7-Zip: User stopped the process\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    throw new Error(
      '[UNKNOWN EXIT CODE] The compressing process probably failed\n'
      + ' ' + insp(rtn)
      + '  at ' + FN + ' (' + MODULE_TITLE + ')'
    );
  }; // }}}

  // zlib.openZip {{{
  /**
   * Open the archive file with 7-Zip.
   *
   * @function openZip
   * @memberof Wsh.ZLIB
   * @param {string} archive - An archive filepath
   * @param {object} [options] - Optional parameters.
   * @param {string} [options.exe7zFM=DEF_7ZFM_EXE] - A custom .exe path of 7-ZIP file manager.
   * @param {string} [options.winStyle='activeDef']
   * @returns {void}
   */
  zlib.openZip = function (archive, options) {
    var FN = 'zlib.openZip';

    if (!isSolidString(archive)) throwErrNonStr(FN, archive);

    var filePath = path.resolve(archive);

    // Setting the .exe path
    var exe7zFM = obtain(options, 'exe7zFM', DEF_7ZFM_EXE);

    var winStyle = obtain(options, 'winStyle', CD.windowStyles.activeDef);
    var op = objAdd({ shell: false, winStyle: winStyle }, options);
    var command = srrd(exe7zFM) + ' ' + srrd(filePath);

    // Executing
    exec(command, op);
  }; // }}}

  // zlib.unzipSync {{{
  /**
   * Extract files from an archive with 7-Zip.
   *
   * @function unzipSync
   * @memberof Wsh.ZLIB
   * @param {string} archive - The archive file path
   * @param {string} [destDir] - The output directory path.
   * @param {object} [options] - Optional parameters.
   * @param {string} [options.exe7z=DEF_7ZIP_EXE] - A custom .exe path of 7-ZIP.
   * @param {string} [options.password] - Specifies password.
   * @param {string} [options.workingDir] - Working directory
   * @param {boolean} [options.makesDestDir=false] - Makes the destination directory. If it is not existing.
   * @param {boolean} [options.makesArchiveNameDir=false] - Makes a new directory with archive file name
   * @param {boolean} [options.outputsLog=false] - Output console logs.
   * @param {boolean} [options.isDryRun=false] - No execute, returns the string of command.
   * @returns {object} - See {@link https://tuckn.net/docs/WshChildProcess/global.html#typeRunSyncReturn|typeRunSyncReturn}.
   */
  zlib.unzipSync = function (archive, destDir, options) {
    var FN = 'zlib.unzipSync';

    if (!isSolidString(archive)) throwErrNonStr(FN, archive);

    var outputsLog = obtain(options, 'outputsLog', false);
    var isDryRun = obtain(options, 'isDryRun', false);

    // Setting the arguments
    var args = [];

    _log(outputsLog, 'x: eXtract files with full paths');
    args.push('x');

    // Setting a zip password (-p{Password})
    var password = obtain(options, 'password', null);
    if (!isEmpty(password)) {
      _log(outputsLog, '-p"****": Set the password (-mem=AES256)');
      args.push('-p"' + password + '"', '-mem=AES256');
    }

    // Assign the working directory (-w[{path}])
    var workingDir = obtain(options, 'workingDir', null);
    if (!isEmpty(workingDir)) {
      _log(outputsLog, '-w"' + workingDir + '": Assign the working directory');
      args.push('-w"' + workingDir + '"');
    }

    var srcPath = path.resolve(archive);
    args.push(srcPath);

    // Setting the output directory path.
    // If it is not specified, set the srcPath directory.
    if (isEmpty(destDir)) destDir = path.dirname(srcPath);

    destDir = path.resolve(destDir);

    // Creating the output directory
    if (!fs.existsSync(destDir) && obtain(options, 'makesDestDir', false)) {
      _log(outputsLog, 'Creating the dest directory');
      if (!isDryRun) fse.ensureDirSync(destDir);
    }

    if (obtain(options, 'makesArchiveNameDir', false)) {
      destDir = path.join(destDir, path.parse(srcPath).name);
      _log(outputsLog, 'Creating the Zip name directory');
      if (!isDryRun) fse.ensureDirSync(destDir);
    }

    _log(outputsLog, '-o: Set output directory. "' + destDir + '"');

    args.push('-o"' + destDir + '"', '-y');

    // Executing
    // Setting the .exe path
    var exe7z = obtain(options, 'exe7z', DEF_7Z_EXE);
    var op = objAdd({ isDryRun: isDryRun }, options);

    _log(outputsLog, 'exe path: ' + exe7z);
    _log(outputsLog, 'arguments: ' + insp(args));
    _log(outputsLog, 'options: ' + insp(op));

    var rtn = execFileSync(exe7z, args, op);

    _log(outputsLog, insp(rtn));
    if (isDryRun) return rtn; // rtn is {string}

    // Exit values
    // 0: No error
    if (rtn.exitCode === 0) {
      _log(outputsLog, 'No error');
      rtn.error = false;
      return rtn;
    }

    // 1: Warning (Non fatal error(s)). For example, one or more files were
    //  locked by some other application, so they were not compressed.
    if (rtn.exitCode === 1) {
      _log(outputsLog, 'Warning. Non fatal error(s).');
      rtn.error = false;
      return rtn;
    }

    // 2: Fatal error.
    if (rtn.exitCode === 2) {
      throw new Error(
        '[ERROR] 7-Zip: Fatal Error\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 3: Command line error.
    if (rtn.exitCode === 3) {
      throw new Error(
        '[ERROR] 7-Zip: Command line error\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 5: Not enough memory for operation.
    if (rtn.exitCode === 5) {
      throw new Error(
        '[ERROR] 7-Zip: Not enough memory for operation\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 255: User stopped the process.
    if (rtn.exitCode === 255) {
      throw new Error(
        '[ERROR] 7-Zip: User stopped the process\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    throw new Error(
      '[UNKNOWN EXIT CODE] The unzip process probably failed\n'
      + ' ' + insp(rtn)
      + '  at ' + FN + ' (' + MODULE_TITLE + ')'
    );
  }; // }}}

  /**
   * [WinRAR archiver, a powerful tool to process RAR and ZIP files](https://www.rarlab.com/)
RAR 6.11 x64   Copyright (c) 1993-2022 Alexander Roshal   3 Mar 2022
Trial version             Type 'rar -?' for help

Usage:     rar <command> -<switch 1> -<switch N> <archive> <files...>
               <@listfiles...> <path_to_extract\>

<Commands>
  a             Add files to archive
  c             Add archive comment
  ch            Change archive parameters
  cw            Write archive comment to file
  d             Delete files from archive
  e             Extract files without archived paths
  f             Freshen files in archive
  i[par]=<str>  Find string in archives
  k             Lock archive
  l[t[a],b]     List archive contents [technical[all], bare]
  m[f]          Move to archive [files only]
  p             Print file to stdout
  r             Repair archive
  rc            Reconstruct missing volumes
  rn            Rename archived files
  rr[N]         Add data recovery record
  rv[N]         Create recovery volumes
  s[name|-]     Convert archive to or from SFX
  t             Test archive files
  u             Update files in archive
  v[t[a],b]     Verbosely list archive contents [technical[all],bare]
  x             Extract files with full path

<Switches>
  -             Stop switches scanning
  @[+]          Disable [enable] file lists
  ac            Clear Archive attribute after compression or extraction
  ad[1,2]       Alternate destination path
  ag[format]    Generate archive name using the current date
  ai            Ignore file attributes
  ao            Add files with Archive attribute set
  ap<path>      Set path inside archive
  as            Synchronize archive contents
  c-            Disable comments show
  cfg-          Disable read configuration
  cl            Convert names to lower case
  cu            Convert names to upper case
  df            Delete files after archiving
  dh            Open shared files
  dr            Delete files to Recycle Bin
  ds            Disable name sort for solid archive
  dw            Wipe files after archiving
  e[+]<attr>    Set file exclude and include attributes
  ed            Do not add empty directories
  ep            Exclude paths from names
  ep1           Exclude base directory from names
  ep2           Expand paths to full
  ep3           Expand paths to full including the drive letter
  ep4<path>     Exclude the path prefix from names
  f             Freshen files
  hp[password]  Encrypt both file data and headers
  ht[b|c]       Select hash type [BLAKE2,CRC32] for file checksum
  id[c,d,n,p,q] Display or disable messages
  ieml[addr]    Send archive by email
  ierr          Send all messages to stderr
  ilog[name]    Log errors to file
  inul          Disable all messages
  ioff[n]       Turn PC off after completing an operation
  isnd[-]       Control notification sounds
  iver          Display the version number
  k             Lock archive
  kb            Keep broken extracted files
  log[f][=name] Write names to log file
  m<0..5>       Set compression level (0-store...3-default...5-maximal)
  ma[4|5]       Specify a version of archiving format
  mc<par>       Set advanced compression parameters
  md<n>[k,m,g]  Dictionary size in KB, MB or GB
  me[par]       Set encryption parameters
  ms[ext;ext]   Specify file types to store
  mt<threads>   Set the number of threads
  n<file>       Additionally filter included files
  n@            Read additional filter masks from stdin
  n@<list>      Read additional filter masks from list file
  o[+|-]        Set the overwrite mode
  oc            Set NTFS Compressed attribute
  oh            Save hard links as the link instead of the file
  oi[0-4][:min] Save identical files as references
  ol[a]         Process symbolic links as the link [absolute paths]
  oni           Allow potentially incompatible names
  op<path>      Set the output path for extracted files
  or            Rename files automatically
  os            Save NTFS streams
  ow            Save or restore file owner and group
  p[password]   Set password
  qo[-|+]       Add quick open information [none|force]
  r             Recurse subdirectories
  r-            Disable recursion
  r0            Recurse subdirectories for wildcard names only
  ri<P>[:<S>]   Set priority (0-default,1-min..15-max) and sleep time in ms
  rr[N]         Add data recovery record
  rv[N]         Create recovery volumes
  s[<N>,v[-],e] Create solid archive
  s-            Disable solid archiving
  sc<chr>[obj]  Specify the character set
  sfx[name]     Create SFX archive
  si[name]      Read data from standard input (stdin)
  sl<size>      Process files with size less than specified
  sm<size>      Process files with size more than specified
  t             Test files after archiving
  ta[mcao]<d>   Process files modified after <d> YYYYMMDDHHMMSS date
  tb[mcao]<d>   Process files modified before <d> YYYYMMDDHHMMSS date
  tk            Keep original archive time
  tl            Set archive time to latest file
  tn[mcao]<t>   Process files newer than <t> time
  to[mcao]<t>   Process files older than <t> time
  ts[m,c,a,p]   Save or restore time (modification, creation, access, preserve)
  u             Update files
  v<size>[k,b]  Create volumes with size=<size>*1000 [*1024, *1]
  vd            Erase disk contents before creating volume
  ver[n]        File version control
  vn            Use the old style volume naming scheme
  vp            Pause before each volume
  w<path>       Assign work directory
  x<file>       Exclude specified file
  x@            Read file names to exclude from stdin
  x@<list>      Exclude files listed in specified list file
  y             Assume Yes on all queries
  z[file]       Read archive comment from file
  */

  // zlib.deflateSyncIntoRar {{{
  /**
   * @typedef {object} typeDeflateRarOption
   * @property {string} [dirWinRar=DEF_DIR_WINRAR] - A custom directory path of WinRAR.
   * @property {boolean} [isGUI=false] - true:WinRar.exe false:Rar.exe
   * @property {boolean} [workingDir] - Assign a working directory. RAR option: "-w<p>"
   * @property {string} [updateMode="add"] - "add" (-u -o+), "sync" (-u -as -o+), "mirror"
   * @property {boolean} [skipsExisting=false] - Skip existing contents
   * @property {string[]|string} [excludingFiles] - Exclude specified file
   * @property {boolean} [excludesUsingFiles=false] - Open shared files. RAR option: "-dh"
   * @property {boolean} [excludesEmptyDir=false] - Do not add empty directories. RAR option: "-ed"
   * @property {boolean} [excludesSubDirWildcard=false] - Recurse subdirectories for wildcard names only. RAR option: "-r0"
   * @property {boolean} [expandsPathsToFull=false] - true: Expand paths to full (-ep2). false (default): Exclude base directory from names (ep1)
   * @property {number} [compressLv=5] - Set compression level (0-store...3-default...5-maximal). RAR option: "-m<0..5>"
   * @property {number} [cpuPriority=0] - Set processing priority (0-default,1-min..15-max) and sleep time in ms
   * @property {number} [recoveryPer=3] - Add data recovery record
   * @property {string} [password] - Encrypt both file data and headers. RAR option: "-hp[password]"
   * @property {string} [dateCode] - If specify "yyyy-MM-dd" to Rar file name is <name>_yyyy-MM-dd.rar
   * @property {boolean} [excludesADS=false] - Do not save NTFS streams. (ADS: NTFS Alternate Data Stream. RAR option: "-os"
   * @property {boolean} [containsSecArea=false] - Save or restore file owner and group. RAR option: "-ow"
   * @property {boolean} [isSolidArchive=true] - Create solid archive
   * @property {boolean} [assumesYes=true] - Assume Yes on all queries
   * @property {boolean} [sendAllMesToStdErr=false] - Send all messages to stderr. RAR option: "-ierr"
   * @property {number} [rarVersion=5] - Specify a version of archiving format. RAR option: "-ma5"
   * @property {boolean} [isSymlinkAsLink=false] - Process symbolic links as the link(RAR 4.x以上、Linuxのみ?) RAR option: "-ol"
   * @property {boolean} [savesTmpList=false] - Does not remove temporary file list.
   * @property {boolean} [outputsLog=false] - Output console logs.
   * @property {boolean} [isDryRun=false] - No execute, returns the string of command.
   */

  /**
   * Compresses and encrypts files into RAR.
   *
   * @example
   * var zlib = Wsh.ZLIB; // Shorthand
   * var path = Wsh.Path;
   *
   * var dirWinRar = path.join(__dirname, '.\\bin\\WinRar');
   *
   * // Archiving a directory as a Rar file
   * var rtn = zlib.deflateSyncIntoRar('C:\\My Data', 'D:\\Backup.rar', {
   *   dirWinRar: dirWinRar
   * });
   *
   * console.dir(rtn);
   * // Outputs:
   * // { command: "C:\My Script\bin\WinRAR\Rar.exe" a -u -o+ -r0 -dh -ep1 -m3 -ma5 -os -s -y "D:\Backup.rar" @"C:\Users\Your Name\AppData\Local\Temp\fs-writeTmpFileSync_radB5E4E.tmp"",
   * //   exitCode: 0,
   * //   stdout: "
   * // RAR 6.11 x64   Copyright (c) 1993-2022 Alexander Roshal   3 Mar 2022
   * // ...
   * // ..
   * // Done
   * // ",
   * //   stderr: "",
   * //   error: false,
   * //   archivedPath: "D:\\Backup.rar" }
   *
   * // With many options
   * var rtn = zlib.deflateSyncIntoRar('C:\\My Data\\*.txt', 'D:\\Backup.rar', {
   *   dateCode: 'yyyyMMdd-HHmmss',
   *   compressLv: 0,
   *   password: 'This is mY&p@ss ^_<',
   *   excludingFiles: ['*utf16*', 'settings.json'],
   *   excludesEmptyDir: true,
   *   excludesSubDirWildcard: true,
   *   isGUI: true,
   *   dirWinRar: dirWinRar
   * });
   *
   * console.dir(rtn);
   * // Outputs:
   * // { command: "C:\My Script\bin\WinRAR\WinRar.exe" a -u -o+ -x@"C:\Users\<Your Name>\AppData\Local\Temp\fs-writeTmpFileSync_rad017F1.tmp" -dh -ed -ep1 -m0 -hp"This is mY&p@ss ^_<" -ma5 -os -s -y "D:\Backup_20220722-103741.rar" @"C:\Users\<Your Name>\AppData\Local\Temp\fs-writeTmpFileSync_rad89C8F.tmp",
   * //  exitCode: 0,
   * //  stdout: "",
   * //  stderr: "",
   * //  error: false,
   * //  archivedPath: "D:\Backup_20220722-103741.rar" }
   * @function deflateSyncIntoRar
   * @memberof Wsh.ZLIB
   * @param {string[]|string} paths - The compressed file paths. If a directory is specified, all of them are compressed, including sub directories. If you use a wildcard to specify the paths, you can use the R option to control the files contained in the sub directories.
   * @param {string} [dest] - The filepath or directory of destination ZIP.
   * @param {typeDeflateRarOption} [options] - Optional parameters.
   * @returns {typeDeflateResult|string} - @see typeDeflateResult. If options.isDryRun is true, returns string.
   */
  zlib.deflateSyncIntoRar = function (paths, dest, options) {
    var FN = 'zlib.deflateSyncIntoRar';

    if (!isSolidArray(paths) && !isSolidString(paths)) {
      throwErrInvalidValue(FN, 'paths', paths);
    }

    var outputsLog = obtain(options, 'outputsLog', false);
    var isDryRun = obtain(options, 'isDryRun', false);

    // Setting the arguments
    var args = [];

    // Setting the command
    _log(outputsLog, 'a: Add files to archive');
    args.push('a');

    // Assign work directory.
    var workingDir = obtain(options, 'workingDir', null);
    if (isSolidString(workingDir)) {
      _log(outputsLog, '-w: Assign work directory to "' + options.workingDir + '"');
      args.push('-w"' + options.workingDir + '"');
    }

    // Setting the mode of existing archive updating
    var updateMode = obtain(options, 'updateMode', 'ADD');
    if (isSameStr(updateMode, 'MIRROR')) {
      // No add switches
    } else {
      _log(outputsLog, '-u: Update a existing RAR file');
      args.push('-u');

      if (isSameStr(updateMode, 'SYNC')) {
        _log(outputsLog, '-as: Synchronize archive contents');
        args.push('-as');
      }

      if (isTrueLike(obtain(options, 'skipsExisting', false))) {
        _log(outputsLog, '-o-: Set the none of overwriting. (Skip existing)');
        args.push('-o-');
      } else {
        _log(outputsLog, '-o+: Set the overwriting (If existing updated, overwrite)');
        args.push('-o+');
      }
    }

    /**
     * Recurse subdirectories. "-r", "-r0"
     * -r and "C:\hoge\foo.exe" -> Targets all foo.exe in all sub folders.
     * -r0 and "C:\hoge\foo.exe" -> Targets only "C:\hoge\foo.exe"
     * -r0 and "C:\hoge\*\foo.exe" -> Targets all foo.exe in all sub folder.
     */
    var excludesSubDirWildcard = obtain(options, 'excludesSubDirWildcard', false);
    if (excludesSubDirWildcard) {
      _log(outputsLog, 'Exclude subdirectories (for wildcard names only)');
    } else {
      _log(outputsLog, '-r0: Recurse subdirectories (for wildcard names only)');
      args.push('-r0');
    }

    // Setting excluding files
    var excludingFiles = obtain(options, 'excludingFiles', null);
    var excludeListFile;

    if (isSolidArray(excludingFiles) || isSolidString(excludingFiles)) {
      var exNames;
      if (isSolidArray(excludingFiles)) {
        exNames = excludingFiles;
      } else if (isSolidString(excludingFiles)) {
        exNames = [excludingFiles];
      }

      var exNamesFormatted = [];
      exNames.forEach(function (name) {
        if (path.isAbsolute(name)) {
          exNamesFormatted.push(name);
        } else if (startsWith(name, '*')) {
          exNamesFormatted.push(name);
        } else {
          exNamesFormatted.push(path.join('*', name));
        }
      });

      excludeListFile = zlib._createTmpListFile(exNamesFormatted);
      args.push('-x@"' + excludeListFile + '"');

      _log(outputsLog, '-x@: Set excluding filepaths ' + insp(exNamesFormatted));
      _log(outputsLog, 'Excluding list file: ' + excludeListFile);
    }

    // Setting handling of open shared files.
    var excludesUsingFiles = obtain(options, 'excludesUsingFiles', false);
    if (excludesUsingFiles) {
      _log(outputsLog, 'Exclude shared files');
    } else {
      _log(outputsLog, '-dh: Open shared files');
      args.push('-dh');
    }

    // Setting handling empty directories.
    var excludesEmptyDir = obtain(options, 'excludesEmptyDir', false);
    if (excludesEmptyDir) {
      _log(outputsLog, '-ed: Does not add empty directories');
      args.push('-ed');
    } else {
      _log(outputsLog, 'Add empty directories');
    }

    // Path
    var expandsPathsToFull = obtain(options, 'expandsPathsToFull', false);
    if (expandsPathsToFull) {
      _log(outputsLog, '-ep2: Expand paths to full');
      args.push('-ep2');
    } else {
      _log(outputsLog, '-ep1: Exclude base directory from names');
      args.push('-ep1');
    }

    // Setting compression level (0-store...3-default...5-maximal).
    var compressLv = parseInt(obtain(options, 'compressLv', 3), 10);

    if (compressLv < 0 || 5 < compressLv) {
      _log(outputsLog, '-m5: Set compression level to 5-maximal');
      args.push('-m5');
    } else {
      _log(outputsLog, '-m' + compressLv + ': Set compression level (0-store..3..5-max)');
      args.push('-m' + compressLv);
    }

    // Encrypt both file data and headers.
    var password = obtain(options, 'password', null);
    if (isSolidString(password)) {
      _log(outputsLog, '-hp"****": Encrypt both file data and headers');
      args.push('-hp"' + password + '"');
    }

    // Specify a Rar version of archiving format.
    var rarVer = parseInt(obtain(options, 'rarVersion', 5), 10);

    if (typeof rarVer !== 'undefined' && rarVer !== null && rarVer < 4) {
      /* RAR2.9 */
    } else if (rarVer === 4) {
      _log(outputsLog, '-ma4: Specify RAR4.x of archiving format');
      args.push('-ma4');
    } else {
      _log(outputsLog, '-ma5: Specify RAR5.0(default) of archiving format');
      args.push('-ma5');

      if (isTrueLike(obtain(options, 'isSymlinkAsLink', false))) {
        _log(outputsLog, '-ol: Process symbolic links as the link(only for RAR5.0');
        args.push('-ol');
      }
    }

    // Add data recovery record.
    var rrLv = parseInt(obtain(options, 'recoveryPer', 0), 10);

    if (rrLv === 0) {
      /* */
    } else if (0 < rrLv && rrLv <= 100) {
      _log(outputsLog, '-ri: Add data recovery record -> ' + rrLv + 'p');
      args.push('-rr' + rrLv + 'p');
    } else {
      _log(outputsLog, '-ri: Add data recovery record -> 3p');
      args.push('-rr3p');
    }

    // Setting priority (0-default,1-min..15-max) and sleep time in ms
    var lv = parseInt(obtain(options, 'cpuPriority', 0), 10);

    if (lv === 0) {
      /* */
    } else if (lv < 0 || 15 < lv) {
      lv = 0;
    } else {
      args.push('-ri' + lv);
    }

    _log(outputsLog, '-ri' + lv + ': Set priority (0-default,1-min..15-max)');

    // Setting whether save NTFS streams (Alternate Data Stream).
    var excludesADS = obtain(options, 'excludesADS', false);
    if (excludesADS) {
      _log(outputsLog, 'Does not save NTFS streams. (ADS: Alternate Data Stream');
    } else {
      _log(outputsLog, '-os: Save NTFS streams. (ADS: Alternate Data Stream');
      args.push('-os');
    }

    // Setting whether save or restore file owner and group (Security info)
    var containsSecArea = obtain(options, 'containsSecArea', false);
    if (containsSecArea) {
      _log(outputsLog, '-ow: Save or restore file owner and group');
      args.push('-ow');
    }

    // Setting whether create solid archive.
    var isSolidArchive = obtain(options, 'isSolidArchive', true);
    if (isSolidArchive) {
      _log(outputsLog, '-s: Create solid archive');
      args.push('-s');
    } else {
      _log(outputsLog, '-s-: Create none of solid archive');
      args.push('-s-');
    }

    // Assume Yes on all queries.
    var assumesYes = obtain(options, 'assumesYes', true);
    if (assumesYes) {
      _log(outputsLog, '-y: Assume Yes on all queries');
      args.push('-y');
    }

    // Setting whether send all messages to stderr.
    var sendAllMesToStdErr = obtain(options, 'sendAllMesToStdErr', false);
    if (sendAllMesToStdErr) {
      _log(outputsLog, '-ierr: Send all messages to stderr');
      args.push('-ierr');
    }

    // Setting the destination RAR file path
    var destRar = zlib._makeDestArchivePath('.rar', paths, dest);

    // Appends the current date string to an archive name.
    var dateCode = obtain(options, 'dateCode', null);
    if (isSolidString(dateCode)) {
      destRar = destRar.replace(/(\.rar)?$/i, '_' + parseDate(dateCode) + '$1');
    }

    var destRarDir = path.dirname(destRar);
    if (!fs.existsSync(destRarDir) && !isDryRun) fse.ensureDirSync(destRarDir);

    // Adding the dest Rar path and source paths.
    args.push(destRar);

    // Setting source file paths
    var srcListFile = zlib._createTmpListFile(paths);
    args.push('@"' + srcListFile + '"');

    _log(outputsLog, 'Set compressed filepaths ' + insp(paths));
    _log(outputsLog, 'Compressed list file: ' + excludeListFile);

    // Executing
    // WinRar.exe or Rar.exe (default:WinRar.exe
    var dirWinRar = obtain(options, 'dirWinRar', DEF_DIR_WINRAR);
    var isGUI = obtain(options, 'isGUI', false);

    var exeRar;
    if (isGUI) {
      exeRar = path.join(dirWinRar, EXENAME_WINRAR);
    } else {
      exeRar = path.join(dirWinRar, EXENAME_RAR);
    }

    var op = objAdd({ isDryRun: isDryRun }, options);

    _log(outputsLog, 'exe path: ' + exeRar);
    _log(outputsLog, 'arguments: ' + insp(args));
    _log(outputsLog, 'options: ' + insp(op));

    var rtn = execFileSync(exeRar, args, op);
    rtn.archivedPath = destRar; // provisional

    // Remove lists
    var savesTmpList = obtain(options, 'savesTmpList', false);
    if (!savesTmpList) {
      if (excludeListFile) fse.removeSync(excludeListFile);
      fse.removeSync(srcListFile);
    }

    // Setting the true archived file path
    if (rtn.stdout && includes(rtn.stdout, 'Creating solid archive ')) {
      rtn.archivedPath = rtn.stdout.match(/Creating solid archive (.+)\r\n/)[1];
    }

    _log(outputsLog, insp(rtn));
    if (isDryRun) return rtn; // rtn is {string}

    // Exit Code Handling
    // @NOTE The GUI app WinRar.exe always returns exitCode = 0?
    // RAR exits with a zero exitCode (0) in case of successful operation. The exit
    // exitCode of non-zero means the operation was cancelled due to an error:
    if (rtn.error) {
      throw new Error('Failed to deflate the files\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')');
    }

    // 0: SUCCESS
    if (rtn.exitCode === 0) {
      _log(outputsLog, '[SUCCESS] success.');
      rtn.error = false;
      return rtn;
    }

    // 1: WARNING          Non fatal error(s) occurred
    if (rtn.exitCode === 1) {
      _log(outputsLog, '[WARNING] Non fatal error(s) occurred.');
      rtn.error = false;
      return rtn;
    }

    // 2: FATAL ERROR      A fatal error occurred
    if (rtn.exitCode === 2) {
      throw new Error(
        '[FATAL ERROR] A fatal error occurred.\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 3: CRC ERROR        A CRC error occurred when unpacking
    if (rtn.exitCode === 3) {
      throw new Error(
        '[CRC ERROR] A CRC error occurred when unpacking.\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 4: LOCKED ARCHIVE   Attempt to modify an archive previously locked
    if (rtn.exitCode === 4) {
      throw new Error(
        '[LOCKED ARCHIVE] Attempt to modify an archive previously locked by the ‘k’ command.\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 5: WRITE ERROR      Write to disk error
    if (rtn.exitCode === 5) {
      throw new Error(
        '[WRITE ERROR] Write to disk error.\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 6: OPEN ERROR       Open file error
    if (rtn.exitCode === 6) {
      throw new Error(
        '[OPEN ERROR] Open file error.\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 7: USER ERROR       Command line option error
    if (rtn.exitCode === 7) {
      throw new Error(
        '[USER ERROR] Command line option error.\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 8: MEMORY ERROR     Not enough memory for operation
    if (rtn.exitCode === 8) {
      throw new Error(
        '[MEMORY ERROR] Not enough memory for operation.\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 9: CREATE ERROR     Create file error
    if (rtn.exitCode === 9) {
      throw new Error(
        '[CREATE ERROR] Create file error.\n'
        + ' ' + rtn.stderr
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 10: SAME ARCHIVE
    if (rtn.exitCode === 10) {
      throw new Error(
        '[SAME ARCHIVE] No updating file. The existing RAR file is not changed'
        + ' ' + rtn.stderr
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 255: USER BREAK       User stopped the process
    if (rtn.exitCode === 255) {
      throw new Error(
        '[USER BREAK] User stopped the process.\n'
        + ' ' + rtn.stderr
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    throw new Error(
      '[UNKNOWN EXIT CODE] The compressing process probably failed\n'
      + ' ' + rtn.stderr
      + '  at ' + FN + ' (' + MODULE_TITLE + ')'
    );
  }; // }}}

  // zlib.testRarSync {{{
  /**
   * Test RAR file
   *
   * @function testRarSync
   * @memberof Wsh.ZLIB
   * @param {string} archive - The archive file path to open.
   * @param {object} [options] - Optional parameters.
   * @param {string} [options.dirWinRar=DEF_DIR_WINRAR] - A custom directory path of WinRAR.
   * @param {boolean} [options.isGUI=false] - true:WinRar.exe false:Rar.exe
   * @param {boolean} [options.outputsLog=false] - Output console logs.
   * @returns {object|string} - See {@link https://tuckn.net/docs/WshChildProcess/global.html#typeRunSyncReturn|typeRunSyncReturn}. If options.isDryRun is true, returns string.
   */
  zlib.testRarSync = function (archive, options) {
    var FN = 'zlib.testRarSync';

    if (!isSolidString(archive)) throwErrNonStr(FN, archive);

    var outputsLog = obtain(options, 'outputsLog', false);
    var isDryRun = obtain(options, 'isDryRun', false);
    var filePath = path.resolve(archive);

    // Set arguments
    var args = ['t', filePath];

    // Executing
    // WinRar.exe or Rar.exe (default:WinRar.exe
    var dirWinRar = obtain(options, 'dirWinRar', DEF_DIR_WINRAR);
    var isGUI = obtain(options, 'isGUI', false);

    var exeRar;
    if (isGUI) {
      exeRar = path.join(dirWinRar, EXENAME_WINRAR);
    } else {
      exeRar = path.join(dirWinRar, EXENAME_RAR);
    }

    var op = objAdd({ isDryRun: isDryRun }, options);

    _log(outputsLog, 'exe path: ' + exeRar);
    _log(outputsLog, 'arguments: ' + insp(args));
    _log(outputsLog, 'options: ' + insp(op));

    var rtn = execFileSync(exeRar, args, op);

    _log(outputsLog, insp(rtn));
    if (isDryRun) return rtn; // rtn is {string}

    // Exit values
    // RAR exits with a zero exitCode (0) in case of successful operation.
    // Non-zero exit exitCode indicates some kind of error:
    if (rtn.error) {
      throw new Error('Failed to test the files\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')');
    }

    // 0: Successful operation.
    if (rtn.exitCode === 0) {
      _log(outputsLog, 'Successful operation.');
      rtn.error = false;
      return rtn;
    }

    // 1: Non fatal error(s) occurred.
    if (rtn.exitCode === 1) {
      _log(outputsLog, 'Non fatal error(s) occurred.');
      rtn.error = false;
      return rtn;
    }

    // 2: A fatal error occurred.
    if (rtn.exitCode === 2) {
      throw new Error(
        'A fatal error occurred.'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 3: Invalid checksum. Data is damaged.
    if (rtn.exitCode === 3) {
      throw new Error(
        'Invalid checksum. Data is damaged.'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 4: Attempt to modify an archive locked by 'k' command.
    if (rtn.exitCode === 4) {
      throw new Error(
        ''
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 5: Write error.
    if (rtn.exitCode === 5) {
      throw new Error(
        '[WRITE ERROR] Write to disk error.\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 6: File open error.
    if (rtn.exitCode === 6) {
      throw new Error(
        '[OPEN ERROR] Open file error.\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 7: Wrong command line option.
    if (rtn.exitCode === 7) {
      throw new Error(
        '[USER ERROR] Command line option error.\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 8: Not enough memory.
    if (rtn.exitCode === 8) {
      throw new Error(
        '[MEMORY ERROR] Not enough memory for operation.\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 9: File create error
    if (rtn.exitCode === 9) {
      throw new Error(
        '[CREATE ERROR] Create file error.\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 10: No files matching the specified mask and options were found.
    if (rtn.exitCode === 10) {
      throw new Error(
        'No files matching the specified mask and options were found.\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 11: Wrong password.
    if (rtn.exitCode === 11) {
      throw new Error(
        'Wrong password.'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 255: User stopped the process.
    if (rtn.exitCode === 255) {
      throw new Error(
        '[USER BREAK] User stopped the process.\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    throw new Error(
      '[UNKNOWN EXIT CODE] The testing process probably failed\n'
      + ' ' + insp(rtn)
      + '  at ' + FN + ' (' + MODULE_TITLE + ')'
    );
  }; // }}}

  // zlib.openRar {{{
  /**
   * Opens the RAR file.
   *
   * @example
   * var zlib = Wsh.ZLIB; // Shorthand
   * var path = Wsh.Path;
   * var dirWinRar = path.join(__dirname, '.\\bin\\WinRar');
   *
   * zlib.openRar('D:\\Backup.rar', { dirWinRar: dirWinRar });
   * @function openRar
   * @memberof Wsh.ZLIB
   * @param {string} archive - An archive filepath
   * @param {object} [options] - Optional parameters.
   * @param {string} [options.dirWinRar=DEF_DIR_WINRAR] - A custom directory path of WinRAR.
   * @param {string} [options.winStyle='activeDef']
   * @returns {void}
   */
  zlib.openRar = function (archive, options) {
    var FN = 'zlib.openRar';

    if (!isSolidString(archive)) throwErrNonStr(FN, archive);

    var filePath = path.resolve(archive);

    // Setting the .exe path
    // WinRar.exe or Rar.exe (default:WinRar.exe
    var dirWinRar = obtain(options, 'dirWinRar', DEF_DIR_WINRAR);
    var exeRar = path.join(dirWinRar, EXENAME_WINRAR);

    var winStyle = obtain(options, 'winStyle', CD.windowStyles.activeDef);
    var op = objAdd({ shell: false, winStyle: winStyle }, options);
    var command = srrd(exeRar) + ' ' + srrd(filePath);

    // Executing
    exec(command, op);
  }; // }}}

  // zlib.unrarSync {{{
  /**
   * Extracts files from archiver with Rar.
   *
   * @example
   * var zlib = Wsh.ZLIB; // Shorthand
   * var path = Wsh.Path;
   * var dirWinRar = path.join(__dirname, '.\\bin\\WinRar');
   *
   * var rtn = zlib.unrarSync('D:\\Backup.rar', 'C:\\Temp', {
   *   makesArchiveNameDir: true,
   *   dirWinRar: dirWinRar;
   * });
   * @function unrarSync
   * @memberof Wsh.ZLIB
   * @param {string} archive An archive file path
   * @param {string} [destDir] A output directory path.
   * @param {object} [options] - Optional parameters.
   * @param {string} [options.dirWinRar=DEF_DIR_WINRAR] - A custom directory path of WinRAR.
   * @param {boolean} [options.isGUI=true] - true:WinRar.exe false:Rar.exe
   * @param {string} [options.password]
   * @param {boolean} [options.makesArchiveNameDir=false] Make a new directory with archive file name
   * @param {boolean} [options.outputsLog=false] - Output console logs.
   * @param {boolean} [options.isDryRun=false] - No execute, returns the string of command.
   * @returns {object} - See {@link https://tuckn.net/docs/WshChildProcess/global.html#typeRunSyncReturn|typeRunSyncReturn}.
   */
  zlib.unrarSync = function (archive, destDir, options) {
    var FN = 'zlib.unrarSync';

    if (!isSolidString(archive)) throwErrNonStr(FN, archive);

    var outputsLog = obtain(options, 'outputsLog', false);
    var isDryRun = obtain(options, 'isDryRun', false);

    // Setting the arguments
    _log(outputsLog, 'x: eXtract files with full paths');
    var args = ['x'];

    // Setting the UnRar password
    var password = obtain(options, 'password', null);
    if (!isEmpty(password)) {
      _log(outputsLog, '-p"****": Set the password');
      args.push('-p"' + password + '"');
    }

    // Assign work directory.
    var workingDir = obtain(options, 'workingDir', null);
    if (isSolidString(workingDir)) {
      _log(outputsLog, '-w: Assign work directory to "' + options.workingDir + '"');
      args.push('-w"' + options.workingDir + '"');
    }

    var srcPath = path.resolve(archive);

    // Setting the output directory path.
    // If it is not specified, set the srcPath directory.
    if (isEmpty(destDir)) destDir = path.dirname(srcPath);

    destDir = path.resolve(destDir);

    // Creating the output directory
    if (!fs.existsSync(destDir) && obtain(options, 'makesDestDir', false)) {
      _log(outputsLog, 'Creating the dest directory');
      if (!isDryRun) fse.ensureDirSync(destDir);
    }

    if (obtain(options, 'makesArchiveNameDir', false)) {
      destDir = path.join(destDir, path.parse(srcPath).name);
      _log(outputsLog, 'Creating the Zip name directory');
      if (!isDryRun) fse.ensureDirSync(destDir);
    }

    _log(outputsLog, 'Set output directory. "' + destDir + '"');

    // Combine
    args.push('-y', '-ri0', srcPath, destDir);

    // Executing
    // WinRar.exe or Rar.exe (default:WinRar.exe
    var dirWinRar = obtain(options, 'dirWinRar', DEF_DIR_WINRAR);
    var isGUI = obtain(options, 'isGUI', false);

    var exeRar;
    if (isGUI) {
      exeRar = path.join(dirWinRar, EXENAME_WINRAR);
    } else {
      exeRar = path.join(dirWinRar, EXENAME_RAR);
    }

    var op = objAdd({ isDryRun: isDryRun }, options);

    _log(outputsLog, 'exe path: ' + exeRar);
    _log(outputsLog, 'arguments: ' + insp(args));
    _log(outputsLog, 'options: ' + insp(op));

    var rtn = execFileSync(exeRar, args, op);
    rtn.destinationDir = destDir;

    if (isDryRun) return rtn; // rtn is {string}

    _log(outputsLog, insp(rtn));
    if (isDryRun) return rtn; // rtn is {string}

    // Exit Code Handling
    // @NOTE The GUI app WinRar.exe always returns exitCode = 0?
    // RAR exits with a zero exitCode (0) in case of successful operation.
    // Non-zero exit exitCode indicates some kind of error:
    if (rtn.error) {
      throw new Error('Failed to unRar the files\n'
        + ' ' + rtn.stderr
        + '  at ' + FN + ' (' + MODULE_TITLE + ')');
    }

    // 0: Successful operation.
    if (rtn.exitCode === 0) {
      _log(outputsLog, '[SUCCESS] Successful operation.');
      rtn.error = false;
      return rtn;
    }

    // 1: Non fatal error(s) occurred.
    if (rtn.exitCode === 1) {
      _log(outputsLog, 'Non fatal error(s) occurred.');
      rtn.error = false;
      return rtn;
    }

    // 2: A fatal error occurred.
    if (rtn.exitCode === 2) {
      throw new Error(
        '[FATAL ERROR] A fatal error occurred.\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 3: Invalid checksum. Data is damaged.
    if (rtn.exitCode === 3) {
      throw new Error(
        'Invalid checksum. Data is damaged.'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 4: Attempt to modify an archive locked by 'k' command.
    if (rtn.exitCode === 4) {
      throw new Error(
        ''
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 5: Write error.
    if (rtn.exitCode === 5) {
      throw new Error(
        '[WRITE ERROR] Write to disk error.\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 6: File open error.
    if (rtn.exitCode === 6) {
      throw new Error(
        '[OPEN ERROR] Open file error.\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 7: Wrong command line option.
    if (rtn.exitCode === 7) {
      throw new Error(
        '[USER ERROR] Wrong command line option.\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 8: Not enough memory.
    if (rtn.exitCode === 8) {
      throw new Error(
        '[MEMORY ERROR] Not enough memory for operation.\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 9: File create error
    if (rtn.exitCode === 9) {
      throw new Error(
        '[CREATE ERROR] Create file error.\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 10: No files matching the specified mask and options were found.
    if (rtn.exitCode === 10) {
      throw new Error(
        'No files matching the specified mask and options were found.\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 11: Wrong password.
    if (rtn.exitCode === 11) {
      throw new Error(
        'Wrong password.'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    // 255: User stopped the process.
    if (rtn.exitCode === 255) {
      throw new Error(
        '[USER BREAK] User stopped the process.\n'
        + ' ' + insp(rtn)
        + '  at ' + FN + ' (' + MODULE_TITLE + ')'
      );
    }

    throw new Error(
      '[UNKNOWN EXIT CODE] The unRar process probably failed\n'
      + ' ' + insp(rtn)
      + '  at ' + FN + ' (' + MODULE_TITLE + ')'
    );
  }; // }}}
}());

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