[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/se3-wpkg/sources/wpkg/ -> wpkg-se3.js (source)

   1  var WPKG_VERSION = "1.3.0";
   2  /*******************************************************************************
   3   * 
   4   * WPKG - Windows Packager pour SambaEdu : http://sambaedu.org
   5   * 
   6   * Copyright 2003 Jerry Haltom<br>
   7   * Copyright 2005 Aleksander Wysocki <papopypu (at) op . pl><br>
   8   * Copyright 2005-2006 Tomasz Chmielewski <mangoo (at) wpkg . org><br>
   9   * Copyright 2007-2011 Rainer Meier <r.meier (at) wpkg.org><br>
  10   * Copyright 2007 Jean Le Bail <jean.lebail (at) etab . ac-caen . fr>
  11   * 
  12   * Please report your issues to the list on http://wpkg.org/
  13   */
  14   
  15   /*******************************************************************************
  16   *  Version de wpkg.js modifiée pour SambaEdu3
  17   
  18   * Le support de la remontée des logs d'execution des scripts batch est suspendu suite a des blocages msiexec du wpkg-se3.js adapté en version 1.2 avec la fonction exec() de la version wpkg-se3.js 1.1 de Jean Le Bail.
  19   
  20   PATCH pour SE3 (Olivier Lacroix)
  21   Les modifs par rapport à wpkg.js officiels sont délimitées par // PATCH SE3 et // FIN PATCH SE3 :
  22   1. ajout de lignes dans installPackage() et removePackage() pour classement du menu démarrer
  23   2. modif de la function QueryPackage pour compatibilité avec l'interface se3 actuelle et la lecture des logs d'install 
  24   */
  25  
  26  /**
  27   * Displays command usage.
  28   */
  29  function showUsage() {
  30  var message = "" +
  31  "If you cannot read this since it is displayed within a dialog-window please \n" +
  32  "execute 'cscript wpkg.js /help' on the command line. This will print all \n" +
  33  "messages to the console. \n\n" +
  34  "Command Line Switches \n" +
  35  "===================== \n" +
  36  "Note: These command line switches overwrite parameters within config.xml. For \n" +
  37  "example the /quiet flag overwrites an eventually present quiet parameter within \n" +
  38  "config.xml. \n" +
  39  "Usually you should specify as few parameters as possible since changing \n" +
  40  "config.xml on the server might be much easier than changing all client-side \n" +
  41  "stored parameters. Typically you would use the following command-line in \n" +
  42  "production: \n" +
  43  "    wpkg.js /synchronize \n" +
  44  "\n" +
  45  "Frequently used parameters (package operations, you need to choose one): \n" +
  46  "======================================================================== \n" +
  47  "\n" +
  48  "/install:<package>[,package2[,package3,[...]]] \n" +
  49  "    Install the specified package(s) on the system. \n" +
  50  "\n" +
  51  "/query:<option> \n" +
  52  "    Display a list of packages matching the specified criteria. Valid \n" +
  53  "    options are: \n" +
  54  "\n" +
  55  "    a - Query all packages (includes installed packages and package database). \n" +
  56  "    x - List packages which are not installed but in package database. \n" +
  57  "    i - List all packages which are currently installed. \n" +
  58  "    I - List packages which are about to be installed during synchronization. \n" +
  59  "    u - List packages which are about to be upgraded during synchronization. \n" +
  60  "    d - List packages which are about to be downgraded during synchronization. \n" +
  61  "    r - List packages which are about to be removed during synchronization. \n" +
  62  "    m - List all modifications which would apply during synchronization \n" +
  63  "        (equal to Iudr) \n" +
  64  "    n - List packages which belong to the profile but are not modified during \n" +
  65  "        synchronization. \n" +
  66  "    s - List host attributes from settings (wpkg.xml). \n" +
  67  "    l - List host attributes read from local host. \n" +
  68  "\n" +
  69  "/remove:<package>[,package2[,package3,[...]]] \n" +
  70  "    Remove the specified package(s) from the system. \n" +
  71  "\n" +
  72  "/show:<package> \n" +
  73  "    Display a summary of the specified package, including it's state. \n" +
  74  "\n" +
  75  "/upgrade:<package>[,package2[,package3,[...]]] \n" +
  76  "    Upgrade the already installed package(s) on the system. \n" +
  77  "\n" +
  78  "/synchronize \n" +
  79  "    Synchronize the current program state with the suggested program state \n" +
  80  "    of the specified profile. This is the action that should be called at \n" +
  81  "    system boot time for this program to be useful. \n" +
  82  "\n" +
  83  "/help \n" +
  84  "    Show this message. \n" +
  85  "\n" +
  86  "\n" +
  87  "Optional parameters (usually defined within config.xml): \n" +
  88  "======================================================== \n" +
  89  "\n" +
  90  "/base:<path> \n" +
  91  "    Set the local or remote path to find the xml input files. \n" +
  92  "    Can also be set to a web URL for direct XML retrieval from wpkg_web. \n" +
  93  "\n" +
  94  "/dryrun[:<true>|<false>] \n" +
  95  "    Do not execute any actions. Implies debug mode. \n" +
  96  "\n" +
  97  "/quiet[:<true>|<false>] \n" +
  98  "    Use the event log to record all error/status messages. Use this when \n" +
  99  "    running unattended. \n" +
 100  "\n" +
 101  "/nonotify[:<true>|<false>] \n" +
 102  "    Logged in users are not notified about impending updates. \n" +
 103  "\n" +
 104  "/noreboot[:<true>|<false>] \n" +
 105  "    System does not reboot regardless of need. \n" +
 106  "\n" +
 107  "/quitonerror[:<true>|<false>] \n" +
 108  "    Quit execution if the installation of any package was unsuccessful \n" +
 109  "    (default: install next package and show the error summary). \n" +
 110  "\n" +
 111  "/sendStatus[:<true>|<false>] \n" +
 112  "    Send status messages on STDOUT which can be parsed by calling program to \n" +
 113  "    display status information to the user. \n" +
 114  "\n" +
 115  "/noUpgradeBeforeRemove[:<true>|<false>] \n" +
 116  "    Usually WPKG upgrades a package to the latest available version before it \n" +
 117  "    removes the package. This allows administrators to fix bugs in the package \n" +
 118  "    and assure proper removal.\n" +
 119  "\n" +
 120  "/applymultiple[:<true>|<false>] \n" +
 121  "    Apply profiles of all host nodes with matching attributes. \n" +
 122  "    Only first matching host node is returned if not switched on. \n" +
 123  "    This parameter must be used with caution, it can break existing setup. \n" +
 124  "\n" +
 125  "Rarely used parameters (mainly for testing): \n" +
 126  "============================================ \n" +
 127  "\n" +
 128  "/config:<path> \n" +
 129  "    Path to the configuration file to be used. The path might be absolute \n" +
 130  "    or relative but including the XML file name. This parameter is entirely \n" +
 131  "    OPTIONAL and should normally not be specified at all. \n" +
 132  "    If not specified the configuration will be searched at: \n" +
 133  "    <script-path>\\config.xml \n" +
 134  "    where <script-path> is the directory from which the script is executed. \n" +
 135  "        e.g. '\\\\server\\share\\directory\\config.xml'. \n" +
 136  "        e.g. 'directory\\config.xml'. \n" +
 137  "    You can use environment variables as well as the following expressions: \n" +
 138  "     [HOSTNAME]  Replaced by the executing hostname. \n" +
 139  "     [PROFILE]   Replaced by the concatenated list of profiles applied. \n" +
 140  "\n" +
 141  "/settings:<path> \n" +
 142  "    Path to the settings (local WPKG database) file to be used. The path might \n" +
 143  "    be absolute or relative but including the XML file name. This parameter is \n" +
 144  "    entirely OPTIONAL and should normally not be specified at all. \n" +
 145  "    If not specified the settings file will be searched at: \n" +
 146  "    %SystemRoot%\\system32\\wpkg.xml \n" +
 147  "        e.g. '%SystemRoot%\\system32\\wpkg-custom.xml'. \n" +
 148  "        e.g. '\\\\server\share\directory\\[HOSTNAME].xml'. \n" +
 149  "    If you store the settings file on a share make sure the names is unique! \n" +
 150  "    You can use environment variables as well as the following expressions: \n" +
 151  "     [HOSTNAME]  Replaced by the executing hostname. \n" +
 152  "     [PROFILE]   Replaced by the concatenated list of profiles applied. \n" +
 153  "\n" +
 154  "/queryMode:<mode> \n" +
 155  "    Allows to switch to remote mode where package verification is skipped. \n" +
 156  "     remote: Disable package checks and assume that packages in settings \n" +
 157  "             database are still correctly installed. In remote mode also the \n" +
 158  "             host information is read from the local settings database. \n" +
 159  "     local:  Default setting. Query verifies package status using all checks. \n" +
 160  "    Note: Query mode can only be used with the query switch. \n" +
 161  "\n" +
 162  "/profile:<profile> \n" +
 163  "    Force the name of the profile to use. If not specified, the profile to use \n" +
 164  "    is looked up in hosts.xml. \n" +
 165  "\n" +
 166  "/debug[:<true>|<false>] or /verbose[:<true>|<false>] \n" +
 167  "    Enable debug output. Please note that this parameter only influences \n" +
 168  "    notification and event log output. It does not affect the logging level. \n" +
 169  "    It is possible to get debug-level output even without using this \n" +
 170  "    switch. \n" +
 171  "\n" +
 172  "/force[:<true>|<false>] \n" +
 173  "    When used in conjunction with /synchronize WPKG will ignore the local \n" +
 174  "    settings file (wpkg.xml) and re-build the database with installed \n" +
 175  "    packages. \n" +
 176  "    When used in conjunction with /remove forces removal of the specified \n" +
 177  "    package even if not all packages which depend on the one to be removed \n" +
 178  "    could be removed. \n" +
 179  "\n" +
 180  "/forceinstall[:<true>|<false>] \n" +
 181  "    Force installation over existing packages. \n" +
 182  "\n" +
 183  "/host:<hostname> \n" +
 184  "    Use the specified hostname instead of reading it from the executing host. \n" +
 185  "\n" +
 186  "/os:<hostos> \n" +
 187  "    Use the specified operating system string instead of reading it from the \n" +
 188  "    executing host. \n" +
 189  "\n" +
 190  "/ip:<ip-address-1,ip-address-2,...,ip-address-n> \n" +
 191  "    Use the specified ipaddresses instead of reading it from the executing host. \n" +
 192  "\n" +
 193  "/domainname:<domain> \n" +
 194  "    Name of the windows domain the computer belongs to. \n" +
 195  "    This permit to use group membership even on a non-member workstation. \n" +
 196  "\n" +
 197  "/group:<group-name-1,group-name-2,...,group-name-n>\n" +
 198  "    Name of the group the computer must belongs to instead of reading it from \n" +
 199  "    the executing host. \n" +
 200  "\n" +
 201  "/ignoreCase[:<true>|<false>] \n" +
 202  "    Disable case sensitivity of packages and profiles. Therefore you can \n" +
 203  "    assign the package 'myapp' to a profile while only a package 'MyApp' is \n" +
 204  "    defined within the packages. \n" +
 205  "    Note that this change requires modification of the package/profile/host nodes \n" +
 206  "    read from the XML files. All IDs are converted to lowercase. \n" +
 207  "    Note: This requires converting all profile/package IDs to lowercase. \n" +
 208  "          Therefore you will only see lowercase entries within the log files \n" +
 209  "          and also within the local package database. \n" +
 210  "\n" +
 211  "/logAppend[:<true>|<false>] \n" +
 212  "    Append log files instead of overwriting existing files. \n" +
 213  "    NOTE: You can specify a log file pattern which will create a new file on \n" +
 214  "    each run. Appending logs might cause problems with some log rotation \n" +
 215  "    scripts as well. So use it with caution. \n" +
 216  "\n" +
 217  "/logfilePattern:<pattern> \n" +
 218  "    Pattern for log file naming: \n" +
 219  "    Recognized patterns: \n" +
 220  "     [HOSTNAME]  Replaced by the executing hostname. \n" +
 221  "     [PROFILE]   Replaced by the concatenated list of profiles applied. \n" +
 222  "     [YYYY]      Replaced by year (4 digits). \n" +
 223  "     [MM]        Replaced by month number (2 digits). \n" +
 224  "     [DD]        Replaced by the day of the month (2 digits). \n" +
 225  "     [hh]        Replaced by hour of the day (24h format, 2 digits). \n" +
 226  "     [mm]        Replaced by minutes (2 digits). \n" +
 227  "     [ss]        Replaced by seconds (2 digits). \n" +
 228  "\n" +
 229  "     Examples: \n" +
 230  "        'wpkg-[YYYY]-[MM]-[DD]-[HOSTNAME].log' \n" +
 231  "             results in a name like 'wpkg-2007-11-04-myhost.log' \n" +
 232  "    NOTE: Using [PROFILE] causes all log messages before reading profiles.xml \n" +
 233  "            to be temporarily written to local %TEMP% folder. So they might \n" +
 234  "            appear on the final log file with some delay. \n" +
 235  "\n" +
 236  "/logLevel:[0-31] \n" +
 237  "    Level of detail for log file: \n" +
 238  "    use the following values: \n" +
 239  "    Log level is defined as a bitmask. Just sum up the values of each log \n" +
 240  "    severity you would like to include within the log file and add this value \n" +
 241  "    to your config.xml or specify it at /logLevel:<#>. \n" +
 242  "    Specify 0 to disable logging. \n" +
 243  "      1: log errors only \n" +
 244  "      2: log warnings \n" +
 245  "      4: log information \n" +
 246  "      8: log audit success \n" +
 247  "     16: log audit failure \n" +
 248  "    Example: \n" +
 249  "     31 log everything (1+2+4+8+16=31) \n" +
 250  "     13 log errors, information and audit success (1+4+8=13) \n" +
 251  "      3 log errors and warnings only (1+2=3) \n" +
 252  "    Default is 0 which will suppress all messages printed before log level is \n" +
 253  "    properly initialized by config.xml or by /logLevel:<#> parameter. \n" +
 254  "\n" +
 255  "/log_file_path:<path> \n" +
 256  "    Path where the log files will be stored. Also allows specifying an UNC \n" +
 257  "    path (e.g. '\\server\share\directory'). Make sure the path exists and \n" +
 258  "    that the executing user has write access to it. \n" +
 259  "    NOTE: If you set this parameter within config.xml please note that you \n" +
 260  "            need to escape backslashes: \n" +
 261  "            e.g. '\\\\server\\share\\directory'. \n" +
 262  "\n" +
 263  "/noforcedremove[:<true>|<false>] \n" +
 264  "    Do not remove packages from local package database if remove fails even \n" +
 265  "    if the package does not exist in the package database on the server and \n" +
 266  "    is not referenced within the profile. \n" +
 267  "    By default packages which have been removed from the server package \n" +
 268  "    database and the profile will be uninstalled and then removed \n" +
 269  "    from the local package database even if uninstall failed. \n" +
 270  "    This has been introduced to prevent a package whose uninstall script \n" +
 271  "    fails to repeat its uninstall procedure on each execution without the \n" +
 272  "    possibility to fix the problem since the package (including its \n" +
 273  "    uninstall string) is available on the local machine only. \n" +
 274  "    HINT: If you like the package to stay in the local database (including \n" +
 275  "    uninstall-retry on next boot) just remove it from the profile but do not \n" +
 276  "    completely delete it from the package database. \n" +
 277  "\n" +
 278  "/noremove[:<true>|<false>] \n" +
 279  "    Disable the removal of all packages. If used in conjunction with the \n" +
 280  "    /synchronize parameter it will just add packages but never remove them. \n" +
 281  "    Instead of removing a list of packages which would have been removed \n" +
 282  "    during that session is printed on exit. Packages are not removed from \n" +
 283  "    the local settings database (wpkg.xml). Therefore it will contain a list \n" +
 284  "    of all packages ever installed. \n" +
 285  "    Note that each package which is requested to be removed (manually or by \n" +
 286  "    a synchronization request) will be checked for its state by executing its \n" +
 287  "    included package checks. If the package has been removed manually it will \n" +
 288  "    also be removed from the settings database. This does not apply to packages \n" +
 289  "    which do not specify any checks. Such packages will remain in the local \n" +
 290  "    settings database even if the software has been removed manually. \n" +
 291  "\n" +
 292  "/noDownload[:<true>|<false>] \n" +
 293  "    Ignore all download nodes in packages. \n" +
 294  "    Useful for testing and in case your download targets already exist. \n" +
 295  "\n" +
 296  "/norunningstate[:<true>|<false>] \n" +
 297  "    Do not export the running state to the registry. \n" +
 298  "\n" +
 299  "/rebootcmd:<option> \n" +
 300  "    Use the specified boot command, either with full path or \n" +
 301  "    relative to the location of wpkg.js \n" +
 302  "    Specifying 'special' as option uses tools\psshutdown.exe \n" +
 303  "    from www.sysinternals.com - if it exists - and a notification loop \n";
 304  
 305      alert(message);
 306  }
 307  
 308  /*******************************************************************************
 309   * 
 310   * Global variables
 311   * 
 312   ******************************************************************************/
 313  /** base where to find the XML input files */
 314  var wpkg_base = "";
 315  
 316  /** forces to check for package existence but ignores wpkg.xml */
 317  var force = false;
 318  
 319  /** force installation */
 320  var forceInstall = false;
 321  
 322  /**
 323   * Forced remove of non-existing packages from wpkg.xml even if uninstall
 324   * command fails.
 325   */
 326  var noForcedRemove = false;
 327  
 328  /** defined if script should quit on error */
 329  var quitonerror = false;
 330  
 331  /** Debug output. */
 332  var debug = false;
 333  
 334  /** Dry run */
 335  var dryrun = false;
 336  
 337  /** notify user using net send? */
 338  var nonotify = false;
 339  
 340  /** timeout for user notifications. Works only if msg.exe is available */
 341  var notificationDisplayTime = 10;
 342  
 343  /** set to true to prevent reboot */
 344  var noreboot = false;
 345  
 346  /** stores if package removal should be skipped - see /noremove flag */
 347  var noRemove = false;
 348  
 349  /** allows disabling/enabling of running state export to registry */
 350  var noRunningState = false;
 351  
 352  /** type of reboot command */
 353  var rebootCmd = "standard";
 354  
 355  /** set to true for quiet mode */
 356  var quietDefault = false;
 357  
 358  /** registry path where WPKG stores its running state */
 359  var sRegWPKG_Running = "HKLM\\Software\\WPKG\\running";
 360  
 361  /** configuration file to hold the settings for the script */
 362  var config_file_name = "config.xml";
 363  
 364  /** name of package database file */
 365  var packages_file_name = "packages.xml";
 366  /** name of profiles database file */
 367  var profiles_file_name = "profiles.xml";
 368  /** name of hosts definition database file */
 369  var hosts_file_name = "hosts.xml";
 370  
 371  /**
 372   * specify if manually installed packages should be kept during synchronization
 373   * true: keep manually installed packages false: remove any manually installed
 374   * package which does not belong to the profile
 375   */
 376  var keepManual = true;
 377  
 378  /**
 379   * path where log-files are stored.<br>
 380   * Defaults to "%TEMP%" if empty.
 381   */
 382  var log_file_path = "%TEMP%";
 383  
 384  /** path where downloads are stored, defaults to %TEMP% if not defined */
 385  var downloadDir = "%TEMP%";
 386  
 387  /** timeout for downloads */
 388  var downloadTimeout = 7200;
 389  
 390  /** if set to true logfiles will be appended, otherwise they are overwritten */
 391  var logAppend = false;
 392  
 393  /**
 394   * set to true to enable sending of status messages to STDOUT, regardless of the
 395   * status of /debug
 396   */
 397  var sendStatus = false;
 398  
 399  /**
 400   * Set to true to disable upgrade-before-remove feature by default
 401   */
 402  var noUpgradeBeforeRemove = false;
 403  
 404  /**
 405   * use the following values: Log level is defined as a bitmask. Just add sum up
 406   * the values of each log severity you would like to include within the log file
 407   * and add this value to your config.xml or specify it at /logLevel:<num>.
 408   *
 409   * Specify 0 to disable logging.
 410   * 
 411   * <pre>
 412   * 1: log errors only
 413   * 2 : log warnings
 414   * 4 : log information
 415   * 8 : log audit success
 416   * </pre>
 417   * 
 418   * Example:
 419   * 
 420   * <pre>
 421   * 31 log everything (1+2+4+8+16=32)
 422   * 13 logs errors, information and audit success (1+4+8=13)
 423   *  3 logs errors and warnings only (1+2=3)
 424   * </pre>
 425   * 
 426   * Default is 0 which will suppress all messages printed before log level is
 427   * properly initialized by config.xml or by /logLevel:<#> parameter.
 428   */
 429  var logLevelDefault = 0xFF;
 430  
 431  /**
 432   * var logfile pattern Recognized patterns:
 433   * 
 434   * <pre>
 435   * [HOSTNAME]    replaced by the executing hostname
 436   * [PROFILE]    replaced by the  name
 437   * [YYYY]        replaced by year (4 digits)
 438   * [MM]            replaced by month number (2 digits)
 439   * [DD]            replaced by the day of the month (2 digits)
 440   * [HH]            replaced by hour of the day (24h format, 2 digits)
 441   * [mm]            replaced by minute (2 digits)
 442   * </pre>
 443   * 
 444   * Examples:
 445   * 
 446   * <pre>
 447   * wpkg-[YYYY]-[MM]-[DD]-[HOSTNAME].log
 448   * </pre>
 449   * 
 450   * results in a name like "wpkg-2007-11-04-myhost.log"
 451   */
 452  var logfilePattern = "wpkg-[HOSTNAME].log";
 453  
 454  /** web file name of package database if base is an http url */
 455  var web_packages_file_name = "packages_xml_out.php";
 456  /** web file name of profile database if base is an http url */
 457  var web_profiles_file_name = "profiles_xml_out.php";
 458  /** web file name of hosts database if base is an http url */
 459  var web_hosts_file_name = "hosts_xml_out.php";
 460  
 461  /** name of local settings file */
 462  var settings_file_name = "wpkg.xml";
 463  
 464  /** path to settings file, defaults to system folder if set to null */
 465  var settings_file_path = null;
 466  
 467  /** defines if package/profile IDs are handled case sensitively */
 468  var caseSensitivity = true;
 469  
 470  /** set to true to want to apply profiles of all matching host nodes */
 471  var applyMultiple = false;
 472  
 473  /** globally disable any downloads */
 474  var noDownload = false;
 475  
 476  /**
 477   * Allows to disable insert of host attributes to local settings DB. This is
 478   * handy for testing as the current test-suite compares the local wpkg.xml
 479   * database and the file will look different on all test machines if these
 480   * attribute are present. This setting might be removed if all test-cases
 481   * are adapted.
 482   */
 483  var settingsHostInfo = true;
 484  
 485  /**
 486   * Query mode. In order to "simulate" the result of a query on a system on
 487   * anohter (remote-) system you can set queryMode to "remote". This will
 488   * disable package checks because they will not return the same results on a
 489   * remote system.
 490   */
 491  var queryMode = "local";
 492  
 493  /**
 494   * XML namespaces.
 495   */
 496  var namespaceSettings = "http://www.wpkg.org/settings";
 497  var namespaceHosts = "http://www.wpkg.org/hosts";
 498  var namespaceProfiles = "http://www.wpkg.org/profiles";
 499  var namespacePackages = "http://www.wpkg.org/packages";
 500  var namespaceConfig = "http://www.wpkg.org/config";
 501  
 502  /*******************************************************************************
 503   * 
 504   * Caching variables - used by internal functions.
 505   * 
 506   ******************************************************************************/
 507  
 508  /** file to which the log is written to */
 509  var logfileHandler = null;
 510  
 511  /** path to the log file (corresponds to logfileHandler) */
 512  var logfilePath = null;
 513  
 514  /** store whether log file was opened in append mode */
 515  var logfileAppendMode = logAppend;
 516  
 517  /** stores if the user was notified about start of actions */
 518  var was_notified = false;
 519  
 520  /**
 521   * holds a list of packages which have been installed during this execution this
 522   * is used to prevent dependency packages without checks and always execution to
 523   * be executed several times as well as preventing infinite- loops on recursive
 524   * package installation.
 525   */
 526  var packagesInstalled = new Array();
 527  
 528  /**
 529   * holds a list of packages which have been removed during this execution This
 530   * is used to prevent removing packages multiple times during a session because
 531   * they are referenced as dependencies by multiple other packages.
 532   */
 533  var packagesRemoved = new Array();
 534  
 535  /** host properties used within script */
 536  var hostName = null;
 537  var hostOs = null;
 538  var domainName = null;
 539  var ipAddresses = null;
 540  var hostGroups = null;
 541  var hostArchitecture = null;
 542  var hostAttributes = null;
 543  
 544  /** String representing path where the settings are stored */
 545  var settings_file = null;
 546  
 547  /** Flag whether settings path was processed to replace parameters */
 548  var settings_file_processed = false;
 549  
 550  /** declare variables for data storage */
 551  var packages = null;
 552  var profiles = null;
 553  var hosts = null;
 554  var settings = null;
 555  var config = null;
 556  
 557  /** List of profiles assigned directly to current host */
 558  var applyingProfilesDirect = null;
 559  
 560  /** profiles applying to the current host (including dependencies) */
 561  var applyingProfilesAll = null;
 562  
 563  /** caches the host node entries applying to the current host */
 564  var applyingHostNodes = null;
 565  
 566  /** Packages belonging to current host (package nodes) */
 567  var profilePackageNodes = null;
 568  
 569  /** stores the locale ID (LCID) which applies for the local executing user */
 570  var LCID = null;
 571  
 572  /** stores the locale ID (LCID) which applies for the local machine */
 573  var LCIDOS = null;
 574  
 575  /** caches the language node applying to the current system locale */
 576  var languageNode = null;
 577  
 578  /** declare log level variable */
 579  var logLevel = null;
 580  
 581  /** actual value for log level */
 582  var logLevelValue = 0x03;
 583  
 584  /** buffer to store log entries while no real log file is available */
 585  var logBuffer = null;
 586  
 587  /** flag which is true if log is ready to be initialize */
 588  var logInitReady = false;
 589  
 590  /** flag which is set to true internally during log initialization */
 591  var logInitializing = false;
 592  
 593  /** declare quiet mode variable */
 594  var quiet = null;
 595  
 596  /** current value of quiet operation flag */
 597  var quietMode = quietDefault;
 598  
 599  /** stores if a postponed reboot is scheduled */
 600  var postponedReboot = false;
 601  
 602  /** set to true when a reboot is in progress */
 603  var rebooting = false;
 604  
 605  /** set to true to skip write attempts to event log */
 606  var skipEventLog = false;
 607  
 608  /** set to true to log event log entries to STDOUT (fallback mode) */
 609  var eventLogFallback = false;
 610  
 611  /** holds an array of packages which were not removed due to the /noremove flag */
 612  var skippedRemoveNodes = null;
 613  
 614  /**
 615   * holds status of change: true: System has been changed (package
 616   * installed/removed/updated/downgraded... false: System has not been touched
 617   * (yet)
 618   */
 619  var systemChanged = false;
 620  
 621  /**
 622   * Holds a list of packages which have been manually installed.
 623   */
 624  var manuallyInstalled = null;
 625  
 626  /**
 627   * Marks volatile releases and "inverts" the algorithm that a longer version
 628   * number is newer. For example 1.0RC2 would be newer than 1.0 because it
 629   * appends characters to the version. Using "rc" as a volatile release marker
 630   * the algorithm ignores the appendix and assumes that the string which omits
 631   * the marker is newer.
 632   *
 633   * Resulting in 1.0 to be treated as newer than 1.0RC2.
 634   *
 635   * NOTE: The strings are compared as lower-case. So use lower-case definitions
 636   * only!
 637   */
 638  var volatileReleaseMarkers = ["rc", "i", "m", "alpha", "beta", "pre", "prerelease"];
 639  
 640  /** stores if system is running on a 64-bit OS */
 641  var x64 = null;
 642  
 643  /** Stores variables assigned to host definitions applying to current host */
 644  var hostsVariables = null;
 645  
 646  /** Stores variables from profiles assigned to current hsot */
 647  var profilesVariables = null;
 648  
 649  /***********************************************************************************************************************
 650   * 
 651   * Program execution
 652   * 
 653   **********************************************************************************************************************/
 654  
 655  /**
 656   * Call the main function with arguments while catching all errors and
 657   * forwarding them to the error output.
 658   */
 659  try {
 660      main();
 661  } catch (e) {
 662      // Log error message.
 663      error("Message:      " + e.message + "\n" +
 664              "Description:  " + e.description + "\n" +
 665              "Error number: " + hex(e.number) + "\n" +
 666              "Stack:        " + e.stack  + "\n" +
 667              "Line:         " + e.lineNumber + "\n"
 668              );
 669      notifyUserFail();
 670      // Make sure log is initialized to write the output.
 671      if (logBuffer != null) {
 672          initializeLog();
 673      }
 674      exit(2);
 675  }
 676  
 677  /**
 678   * Main execution method. Actually runs the script
 679   */
 680  function main() {
 681      // Do not open pop-up window while initializing.
 682      setQuiet(true);
 683      
 684      // PATCH SE3 : oubli dans le script officiel.
 685      if (!isNoRunningState()) {
 686          setRunningState("true");
 687      }
 688      // FIN PATCH SE3.
 689  
 690      // Initialize WPKG internals.
 691      initialize();
 692  
 693      // Process command line arguments to determine course of action.
 694      // Get special purpose argument lists.
 695      var argv = getArgv();
 696      var argn = argv.Named;
 697      // var argu = argv.Unnamed;
 698      if (argn.Item("query") != null) {
 699          // Do not log to event log during query.
 700          var eventLogStatus = isSkipEventLog();
 701          setSkipEventLog(true);
 702  
 703          if (getQueryMode() == "remote") {
 704              getSettingHostAttributes();
 705          }
 706          
 707          // Now all parameters are set to build the final log file name
 708          // even if [PROFILE] is used.
 709          // WScript.Echo("Initializing Log");
 710          // WScript.Echo("Buffer: " + logBuffer);
 711          // Flag log file ready for initialization.
 712          logInitReady = true;
 713  
 714          // Do not log to log file during query.
 715          var logValue = getLogLevel();
 716          // setLogLevel(0);
 717  
 718          // Read query argument characters.
 719          var queryRequest = argn.Item("query").split("");
 720          
 721          // Supported arguments:
 722          // a Query all packages.
 723          // x List packages which are not installed but in package database.
 724          // i List all packages which are currently installed.
 725          // I List packages which are about to be installed during synchronization.
 726          // u List packages which are about to be upgraded during synchronization.
 727          // d List packages which are about to be downgraded during synchronization.
 728          // r List packages which are about to be removed during synchronization.
 729          // m List all modifications which would apply during synchronization (equal to Iudr)
 730          // n List packages which belong to the profile but are not modified during synchronization.
 731          // s List host attributes from settings (wpkg.xml).
 732          // l List host attributes read from local host.
 733          var listSyncInstall = false;
 734          var listSyncUpgrade = false;
 735          var listSyncDowngrade = false;
 736          var listSyncRemove = false;
 737          var listSyncUnmodified = false;
 738          for (var i=0; i<queryRequest.length; i++) {
 739              var requestCharacter = queryRequest[i];
 740              switch (requestCharacter) {
 741              case "a":
 742                  queryAllPackages();
 743                  break;
 744  
 745              case "x":
 746                  queryUninstalledPackages();
 747                  break;
 748  
 749              case "i":
 750                  queryInstalledPackages();
 751                  break;
 752  
 753              case "I":
 754                  listSyncInstall = true;
 755                  break;
 756  
 757              case "u":
 758                  listSyncUpgrade = true;
 759                  break;
 760  
 761              case "d":
 762                  listSyncDowngrade = true;
 763                  break;
 764  
 765              case "r":
 766                  listSyncRemove = true;
 767                  break;
 768  
 769              case "n":
 770                  listSyncUnmodified = true;
 771                  break;
 772  
 773              case "m":
 774                  listSyncInstall = true;
 775                  listSyncUpgrade = true;
 776                  listSyncDowngrade = true;
 777                  listSyncRemove = true;
 778                  break;
 779  
 780              case "s":
 781                  queryHostInformationFromSettings();
 782                  break;
 783  
 784              case "l":
 785                  queryHostInformation();
 786                  break;
 787  
 788              default:
 789                  break;
 790              }
 791          }
 792          // Print requested output.
 793          if (listSyncInstall || listSyncUpgrade || listSyncDowngrade || listSyncRemove || listSyncUnmodified) {
 794              queryProfilePackages(listSyncInstall, listSyncUpgrade, listSyncDowngrade, listSyncRemove, listSyncUnmodified);
 795          }
 796  
 797          setSkipEventLog(eventLogStatus);
 798          setLogLevel(logValue);
 799      } else {
 800  
 801          // set profile-based log level (if available)
 802          var profileLogLevel = getProfilesLogLevel();
 803          if (profileLogLevel != null) {
 804              setLogLevel(profileLogLevel);
 805          }
 806  
 807          // Now all parameters are set to build the final log file name
 808          // even if [PROFILE] is used.
 809          // WScript.Echo("Initializing Log");
 810          // WScript.Echo("Buffer: " + logBuffer);
 811          // Flag log file ready for initialization.
 812          logInitReady = true;
 813  
 814          var message;
 815          if(isDebug()) {
 816              var hst = getHostNodes();
 817              message = "Hosts file contains " + hst.length + " hosts:";
 818              for (var iHost = 0; iHost < hst.length; iHost++ ) {
 819                  message += "\n'" + getHostNodeDescription(hst[iHost]) + "'";
 820              }
 821              dinfo(message);
 822  
 823              var settingsPkg = getSettingNodes();
 824              message = "Settings file contains " + settingsPkg.length + " packages:";
 825              for (var iSettings = 0; iSettings < settingsPkg.length; iSettings++) {
 826                  if (settingsPkg[iSettings] != null) {
 827                       message += "\n'" + getPackageID(settingsPkg[iSettings]) + "'";
 828                  }
 829              }
 830              dinfo(message);
 831  
 832              var packageNodes = getPackageNodes();
 833              message = "Packages file contains " + packageNodes.length + " packages:";
 834              for (var iPackage = 0; iPackage < packageNodes.length; iPackage++) {
 835                  if (packageNodes[iPackage] != null) {
 836                       message += "\n'" + getPackageID(packageNodes[iPackage]) + "'";
 837                  }
 838              }
 839              dinfo(message);
 840  
 841              var profileNodes = getProfileNodes();
 842              message = "Profile file contains " + profileNodes.length + " profiles:";
 843              for (var iProfile = 0; iProfile < profileNodes.length; iProfile++) {
 844                  if (profileNodes[iProfile] != null) {
 845                       message += "\n'" + getProfileID(profileNodes[iProfile]) + "'";
 846                  }
 847              }
 848              dinfo(message);
 849  
 850              // Get list of profiles directly assigned to host.
 851              var profiles = getProfileList();
 852              message = "Using profile(s): ";
 853              for (var i=0; i < profiles.length; i++) {
 854                  message += "\n'" + profiles[i] + "'";
 855              }
 856              dinfo(message);
 857          }
 858  
 859          // Check if all referenced profiles are available.
 860          var profileList = getProfileList();
 861          var error = false;
 862          message = "Could not locate referenced profile(s):\n";
 863          for (var iProf = 0; iProf < profileList.length; iProf++) {
 864              var currentProfile = getProfileNode(profileList[iProf]);
 865              if (currentProfile == null) {
 866                  error = true;
 867                  message += profileList[iProf] + "\n";
 868              }
 869          }
 870          if (error) {
 871              throw new Error(message);
 872          }
 873          
 874          if (argn.Item("show") != null) {
 875              var requestedPackageName = argn.Item("show");
 876              // if case sensitive mode is disabled convert package name to lower case
 877              if (!isCaseSensitive()) {
 878                  requestedPackageName = requestedPackageName.toLowerCase();
 879              }
 880              var message = queryPackage(getPackageNodeFromAnywhere(requestedPackageName), null);
 881              info(message);
 882          } else if (argn.Item("install") != null) {
 883              var packageList = argn.Item("install").split(",");
 884              for (var iPackage=0; iPackage < packageList.length; iPackage++) {
 885                  installPackageName(packageList[iPackage], true);
 886              }
 887          } else if (argn.Item("remove") != null) {
 888              var packageList = argn.Item("remove").split(",");
 889              for (var iPackage=0; iPackage < packageList.length; iPackage++) {
 890                  removePackageName(packageList[iPackage]);
 891              }
 892          } else if (argn.Item("upgrade") != null) {
 893              var packageList = argn.Item("upgrade").split(",");
 894              for (var iPackage=0; iPackage < packageList.length; iPackage++) {
 895                  installPackageName(packageList[iPackage], false);
 896              }
 897          } else if (isArgSet(argv, "/synchronize")) {
 898              synchronizeProfile();
 899          } else {
 900              showUsage();
 901              throw new Error("No action specified.");
 902          }
 903      }
 904      exit(0);
 905  }
 906  
 907  
 908  /**
 909   * Adds a sub-node for the given XML node entry.
 910   * 
 911   * @param XMLNode
 912   *            the XML node to add to (e.g. packages or settings)
 913   * @param subNode
 914   *            the node to be added to the XMLNode (for example a package node)
 915   *            NOTE: The node will be cloned before add
 916   * @return Returns true in case of success, returns false if no node could be
 917   *         added
 918   */
 919  function addNode(XMLNode, subNode) {
 920      var returnvalue = false;
 921      var result = XMLNode.appendChild(subNode.cloneNode(true));
 922      if(result != null) {
 923          returnvalue = true;
 924      }
 925      return returnvalue;
 926  }
 927  
 928  
 929  /**
 930   * Adds a package node to the settings XML node. In case a package with the same
 931   * ID already exists it will be replaced.
 932   * 
 933   * @param packageNode
 934   *            the package XML node to add.
 935   * @param saveImmediately
 936   *            Set to true in order to save settings immediately after adding.
 937   *            Settings will not be saved immediately if value is false.
 938   * @return true in case of success, otherwise returns false
 939   */
 940  function addSettingsNode(packageNode, saveImmediately) {
 941      // first remove entry if one already exists
 942  
 943      // get current settings node
 944      var packageID = getPackageID(packageNode);
 945      var settingsNode = getSettingNode(packageID);
 946  
 947      if (settingsNode != null) {
 948          dinfo("Removing currently existing settings node first: '" +
 949                  getPackageName(settingsNode) + "' (" + getPackageID(settingsNode) +
 950                  "), Revision " + getPackageRevision(settingsNode) + ".");
 951          removeSettingsNode(settingsNode, false);
 952      }
 953  
 954      dinfo("Adding settings node: '" +
 955               getPackageName(packageNode) + "' (" + getPackageID(packageNode) +
 956               "), Revision " + getPackageRevision(packageNode) + ".");
 957  
 958      var success = addNode(getSettings(), packageNode);
 959      // save settings if remove was successful
 960      if (success && saveImmediately) {
 961          saveSettings(true);
 962      }
 963      return success;
 964  }
 965  
 966  /**
 967   * Adds a package node to the list of skipped packages during removal process.
 968   * 
 969   * @param packageNode
 970   *            the node which has been skipped during removal
 971   */
 972  function addSkippedRemoveNodes(packageNode) {
 973      var skippedNodes = getSkippedRemoveNodes();
 974      skippedNodes.push(packageNode);
 975  }
 976  
 977  /**
 978   * Appends dependent profile nodes of the specified profile to the specified
 979   * array. Recurses into self to get an entire dependency tree.
 980   */
 981  function appendProfileDependencies(profileArray, profileNode) {
 982      var profileNodes = getProfileDependencies(profileNode);
 983  
 984      // add nodes if they are not yet part of the array
 985      for (var i=0; i < profileNodes.length; i++) {
 986          var currentNode = profileNodes[i];
 987          if(!searchArray(profileArray, currentNode)) {
 988              dinfo("Adding profile dependencies of profile '" +
 989                      getProfileID(profileNode) + "': '" +
 990                      getProfileID(currentNode) + "'");
 991              profileArray.push(currentNode);
 992  
 993              // add dependencies of these profiles as well
 994              appendProfileDependencies(profileArray, currentNode);
 995          } else {
 996              dinfo("Profile '" +
 997                      getProfileID(currentNode) + "' " +
 998                      "already exists in profile dependency tree. Skipping.");
 999          }
1000      }
1001  }
1002  
1003  /**
1004   * Evaluates all checks in the check nodes array and returns its result.
1005   * @param checkNodes Array of XML <check /> nodes to be evaluated.
1006   * @returns {Boolean} true if all checks are true. False if at least one failed.
1007   */
1008  function checkAll(checkNodes) {
1009      if (checkNodes == null) {
1010          return true;
1011      }
1012  
1013      // Initialize return value.
1014      var result = true;
1015  
1016      // Save environment.
1017      var previousEnv = getEnv();
1018      
1019      // Loop over every condition check.
1020      // If all are successful, we consider package as installed.
1021      for (var i = 0; i < checkNodes.length; i++) {
1022          try {
1023              if (!checkCondition(checkNodes[i])) {
1024                  result = false;
1025                  break;
1026              }
1027          } catch (err) {
1028              message = "Error evaluating check: " + err.description;
1029              if (isQuitOnError()) {
1030                  throw new Error(message);
1031              } else {
1032                  error(message);
1033                  result = false;
1034                  break;
1035              }
1036          } finally {
1037              // Restore environment.
1038              loadEnv(previousEnv);
1039          }
1040      }
1041      return result;
1042  }
1043  
1044  /**
1045   * Checks for the success of a check condition for a package.
1046   * 
1047   * @param checkNode
1048   *            XML check node to be evaluated
1049   * @throws Error
1050   *             Throws error in case of invalid XML node definition
1051   */
1052  function checkCondition(checkNode) {
1053      var shell = new ActiveXObject("WScript.Shell");
1054  
1055      // get attributes of check
1056      var checkType = checkNode.getAttribute("type");
1057      var checkCond = checkNode.getAttribute("condition");
1058      var checkPath = checkNode.getAttribute("path");
1059      var checkValue = checkNode.getAttribute("value");
1060  
1061      // In remote mode try to verify the check using cached check results in
1062      // settings database.
1063      if (getQueryMode() == "remote") {
1064          // Logical checks shall be evaluated as usual.
1065          // Only look for previous check results for other types of checks.
1066          if (checkType != "logical") {
1067              var result = getSettingsCheckResult(checkNode);
1068              if (result == null) {
1069                  error("Result of check of type '" + checkType + "' with condition '" +
1070                          checkCond + "', path '" + checkPath + "' and value '" +
1071                          checkValue + "' is missing in settings database. " +
1072                          "Trying to evaluate locally. Results might be inaccurate");
1073              } else {
1074                  return result;
1075              }
1076          }
1077      }
1078      
1079      // Sanity check: must have Type set here.
1080      if (checkType == null) {
1081          throw new Error("Check Type is null - this is not permitted. Perhaps a typo? " +
1082                          "To help find it, here are the other pieces of information: " +
1083                          "condition='" + checkCond + "', path='" + checkPath +
1084                          "', value='" + checkValue + "'.");
1085      }
1086  
1087      // Initialize return value;
1088      var returnValue = false;
1089      
1090      // get expanded values for path and value used by some checks
1091      var checkPathExpanded = null;
1092      if (checkPath != null) {
1093          checkPathExpanded = shell.ExpandEnvironmentStrings(checkPath);
1094      }
1095      var checkValueExpanded = null;
1096      if (checkValue != null) {
1097          checkValueExpanded = shell.ExpandEnvironmentStrings(checkValue);
1098      }
1099  
1100      switch(checkType) {
1101      // check type: registry
1102      case "registry":
1103          // Sanity check: must have Cond and Path set for all registry checks.
1104          if ((checkCond == null) || (checkPath == null)) {
1105              throw new Error("Condition and / or path is null for a registry check. Perhaps " +
1106                              "a typo? To help find it, here are the other pieces of information: " +
1107                              "condition='" + checkCond + "', path='" + checkPath +
1108                              "', value='" + checkValue + "'.");
1109          }
1110  
1111          // branch on check condition
1112          switch (checkCond) {
1113          case "exists":
1114              if (getRegistryValue(checkPath) != null) {
1115                  // Some debugging information.
1116                  dinfo("The registry path '" + checkPath + "' exists: the check was successful.");
1117                  returnValue = true;
1118              } else if (getRegistryValue(checkPathExpanded) != null) {
1119                  dinfo("The expanded registry path '" + checkPathExpanded + "' exists: the check was successful.");
1120                  returnValue = true;
1121              } else {
1122                  // path does not exist
1123                  dinfo("Neither the registry path '" + checkPath + "' nor its expanded value of '" +
1124                          checkPathExpanded + "' exist: the check failed.");
1125                  returnValue = false;
1126              }
1127              break;
1128  
1129          case "equals":
1130              // read registry value and convert it to string in order to compare
1131              // to supplied
1132              // string within the 'value' attribute
1133              var readValue = getRegistryValue(checkPath);
1134  
1135              // check if value is eventually null (non-existing)
1136              if (readValue == null) {
1137                  // the path might have to be expanded
1138                  readValue = getRegistryValue(checkPathExpanded);
1139                  if (readValue == null) {
1140                      dinfo("The registry path '" + checkPath + "' did not exist. Check failed.");
1141                      returnValue = false;
1142                      break;
1143                  }
1144                  dinfo("The expanded registry path '" + checkPathExpanded + "' could be read.");
1145              } else {
1146                  dinfo("The registry path '" + checkPath+ "' could be read.");
1147              }
1148  
1149              // try treating the value as array
1150              var registyValue = "";
1151              try {
1152                  var readArray = readValue.toArray();
1153                  dinfo("The registry value received is an array, concatenating values for comparison.");
1154                  for (var iRegKey=0; iRegKey<readArray.length; iRegKey++) {
1155                      registyValue = registyValue + readArray[iRegKey] + "";
1156                      if ( (iRegKey+1) < readArray.length) {
1157                          registyValue += "\n";
1158                      }
1159                  }
1160              } catch(notAnArray) {
1161                  dinfo("The registry value received is a scalar value.");
1162                  registyValue = readValue + "";
1163              }
1164  
1165              if (registyValue == checkValue) {
1166                  // Some debugging information.
1167                  dinfo("The registry path '" + checkPath + "' contained the correct value: '" +
1168                          checkValue + "'. The check was successful.");
1169                  returnValue = true;
1170              } else {
1171                  // Try if expanded value matches (case-insensitive).
1172                  if (registyValue.toLowerCase() == checkValueExpanded.toLowerCase()) {
1173                      dinfo("The registry path '" + checkPath + "' contained the expanded value: '" +
1174                              checkValueExpanded + "'. The check was successful.");
1175                      returnValue = true;
1176                  } else {
1177                      dinfo("The registry path '" + checkPath + "' did not contain the value: '" +
1178                               checkValue + "'. Instead it contained '" + registyValue + "'. the check failed.");
1179                      returnValue = false;
1180                  }
1181              }
1182              break;
1183  
1184          default:
1185              throw new Error("Check condition " + checkCond + " unknown " +
1186                              "for type registry.");
1187              break;
1188          }
1189          
1190          // The result of Registry checks shall be stored in local settings node.
1191          addSettingsCheckResult(checkNode, returnValue);
1192          
1193          break;
1194  
1195      // check type: file
1196      case "file":
1197          // Sanity check: must have Cond and Path set for all file checks.
1198          if ((checkCond == null) ||
1199              (checkPath == null)) {
1200              throw new Error("Condition and / or path is null for a file check. Perhaps " +
1201                              "a typo? To help find it, here are the other pieces of information: " +
1202                              "condition='" + checkCond + "', path='" + checkPath +
1203                              "', value='" + checkValue + "'");
1204          }
1205  
1206          // expand environment variables
1207          // use only expanded value here
1208          checkPath = checkPathExpanded;
1209  
1210          if (checkCond == "exists") {
1211              var fso = new ActiveXObject("Scripting.FileSystemObject");
1212              if (fso.FileExists(checkPath)) {
1213                  // Some debugging information.
1214                  dinfo("The path '" + checkPath + "' exists and is a file: the test was successful.");
1215                  returnValue = true;
1216              } else if (fso.FolderExists(checkPath)) {
1217                  // Some debugging information.
1218                  dinfo("The path '" + checkPath + "' exists and is a folder: the test was successful.");
1219                  returnValue = true;
1220              } else {
1221                  // Some debugging information.
1222                  dinfo("The path '" + checkPath + "' does not exist: the test failed.");
1223                  returnValue = false;
1224              }
1225  
1226          } else if (checkCond == "sizeequals") {
1227              // sanity check: must have Value set for a size check.
1228              if (checkValue == null) {
1229                  throw new Error("Value is null for a file sizeequals check. Perhaps " +
1230                                  "a typo? To help find it, here are the other pieces of information: " +
1231                                  "condition='" + checkCond +
1232                                  "', path='" + checkPath +
1233                                  "', value='" + checkValue + "'.");
1234              }
1235  
1236              var filesize = getFileSize(checkPath);
1237              if (filesize == checkValueExpanded) {
1238                  dinfo("The file '" + checkPath + "' has size " + filesize + ": the test was successful.");
1239                  returnValue = true;
1240              } else {
1241                  dinfo("The file '" + checkPath + "' has size " + filesize + " - wanted " +
1242                          checkValueExpanded + ": the test fails.");
1243                  returnValue = false;
1244              }
1245          } else if (checkCond.substring(0,7) == "version") {
1246              // Sanity check: Must have a value set for version check.
1247              if (checkValue == null) {
1248                  throw new Error("Value is null for a file version check. Perhaps " +
1249                                  "a type? To help find it, here are the other pieces of information: " +
1250                                  "condition='" + checkCond + "', path='" + checkPath +
1251                                  "', value='" + checkValue + "'.");
1252              } // if checkValue == null
1253  
1254              var fileVersion = getFileVersion(checkPath);
1255  
1256              if (fileVersion == null || fileVersion == "") {
1257                   // no file version could be obtained
1258                   dinfo("Unable to find the file version for '" + checkPath + "'.");
1259                   returnValue = false;
1260              } else {
1261  
1262                  var fileVersionCompare = versionCompare(fileVersion, checkValueExpanded);
1263                  dinfo ("Checking file version " + fileVersion + " is " + checkCond +
1264                           " (than) " + checkValueExpanded + " - got result " + fileVersionCompare + ".");
1265      
1266                  var fileVersionCompResult = false;
1267                  switch (checkCond) {
1268                      case "versionsmallerthan":
1269                          if (fileVersionCompare < 0) {
1270                              fileVersionCompResult = true;
1271                          }
1272                          break;
1273                      case "versionlessorequal":
1274                          if (fileVersionCompare <= 0) {
1275                              fileVersionCompResult = true;
1276                          }
1277                          break;
1278                      case "versionequalto":
1279                          if (fileVersionCompare == 0) {
1280                              fileVersionCompResult = true;
1281                          }
1282                          break;
1283                      case "versiongreaterorequal":
1284                          if (fileVersionCompare >= 0) {
1285                              fileVersionCompResult = true;
1286                          }
1287                          break;
1288                      case "versiongreaterthan":
1289                          if (fileVersionCompare > 0) {
1290                              fileVersionCompResult = true;
1291                          }
1292                          break;
1293                      default:
1294                          error("Unknown operation on file versions : " + checkCond);
1295                          fileVersionCompResult = false;
1296                          break;
1297                  }
1298      
1299                  dinfo("File version check for file '" + checkPath + "' returned " +
1300                      fileVersionCompResult + " for operation type " + checkCond + ".");
1301                  returnValue = fileVersionCompResult;
1302              }
1303  
1304          } else if (checkCond.substring(0,4) == "date") {
1305              var fileDate = null;
1306              var comparisonDesc = "";
1307              var dateType = checkCond.substring(4,10);
1308              // Evaluate if modification date shall be checked.
1309              if (dateType == "modify") {
1310                  dinfo("Checking file modification date.");
1311                  // Evaluate file modification date.
1312                  fileDate = getFileDateModification(checkPath);
1313                  comparisonDesc = "Modification";
1314              } else if (dateType == "create") {
1315                  dinfo("Checking file creation date.");
1316                  // Evaluate file creation date.
1317                  fileDate = getFileDateCreation(checkPath);
1318                  comparisonDesc = "Creation";
1319              } else if (dateType == "access") {
1320                  dinfo("Checking file access date.");
1321                  // Evaluate file access date.
1322                  fileDate = getFileDateLastAccess(checkPath);
1323                  comparisonDesc = "Access";
1324              } else {
1325                  throw new Error ("Invalid file date comparison type: " + checkCond + ".");
1326              }
1327  
1328              // If file date could not be read: Comparison failed.
1329              if (fileDate == null) {
1330                  dinfo("File modification date could not be read, check failed.");
1331                  returnValue = false;
1332                  break;
1333              }
1334              // Make sure file date is in Date() format.
1335              fileDate = new Date(fileDate);
1336              
1337              // Parse comparison date.
1338              var firstChar = checkValueExpanded.substring(0,1);
1339              var comparisonDate = null;
1340              var comparisonType = "string";
1341              if (firstChar == "+" || firstChar == "-") {
1342                  // Relative date. Create time offset in minutes.
1343                  dinfo("Reading relative comparison date: " + checkValueExpanded + " minutes.");
1344                  var timeOffset = parseInt(checkValueExpanded) * 1000 * 60;
1345                  var now = new Date();
1346                  comparisonDate = new Date(now.getTime() + timeOffset);
1347              } else if (firstChar == "@" ) {
1348                  // Remember type of comparison.
1349                  comparisonType = "file";
1350                  // Evaluate date of reference file.
1351                  var filePath = checkValueExpanded.substring(1);
1352                  if (dateType == "modify") {
1353                      dinfo("Reading file modification date of reference file '" + filePath + "'.");
1354                      // Evaluate file modification date.
1355                      comparisonDate = getFileDateModification(filePath);
1356                  } else if (dateType == "create") {
1357                      dinfo("Reading file creation date of reference file '" + filePath + "'.");
1358                      // Evaluate file creation date.
1359                      comparisonDate = getFileDateCreation(filePath);
1360                  } else if (dateType == "access") {
1361                      dinfo("Reading file access date of reference file '" + filePath + "'.");
1362                      // Evaluate file access date.
1363                      comparisonDate = getFileDateLastAccess(filePath);
1364                  }
1365                  // If comparison date could not be read then comparison failed.
1366                  if (comparisonDate == null) {
1367                      dinfo("File comparison date could not be read, check failed.");
1368                      returnValue = false;
1369                      break;
1370                  }
1371                  // Make sure comparison date is in Date() format.
1372                  comparisonDate = new Date(comparisonDate);
1373  
1374              } else {
1375                  dinfo("Reading comparison date: " + checkValueExpanded + ".");
1376                  switch (checkValueExpanded) {
1377                  case "yesterday":
1378                      // Relative date. Create time offset of one day.
1379                      var timeOffset = -1000 * 60 * 60 * 24;
1380                      var now = new Date();
1381                      comparisonDate = new Date(now.getTime() + timeOffset);
1382                      break;
1383  
1384                  case "last-week":
1385                      // Relative date. Create time offset of one week ago.
1386                      var timeOffset = -1000 * 60 * 60 * 24 * 7;
1387                      var now = new Date();
1388                      comparisonDate = new Date(now.getTime() + timeOffset);
1389                      break;
1390  
1391                  case "last-month":
1392                      // Relative date. Create time offset of one month ago.
1393                      var timeOffset = -1000 * 60 * 60 * 24 * 30;
1394                      var now = new Date();
1395                      comparisonDate = new Date(now.getTime() + timeOffset);
1396                      break;
1397  
1398                  case "last-year":
1399                      // Relative date. Create time offset of one year ago.
1400                      var timeOffset = -1000 * 60 * 60 * 24 * 365;
1401                      var now = new Date();
1402                      comparisonDate = new Date(now.getTime() + timeOffset);
1403                      break;
1404  
1405                  default:
1406                      // Date is supposed to be in ISO format.
1407                      comparisonDate = parseISODate(checkValueExpanded, false);
1408                      break;
1409                  }
1410              }
1411              // Check whether comparison date has been evaluated properly.
1412              if (comparisonDate == null) {
1413                  throw new Error ("Unable to evaluate date from value '" + checkValueExpanded + "'.");
1414              }
1415  
1416              var success = false;
1417  
1418              // Get file date of file specified in path.
1419              var comparison = checkCond.substring(10);
1420  
1421              var comparisonCond = "";
1422              switch (comparison) {
1423              case "olderthan":
1424                  comparisonCond = "older than";
1425                  if (fileDate.getTime() < comparisonDate.getTime()) {
1426                      success =  true;
1427                  } else {
1428                      success = false;
1429                  }
1430                  break;
1431  
1432              case "equalto":
1433                  var fileDateCompare = new Date(fileDate);
1434                  // Reduce accuracy to milliseconds for equal comparison when comparing to user string.
1435                  if (comparisonType != "file") {
1436                      fileDateCompare.setMilliseconds(0);
1437                      comparisonDate.setMilliseconds(0);
1438                  }
1439                  comparisonCond = "equal to";
1440                  if (fileDateCompare.getTime() == comparisonDate.getTime()) {
1441                      success =  true;
1442                  } else {
1443                      success = false;
1444                  }
1445                  break;
1446  
1447              case "newerthan":
1448                  comparisonCond = "newer than";
1449                  if (fileDate.getTime() > comparisonDate.getTime()) {
1450                      success =  true;
1451                  } else {
1452                      success = false;
1453                  }
1454                  break;
1455                  
1456              default:
1457                  throw new Error ("Invalid file date comparison parameter: '" + checkCond + "'.");
1458                  break;
1459              }
1460  
1461              if (success) {
1462                  dinfo(comparisonDesc + " date of file '" + checkPath + "' is " + fileDate +
1463                          " which is " + comparisonCond + " the comparison date " +
1464                          comparisonDate + " check succeeded.");
1465              } else {
1466                  dinfo(comparisonDesc + " date of file '" + checkPath + "' is " + fileDate +
1467                          " which isn't " + comparisonCond + " the comparison date " +
1468                          comparisonDate + " check failed.");
1469              }
1470              returnValue = success;
1471          } else {
1472              throw new Error("Check condition " + checkCond + " unknown for " +
1473                              "type file.");
1474          }
1475  
1476          // The result of Registry checks shall be stored in local settings node.
1477          addSettingsCheckResult(checkNode, returnValue);
1478          
1479          break;
1480  
1481      // check type: uninstall
1482      case "uninstall":
1483          // Sanity check: must have Cond and Path set for all uninstall checks.
1484          if ((checkCond == null) ||
1485              (checkPath == null)) {
1486              throw new Error("Condition and / or path is null for an uninstall check. Perhaps " +
1487                              "a typo? To help find it, here are the other pieces of information: " +
1488                              "condition='" + checkCond +
1489                              "', path='" + checkPath + "'.");
1490          }
1491          var uninstallLocations = scanUninstallKeys(checkPath);
1492          // If expanded path is different to path read these keys too.
1493          if (checkPath != checkPathExpanded) {
1494              var uninstallLocationsExpanded = scanUninstallKeys(checkPathExpanded);
1495              for (var i=0; i < uninstallLocationsExpanded.length; i++) {
1496                  uninstallLocations.push(uninstallLocationsExpanded[i]);
1497              }
1498          }
1499  
1500          if (checkCond == "exists") {
1501              if (uninstallLocations.length > 0) {
1502                  dinfo("Uninstall entry for " + checkPath + " was found: test successful.");
1503                  returnValue = true;
1504              } else {
1505                  dinfo("Uninstall entry for " + checkPath + " missing: test failed.");
1506                  returnValue = false;
1507              }
1508          } else if (checkCond.substring(0,7) == "version") {
1509              // check versions of all installed instances
1510              // for version checks we need a value
1511              if (checkValue == null) {
1512                  throw new Error ("Uninstall entry version check has been specified but no" +
1513                          "'value' is defined. Please add a 'value=<version>' attribute.");
1514              }
1515  
1516              if (uninstallLocations.length <= 0) {
1517                  dinfo("No uninstall entry for '" + checkPath + "' found. " +
1518                          "Version comparison check failed.");
1519                  returnValue = false;
1520              } else {
1521      
1522                  var uninstallCheckResult = true;
1523                  for (var iUninstKey=0; iUninstKey < uninstallLocations.length; iUninstKey++) {
1524                      var uninstallValue = getRegistryValue(uninstallLocations[iUninstKey] + "\\DisplayVersion");
1525      
1526                      dinfo("Found version of '" + checkPath + "' at " + uninstallLocations[iUninstKey] +
1527                          ": " + uninstallValue + "\n" + "Comparing to expected version: " + checkValue + ".");
1528      
1529                      // check if valid version value was returned
1530                      if (uninstallValue == null || uninstallValue == "") {
1531                          error("Check condition '" + checkCond + "' cannot be executed" +
1532                              " since no version information is available for '" + checkPath + "'" +
1533                              " at " + uninstallLocations[iUninstKey] + ".");
1534                          uninstallCheckResult = false;
1535                          break;
1536                      } else {
1537      
1538                          var uninstallVersionCompare = versionCompare(uninstallValue, checkValueExpanded);
1539                          dinfo ("Comparing uninstall version '" + uninstallValue + "' to expected version '" +
1540                                  checkValueExpanded + "' using condition '" + checkCond  + "' returned " + uninstallVersionCompare + ".");
1541          
1542                          var uninstallVersionCompResult = false;
1543                          switch (checkCond) {
1544                              case "versionsmallerthan":
1545                                  if (uninstallVersionCompare < 0) {
1546                                      uninstallVersionCompResult = true;
1547                                  }
1548                                  break;
1549                              case "versionlessorequal":
1550                                  if (uninstallVersionCompare <= 0) {
1551                                      uninstallVersionCompResult = true;
1552                                  }
1553                                  break;
1554                               case "versionequalto":
1555                                  if (uninstallVersionCompare == 0) {
1556                                      uninstallVersionCompResult = true;
1557                                  }
1558                                  break;
1559                              case "versiongreaterorequal":
1560                                  if (uninstallVersionCompare >= 0) {
1561                                      uninstallVersionCompResult = true;
1562                                  }
1563                                  break;
1564                               case "versiongreaterthan":
1565                                  if (uninstallVersionCompare > 0) {
1566                                      uninstallVersionCompResult = true;
1567                                  }
1568                                  break;
1569                              default:
1570                                  error("Unknown operation on uninstall version check: " + checkCond + ".");
1571                                  uninstallVersionCompResult = false;
1572                                  break;
1573                          }
1574          
1575                          dinfo("Uninstall version check for package '" + checkPath + "' returned " +
1576                              uninstallVersionCompResult + " for operation type " + checkCond + ".");
1577          
1578                          // in case the current entry does not match the condition,
1579                          // immediately return
1580                          // else the next uninstall entry might be checked
1581                          if (uninstallVersionCompResult == false) {
1582                              uninstallCheckResult = false;
1583                              break;
1584                          }
1585                      }
1586                  }
1587                  // If all checks succeeded, set return value to true.
1588                  if (uninstallCheckResult) {
1589                      returnValue = true;
1590                  }
1591              }
1592          } else {
1593              throw new Error("Check condition " + checkCond + " unknown for " +
1594                              "type uninstall.");
1595          }
1596  
1597          // The result of Registry checks shall be stored in local settings node.
1598          addSettingsCheckResult(checkNode, returnValue);
1599          
1600          break;
1601  
1602      // check type: execution
1603      case "execute":
1604          // check if path to script is given
1605          if (checkPath == null) {
1606              throw new Error("No path is specified for execute check!");
1607          }
1608          if (checkCond == null) {
1609              dinfo("No execute condition specified, assuming 'exitcodeequalto'.");
1610              checkCond = "exitcodeequalto";
1611          }
1612          if (checkValueExpanded == null || checkValueExpanded == "") {
1613              dinfo("No execute value specified, assuming '0'.");
1614              checkValueExpanded = 0;
1615          } else {
1616              checkValueExpanded = parseInt(checkValueExpanded);
1617              if(isNaN(checkValueExpanded) == true) {
1618                  checkValueExpanded = 0;
1619              }
1620          }
1621  
1622          // use expanded path only
1623          checkPath = checkPathExpanded;
1624          // execute and catch return code
1625          var exitCode = exec(checkPath, 3600, null);
1626  
1627          var executeResult = false;
1628          switch (checkCond) {
1629              case "exitcodesmallerthan":
1630                  if (exitCode < checkValueExpanded) {
1631                      executeResult = true;
1632                  }
1633                  break;
1634              case "exitcodelessorequal":
1635                  if (exitCode <= checkValueExpanded) {
1636                      executeResult = true;
1637                  }
1638                  break;
1639               case "exitcodeequalto":
1640                  if (exitCode == checkValueExpanded) {
1641                      executeResult = true;
1642                  }
1643                  break;
1644              case "exitcodegreaterorequal":
1645                  if (exitCode >= checkValueExpanded) {
1646                      executeResult = true;
1647                  }
1648                  break;
1649               case "exitcodegreaterthan":
1650                  if (exitCode > checkValueExpanded) {
1651                      executeResult = true;
1652                  }
1653                  break;
1654              default:
1655                  dinfo("Invalid execute condition specified '" + checkCond
1656                      + "', check failed.");
1657                  executeResult = false;
1658                  break;
1659          }
1660  
1661          dinfo("Execute check for program '" + checkPath + "' returned '" +
1662                  exitCode + "'. Evaluating condition '" + checkCond +
1663                  "' revealed " + executeResult + " when comparing to expected" +
1664                  " value of '" + checkValueExpanded + "'.");
1665          returnValue = executeResult;
1666          break;
1667  
1668      // check type: logical
1669      case "logical":
1670  
1671          // check if logical condition is set
1672          if (checkCond == null) {
1673              throw new Error("Condition is null for a logical check.");
1674          }
1675  
1676          var subcheckNodes = getChecks(checkNode);
1677  
1678          switch (checkCond) {
1679          case "not":
1680              var checkResult = false;
1681              for (var iNotNodes=0; iNotNodes < subcheckNodes.length; iNotNodes++) {
1682                  // check if one of the subchecks return false
1683                  if (!checkCondition(subcheckNodes[iNotNodes])) {
1684                      checkResult = true;
1685                      break;
1686                  }
1687              }
1688              if (checkResult) {
1689                  dinfo("Result of logical 'NOT' check is true.");
1690              } else {
1691                  dinfo("Result of logical 'NOT' check is false.");
1692              }
1693              returnValue = checkResult;
1694              break;
1695  
1696          case "and":
1697              var checkResult = true;
1698              for (var iAndNodes = 0; iAndNodes < subcheckNodes.length; iAndNodes++) {
1699                  // check if one of the subchecks return false
1700                  if (!checkCondition(subcheckNodes[iAndNodes])) {
1701                      checkResult = false;
1702                      break;
1703                  }
1704              }
1705              if (checkResult) {
1706                  dinfo("Result of logical 'AND' check is true.");
1707              } else {
1708                  dinfo("Result of logical 'AND' check is false.");
1709              }
1710              returnValue = checkResult;
1711              break;
1712  
1713          case "or":
1714              // check if one of the sub-checks returns true
1715              var checkResult = false;
1716              for (var iOrNodes = 0; iOrNodes < subcheckNodes.length; iOrNodes++) {
1717                  if (checkCondition(subcheckNodes[iOrNodes])) {
1718                      checkResult = true;
1719                      break;
1720                  }
1721              }
1722              if (checkResult) {
1723                  dinfo("Result of logical 'OR' check is true.");
1724              } else {
1725                  dinfo("Result of logical 'OR' check is false");
1726              }
1727              returnValue = checkResult;
1728              break;
1729  
1730          case "atleast":
1731              if (checkValue == null) {
1732                  throw new Error("Check condition logical 'atleast' requires a value.");
1733              }
1734  
1735              // count number of checks which return true
1736              var numAtLeastNodes=0;
1737              var checkResult = false;
1738              for (var iAtLeastNodes = 0; iAtLeastNodes < subcheckNodes.length; iAtLeastNodes++) {
1739                  if (checkCondition(subcheckNodes[iAtLeastNodes])) {
1740                      numAtLeastNodes++;
1741                  }
1742                  // check if at least x checks revealed true
1743                  if (numAtLeastNodes >= checkValue) {
1744                      checkResult = true;
1745                      break;
1746                  }
1747              }
1748              if (checkResult) {
1749                  dinfo("Result of logical 'AT LEAST' check is true.");
1750              } else {
1751                  dinfo("Result of logical 'AT LEAST' check is false.");
1752              }
1753              returnValue = checkResult;
1754              break;
1755  
1756          case "atmost":
1757              // check if maximum x checks return true
1758              var checkResult = true;
1759              var numAtMostNodes = 0;
1760              for (var iAtMostNodes = 0; iAtMostNodes < subcheckNodes.length; iAtMostNodes++) {
1761                  if (checkCondition(subcheckNodes[iAtMostNodes])) {
1762                      numAtMostNodes++;
1763                  }
1764                  if (numAtMostNodes > checkValue) {
1765                      checkResult = false;
1766                      break;
1767                  }
1768              }
1769              if (checkResult) {
1770                  dinfo("Result of logical 'AT MOST' check is true.");
1771              } else {
1772                  dinfo("Result of logical 'AT MOST' check is false.");
1773              }
1774              returnValue = checkResult;
1775              break;
1776  
1777          default:
1778              throw new Error("Check condition " + checkCond + " unknown for " +
1779              "type logical.");
1780              break;
1781          }
1782          
1783          // Logical checks shall not be added to local settings node.
1784          break;
1785  
1786      // Check type: host
1787      case "host":
1788          // check if logical condition is set
1789          if (checkCond == null) {
1790              throw new Error("Condition is null for a host check.");
1791          }
1792          if (checkValueExpanded == null) {
1793              throw new Error("Value is null for a host check.");
1794          }
1795  
1796          // Verify if the host check matches current host.
1797          returnValue = checkHostAttribute(checkCond, checkValueExpanded);
1798  
1799          // The result of Registry checks shall be stored in local settings node.
1800          addSettingsCheckResult(checkNode, returnValue);
1801  
1802          break;
1803  
1804      // no such check type
1805      default:
1806          throw new Error("Check condition type " + checkType + " unknown.");
1807          break;
1808      }
1809  
1810      return returnValue;
1811  }
1812  
1813  /**
1814   * Checks whether the specified host attribute matches the expression passed as
1815   * argument.
1816   * 
1817   * @param attributeName
1818   *              Name of host attribute to match. See getHostInformation()
1819   *              function for valid host attributes.
1820   * @param expression
1821   *              Regular expression (or list for certain attributes) to use for
1822   *              matching.
1823   * @returns {Boolean} True if attribute matches the expression.
1824   */
1825  function checkHostAttribute(attributeName, expression) {
1826      // Terminate if attribute name is not specified.
1827      if (attributeName == null) {
1828          error("Host attribute matching failed. No attribute name specified.");
1829          return false;
1830      }
1831      var hostAttribute = attributeName;
1832  
1833      // Terminate if expression is not specified.
1834      if (expression == null) {
1835          error("Host attribute matching for attribute '" + hostAttribute + "' failed. No expression specified.");
1836          return false;
1837      }
1838      // Expand environment variables in expressions.
1839      var checkExpression = new ActiveXObject("WScript.Shell").ExpandEnvironmentStrings(expression);
1840  
1841      // Initialize return value.
1842      var returnValue = false;
1843  
1844      // Fetch current host attributes.
1845      var globalHostInformation = getHostInformation();
1846  
1847      // Add "environment" key since we want to support environment matching too.
1848      var hostInformation = new ActiveXObject("Scripting.Dictionary");
1849      var keys = globalHostInformation.keys().toArray();
1850      for (var i=0; i<keys.length; i++) {
1851          hostInformation.Add(keys[i], globalHostInformation.Item(keys[i]));
1852      }
1853      hostInformation.Add("environment", "");
1854  
1855      // First verify if the requested host information attribute exists.
1856      var hostInfoValue = hostInformation.Item(hostAttribute);
1857      if (hostInfoValue == null || (typeof(hostInfoValue) == "object" && hostInfoValue.length <= 0) ) {
1858          dinfo("Host match requires attribute '" + hostAttribute + "' "
1859                  + "which is not defined for current host. No match found."); 
1860          return false;
1861      }
1862      
1863      var attrMatchExpression = new RegExp(checkExpression, "i");
1864      // First try to match array objects.
1865      if (typeof(hostInfoValue) == "object" && hostInfoValue.length > 0) {
1866          for (var iHostInfo=0; iHostInfo < hostInfoValue.length; iHostInfo++) {
1867              // Get value from attribute array
1868              var hostInfoElement = hostInfoValue[iHostInfo];
1869              dinfo("Comparing multi-valued attribute '" + hostAttribute + "' with value '" +
1870                      hostInfoElement + "' using expression '" + checkExpression + "'.");
1871  
1872              // Compare attribute array element with expected
1873              // value.
1874              if (attrMatchExpression.test(hostInfoElement) == true) {
1875                  dinfo("Match for attribute '" + hostAttribute + "' with value '" + hostInfoElement + "' found.");
1876                  returnValue = true;
1877                  break;
1878              }
1879          }
1880      // } else if (typeof(host[hostNodeAttrName]) != "object") {
1881      } else {
1882          // Match simple attributes.
1883          switch (hostAttribute) {
1884              case "environment":
1885                  // Match environment condition to actual environment variable.
1886  
1887                  // Get condition value from from parameter, could be multiple, separated by '|'.
1888                  var environmentConditions = checkExpression.split('|');
1889                  returnValue = true;
1890                  for (var iEnv=0; iEnv < environmentConditions.length; iEnv++) {
1891                      var environmentCondition = environmentConditions[iEnv];
1892                      // Split environment conditions into key and value pairs.
1893                      var envConditionSplit = environmentCondition.split("=");
1894                      // Need at least the key and value. If there are less components, then skip it.
1895                      if (envConditionSplit.length >= 2) {
1896                          // The first value is the key.
1897                          var envKey = envConditionSplit[0];
1898                          if (envKey == "") {
1899                              dinfo("Invalid empty environment variable name.");
1900                              returnValue = false;
1901                              break;
1902                          }
1903  
1904                          // Fetch environment value.
1905                          var expandString = "%" + envKey + "%";
1906                          var envValueRead = new ActiveXObject("WScript.Shell").ExpandEnvironmentStrings(expandString);
1907                          
1908                          if (envValueRead == expandString) {
1909                              // Environment variable is not defined, match failed.
1910                              dinfo("Required environment not matched. Environment variable '" + envKey + "' not defined.");
1911                              returnValue = false;
1912                              break;
1913                          }
1914  
1915                          // All following values are belonging to the value.
1916                          /*
1917                          var valueParts = new Array();
1918                          for (var iValues=1; iValues < envConditionSplit.length; iValues++) {
1919                              valueParts.push(envConditionSplit[iValues]);
1920                          }
1921                          // Join values to re-assemble the value specified.
1922                          var envValue = valueParts.join("");
1923                          */
1924  
1925                          // Re-assemble value.
1926                          var valueStartOffset = envKey.length + 1;
1927                          var envValue = environmentCondition.substr(valueStartOffset);
1928  
1929                          // Check environment using regular expression match.
1930                          var envMatchExpression = new RegExp(envValue, "i");
1931                          if (envMatchExpression.test(envValueRead) == true) {
1932                              dinfo("Required environment matched. Environment variable '" + envKey +
1933                                      "' with value '" + envValueRead + "' matches '" + envValue + "'.");
1934                              // Check next value. All of them need to be true.
1935                              continue;
1936                          } else {
1937                              dinfo("Required environment dit not match. Environment variable '" + envKey +
1938                                      "' with value '" + envValueRead + "' does not match '" + envValue + "'.");
1939                              returnValue = false;
1940                              break;
1941                          }
1942                      } else {
1943                          error("Invalid environment match expression '" + environmentCondition + "'. Match skipped.");
1944                      }
1945                  }
1946                  break;
1947  
1948              case "lcid":
1949                  // Check whether any LCID matches the current host executing user LCID.
1950                  var attributeLCIDs = checkExpression.split(",");
1951                  for (var iLCID=0; iLCID < attributeLCIDs.length; iLCID++) {
1952                      // check if it corresponds to the system LCID
1953                      var currentLcid = trimLeadingZeroes(trim(attributeLCIDs[iLCID]));
1954                      if (currentLcid == hostInfoValue) {
1955                          dinfo("Required LCID match found. LCID '" + currentLcid + "' matches current user LCID.");
1956                          returnValue = true;
1957                          break;
1958                      }
1959                  }
1960                  if (!returnValue) {
1961                      dinfo("None of the required LCID values (" + checkExpression +
1962                              ") matched the current host LCID of '" + hostInfoValue + "'.");
1963                  }
1964                  break;
1965  
1966              case "lcidOS":
1967                  // Check whether any LCID matches the current host OS LCID.
1968                  var attributeLCIDs = checkExpression.split(",");
1969                  for (var iLCIDOS=0; iLCIDOS < attributeLCIDs.length; iLCIDOS++) {
1970                      // check if it corresponds to the system LCID
1971                      var currentLcid = trimLeadingZeroes(trim(attributeLCIDs[iLCIDOS]));
1972                      if (currentLcid == hostInfoValue) {
1973                          dinfo("Required OS LCID match found. LCID '" + currentLcid + "' matches current host LCID.");
1974                          returnValue = true;
1975                          break;
1976                      }
1977                  }
1978                  if (!returnValue) {
1979                      // Check if any LCID matched the current host.
1980                      dinfo("None of the required LCID values (" + checkExpression +
1981                              ") matched the current host LCID of '" + hostInfoValue + "'.");
1982                  }
1983                  break;
1984                  
1985              default:
1986                  // perform simple regular expression match of
1987                  // attribute
1988                  if (attrMatchExpression.test(hostInfoValue) == true) {
1989                      dinfo("Host attribute '" + hostAttribute + "' with value '" +
1990                              hostInfoValue + "' matches expression '" + checkExpression + "'.");
1991                      returnValue = true;
1992                  } else {
1993                      dinfo("Host attribute '" + hostAttribute + "' with value '" +
1994                              hostInfoValue + "' does not match expression '" + checkExpression + "'.");
1995                      returnValue = false;
1996                  }
1997                  break;
1998          }
1999      }
2000      return returnValue;
2001  }
2002  
2003  
2004  /**
2005   * Creates a new hosts XML root-node and returns it
2006   * 
2007   * @return new hosts node
2008   */
2009  function createHosts() {
2010      var newHosts = createXml("wpkg:wpkg", namespaceHosts);
2011      return newHosts;
2012  }
2013  
2014  /**
2015   * Creates a new packages XML root-node and returns it
2016   * 
2017   * @return new profiles node
2018   */
2019  function createPackages() {
2020      var newPackages = createXml("packages:package", namespacePackages);
2021      return newPackages;
2022  }
2023  
2024  /**
2025   * Creates a new profiles XML root-node and returns it
2026   * 
2027   * @return new profiles node
2028   */
2029  function createProfiles() {
2030      var newProfiles = createXml("profiles:profiles", namespaceProfiles);
2031      return newProfiles;
2032  }
2033  
2034  /**
2035   * Creates a new settings XML root-node and returns it
2036   * 
2037   * @return new settings node
2038   */
2039  function createSettings() {
2040      var newSettings = createXml("wpkg:wpkg", namespaceSettings);
2041      if (settingsHostInfo) {
2042          // Add host attributes.
2043          // NOTE: These attributes are currently not used by WPKG but might be
2044          // useful if wpkg.xml is copied to an external system so wpkg.xml
2045          // will include some host information.
2046          var hostInformation = getHostInformation();
2047          var attributes = hostInformation.keys().toArray();
2048          for (var i=0; i<attributes.length; i++) {
2049              var value = hostInformation.Item(attributes[i]);
2050              newSettings.setAttribute(attributes[i], value);
2051          }
2052      }
2053      return newSettings;
2054  }
2055  
2056  /**
2057   * Create a new settings XML root-node by reading a file and returns it
2058   * 
2059   * @param fileName String pointing to the settings file to be created
2060   *                 (full path).
2061   * @return settings root node as stored within the file
2062   */
2063  function createSettingsFromFile(fileName) {
2064      var newSettings = loadXml(fileName, null, "settings");
2065      return newSettings;
2066  }
2067  
2068  /**
2069   * Downloads a file as specified within a download node.
2070   * 
2071   * @param downloadNode
2072   *            XML 'download' node to be used
2073   * @return true in case of successful download, false in case of error
2074   */
2075  function download(downloadNode) {
2076      // get attributes
2077      var url = getDownloadUrl(downloadNode);
2078      var target = getDownloadTarget(downloadNode);
2079      var timeout = getDownloadTimeout(downloadNode);
2080      var expandURL = getDownloadExandURL(downloadNode);
2081  
2082      // initiate download
2083      return downloadFile(url, target, timeout, expandURL);
2084  }
2085  
2086  /**
2087   * Downloads all files from the given array of download XML nodes
2088   * 
2089   * @param downloadNodes
2090   *            Array of download XML nodes to be downloaded
2091   * @return true in case of successful download, false in case of error
2092   */
2093  function downloadAll(downloadNodes) {
2094      var returnValue = true;
2095      if (downloadNodes != null) {
2096          for (var i=0; i<downloadNodes.length; i++) {
2097              var result = download(downloadNodes[i]);
2098              // stop downloading if
2099              if (result != true) {
2100                  returnValue = false;
2101              }
2102          }
2103      }
2104      return returnValue;
2105  }
2106  
2107  /**
2108   * Removes eventually existing temporary downloads of the specified XML node
2109   * 
2110   * @param downloadNode
2111   *            XML node which contains the download definition to clean
2112   */
2113  function downloadClean(downloadNode) {
2114      // get download attributes
2115      var target = getDownloadTarget(downloadNode);
2116  
2117      // evaluate target directory
2118      if (target == null || target == "") {
2119              error("Invalid download target specified: " + target);
2120          target = downloadDir;
2121      } else {
2122          target = downloadDir + "\\" + target;
2123      }
2124      target = new ActiveXObject("WScript.Shell").ExpandEnvironmentStrings(target);
2125      var fso = new ActiveXObject("Scripting.FileSystemObject");
2126      // delete temporary file if it already exists
2127      if (fso.FileExists(target)) {
2128          fso.DeleteFile(target);
2129      }
2130  }
2131  
2132  
2133  /**
2134   * Cleans all temporary files belonging to the download XML nodes within the
2135   * passed array of download XML nodes
2136   * 
2137   * @param downloadNodes
2138   *            Array of download XML nodes
2139   */
2140  function downloadsClean(downloadNodes) {
2141      if (downloadNodes != null) {
2142          for (var i=0; i<downloadNodes.length; i++) {
2143              downloadClean(downloadNodes[i]);
2144          }
2145      }
2146  }
2147  
2148  
2149  /**
2150   * Builds settings document tree containing actually installed packages. Tests
2151   * all packages from given doc tree for "check" conditions. If given conditions
2152   * are positive, package is considered as installed.
2153   */
2154  function fillSettingsWithInstalled() {
2155  
2156      var packagesNodes = getPackageNodes();
2157  
2158      // check each available package
2159      var foundPackage = false;
2160      for (var i = 0; i < packagesNodes.length; i++) {
2161          var packNode = packagesNodes[i];
2162  
2163          // add package node to settings if it is installed
2164          if (isInstalled(packNode)) {
2165              addSettingsNode(packNode, true);
2166              foundPackage = true;
2167          }
2168      }
2169      if (foundPackage) {
2170          saveSettings(true);
2171      }
2172  }
2173  
2174  /**
2175   * Returns the command line argument for this command node. A command node can
2176   * be an <install/>, <upgrade/> or <remove/> node.
2177   * 
2178   * @param cmdNode
2179   *            cmd XML node to read from
2180   * @return command defined within the given cmd XML node, returns null
2181   *         if no command is defined.
2182   */
2183  function getCommandCmd(cmdNode) {
2184      return cmdNode.getAttribute("cmd");
2185  }
2186  
2187  /**
2188   * Returns the value of an exit code node within the given command node. A
2189   * command node can be an <install/>, <upgrade/> or <remove/> node. In case no
2190   * such exit code was defined null will be returned. In case the code is defined
2191   * the string "success" is returned. In case the exit code specifies an
2192   * immediate reboot then the string "reboot" is returned.
2193   * 
2194   * @return returns string "reboot" in case a reboot is required.<br>
2195   *         returns string "delayedReboot" in case a reboot should be scheduled
2196   *         as soon as possible<br>
2197   *         returns string "postponedReboot" in case a reboot after installing
2198   *         all packages is required<br>
2199   *         returns string "success" in case exit code specifies successful
2200   *         installation.<br>
2201   *         returns null in case the exit code is not defined.
2202   */
2203  function getCommandExitCodeAction(cmdNode, exitCode) {
2204      var returnValue = null;
2205      var exitNode = cmdNode.selectSingleNode("exit[@code='" + exitCode + "']");
2206      if (exitNode == null) {
2207          exitNode = cmdNode.selectSingleNode("exit[@code='any']");
2208      }
2209      if (exitNode == null) {
2210          exitNode = cmdNode.selectSingleNode("exit[@code='*']");
2211      }
2212      if (exitNode != null) {
2213          if (exitNode.getAttribute("reboot") == "true") {
2214              // This exit code forces a reboot.
2215              info("Command '" + getCommandCmd(cmdNode) + "' returned " +
2216                  " exit code [" + exitCode + "]. This exit code " +
2217                  "requires an immediate reboot.");
2218              returnValue = "reboot";
2219          } else if (exitNode.getAttribute("reboot") == "delayed")  {
2220              info("Command '" + getCommandCmd(cmdNode) + "' returned " +
2221                  " exit code [" + exitCode + "]. This exit code " +
2222                  "schedules a reboot after execution of all commands.");
2223              returnValue = "delayedReboot";
2224          } else if (exitNode.getAttribute("reboot") == "postponed")  {
2225              info("Command '" + getCommandCmd(cmdNode) + "' returned " +
2226                  " exit code [" + exitCode + "]. This exit code " +
2227                  "schedules a reboot after execution of all packages.");
2228              returnValue = "postponedReboot";
2229          } else {
2230              // This exit code is successful.
2231              info("Command '" + getCommandCmd(cmdNode) + "' returned " +
2232                  " exit code [" + exitCode + "]. This exit code " +
2233                  "indicates success.");
2234              returnValue = "success";
2235          }
2236      }
2237      return returnValue;
2238  }
2239  
2240  
2241  /**
2242   * Return value of include attribute of the given cmd node.
2243   * Returns null if include attribute is not set.
2244   * 
2245   * @param cmdNode
2246   *         The command node to read the include attribute from.
2247   * 
2248   * @returns Value of include attribute, returns null if attribute is undefined.
2249   */
2250  function getCommandInclude(cmdNode) {
2251      return cmdNode.getAttribute("include");
2252  }
2253  
2254  
2255  /**
2256   * Returns the timeout value for this command node. A command node can be an
2257   * <install/>, <upgrade/> or <remove/> node.
2258   * 
2259   * @param cmdNode
2260   *            cmd XML node to read from.
2261   * @return the timeout for the given cmd XML node - returns 0 if no timeout is
2262   *         defined
2263   */
2264  function getCommandTimeout(cmdNode) {
2265      var timeout = cmdNode.getAttribute("timeout");
2266      if (timeout == null) {
2267          timeout = 0;
2268      }
2269      return parseInt(timeout);
2270  }
2271  
2272  /**
2273   * Returns the value of the workdir attribute of the given cmd XML node.
2274   * 
2275   * @param cmdNode
2276   *            cmd XML node to read from
2277   * @return the workdir attribute value. Returns null in case value is not
2278   *         defined.
2279   */
2280  function getCommandWorkdir(cmdNode) {
2281      var workdir = cmdNode.getAttribute("workdir");
2282      return workdir;
2283  }
2284  
2285  /**
2286   * Returns condition node of a given XML node. Returns null if there is no
2287   * condition node specified.
2288   * 
2289   * @param xmlNode XML node which is supposed to have a <condition /> sub-node.
2290   * @returns Array of condition XML-nodes, might be null if no condition is specified
2291   */
2292  function getConditions(xmlNode) {
2293      // Read condition nodes (might be 0, 1 or any number)
2294      var conditionNodes = xmlNode.selectNodes("condition");
2295  
2296      /*
2297      var conditionNodes = xmlNode.selectNodes("wpkg:condition");
2298      if (conditionNodes.length <= 0) {
2299          // Maybe namespace has not been specified correctly.
2300          // Try reading from default namespace.
2301          conditionNodes = xmlNode.selectNodes("condition");
2302      }
2303      */
2304  
2305      // Per specification only one single condition node shall be specified
2306      /*
2307      if (conditionNodes != null && conditionNodes.length > 1) {
2308          error("More than one condition node specified. Ignoring all but the first condition.");
2309      }
2310      */
2311  
2312      // Return condition node.
2313      return conditionNodes;
2314  }
2315  
2316  /**
2317   * Returns XML node which contains the configuration
2318   */
2319  function getConfig() {
2320      if (config == null) {
2321          // load config
2322  
2323          // get argument list
2324          var argv = getArgv();
2325          // Get special purpose argument lists.
2326          var argn = argv.Named;
2327  
2328          // if set to true it will throw an error to quit in case of
2329          // file-not-found
2330          var exitIfNotFound = false;
2331  
2332          // stores config file path
2333          var config_file = null;
2334  
2335          // will be used for file operations
2336          var fso = new ActiveXObject("Scripting.FileSystemObject");
2337  
2338          if (argn("config") != null) {
2339              var configPath = argn("config");
2340              var wshObject = new ActiveXObject("WScript.Shell");
2341              var expConfigPath = wshObject.ExpandEnvironmentStrings(configPath);
2342              config_file = fso.GetAbsolutePathName(expConfigPath);
2343              // config was explicitly specified - I think we should quit if it
2344              // is not available
2345              exitIfNotFound = true;
2346          } else {
2347              // if config_file_name (config.xml) exists, use it
2348              var fullScriptPATH = WScript.ScriptFullName;
2349              var base = fso.GetParentFolderName(fullScriptPATH);
2350              config_file = fso.BuildPath(base, config_file_name);
2351              // config is optional in this case
2352              exitIfNotFound = false;
2353          }
2354  
2355          if (fso.FileExists(config_file)) {
2356              try {
2357                  // Read in config.xml.
2358                  config = loadXml(config_file, null, "config");
2359                  if (config == null) {
2360                      throw new Error("Unable to parse config file!");
2361                  }
2362              } catch (e) {
2363                  // There was an error processing the config.xml file. Alert the
2364                  // user
2365                  error("Error reading "+ config_file + ": " + e.description);
2366                  exit(99); // Exit code 99 means config.xml read error.
2367              }
2368          } else {
2369              var message = config_file + " could not be found.";
2370              if (exitIfNotFound) {
2371                  error(message);
2372                  exit(99); // Exit code 99 means config.xml read error.
2373              } else {
2374                  dinfo(message);
2375              }
2376          }
2377          // create empty config if no config could be read
2378          if (config == null) {
2379              config = createXml("config");
2380          }
2381      }
2382      return config;
2383  }
2384  
2385  /**
2386   * Returns array of <param> nodes from the configuration. Returns array of size
2387   * 0 in case no parameter is defined.
2388   * 
2389   * @return <param> nodes
2390   */
2391  function getConfigParamArray() {
2392      return getConfig().selectNodes("param");
2393  }
2394  
2395  /**
2396   * Returns download XML node array on a given XML node
2397   * 
2398   * @param xmlNode
2399   *            the xml node to read child-nodes of type download from
2400   * @param downloadsArray
2401   *            array of downloads to be extended with the ones from the given XML
2402   *            node, specify null to return a new array.
2403   * @return XML node array on a given package XML node containing all package
2404   *         downloads. returns empty array if no downloads are defined
2405   */
2406  function getDownloads(xmlNode, downloadsArray) {
2407      var downloadsArrayRef = downloadsArray;
2408      if (downloadsArrayRef == null) {
2409          downloadsArrayRef = new Array();
2410      }
2411      // Only fetch download nodes if downloads are not disabled.
2412      // Just hide download nodes in case downloads are disabled.
2413      if (!isNoDownload()) {
2414          var downloads = xmlNode.selectNodes("download");
2415          if (downloads != null) {
2416              var filteredDownloads = filterConditionalNodes(downloads, true);
2417              for(var i=0; i<filteredDownloads.length; i++) {
2418                  downloadsArrayRef.push(filteredDownloads[i]);
2419              }
2420          }
2421      }
2422      return downloadsArrayRef;
2423  }
2424  
2425  /**
2426   * Returns 'target' attribute from the given download XML node
2427   * 
2428   * @param downloadNode
2429   *            download XML node
2430   * @return value of 'target' attribute, null if attribute is not defined
2431   */
2432  function getDownloadTarget(downloadNode){
2433      return downloadNode.getAttribute("target");
2434  }
2435  
2436  /**
2437   * Returns 'timeout' attribute from the given download XML node
2438   * 
2439   * @param downloadNode
2440   *            download XML node
2441   * @return {Number} Value of 'timeout' attribute, returns value of downloadTimeout if no
2442   *         timeout value exists or it cannot be parsed. Returns integer.
2443   */
2444  function getDownloadTimeout(downloadNode) {
2445      var returnValue = downloadTimeout;
2446      var timeout = downloadNode.getAttribute("timeout");
2447      if (timeout != null) {
2448          try {
2449              returnValue = parseInt(timeout);
2450          } catch(e) {
2451              error("Error parsing timeout attribute: " + e.description);
2452          }
2453      }
2454  
2455      return returnValue;
2456  }
2457  
2458  /**
2459   * Returns value of expandURL attribute from a download node.
2460   * @param downloadNode The download XML node.
2461   * @returns true if variables shall be expanded in URL attribute,
2462   *         false if they should not be expanded. Defaults to true if attribute is undefined.
2463   */
2464  function getDownloadExandURL(downloadNode) {
2465      var returnValue = true;
2466      var attributeValue = downloadNode.getAttribute("expandURL");
2467      if (attributeValue != null && attributeValue == "false") {
2468          returnValue = false;
2469      }
2470      return returnValue;
2471  }
2472  
2473  /**
2474   * Returns 'url' attribute from the given download XML node
2475   * 
2476   * @param downloadNode
2477   *            download XML node
2478   * @return value of 'url' attribute, null if attribute is not defined
2479   */
2480  function getDownloadUrl(downloadNode) {
2481      return downloadNode.getAttribute("url");
2482  }
2483  
2484  /**
2485   * Gets the size of a file (in Bytes). The path is allowed to contain
2486   * environment variables like "%TEMP%\somefile.txt".
2487   * 
2488   * @param file
2489   *          path to the file whose size has to be returned
2490   * @return size of the file (in Bytes), returns -1 if file size could not be
2491   *         determined
2492   */
2493  function getFileSize (file) {
2494      var size = -1;
2495      try {
2496          dinfo ("Finding size of '" + file + "'\n");
2497          var expandedPath = new ActiveXObject("WScript.Shell").ExpandEnvironmentStrings(file);
2498          var FSO = new ActiveXObject("Scripting.FileSystemObject");
2499          var fsof = FSO.GetFile(expandedPath);
2500          size = fsof.Size;
2501      } catch (e) {
2502          size = -1;
2503          dinfo("Unable to get file size for '" + file + "': " +
2504                   e.description);
2505      }
2506      dinfo ("Leaving getFileSize with size " + size);
2507      return size;
2508  }
2509  
2510  /**
2511   * Gets the creation date of a file.
2512   * 
2513   * @param file
2514   *          Path to the file from which to read the creation date.
2515   * @returns Date when the file has been created.
2516   * Returns null if file date could not be read.
2517   */
2518  function getFileDateCreation(file) {
2519      var fileDate = null; // new Date();
2520      try {
2521          dinfo ("Reading creation date of '" + file + "'.");
2522          var expandedPath = new ActiveXObject("WScript.Shell").ExpandEnvironmentStrings(file);
2523          var FSO = new ActiveXObject("Scripting.FileSystemObject");
2524          var fsof = FSO.GetFile(expandedPath);
2525          fileDate = fsof.DateCreated;
2526      } catch (e) {
2527          fileDate = null;
2528          dinfo("Unable to get file creation date for '" + file + "': " +
2529                   e.description);
2530      }
2531      return fileDate;
2532  }
2533  
2534  /**
2535   * Gets the last modified date of a file.
2536   * 
2537   * @param file
2538   *          Path to the file from which to read the last modification date.
2539   * @returns Date when the file has been last modified.
2540   * Returns null if file date could not be read.
2541   */
2542  function getFileDateModification(file) {
2543      var fileDate = null; // new Date();
2544      try {
2545          dinfo ("Reading last modification date of '" + file + "'.");
2546          var expandedPath = new ActiveXObject("WScript.Shell").ExpandEnvironmentStrings(file);
2547          var FSO = new ActiveXObject("Scripting.FileSystemObject");
2548          var fsof = FSO.GetFile(expandedPath);
2549          fileDate = fsof.DateLastModified;
2550      } catch (e) {
2551          fileDate = null;
2552          dinfo("Unable to get file last modification date for '" + file + "': " +
2553                   e.description);
2554      }
2555      return fileDate;
2556  }
2557  
2558  /**
2559   * Gets the last access date of a file.
2560   * 
2561   * @param file
2562   *          Path to the file from which to read the last access date.
2563   * @returns Date when the file has been last accessed.
2564   * Returns null if file date could not be read.
2565   */
2566  function getFileDateLastAccess(file) {
2567      var fileDate = null; // new Date();
2568      try {
2569          dinfo ("Reading last access date of '" + file + "'.");
2570          var expandedPath = new ActiveXObject("WScript.Shell").ExpandEnvironmentStrings(file);
2571          var FSO = new ActiveXObject("Scripting.FileSystemObject");
2572          var fsof = FSO.GetFile(expandedPath);
2573          fileDate = fsof.DateLastAccessed;
2574      } catch (e) {
2575          fileDate = null;
2576          dinfo("Unable to get file last accessed date for '" + file + "': " +
2577                   e.description);
2578      }
2579      return fileDate;
2580  }
2581  
2582  /**
2583   * Returns the version of a file.
2584   * 
2585   * @return string representation of version, null in case no version could be
2586   *         read.
2587   */
2588  function getFileVersion (file) {
2589      var version = null;
2590      try {
2591          dinfo ("Trying to find version of " + file);
2592          var FSO = new ActiveXObject("Scripting.FileSystemObject");
2593          version = FSO.GetFileVersion(file);
2594          dinfo ("Obtained version '" + version + "'.");
2595      } catch (e) {
2596          version = null;
2597          dinfo("Unable to find file version for " + file + " : " +
2598              e.description);
2599      }
2600      return version;
2601  }
2602  
2603  /**
2604   * Returns the hostname of the machine running this script. The hostname might
2605   * be overwritten by the /host:<hostname> switch.
2606   */
2607  function getHostname() {
2608      if (hostName == null) {
2609          var WshNetwork = WScript.CreateObject("WScript.Network");
2610          setHostname(WshNetwork.ComputerName.toLowerCase());
2611      }
2612      return hostName;
2613  }
2614  
2615  /**
2616   * Returns a string representing the regular expression associated to the host
2617   * definition in hosts.xml.
2618   */
2619  function getHostNameAttribute(hostNode) {
2620      return hostNode.getAttribute("name");
2621  }
2622  
2623  /**
2624   * Returns the operating system of the machine running this script. The return
2625   * format is:
2626   * 
2627   * <pre>
2628   * <OS-caption>, <OS-description>, <CSD-version>, <OS-version>
2629   * example output:
2630   * microsoft windows 7 professional, , sp1, 6.1.7601
2631   * </pre>
2632   * 
2633   * It might be overwritten by the /os:<hostos> switch.
2634   * 
2635   * Note: Some values might be empty.
2636   * 
2637   * @returns Host operating system specification as a plain string converted to
2638   *          lower case letters to ease parsing
2639   */
2640  function getHostOS() {
2641      if (hostOs == null) {
2642          var strComputer = ".";
2643          var strQuery = "Select * from Win32_OperatingSystem";
2644              try {
2645                  var objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\\\" +
2646                                                  strComputer + "\\root\\cimv2");
2647                  var colOSes = objWMIService.ExecQuery(strQuery,"WQL",48);
2648                  var osEnum = new Enumerator(colOSes);
2649                  for (; !osEnum.atEnd(); osEnum.moveNext()) {
2650                      var osItem = osEnum.item();
2651                      var OtherTypeDescription = "";
2652                      var CSDVersion = "";
2653                          if (osItem.OtherTypeDescription != null) {
2654                              OtherTypeDescription = osItem.OtherTypeDescription;
2655                          }
2656                          if (osItem.CSDVersion != null) {
2657                              CSDVersion = osItem.CSDVersion.replace(/Service Pack /i,"SP");
2658                          }
2659                          var strSystem = trim(osItem.Caption) + ", "
2660                                  + OtherTypeDescription + ", "
2661                                  + CSDVersion + ", "
2662                                  + osItem.Version;
2663                          hostOs = strSystem.toLowerCase();
2664                          dinfo("Host operating system: " + hostOs);
2665                  }
2666              } catch (e) {
2667                  dinfo("Warning: unable to get operating system information.");
2668              }
2669      }
2670      return hostOs;
2671  }
2672  
2673  /**
2674   * Returns name of domain on which the executing host is member of.
2675   * 
2676   * @returns Returns domain name string.
2677   */
2678  function getDomainName() {
2679      if (domainName == null) {
2680          try {
2681              var strComputer = "." ;
2682  
2683              // Get WMI object to read information from.
2684              var WMIServiceStr = "winmgmts:{impersonationLevel=impersonate}!\\\\"
2685                                  + strComputer + "\\root\\cimv2";
2686              var objWMIService = GetObject(WMIServiceStr) ;
2687  
2688              // Query domain name from WMI.
2689              var QueryRes = objWMIService.ExecQuery("Select * from Win32_ComputerSystem where PartOfDomain=True ");
2690              var items=new Enumerator(QueryRes);
2691              items.moveFirst();
2692              if (items.atEnd() == true) {
2693                  // Not a domain member
2694                  dinfo("Not a domain member.");
2695                  // set
2696                  domainName = "";
2697              } else {
2698                  var First = items.item();
2699                  domainName = First.Domain.toLowerCase();
2700                  dinfo("Domain Name: " + domainName);
2701              }
2702          } catch (e) {
2703              dinfo("Message: Unable to get domain information.");
2704          }
2705      }
2706      return domainName;
2707  }
2708  
2709  /**
2710   * Returns array of group names where the executing host is member of.
2711   * 
2712   * @returns Returns list of membership groups.
2713   */
2714  function getHostGroups() {
2715      if (hostGroups == null) {
2716          hostGroups = new Array();
2717          try {
2718              var hostName = getHostname();
2719              var domainName = getDomainName();
2720              var obj = GetObject("WinNT://" + domainName + "/" + hostName + "$,user") ;
2721              var groups = obj.Groups();
2722              for (var item =new Enumerator(groups); !item.atEnd(); item.moveNext() ) {
2723                  var group = item.item();
2724                  dinfo("Found computer group: " + group.Name);
2725                  hostGroups.push(group.Name);
2726              }
2727          } catch (e) {
2728              dinfo("Message: Unable to fetch computer membership groups. Probably not a domain member.");
2729          }
2730      }
2731      return hostGroups;
2732  }
2733  
2734  /**
2735   * Returns a list of attribute/value pair associated to the host
2736   * definition in hosts.xml.
2737   *
2738   * @param hostNode XML node of the host definition
2739   * @return dictionary of attribute/value pair.
2740   */
2741  function getHostAttributes(hostNode) {
2742      var hostAttributes = new  ActiveXObject("Scripting.Dictionary");
2743  
2744      if(hostNode.attributes != null) {
2745          for (var i=0; i<hostNode.attributes.length; i++) {
2746              if (hostNode.attributes[i].value != null) {
2747                  hostAttributes.Add(hostNode.attributes[i].name, hostNode.attributes[i].value);
2748              }
2749          }
2750      }
2751      return  hostAttributes;
2752  }
2753  
2754  /**
2755   * Returns a string identifying a host node including all attributes.
2756   * 
2757   * @param hostNode
2758   *            XML node of the host definition
2759   * @return a string of concatenate 'attribute=value'
2760   */
2761  function getHostNodeDescription(hostNode) {
2762      // Get dictionary object of all attributes.
2763      var hostNodeAttrs = getHostAttributes(hostNode);
2764  
2765      // Fill all attributes into array.
2766      var attrsKeys = hostNodeAttrs.keys().toArray();
2767      var attrDesc = new Array();
2768      for (var i=0; i<attrsKeys.length; i++) {
2769          var attrName = attrsKeys[i];
2770          var attrValue = hostNodeAttrs.Item(attrName);
2771          attrDesc.push(attrName + "='" + attrValue + "'");
2772      }
2773      // Convert array to comma-separated list
2774      // attr1='value1',attr2='value2'
2775      return attrDesc.join(",");
2776  }
2777  
2778  
2779  /**
2780   * Collects information from local host and stores it into a scripting
2781   * dictionary object.
2782   * 
2783   * @returns host attributes stored within a dictionary object. This currently
2784   *          includes the following attributes: name, architecture, os,
2785   *          ipaddresses, domainname, groups, lcid
2786   */
2787  function getHostInformation() {
2788      // Fetch host information if not already collected.
2789      // This information is supposed to be static during execution and
2790      // therefore it will be cached.
2791      if (hostAttributes == null) {
2792          hostAttributes = new ActiveXObject("Scripting.Dictionary");
2793          hostAttributes.Add("hostname", getHostname());
2794          hostAttributes.Add("architecture", getArchitecture());
2795          hostAttributes.Add("os", getHostOS());
2796          hostAttributes.Add("ipaddresses", getIPAddresses());
2797          hostAttributes.Add("domainname", getDomainName());
2798          hostAttributes.Add("groups", getHostGroups());
2799          hostAttributes.Add("lcid", getLocale());
2800          hostAttributes.Add("lcidOS", getLocaleOS());
2801  
2802          // Print information found for debug purposes.
2803          dinfo("Host properties: "
2804              + "hostname='" + hostAttributes.Item("hostname") + "'\n"
2805              + "architecture='" + hostAttributes.Item("architecture") + "'\n"
2806              + "os='" + hostAttributes.Item("os") + "'\n"
2807              + "ipaddresses='" + hostAttributes.Item("ipaddresses").join(",") + "'\n"
2808              + "domain name='" + hostAttributes.Item("domainname") + "'\n"
2809              + "groups='" + hostAttributes.Item("groups").join(",") + "'\n"
2810              + "lcid='" + hostAttributes.Item("lcid") + "'\n"
2811              + "lcidOS='" + hostAttributes.Item("lcidOS") + "'"
2812          );
2813      }
2814      return hostAttributes;
2815  }
2816  
2817  /**
2818   * Accepts a list of XML nodes (Array of XML nodes) which is then filtered for
2819   * XML nodes which either do not specify specific host matches or all specified
2820   * attributes match the current host. For example the following XML nodes would
2821   * match:
2822   * 
2823   * E.g.
2824   * 
2825   * <pre>
2826   * <host name="nodename"; os="windows"; attributeX="value" profile-id="default" />
2827   * <host name="nodename" profile-id="default" />
2828   * <package os="windows" package-id="value" ipaddresses="192\.168\.1\..*" />
2829   * <package package-id="value" />
2830   * </pre>
2831   * 
2832   * The last example matches since there is no limitation to host attributes in the definition.
2833   * 
2834   * The return value will be an Array object listing only the XML nodes which
2835   * match.
2836   * 
2837   * @param xmlNodes
2838   *            Array of XML nodes which shall be verified for current host match.
2839   * @param getAllMatches
2840   *            If set to true returns all matches. If set to false just returns the first matching node from xmlNodes. In this case the return array will contain only one element (or 0 if no match was found).
2841   * @returns Array of XML nodes which match the current host.
2842   */
2843  function filterConditionalNodes(xmlNodes, getAllMatches) {
2844      // Create array to store the XML nodes which match this host.
2845      var applyingNodes = new Array();
2846  
2847      if(getAllMatches == null) {
2848          getAllMatches = true;
2849      }
2850      
2851      // Check if xmlNode array passed as argument is valid
2852      if (xmlNodes == null || xmlNodes.length <= 0) {
2853          return applyingNodes;
2854      }
2855  
2856      // Fetch current host attributes.
2857      var globalHostInformation = getHostInformation();
2858  
2859      // Add "environment" key since we want to support environment matching too.
2860      var hostInformation = new ActiveXObject("Scripting.Dictionary");
2861      var keys = globalHostInformation.keys().toArray();
2862      for (var i=0; i<keys.length; i++) {
2863          hostInformation.Add(keys[i], globalHostInformation.Item(keys[i]));
2864      }
2865      hostInformation.Add("environment", "");
2866  
2867      // Check all nodes whether they match the current host.
2868      for (var i=0; i < xmlNodes.length; i++) {
2869          var xmlNode = xmlNodes[i];
2870          if (xmlNode == null) {
2871              // Skip to next node
2872              continue;
2873          }
2874          // Set to true if all host attributes from XML specification match
2875          // this host.
2876          var hostMatchFound = true;
2877  
2878          // Fetch all XML attributes which correspond to a defined host property.
2879          var xmlNodeAttrs = new  ActiveXObject("Scripting.Dictionary");
2880          for (var iAttribute=0; iAttribute < xmlNode.attributes.length; iAttribute++) {
2881              if( hostInformation.Item(xmlNode.attributes[iAttribute].name) != null ) {
2882                  xmlNodeAttrs.Add(xmlNode.attributes[iAttribute].name, xmlNode.attributes[iAttribute].value);
2883              }
2884          }
2885          
2886          // Check whether all of the attributes match the current host.
2887          var attrsKeys = xmlNodeAttrs.keys().toArray();
2888          for (var iAttr=0; iAttr<attrsKeys.length; iAttr++) {
2889              var xmlNodeAttrName = attrsKeys[iAttr];
2890              var xmlNodeAttrValue = xmlNodeAttrs.Item(xmlNodeAttrName);
2891  
2892              // Check whether the attribute matches the current host.
2893              var attributeMatchFound = checkHostAttribute(xmlNodeAttrName, xmlNodeAttrValue);
2894              
2895              // Verify if the attribute does match to current host.
2896              if (attributeMatchFound != true) {
2897                  // No match found. Advance to next host.
2898                  dinfo("No value of '" + xmlNodeAttrName + "' matched '" + xmlNodeAttrValue + "'. Skipping to next definition.");
2899                  hostMatchFound = false;
2900                  break;
2901              }
2902              /*
2903               * else { // This attribute matched, continue with next attribute hostMatchFound = true; continue; }
2904               */
2905          }
2906  
2907          // If not all attributes match the current host definition then the node is not included.
2908          // All nodes which do not specify advanced host match attributes are included too.
2909          if (hostMatchFound) {
2910              // All attributes matched
2911  
2912              // Print some debug information about which extended host attributes matched.
2913              if (xmlNodeAttrs.count > 0) {
2914                  var attrsKeys = xmlNodeAttrs.keys().toArray();
2915                  var attrDesc = new Array();
2916                  for (var iAttrKeys=0; iAttrKeys<attrsKeys.length; iAttrKeys++) {
2917                      attrDesc.push(attrsKeys[iAttrKeys] + "=" + xmlNodeAttrs.Item(attrsKeys[iAttrKeys]));
2918                  }
2919                  dinfo("XML node with special host attribute match found: " + attrDesc.join(", "));
2920              }
2921  
2922              // Verify if the XML node has a <condition /> sub-node
2923              var conditionMatched = true;
2924              var conditionNode = getConditions(xmlNode);
2925              if (conditionNode != null) {
2926                  for (var iCond=0; iCond < conditionNode.length; iCond++) {
2927                      var condition = conditionNode[iCond];
2928                      // Run all checks
2929                      conditionMatched = checkAll(getChecks(condition));
2930                      if (conditionMatched) {
2931                          dinfo("Additional conditions matched successfully.");
2932                      } else {
2933                          conditionMatched = false;
2934                          dinfo("Additional conditions did not match.");
2935                          break;
2936                      }
2937                  }
2938              }
2939  
2940              // Insert node to list of matched nodes.
2941              if (conditionMatched) {
2942                  applyingNodes.push(xmlNode);
2943                  if (!getAllMatches) {
2944                      dinfo("Single-match mode. Host match finished.");
2945                      break;
2946                  }
2947              }
2948          } else {
2949              dinfo("Could not match all attributes of XML node to current host. Skipping to next definition.");
2950          }
2951      }
2952  
2953      return applyingNodes;
2954  }
2955  
2956  /**
2957   * Retrieves host nodes from given "hosts" XML documents. Searches for nodes
2958   * having matching attributes and returns their array.
2959   * 
2960   * First matching host node is returned by default. If switch /applymultiple is
2961   * used all matching host nodes are returned.
2962   * 
2963   * @return returns the first matching host XML node or the list of all matching
2964   *         host XML nodes if applymultiple is true. Returns null if no host node
2965   *         matches.
2966   */
2967  function getHostsApplying() {
2968      if (applyingHostNodes == null) {
2969          // Create new array to store matching hosts.
2970          hostNodesApplying = new Array();
2971  
2972          // Get available host definitions.
2973          var hostNodes = getHostNodes();
2974  
2975          // Check each node independently.
2976          for (var iHost=0; iHost < hostNodes.length; iHost++) {
2977              var hostNode = hostNodes[iHost];
2978  
2979              // Check conditions to determine whether the host definition is
2980              // applied.
2981              var previousEnv = getEnv();
2982              var variables = getVariables(hostNode, null);
2983  
2984              // Apply variables to environment.
2985              for (var iVariable=0; iVariable < variables.length; iVariable++) {
2986                  var varDefinition = variables[iVariable];
2987                  var variableKeys = varDefinition.keys().toArray();
2988                  for (var iVarKey = 0; iVarKey < variableKeys.length; iVarKey++) {
2989                      var key = variableKeys[iVarKey];
2990                      var value = varDefinition.Item(key);
2991                      setEnv(key, value);
2992                  }
2993              }
2994              
2995              // Checkthis host node for special conditions.
2996              var hostList = new Array();
2997              hostList.push(hostNode);
2998              hostList = filterConditionalNodes(hostList, true);
2999              if (hostList.length < 1) {
3000                  // Restore environment.
3001                  loadEnv(previousEnv);
3002                  // Skipt to next host node.
3003                  continue;
3004              }
3005              
3006              // Get host name attribute.
3007              var hostNameAttribute = getHostNameAttribute(hostNode);
3008  
3009              if (hostNameAttribute != null && hostNameAttribute != "") {
3010                  // Try direct match first (non-regular-expression matching).
3011                  if (hostNameAttribute.toUpperCase() == getHostname().toUpperCase()) {
3012                      // Append host to applying hosts.
3013                      hostNodesApplying.push(hostNode);
3014  
3015                  } else {
3016                      
3017                      // Flag to check if IP-address match succeeded.
3018                      var ipMatchSuccess = false;
3019                      try {
3020                          // Try IPv4-address matching.
3021                          // Get IPv4 addresses (might be multiple).
3022                          var ipAddresses = getIPAddresses();
3023      
3024                          // check for each address if a host node matches
3025                          // try non-regular-expression matching
3026                          for (var iIPAdresses=0; iIPAdresses < ipAddresses.length; iIPAdresses++) {
3027                              var ipAddress = ipAddresses[iIPAdresses];
3028      
3029                              // splitvalues
3030                              // dinfo("Trying to match IP '" + ipAddress + "' to " +
3031                              // "'" + matchPattern + "'");
3032                              var splitIP = ipAddress.split(".");
3033                              var splitPattern = hostNameAttribute.split(".");
3034                              // check if format was correct
3035                              if (splitIP.length == 4 &&
3036                                  splitPattern.length == 4) {
3037                                  var firstValue = 0;
3038                                  var secondValue = 0;
3039                                  var match = true;
3040                                  for (var k=0; k<splitIP.length; k++) {
3041                                      // get first range value
3042                                      var ipOctet = parseInt(splitIP[k]);
3043                                      var matchOctet = splitPattern[k];
3044  
3045                                      // check if ip octet defines a range
3046                                      var splitMatchOctet = matchOctet.split("-");
3047                                      firstValue = parseInt(splitMatchOctet[0]);
3048                                      if (splitMatchOctet.length > 1) {
3049                                          secondValue = parseInt(splitMatchOctet[1]);
3050                                      } else {
3051                                          secondValue = firstValue;
3052                                      }
3053                                      if (firstValue > secondValue) {
3054                                          // swap values
3055                                          var temp = firstValue;
3056                                          firstValue = secondValue;
3057                                          secondValue = temp;
3058                                      }
3059                                      // let's finally see if the ip octet is outside the range
3060                                      if ((ipOctet < firstValue || ipOctet > secondValue)) {
3061                                          // if octet did not match the requirements
3062                                          // dinfo("no match!");
3063                                          match = false;
3064                                          // no need to continue
3065                                          break;
3066                                      }
3067                                  }
3068                                  // If all matched, take this profile.
3069                                  if (match) {
3070                                      dinfo("Found host '" + hostNameAttribute +
3071                                              "' matching IP '" + ipAddress + "'");
3072                                      // Append host to applying hosts.
3073                                      hostNodesApplying.push(hostNode);
3074                                      ipMatchSuccess = true;
3075                                      break;
3076                                  }
3077                              }
3078                          }
3079                      } catch(e) {
3080                          ipMatchSuccess = false;
3081                          dinfo("IP-Address match failed: " + e.description);
3082                      }
3083  
3084                      // If we still got no match with, then try regular expression matching.
3085                      if (!ipMatchSuccess) {
3086                          try {
3087                              var hostNameAttributeMatcher = new RegExp("^" + hostNameAttribute + "$", "i");
3088      
3089                              if (hostNameAttributeMatcher.test(getHostname()) == true) {
3090                                  hostNodesApplying.push(hostNode);
3091                              }
3092                          } catch (e) {
3093                              warning("Invalid regular expression for host name matching: '" +
3094                                      hostNameAttribute + "'.");
3095                          }
3096                      }
3097                  }
3098  
3099              } else {
3100  
3101                  // Host "name" attribute is missing or empty. Include host as potential match.
3102                  // This allows to filter this host later using extended host matching
3103                  hostNodesApplying.push(hostNode);
3104              }
3105              
3106              // Restore environment.
3107              loadEnv(previousEnv);
3108          }
3109          
3110          // Filter host nodes by matching them to the local host.
3111          // hostNodesApplying = filterConditionalNodes(hostNodesApplying, isApplyMultiple());
3112  
3113          // Matches might have returned multiple matching results. In case of
3114          // single-matching mode (default) only the first result shall be
3115          // returned
3116          if (!isApplyMultiple() && hostNodesApplying.length > 1) {
3117              var applyingHostNode = hostNodesApplying[0];
3118              hostNodesApplying = new Array();
3119              hostNodesApplying.push(applyingHostNode);
3120          }
3121  
3122          if (hostNodesApplying.length <= 0) {
3123              hostNodesApplying = null;
3124              throw new Error("Unable to find any matching host definition!");
3125          }
3126          applyingHostNodes = hostNodesApplying;
3127      }
3128  
3129      return applyingHostNodes;
3130  }
3131  
3132  /**
3133   * Returns an array of host nodes which specify the host regular expression and
3134   * the corresponding profile
3135   */
3136  function getHostNodes() {
3137      return getHosts().selectNodes("host");
3138  }
3139  
3140  /**
3141   * Returns the profile-id associated with the given host node.
3142   * The node structure is defined as follows:
3143   * 
3144   * The profile-id or the enclosed <profile... /> nodes might be omitted but not
3145   * both!
3146   * 
3147   * @param hostNode XML node of the host definition
3148   * @return array of strings with referenced profiles
3149   *         (array might be of length 0 if no profiles are defined)
3150   */
3151  function getHostProfiles(hostNode) {
3152      // create array to store profile IDs
3153      var profileList = new Array();
3154  
3155      // try to receive profile ID from host node
3156      var profileID = hostNode.getAttribute("profile-id");
3157  
3158      if (profileID != null) {
3159          // convert to lower case if case-sensitivity is off
3160          if (!isCaseSensitive()) {
3161              profileList.push(profileID.toLowerCase());
3162          } else {
3163              profileList.push(profileID);
3164          }
3165      }
3166  
3167      // Load host definition environment (environment might be used in condition
3168      // checks.
3169      var previousEnv = getEnv();
3170      var variables = getVariables(hostNode, null);
3171  
3172      // Apply variables to environment.
3173      for (var iVariable=0; iVariable < variables.length; iVariable++) {
3174          var varDefinition = variables[iVariable];
3175          var variableKeys = varDefinition.keys().toArray();
3176          for (var iVarKey = 0; iVarKey < variableKeys.length; iVarKey++) {
3177              var key = variableKeys[iVarKey];
3178              var value = varDefinition.Item(key);
3179              setEnv(key, value);
3180          }
3181      }
3182      
3183      var profileNodes = hostNode.selectNodes("profile");
3184      if (profileNodes != null) {
3185          // Get only dependencies which match the current host.
3186          var matchingProfileNodes = filterConditionalNodes(profileNodes, true);
3187          for (var iProfile=0; iProfile<matchingProfileNodes.length; iProfile++) {
3188              var profileNode = matchingProfileNodes[iProfile];
3189              // get id attribute
3190              var profileId = profileNode.getAttribute("id");
3191  
3192              // convert to lower case if case-sensitivity is off
3193              if (!isCaseSensitive()) {
3194                  profileList.push(profileId.toLowerCase());
3195              } else {
3196                  profileList.push(profileId);
3197              }
3198          }
3199      }
3200  
3201      // Restore environment.
3202      loadEnv(previousEnv);
3203      
3204      if (profileList.length > 0) {
3205          var message = "Profiles applying to the current host:\n";
3206          for (var iProfileIndex=0; iProfileIndex<profileList.length; iProfileIndex++) {
3207              message += profileList[iProfileIndex] + "\n";
3208          }
3209          dinfo(message);
3210      } else {
3211          error("No profiles assigned to the current host!");
3212      }
3213  
3214      return profileList;
3215  }
3216  
3217  /**
3218   * Returns XML node which contains all host configurations
3219   */
3220  function getHosts() {
3221      if(hosts == null) {
3222          var newHosts = createHosts();
3223          setHosts(newHosts);
3224      }
3225      return hosts;
3226  }
3227  
3228  /**
3229   * Returns a list of variables from the applying hosts definition.
3230   * 
3231   * @param array
3232   *            Object of type Array to which the the variables appended.
3233   *            In case null is supplied it returns a new Array object.
3234   * @return Object of type Scripting.Dictionary which contains all key/value
3235   *         pairs from the applying hosts.
3236   */
3237  function getHostsVariables(array) {
3238      dinfo("Reading variables from hosts[s]");
3239  
3240      // Fetch host definitions which apply to current host.
3241      if (hostsVariables == null) {
3242          hostsVariables = new Array();
3243          var hostNodes = getHostsApplying() ;
3244          for (var iHostNode=0; iHostNode < hostNodes.length; iHostNode++) {
3245              var hostNode = hostNodes[iHostNode];
3246              dinfo("Reading variables from host: " + getHostNodeDescription(hostNode));
3247  
3248              // Add variables from host XML node.
3249              hostsVariables = getVariables(hostNode, hostsVariables);
3250          }
3251      }
3252  
3253      // Concatenate variable list if list was passed as parameter.
3254      var concatenatedVariables = hostsVariables;
3255      if (array != null) {
3256          // concatenatedVariables = concatenateDictionary(dictionary, hostsVariables);
3257          concatenatedVariables = hostsVariables.concat(array);
3258      }
3259  
3260      return concatenatedVariables;
3261  }
3262  
3263  /**
3264   * Returns the corresponding string defined within the configuration.
3265   * 
3266   * @param stringID
3267   *            the identification of the corresponding string as listed within
3268   *            the configuration
3269   * 
3270   * @return returns the string as it appears within the configuration. Returns
3271   *         null if the string id is not defined.
3272   */
3273  function getLocalizedString(stringID) {
3274      if (languageNode == null && getConfig() != null) {
3275          // read node which contains all the strings
3276          var languagesNodes = getConfig().selectNodes("languages");
3277  
3278          if (languagesNodes != null) {
3279              // there might be multiple languages nodes
3280              for (var i=0; i < languagesNodes.length; i++) {
3281                  // get language nodes
3282                  var languageNodes = languagesNodes[i].selectNodes("language");
3283  
3284                  for (var j=0; j < languageNodes.length && languageNode == null; j++) {
3285                      var currentLangNode = languageNodes[j];
3286  
3287                      // get associated language LCIDs
3288                      var lcidString = currentLangNode.getAttribute("lcid");
3289                      var lcids = lcidString.split(",");
3290                      for (var k=0; k < lcids.length; k++) {
3291                          // check if it corresponds to the system LCID
3292                          var currentLcid = trimLeadingZeroes(trim(lcids[k]));
3293                          if (currentLcid == getLocale()) {
3294                              dinfo("Found language definition node for language ID " + currentLcid);
3295                              languageNode = currentLangNode;
3296                              break;
3297                          }
3298                      }
3299                  }
3300              }
3301          }
3302  
3303      }
3304  
3305      // check if language has not been found
3306      if (languageNode == null) {
3307          // create empty node
3308          languageNode = createXml("language");
3309      }
3310  
3311      // try to find node matching the requested sting id
3312      var stringNode = languageNode.selectSingleNode("string[@id='" + stringID + "']");
3313      if (stringNode != null) {
3314          return stringNode.text;
3315      } else {
3316          dinfo("No locale language definition found for message ID '" + stringID +
3317              "' (language LCID '" + getLocale() + "').");
3318          return null;
3319      }
3320  }
3321  
3322  /**
3323   * Returns array of package IDs which includes package IDs of chained packages.
3324   * Returns empty array in case the package does not have any chained packages.
3325   * 
3326   * @param packageNode
3327   *            the package node to read the list of chained packages from
3328   * @param packageList
3329   *            optional reference to an array which is used to insert the chained
3330   *            packages to. Specify null to create a new Array
3331   * @return Array specified in packageList parameter extended by package IDs
3332   *         (string values) which represent the chained packages
3333   */
3334  function getPackageChained(packageNode, packageList) {
3335      // output array
3336      if (packageList == null) {
3337          packageList = new Array();
3338      }
3339  
3340      if(packageNode != null) {
3341          var includeNodes = packageNode.selectNodes("chain");
3342          if (includeNodes != null) {
3343              matchingChainNodes = filterConditionalNodes(includeNodes, true);
3344              for (var i=0; i < matchingChainNodes.length; i++) {
3345                  var dependId = matchingChainNodes[i].getAttribute("package-id");
3346  
3347                  // convert to lower case if case-insensitive mode is on
3348                  if (dependId != null) {
3349                      if (!isCaseSensitive()) {
3350                          dependId = dependId.toLowerCase();
3351                      }
3352                      packageList.push(dependId);
3353                  }
3354              }
3355          }
3356      }
3357  
3358      return packageList;
3359  }
3360  
3361  /**
3362   * Defines how package checks are used during package installation.
3363   * 
3364   * Currently supported values:
3365   *
3366   * "always" (default):
3367   * When a package is new to the host then first the checks are run in order to
3368   * verify whether the package is already installed. If the checks succeed then
3369   * it is assumed that no further installation is needed. The package is silently
3370   * added to the host without executing any commands.
3371   * 
3372   * "never":
3373   * When a package is new to the host then the install commands are run in any
3374   * case (without doing checks first). Note: Checks will still be done after
3375   * package installation to verify whether installation was successful.
3376   *
3377   * @param packageNode Package XML node to read attribute from.
3378   * @returns "always" or "never" according to precheck-install attribute of
3379   *           package.
3380   */
3381  function getPackagePrecheckPolicyInstall(packageNode) {
3382      var checkPolicy = "always";
3383      var installCheckPolicy = packageNode.getAttribute("precheck-install");
3384      if (installCheckPolicy != null) {
3385          checkPolicy = installCheckPolicy;
3386      }
3387      return checkPolicy;
3388  }
3389  
3390  /**
3391   * Defines how package checks are used during package removal.
3392   * 
3393   * Currently supported values:
3394   * 
3395   * "always":
3396   * When a package is removed from a host then the checks will be executed
3397   * before removal is processes. If the checks fail this potentially means that
3398   * the package has been removed already. In such case the package remove
3399   * commands will be skipped.
3400   * 
3401   * "never" (default):
3402   * When a package is about to be removed from the host then WPKG will execute
3403   * the remove commands in any case without executing the checks first.
3404   * Note: Checks will still be done after package removal to verify whether the
3405   * removal was successful.
3406   * 
3407   * @param packageNode Package XML node to read attribute from.
3408   * @returns "always" or "never" according to precheck-remove attribute of
3409   *           package.
3410   */
3411  function getPackagePrecheckPolicyRemove(packageNode) {
3412      var checkPolicy = "never";
3413      var removeCheckPolicy = packageNode.getAttribute("precheck-remove");
3414      if (removeCheckPolicy != null) {
3415          checkPolicy = removeCheckPolicy;
3416      }
3417      return checkPolicy;
3418  }
3419  
3420  /**
3421   * Defines how package checks are used during package upgrade.
3422   * 
3423   * Currently supported values:
3424   *
3425   * "always":
3426   * When a package is upgraded the checks specified will be be executed before
3427   * the upgrade takes place. If checks succeed, then the upgrade will not be
3428   * performed (WPKG just assumes that the new version is already applied
3429   * correctly.
3430   * Please note that your checks shall verify a specific software version and
3431   * not just a generic check which is true for all versions. If your checks
3432   * are true for the old version too then WPKG would never perform the upgrade
3433   * in this mode.
3434   * 
3435   * "never" (default):
3436   * When a package is about to be upgraded then WPKG will execute the upgrade
3437   * commands in any case without executing the checks first. This is the
3438   * recommended behavior.
3439   * Note: Checks will still be done after package upgrade to verify whether the
3440   * upgrade was successful.
3441   * 
3442   * @param packageNode Package XML node to read attribute from.
3443   * @returns "always" or "never" according to precheck-upgrade attribute of
3444   *           package.
3445   */
3446  function getPackagePrecheckPolicyUpgrade(packageNode) {
3447      var checkPolicy = "never";
3448      var upgradeCheckPolicy = packageNode.getAttribute("precheck-upgrade");
3449      if (upgradeCheckPolicy != null) {
3450          checkPolicy = upgradeCheckPolicy;
3451      }
3452      return checkPolicy;
3453  }
3454  
3455  /**
3456   * Defines how package checks are used during package downgrade.
3457   * 
3458   * Currently supported values:
3459   *
3460   * "always":
3461   * When a package is downgraded the checks specified will be be executed before
3462   * the downgrade takes place. If checks succeed, then the downgrade will not be
3463   * performed (WPKG just assumes that the old version is already applied
3464   * correctly.
3465   * Please note that your checks shall verify a specific software version and
3466   * not just a generic check which is true for all versions. If your checks
3467   * are true for the new/current version too then WPKG would never perform the
3468   * downgrade in this mode.
3469   * 
3470   * "never" (default):
3471   * When a package is about to be downgraded then WPKG will execute the
3472   * downgrade commands in any case without executing the checks first. This is
3473   * the recommended behavior.
3474   * Note: Checks will still be done after package downgrade to verify whether
3475   * the downgrade was successful.
3476   * 
3477   * @param packageNode Package XML node to read attribute from.
3478   * @returns "always" or "never" according to precheck-downgrade attribute of
3479   *           package.
3480   */
3481  function getPackagePrecheckPolicyDowngrade(packageNode) {
3482      var checkPolicy = "never";
3483      var downgradeCheckPolicy = packageNode.getAttribute("precheck-downgrade");
3484      if (downgradeCheckPolicy != null) {
3485          checkPolicy = downgradeCheckPolicy;
3486      }
3487      return checkPolicy;
3488  }
3489  
3490  /**
3491   * Returns an array of <check /> XML sub-nodes on a given XML node.
3492   * In case extended host matching attributes are used only the checks which match the
3493   * current host are returned.
3494   * 
3495   * @param xmlNode The XML node from which all 'check' sub-nodes are read
3496   * @return Array of XML nodes containing all 'check'-nodes which match to the current host.
3497   *         Returns empty array if no checks are defined.
3498   *         If extended host matching attributes like "hostname", "os" or similar are used
3499   *         then checks which do not match the current host are not returned.
3500   */
3501  function getChecks(xmlNode) {
3502      var checkNodes = xmlNode.selectNodes("check");
3503      /*
3504      var checkNodes = xmlNode.selectNodes("wpkg:check");
3505      if (checkNodes.length <= 0) {
3506          // Maybe amespace was wrongly specified.
3507          // Try default namespace.
3508          checkNodes = xmlNode.selectNodes("check");
3509      }
3510      */
3511      return filterConditionalNodes(checkNodes);
3512  }
3513  
3514  /**
3515   * This is a convenience-method to get all downgrade commands.
3516   * 
3517   * @param packageNode
3518   *            package XML node which contains 'downgrade' nodes
3519   * @return Array of 'downgrade' XML nodes, returns empty array if no nodes are
3520   *         defined
3521   */
3522  function getPackageCmdDowngrade(packageNode, includeChain) {
3523      // Fetch commands from package node.
3524      var commandNodes = getPackageCmd(packageNode, "downgrade", null);
3525  
3526      // Return list of applying install commands.
3527      return commandNodes;
3528  }
3529  
3530  /**
3531   * This is a convenience-method to get all install commands.
3532   * 
3533   * @param packageNode
3534   *            package XML node which contains 'install' nodes
3535   * @return Array of 'install' XML nodes, returns empty array if no nodes are
3536   *         defined
3537   */
3538  function getPackageCmdInstall(packageNode, includeChain) {
3539      // Fetch commands from package node.
3540      var commandNodes = getPackageCmd(packageNode, "install", null);
3541  
3542      // Return list of applying install commands.
3543      return commandNodes;
3544  }
3545  
3546  
3547  /**
3548   * This is a convenience-method to get all remove commands.
3549   * 
3550   * @param packageNode
3551   *            package XML node which contains 'remove' nodes
3552   * @return Array of 'remove' XML nodes, returns empty array if no nodes are
3553   *         defined
3554   */
3555  function getPackageCmdRemove(packageNode, includeChain) {
3556      // Fetch commands from package node.
3557      var commandNodes = getPackageCmd(packageNode, "remove", null);
3558  
3559      // Return list of applying install commands.
3560      return commandNodes;
3561  }
3562  
3563  /**
3564   * This is a convenience-method to get all upgrade commands.
3565   * 
3566   * @param packageNode
3567   *            package XML node which contains 'remove' nodes
3568   * @return Array of 'upgrade' XML nodes, returns empty array if no nodes are
3569   *         defined
3570   */
3571  function getPackageCmdUpgrade(packageNode, includeChain) {
3572      // Fetch commands from package node.
3573      var commandNodes = getPackageCmd(packageNode, "upgrade", null);
3574  
3575      // Return list of applying install commands.
3576      return commandNodes;
3577  }
3578  
3579  
3580  /**
3581   * Returns a list of commands which apply to the given command type.
3582   * Common types are 'install', 'upgrade', 'downgrade' or 'remove' but WPKG
3583   * allows any custom type definition within the commands/command XML structure.
3584   * For example it is possible to specify <command type="test-type" /> and then
3585   * receive all "test-type" commands using this method.
3586   * 
3587   * @param packageNode
3588   *            package XML node which contains command nodes.
3589   * @param type
3590   *            Type description. Defines which command group to receive.
3591   * @param includeChain
3592   *             Array of command types (install/upgrade/downgrade/remove) already
3593   *          included.
3594   *          This is used to detect inclusion loops (recursive inclusion).
3595   * @return Array of command XML nodes, returns empty array if no nodes are
3596   *         defined
3597   */
3598  function getPackageCmd(packageNode, type, includeChain) {
3599      // Verify input parameters.
3600      if (packageNode == null) {
3601          return null;
3602      }
3603  
3604      // Type must be specified in order to get command group.
3605      if (type == null || type == "") {
3606          return null;
3607      }
3608  
3609      var alreadyIncluded;
3610      if (includeChain == null) {
3611          alreadyIncluded = new Array();
3612      } else {
3613          alreadyIncluded = includeChain;
3614      }
3615      alreadyIncluded.push(type);
3616      
3617      // This variable holds the result set returned.
3618      var commandNodeList = new Array();
3619      
3620      // Fetch commands directly attached to package node
3621      var directCommandNodes = null;
3622      switch (type) {
3623      case "install":
3624          directCommandNodes = packageNode.selectNodes("install");
3625          break;
3626      case "upgrade":
3627          directCommandNodes = packageNode.selectNodes("upgrade");
3628          break;
3629      case "downgrade":
3630          directCommandNodes = packageNode.selectNodes("downgrade");
3631          break;
3632      case "remove":
3633          directCommandNodes = packageNode.selectNodes("remove");
3634          break;
3635      default:
3636          // Command type is none of the "default" types This command type is
3637          // supported in command nodes only.
3638          break;
3639      }
3640  
3641      // Fetch command-nodes from <commands><command type="type" /></commands> structure.
3642      var commandNodes = packageNode.selectNodes("commands/command[@type=\"" + type + "\"]");
3643  
3644      // Merge command lists.
3645      if (directCommandNodes != null) {
3646          for (var iCmd=0; iCmd < directCommandNodes.length; iCmd++) {
3647              commandNodeList.push(directCommandNodes[iCmd]);
3648          }
3649      }
3650      if (commandNodes != null) {
3651          for (var iCmd=0; iCmd < commandNodes.length; iCmd++) {
3652              commandNodeList.push(commandNodes[iCmd]);
3653          }
3654      }
3655      
3656      // Filter out all packages which do not apply to current host.
3657      commandNodeList = filterConditionalNodes(commandNodeList, true);
3658  
3659      // Expand command includes.
3660      // Create array which is returned as a complete command list.
3661      var fullCommandList = new Array();
3662  
3663      // Check all commands for inclusion.
3664      for (var iTypeCommands=0; iTypeCommands<commandNodeList.length; iTypeCommands++) {
3665          var command = commandNodeList[iTypeCommands];
3666          var include = getCommandInclude(command);
3667          
3668          // Inclusion found.
3669          if (include != null) {
3670              dinfo("Found inclusion for command type " + include + ".");
3671              
3672              // Clone array of already included command types which helps to
3673              // detect duplicated includes.
3674              // The same loop will check whether the type to be included has
3675              // already been included (recursive inclusion detection).
3676              var prevIncluded = new Array();
3677              for (var j=0; j<alreadyIncluded.length; j++) {
3678                  var includeElement = alreadyIncluded[j];
3679                  if (includeElement == include) {
3680                      throw new Error("Recursive inclusion detected!");
3681                  } else {
3682                      prevIncluded.push(alreadyIncluded[j]);
3683                  }
3684              }
3685  
3686              // Fetch commands of specified type (if any)
3687              var includedCommands = getPackageCmd(packageNode, include, prevIncluded);
3688  
3689              // Insert fetched commands to command list.
3690              if (includedCommands != null) {
3691                  for (var iIncCmds=0; iIncCmds<includedCommands.length; iIncCmds++) {
3692                      fullCommandList.push(includedCommands[iIncCmds]);
3693                  }
3694              }
3695          } else {
3696              // Include command in command-list.
3697              fullCommandList.push(command);
3698          }
3699      }
3700  
3701      // Return list of applying commands.
3702      return fullCommandList;
3703  }
3704  
3705  
3706  /**
3707   * Returns array of package IDs which represent the package dependencies.
3708   * Returns empty array in case the package does not have any dependency.
3709   * 
3710   * @param packageNode
3711   *            the package node to read the list of dependencies from
3712   * @param packageList
3713   *            optional reference to an array which is used to insert the
3714   *            dependencies to. Specify null to create a new Array
3715   * @return Array specified in packageList parameter extended by package IDs
3716   *         (string values) which represent the dependencies
3717   */
3718  function getPackageDependencies(packageNode, packageList) {
3719      // output array
3720      if (packageList == null) {
3721          packageList = new Array();
3722      }
3723  
3724      if(packageNode != null) {
3725          var dependNodes = packageNode.selectNodes("depends");
3726          if (dependNodes != null) {
3727              // Get only dependencies which match the current host.
3728              var matchingDependNodes = filterConditionalNodes(dependNodes, true);
3729              for (var i=0; i < matchingDependNodes.length; i++) {
3730                  var dependId = matchingDependNodes[i].getAttribute("package-id");
3731  
3732                  // convert to lower case if case-insensitive mode is on
3733                  if (dependId != null) {
3734                      if (!isCaseSensitive()) {
3735                          dependId = dependId.toLowerCase();
3736                      }
3737                      packageList.push(dependId);
3738                  }
3739              }
3740          }
3741      }
3742  
3743      return packageList;
3744  }
3745  
3746  /**
3747   * Returns the package execute attribute value (String)
3748   * 
3749   * @param packageNode
3750   *            the package node to get the attribute from
3751   * @return package execute attribute value, empty string if undefined
3752   */
3753  function getPackageExecute(packageNode) {
3754      var execAttr = packageNode.getAttribute("execute");
3755      if (execAttr == null) {
3756          execAttr = "";
3757      }
3758      return execAttr;
3759  }
3760  
3761  /**
3762   * Returns the package ID string from the given package XML node.
3763   * 
3764   * @return package ID
3765   */
3766  function getPackageID(packageNode) {
3767      return packageNode.getAttribute("id");
3768  }
3769  
3770  /**
3771   * Returns array of package IDs which represent the package includes. Returns
3772   * empty array in case the package does not have any dependency.
3773   * 
3774   * @param packageNode
3775   *            the package node to read the list of includes from
3776   * @param packageList
3777   *            optional reference to an array which is used to insert the
3778   *            includes to. Specify null to create a new Array
3779   * @return Array specified in packageList parameter extended by package IDs
3780   *         (string values) which represent the includes
3781   */
3782  function getPackageIncludes(packageNode, packageList) {
3783      // output array
3784      if (packageList == null) {
3785          packageList = new Array();
3786      }
3787  
3788      if(packageNode != null) {
3789          var includeNodes = packageNode.selectNodes("include");
3790          if (includeNodes != null) {
3791              var matchingIncludeNodes = filterConditionalNodes(includeNodes, true);
3792              for (var i=0; i < matchingIncludeNodes.length; i++) {
3793                  var dependId = matchingIncludeNodes[i].getAttribute("package-id");
3794  
3795                  // convert to lower case if case-insensitive mode is on
3796                  if (dependId != null) {
3797                      if (!isCaseSensitive()) {
3798                          dependId = dependId.toLowerCase();
3799                      }
3800                      packageList.push(dependId);
3801                  }
3802              }
3803          }
3804      }
3805  
3806      return packageList;
3807  }
3808  
3809  /**
3810   * Reads the "manualInstall" attribute from a package node.
3811   * This attribute is true only if the package as installed manually via
3812   * command line. It is false for packages which are initially installed by
3813   * package synchronization.
3814   * 
3815   * @param packageNode the package from which the attribute is read.
3816   * @returns {Boolean} True if package was installed manually, false if it is
3817   *        applied by profile.
3818   */
3819  function getPackageManualInstallation(packageNode) {
3820      // Initialize return variable.
3821      var isManualInstall = false;
3822  
3823      // Read yctual value.
3824      var manualInstall = packageNode.getAttribute("manualInstall");
3825  
3826      // Evaluate result.
3827      if (manualInstall != null && manualInstall == "true") {
3828          isManualInstall = true;
3829      }
3830      return isManualInstall;
3831  }
3832  
3833  /**
3834   * Returns the package name from the given package XML node
3835   * 
3836   * @return returns the package name attribute - empty string if no name is
3837   *         defined
3838   */
3839  function getPackageName(packageNode) {
3840      var packageName = "";
3841      if(packageNode != null) {
3842          packageName = packageNode.getAttribute("name");
3843          if (packageName == null) {
3844              packageName = "";
3845          }
3846      }
3847      return packageName;
3848  }
3849  
3850  /**
3851   * Returns the corresponding package XML node from the package database
3852   * (packages.xml). Returns null in case no such package exists.
3853   */
3854  function getPackageNode(packageID) {
3855      // get first node which matched the specified ID
3856      return getPackages().selectSingleNode("package[@id='" + packageID +"']");
3857  }
3858  
3859  /**
3860   * Returns the corresponding package XML node to the requested package ID by
3861   * searching the packages database first. If the package cannot be located
3862   * within the package database it prints an error and looks for the node within
3863   * the local settings database.
3864   * If even the local database does not contain such a package entry then it
3865   * prints an error about missing package definition. In case '/quitonerror' is
3866   * set it exits.
3867   *
3868   * If the package could be located within the local package database it prints
3869   * a warning and returns the local package node.
3870   *
3871   * Algorithmic description:
3872   *
3873   * <pre>
3874   * search package node within local package database
3875   * if found
3876   *         return it
3877   * else
3878   *         print warning
3879   *         look for package within local settings
3880   *         if found
3881   *             print warning
3882   *             return it
3883   *         else
3884   *             print error (or exit by throwing error in case of /quitonerror)
3885   *             return null
3886   *         fi
3887   * fi
3888   * </pre>
3889   */
3890  function getPackageNodeFromAnywhere(packageID) {
3891      var packageNode = null;
3892  
3893      // try to get package node from package database
3894      var packageDBNode = getPackageNode(packageID);
3895  
3896      // check if node exists; if not then try to get the node from the settings
3897      if(packageDBNode != null) {
3898          // package found in package database, mark for installation/upgrade
3899          dinfo("Found package node '" + getPackageName(packageDBNode) + "' (" +
3900                  getPackageID(packageDBNode) + ") in package database.");
3901          packageNode = packageDBNode;
3902      } else {
3903          // error package not in package database
3904          // looking for package node within the local settings file
3905          /*
3906           * var packageNotFoundMessage = "Profile inconsistency: Package '" + packageID + "' does not exist within the
3907           * package database. " + "Please contact your system administrator!";
3908           * 
3909           * warning(packageNotFoundMessage);
3910           */
3911  
3912          // try to get package node from local settings
3913          var packageSettingsNode = getSettingNode(packageID);
3914  
3915          // if no package definition has been found jet the package is not
3916          // installed
3917          if(packageSettingsNode != null) {
3918              // Check if the package has been manually installed.
3919              var messageLocalOnly = "";
3920              var isManualInstall = getPackageManualInstallation(packageSettingsNode);
3921              if (isManualInstall == true) {
3922                  messageLocalOnly = "Manually installed package not found in server database.";
3923              } else {
3924                  messageLocalOnly = "Database inconsistency: Package with package ID '" +
3925                                  packageID + "' missing in package database. Package information " +
3926                                  "found on local installation:\n";
3927              }
3928              messageLocalOnly += "Package ID: " + messageLocalOnly + "\n" +
3929                              "Package Name: " + getPackageName(packageSettingsNode) + "\n" +
3930                              "Package Revision: " + getPackageRevision(packageSettingsNode) + "\n";
3931              warning(messageLocalOnly);
3932              packageNode = packageSettingsNode;
3933          } else {
3934              var messageNotFound = "Database inconsistency: Package with ID '" + packageID +
3935                      "' does not exist within the package database or the local settings file. " +
3936                      "Please contact your system administrator!";
3937              if (isQuitOnError()) {
3938                  throw new Error(messageNotFound);
3939              } else {
3940                  error(messageNotFound);
3941              }
3942          }
3943      }
3944  
3945      // return result
3946      return packageNode;
3947  }
3948  
3949  /**
3950   * Returns an array of all package nodes that can be installed. This list
3951   * includes all packages found in the package database. It does not include
3952   * local packages from the settings file (currently installed ones).
3953   * 
3954   * @return Array containing XML nodes (package nodes). Array might be of size 0
3955   */
3956  function getPackageNodes() {
3957      // Retrieve packages.
3958      var packageNodes = getPackages().selectNodes("package");
3959  
3960      // make sure a package ID exists only once
3961      packageNodes = uniqueAttributeNodes(packageNodes, "id");
3962  
3963      // return this array
3964      return packageNodes;
3965  }
3966  
3967  /**
3968   * Returns the package notify attribute value
3969   * 
3970   * @param packageNode
3971   *            the package node to get the notify attribute from
3972   * @return Notify attribute value (true in case of String "true" false
3973   *         otherwise.
3974   */
3975  function getPackageNotify(packageNode) {
3976      var returnvalue = true;
3977      var notify = packageNode.getAttribute("notify");
3978      if (notify == "false") {
3979          returnvalue = false;
3980      }
3981      return returnvalue;
3982  }
3983  
3984  /**
3985   * Returns the package priority from the given package XML node
3986   * 
3987   * @return package priority - returns 0 if no priority is defined
3988   */
3989  function getPackagePriority(packageNode) {
3990      var priority = packageNode.getAttribute("priority");
3991      if (priority == null) {
3992          priority = 0;
3993      }
3994      return parseInt(priority);
3995  }
3996  
3997  
3998  /**
3999   * Returns the package reboot attribute value. This attribute can add
4000   * additional reboots but not limit or invalidate reboot flags set on the
4001   * command-level.
4002   *
4003   * This value can have three states:
4004   * 
4005   * <pre>
4006   * "true"      Immediate reboot after package installation.
4007   *             This will take precedence of any command-level reboot="postponed"
4008   *             attribute if present and reboot immediately after package
4009   *             installation.
4010   *             A reboot="true" attribute on command-level will still result in
4011   *             an immediate reboot.
4012   *             Resulting status depending on command-level reboot flag:
4013   *             "true"      immediate reboot after command execution
4014   *             "delayed"   reboot after package installation
4015   *             "postponed" reboot after package installation
4016   *             "false"     reboot after package installation
4017   * "postponed" Schedule reboot after installing all packages within this
4018   *             session, for example after synchronizing.
4019   *             Resulting status depending on command-level reboot flag:
4020   *             "true"      immediate reboot after command execution
4021   *             "delayed"   reboot after package installation
4022   *             "postponed" reboot after all actions are completed
4023   *             "false"     reboot after all actions are completed
4024   * "false"     No reboot unless one is defined at command-level.
4025   * or not set  Resulting status depending on command-level reboot flag:
4026   *             "true"      immediate reboot after command execution
4027   *             "delayed"   reboot after package installation
4028   *             "postponed" reboot after all actions are completed
4029   *             "false"     no reboot
4030   * </pre>
4031   *
4032   * As a result there are four possibilities to schedule a reboot in order of
4033   * precedence:
4034   * 
4035   * <pre>
4036   * immediate   Command node specified reboot=true, immediate reboot takes place.
4037   * package     Reboot is issued right after installing:
4038   *             - package specifies reboot="true"
4039   *               OR
4040   *             - any command node specified reboot="delayed"
4041   * postponed   Reboot will take place after all packages have been applied.
4042   *             - package specifies reboot="postponed"
4043   *               OR
4044   *             - any command node specified reboot="postponed"
4045   * none        No reboot is issued by this package:
4046   *             - package does not specify reboot or specifies reboot="false"
4047   *               AND
4048   *             - no command node specified any form of reboot reboot
4049   * </pre>
4050   *
4051   * This means that an immediate reboot always has the highest priority. You
4052   * can just set "reboot markers" on a "timeline" on package and command level
4053   * where the closest reboot marker will be executed:
4054   * immediate => package => postponed => none
4055   *
4056   * @return one of the states (string values):
4057   *             "true", always reboot after package installation
4058   *             "postponed", reboot before script exits
4059   *             "false", reboot only if command specified reboot=delayed/postponed
4060   *
4061   */
4062  function getPackageReboot(packageNode) {
4063      var rebootAction = "false";
4064      var packageReboot = packageNode.getAttribute("reboot");
4065      if (packageReboot != null) {
4066          if (packageReboot == "true") {
4067              rebootAction = packageReboot;
4068          } else if (packageReboot == "postponed") {
4069              rebootAction = packageReboot;
4070          }
4071      }
4072      return rebootAction;
4073  }
4074  
4075  /**
4076   * Adds all packages referenced by the specified package node to the given
4077   * array. In other words all dependencies, chained packages and includes of the
4078   * given node will be appended to the array. If you specify null or an empty
4079   * array the array returned will contain all packages from the dependency tree
4080   * of the given package node.
4081   * 
4082   * @param packageNode
4083   *            full dependency tree of the specified package will be added to the
4084   *            given array.
4085   * @param packageArray
4086   *            Array to which all referenced packages are added to. Specify null
4087   *            to create a new array finally containing only the dependency tree
4088   *            of the specified package.
4089   * @return array containing all referenced packages (full package nodes). NOTE:
4090   *         The returned array is not sorted.
4091   */
4092  function getPackageReferences(packageNode, packageArray) {
4093      if (packageArray == null) {
4094          packageArray = new Array();
4095      }
4096  
4097      // get dependencies, includes and chains
4098      var linkedPackageIDs = getPackageDependencies(packageNode, null);
4099      getPackageIncludes(packageNode, linkedPackageIDs);
4100      getPackageChained(packageNode, linkedPackageIDs);
4101  
4102      // add nodes if they are not yet part of the array
4103      for (var i=0; i < linkedPackageIDs.length; i++) {
4104          var currentNode = getPackageNodeFromAnywhere(linkedPackageIDs[i]);
4105          if (currentNode != null) {
4106              if(!searchArray(packageArray, currentNode)) {
4107                  dinfo("Adding referenced package '" + getPackageName(currentNode) + "' (" +
4108                          getPackageID(currentNode) + ") for package '" +
4109                          getPackageName(packageNode) + "' (" + getPackageID(packageNode) +
4110                          ")");
4111                  // add the package first (so it's not added again, this prevents
4112                  // loops)
4113                  packageArray.push(currentNode);
4114  
4115                  // add dependencies of these package as well
4116                  getPackageReferences(currentNode, packageArray);
4117              } else {
4118                  dinfo("Referenced package '"  + getPackageName(currentNode) + "' (" +
4119                          getPackageID(currentNode) + ") for package '" +
4120                          getPackageName(packageNode) + "' (" + getPackageID(packageNode) +
4121                          ") already added.");
4122              }
4123          }
4124      }
4125  }
4126  
4127  /**
4128   * Returns the package version string from the given package XML node. Returns 0
4129   * if package has no revision specified.
4130   * 
4131   * @return String representing the package revision (might be a dot-separated
4132   *         version) <#>[.<#>]*
4133   */
4134  function getPackageRevision(packageNode) {
4135      var packageRevision = packageNode.getAttribute("revision");
4136      if (packageRevision == null) {
4137          // set to string "0" if no revision is defined
4138          packageRevision = 0 + "";
4139      } else {
4140          // check if the revision contains the "%" character (environment
4141          // variable)
4142          if( packageRevision.match(new RegExp("%.+%"), "ig") ) {
4143              // Generate the correct environment.
4144              var previousEnv = getEnv();
4145  
4146              // set package specific environment
4147              loadPackageEnv(packageNode);
4148  
4149              // expand environment strings
4150              var wshObject = new ActiveXObject("WScript.Shell");
4151              packageRevision = wshObject.ExpandEnvironmentStrings(packageRevision);
4152  
4153              // reset environment
4154              loadEnv(previousEnv);
4155          }
4156      }
4157      return packageRevision;
4158  }
4159  
4160  /**
4161   * Returns XML node which contains all packages (package database).
4162   */
4163  function getPackages() {
4164      if(packages == null) {
4165          var newPackages = createPackages();
4166          setPackages(newPackages);
4167      }
4168      return packages;
4169  }
4170  
4171  /**
4172   * Returns the action to be performed on a given package if the package is
4173   * applied to the current host.
4174   * Valid actions are:
4175   * "none"      No action; package installed already
4176   * "install"   Installation, package is new on the host
4177   * "upgrade"   Upgrade package which already exists on the system
4178   *             New version higher than installed version
4179   * "downgrade" Downgrade package which already exists on the system
4180   *             New version lower than installed version
4181   * 
4182   * @param packageNode
4183   *           The package to be checked.
4184   * @returns Action to be performed. Can be 0=nothing, 1=install, 2=upgrade, 3=downgrade.
4185   */
4186  function getPackageInstallAction(packageNode) {
4187      // Action to be performed when
4188      var actionNone = "none";
4189      var actionInstall = "install";
4190      var actionUpgrade = "upgrade";
4191      var actionDowngrade = "downgrade";
4192      var action = actionNone;
4193  
4194      var packageName = getPackageName(packageNode);
4195      var packageID   = getPackageID(packageNode);
4196      var packageRev  = getPackageRevision(packageNode);
4197      var executeAttr = getPackageExecute(packageNode);
4198      // var notifyAttr  = getPackageNotify(packageNode);
4199  
4200       // Search for the package in the local settings.
4201      var installedPackage = getSettingNode(packageID);
4202  
4203      // String to print in events which identifies the package.
4204      var packageMessage = "Package '" + packageName + "' (" + packageID + "): ";
4205  
4206      // Evaluate type of installation (install/upgrade/downgrade/none).
4207      // INSTALL:
4208      if (installedPackage == null) {
4209          // ONE-TIME INSTALL PACKAGE, NOT INSTALLED YET (according to settings)
4210          // Install the package after checking that it is not installed already.
4211          dinfo(packageMessage + "Not in local package database; Marking for installation.");
4212          action = actionInstall;
4213  
4214      // UPGRADE/DOWNGRADE:
4215      } else {
4216          // Get revision of installed package.
4217          var packageRevInstalled = getPackageRevision(installedPackage);
4218          // Compare Versions.
4219          var comparisonResult = versionCompare(packageRev, packageRevInstalled);
4220  
4221          if (comparisonResult > 0) {
4222              // ONE-TIME INSTALL PACKAGE, UPGRADE:
4223              info(packageMessage +
4224                  "Already installed but version mismatch.\n" +
4225                  "Installed revision: '" + packageRevInstalled + "'\n" +
4226                  "Available revision: '" + packageRev + "'.\n" +
4227                  "Preparing upgrade."
4228                  );
4229              action = actionUpgrade;
4230  
4231          } else if (comparisonResult < 0) {
4232              // ONE-TIME INSTALL PACKAGE, DOWNGRADE:
4233              info(packageMessage +
4234                  "Already installed but version mismatch.\n" +
4235                  "Installed revision '" + packageRevInstalled + "'\n" +
4236                  "Available revision: '" + packageRev + "'.\n" +
4237                  "Preparing downgrade."
4238                  );
4239              action = actionDowngrade;
4240  
4241          } else {
4242              // ONE-TIME INSTALL PACKAGE, ALREADY INSTALLED:
4243  
4244              if (executeAttr == "always") {
4245                  // ALWAYS EXECUTION PACKAGE
4246                  // Packages with exec attribute "always" will be installed on each run; regardless of their version.
4247                  dinfo(packageMessage + "Is requested to be executed 'always'. Preparing installation.");
4248                  action = actionInstall;
4249  
4250              } else if (isForceInstall()) {
4251                  // if installation is forced, install anyway
4252                  info(packageMessage + "Already installed. Re-installation enforced.");
4253                  action = actionInstall;
4254  
4255              } else {
4256                  // If execute is 'once' then package checks are not executed.
4257                  // We just trust that the package is installed.
4258                  if (executeAttr == "once") {
4259                      dinfo(packageMessage + "Installed already.");
4260                      action = actionNone;
4261                  } else {
4262                      // In case no execution attribute is defined
4263                      // check real package state.
4264                      if (getQueryMode() == "remote") {
4265                          // Assume package is properly installed.
4266                          action = actionNone;
4267                      } else {
4268                          // Verify that package is still installed.
4269                          if (isInstalled(installedPackage)) {
4270                              action = actionNone;
4271                          } else {
4272                              // Package found in local database but checks failed.
4273                              // Maybe the user uninstalled the package manually.
4274                              dinfo(packageMessage + "Installed but checks failed. Re-Installing.");
4275                              action = actionInstall;
4276                          }
4277                      }
4278                  }
4279              }
4280          }
4281      }
4282      return action;
4283  }
4284  
4285  /**
4286   * Returns list of packages which have been manually installed.
4287   * 
4288   * @returns List of packages manually installed in local settings database.
4289   *          Returns empty array if no package is found.
4290   */
4291  function getPackagesManuallyInstalled() {
4292      if (manuallyInstalled == null) {
4293          // Get list of currently installed packages.
4294          var settings = getSettings();
4295          
4296          // Filter manually installed packages.
4297          // Fetch command-nodes from <commands><command type="type" /></commands> structure.
4298          manuallyInstalled = settings.selectNodes("package[@manualInstall=\"true\"]");
4299  
4300          // Return empty array if no package is found.
4301          if (manuallyInstalled == null) {
4302              manuallyInstalled = new Array();
4303          }
4304      }
4305      return manuallyInstalled;
4306  }
4307  
4308  /**
4309   * Returns an array of packages which are not assigned to the current host any more.
4310   * 
4311   * Packages which are manually installed are not included in the list of packages
4312   * to be removed. Except if the package does not exist on server side any more.
4313   * Therefore in case a package is removed from the server it is removed from
4314   * clients as well even if the package was installed manually because it is to be
4315   * assumed tha the administrator no longer wants to support this type of software.
4316   *  
4317   * @return Array of packages which will be removed during synchronization.
4318   */
4319  function getPackagesRemoved() {
4320      dinfo("Evaluating packages to be removed.");
4321      /**
4322       * Get package nodes referenced within the profile (and profile
4323       * dependencies). This includes package dependencies as well.
4324       */
4325      var profilePackageNodes = getProfilePackageNodes();
4326  
4327      // Get list of currently installed packages.
4328      var installedPackages = getSettingNodes();
4329  
4330      // Array to store packages to be removed.
4331      var removablesArray = new Array();
4332  
4333      // Loop over each installed package and check whether it still applies.
4334      for (var iInstalledPkg = 0; iInstalledPkg < installedPackages.length; iInstalledPkg++) {
4335          var installedPackageNode = installedPackages[iInstalledPkg];
4336          dinfo("Found installed package '" + getPackageName(installedPackageNode) + "' (" +
4337                  getPackageID(installedPackageNode) + ").");
4338  
4339          // Search for the installed package in available packages.
4340          var found = false;
4341  
4342          for (var j=0; j < profilePackageNodes.length; j++) {
4343              var profilePackageNode = profilePackageNodes[j];
4344              if (getPackageID(installedPackageNode) == getPackageID(profilePackageNode)) {
4345                  dinfo("Package '" + getPackageName(installedPackageNode) + "' (" +
4346                          getPackageID(installedPackageNode) + ") found in profile packages.");
4347                  found = true;
4348                  break;
4349              }
4350          }
4351  
4352          // If package is no longer present, mark for remove if not installed manually.
4353          if (!found) {
4354              // Check if package was installed manually.
4355              // Manually installed packages remain on the system.
4356              var packageMessage = "Package '" + getPackageName(installedPackageNode) + "' (" +
4357              getPackageID(installedPackageNode) + "): ";
4358              var isManuallyInstalled = getPackageManualInstallation(installedPackageNode);
4359              if (isManuallyInstalled == true) {
4360                  if (isZombie(installedPackageNode)) {
4361                      // Package is not in server package database any more.
4362                      dinfo("Package was manually installed but is " +
4363                              "not in package database any more.  Marking package for removal.");
4364                      removablesArray.push(installedPackageNode);
4365                  } else {
4366                      dinfo("Package was manually installed and is " +
4367                              "still available in package database. Keeping package.");
4368                  }
4369              } else {
4370                  dinfo(packageMessage + "Marked for removal.");
4371                  removablesArray.push(installedPackageNode);
4372              }
4373          }
4374      }
4375  
4376      return removablesArray;
4377  }
4378  
4379  
4380  /**
4381   * Returns a list of variables for the given package.
4382   * 
4383   * @param packageNode
4384   *            The package node to get the variables from.
4385   * @param array
4386   *            Object of type Array to which the the variables appended.
4387   *            In case null is supplied it returns a new Array object.
4388   * @return Object of type Scripting.Dictionary which contains all key/value
4389   *         pairs from the given package including its dependencies
4390   */
4391  function getPackageVariables(packageNode, array) {
4392      dinfo("Reading variables from package '" + getPackageName(packageNode) + "'.");
4393      array = getVariables(packageNode, array);
4394      return array;
4395  }
4396  
4397  /**
4398   * Returns array of profile nodes which represent the profile dependencies.
4399   * Returns empty array in case the profile does not have any dependency.
4400   * 
4401   * @return Array of strings representing the references to dependent profiles
4402   */
4403  function getProfileDependencies(profileNode) {
4404      // output array
4405      var dependencyNodes = new Array();
4406  
4407      var dependNodes = profileNode.selectNodes("depends");
4408      if (dependNodes != null) {
4409          // Get only dependencies which match the current host.
4410          var matchingDependNodes = filterConditionalNodes(dependNodes, true);
4411          for (var i=0; i < matchingDependNodes.length; i++) {
4412              var dependencyId = matchingDependNodes[i].getAttribute("profile-id");
4413  
4414              // convert dependency to lower case if case-sensitive mode is off
4415              if (dependencyId != null && !isCaseSensitive()) {
4416                  dependencyId = dependencyId.toLowerCase();
4417              }
4418  
4419              // get the profile node
4420              var dependencyNode = getProfileNode(dependencyId);
4421              if (dependencyNode != null) {
4422                  dependencyNodes.push(dependencyNode);
4423              } else {
4424                  error("Profile '" + dependencyId + "' referenced but not " +
4425                          "found. Ignoring profile.");
4426              }
4427          }
4428      }
4429  
4430      return dependencyNodes;
4431  }
4432  
4433  /**
4434   * Returns the corresponding profile ID stored within the given profile XML
4435   * node.
4436   * 
4437   * @return String representing the ID of the supplied profile node.
4438   */
4439  function getProfileID(profileNode) {
4440      return profileNode.getAttribute("id");
4441  }
4442  
4443  /**
4444   * Returns an array of strings which represents the profiles directly referenced
4445   * by the applying host node. The profiles are evaluated as follows:
4446   * <pre>
4447   * - /profile:<profile> parameter
4448   * - /host:<hostname> parameter matching within hosts.xml
4449   * - profiles defined within host.xml which are assigned to the matching hosts entry
4450   * </pre>
4451   *
4452   * @return array of strings representing the referenced profiles
4453   */
4454  function getProfileList() {
4455      if (applyingProfilesDirect == null) {
4456          var profilesMatching = new Array();
4457  
4458          // get arguments
4459          var argn = getArgv().Named;
4460  
4461          // Set the profile from either the command line or the hosts file.
4462          if (argn("profile") != null) {
4463              profilesMatching.push(argn("profile"));
4464          } else {
4465              var hostNodes = getHostsApplying();
4466              for (var ihostNode=0; ihostNode < hostNodes.length; ihostNode++) {
4467                  profilesMatching = profilesMatching.concat(getHostProfiles(hostNodes[ihostNode]));
4468              }
4469              if (profilesMatching.length <= 0) {
4470                  throw new Error("Could not find any profile for host " + getHostname() + ".");
4471              }
4472          }
4473          applyingProfilesDirect = profilesMatching;
4474      }
4475      return applyingProfilesDirect;
4476  }
4477  
4478  /**
4479   * Returns the corresponding profile XML node from the profile database
4480   * (profile.xml). Returns null in case no such profile exists.
4481   * 
4482   * @param profileID
4483   *            String representation of profile to get the node from.
4484   */
4485  function getProfileNode(profileID) {
4486      // get first node which matched the specified ID
4487      return getProfiles().selectSingleNode("profile[@id='" + profileID +"']");
4488  }
4489  
4490  /**
4491   * Returns an array of all profile nodes available.
4492   * 
4493   * @return array of profile XML nodes.
4494   */
4495  function getProfileNodes() {
4496      // Retrieve packages.
4497      var profileNodes = getProfiles().selectNodes("profile");
4498  
4499      // make sure a package ID exists only once
4500      profileNodes = uniqueAttributeNodes(profileNodes, "id");
4501  
4502      // return this array
4503      return profileNodes;
4504  }
4505  
4506  /**
4507   * Returns an array of strings which contains a list of package IDs referenced
4508   * by the currently applied profile(s).
4509   * 
4510   * The list will contain all referenced IDs within profile.xml which apply to
4511   * the current profile(s) (including profile dependencies). Packages which are
4512   * referenced but do not exist within the package database (packages.xml) are
4513   * included as well. So be aware that in case of inconsistency between
4514   * profiles.xml and packages.xml it might be possible that the returned list
4515   * refers to packages not available within packages.xml.
4516   * 
4517   * NOTE: The list does NOT contain IDs of package dependencies. Just the list of
4518   * packages as referred in profiles.xml. Dependency information is only available
4519   * within the concrete package nodes within packages.xml. Refer to
4520   * getProfilePackageNodes() to get packages including dependencies.
4521   * 
4522   * If you like to get a list of full package nodes have a look at
4523   * getProfilePackageNodes() but note that it cannot return full nodes for
4524   * packages referenced within profiles.xml but missing in the package database.
4525   * 
4526   * @return array of package IDs applying to this profile (empty array if no
4527   *         packages are assigned).
4528   */
4529  function getProfilePackageIDs() {
4530      // Get array of all profiles that apply to the base profile.
4531      // This includes depending profiles
4532      var profileArray = getProfilesApplying();
4533  
4534      // Create array to store all referenced package IDs
4535      var packageIDs = new Array();
4536  
4537      // New date object, used for install/uninstall date comparison.
4538      var now = new Date();
4539  
4540      // Add each profile's package IDs to the array.
4541      for (var i=0; i < profileArray.length; i++) {
4542          profileNode = profileArray[i];
4543  
4544          // Load profile environment.
4545          var previousEnv = getEnv();
4546  
4547          // Array to store all variables found.
4548          var variables =  new Array();
4549  
4550          // Host variables first...
4551          variables = getHostsVariables(variables);
4552  
4553          // Get variables of this profile.
4554          variables = getVariables(profileNode, variables);
4555  
4556          // Apply variables to environment.
4557          for (var iVariable=0; iVariable < variables.length; iVariable++) {
4558              var varDefinition = variables[iVariable];
4559              var variableKeys = varDefinition.keys().toArray();
4560              for (var iVarKey = 0; iVarKey < variableKeys.length; iVarKey++) {
4561                  var key = variableKeys[iVarKey];
4562                  var value = varDefinition.Item(key);
4563                  setEnv(key, value);
4564              }
4565          }
4566  
4567          // Fetch packages from profile.
4568          var profilePackageNodes = profileNode.selectNodes("package");
4569          // Filter out packages which shall not apply to this host
4570          var packageNodes = filterConditionalNodes(profilePackageNodes, true);
4571  
4572          // Add all package IDs to the array and avoid duplicates
4573          for (var j = 0; j < packageNodes.length; j++) {
4574              // get package ID
4575              var packageNode = packageNodes[j];
4576              var packageId = packageNode.getAttribute("package-id");
4577              // Skip package if package ID is not defined.
4578              if (packageId == null || packageId == "") {
4579                  continue;
4580              }
4581              
4582              // Use package methods for profile package node because the
4583              // attribute is the same.
4584              var installDate = getProfilePackageInstallDate(packageNode);
4585              var uninstallDate = getProfilePackageUninstallDate(packageNode);
4586              var includePackage = true;
4587  
4588              // Check if package
4589              
4590              // Check if the package should be included regarding installation
4591              // period.
4592              if (installDate != null || uninstallDate != null) {
4593                  // either install or uninstall date was defined
4594                  if (now >= installDate &&
4595                      now <= uninstallDate) {
4596                      includePackage = true;
4597                      dinfo("Package'" + packageId + "' specified an install date range: " +
4598                          installDate + " to " + uninstallDate +
4599                          "; current time (" + now + ") is within the time frame. Including package.");
4600                  } else {
4601                      includePackage = false;
4602                      dinfo("Package '" + packageId + "' specified an install date range: " +
4603                          installDate + " to " + uninstallDate +
4604                          "; out of range, skipping package (local time: " + now + ").");
4605                  }
4606              }
4607  
4608              // Search array for pre-existing ID, we don't want duplicates.
4609              if (includePackage) {
4610                  // Check if package shall be included case-sensitive. If not;
4611                  // convert to lower-case.
4612                  if (!isCaseSensitive()) {
4613                      packageId = packageId.toLowerCase();
4614                  }
4615                  var alreadyAdded = false;
4616                  for (var k=0; k < packageIDs.length; k++) {
4617                      if (packageIDs[k] == packageId) {
4618                          alreadyAdded = true;
4619                          break;
4620                      }
4621                  }
4622                  if (!alreadyAdded) {
4623                      packageIDs.push(packageId);
4624                  }
4625              }
4626          }
4627          // Restore environment.
4628          loadEnv(previousEnv);
4629      }
4630  
4631      return packageIDs;
4632  }
4633  
4634  /**
4635   * Returns date object reflecting installation date defined in given node
4636   * 
4637   * @param packageNode
4638   *            the package definition node as specified within the profile
4639   *            definition
4640   * @return date object representing installation date. Null if date is undefined.
4641   */
4642  function getProfilePackageInstallDate(packageNode) {
4643      var installDate = null;
4644      var packageInstallDate = packageNode.getAttribute("installdate");
4645      if (packageInstallDate != null) {
4646          installDate = parseISODate(packageInstallDate, false);
4647      }
4648      return installDate;
4649  }
4650  
4651  /**
4652   * Returns an array of package nodes that should be applied to the current
4653   * profile. This function returns full package nodes.
4654   *
4655   * NOTE: Since the profile
4656   * just contains the package IDs referenced within profiles.xml but not
4657   * existing within the packages database (packages.xml) will not be part of the
4658   * list.
4659   * 
4660   * In case you like to get a list of package IDs referenced by the profile
4661   * (regardless if the package definition exists) have a look at
4662   * getProfilePackageIDs().
4663   * 
4664   * @return array of package nodes applying to the assigned profile(s)
4665   */
4666  function getProfilePackageNodes() {
4667      if (profilePackageNodes == null) {
4668          // Create a new empty package array.
4669          packageNodes = new Array();
4670  
4671          /*
4672           * get package IDs which apply to the profile (without dependencies, includes and chained packages) regardless
4673           * if the package definition is available or not.
4674           */
4675          var packageIDs = getProfilePackageIDs();
4676  
4677          // get package definitions and all dependencies
4678          for ( var i = 0; i < packageIDs.length; i++) {
4679              var packageID = packageIDs[i];
4680              dinfo("Adding package with ID '" + packageID + "' to profile packages.");
4681              var packageNode = getPackageNodeFromAnywhere(packageID);
4682  
4683              // add dependencies first
4684              if (packageNode != null) {
4685                  getPackageReferences(packageNode, packageNodes);
4686                  if (!searchArray(packageNodes, packageNode)) {
4687                      // Add the new node to the array _after_ adding dependencies.
4688                      packageNodes.push(packageNode);
4689                  }
4690              }
4691          }
4692          profilePackageNodes = packageNodes;
4693      }
4694      return profilePackageNodes;
4695  }
4696  
4697  /**
4698   * Returns Date representation of 'uninstalldate' attribute from the given
4699   * package definition as specified within the profile.
4700   * 
4701   * @param packageNode
4702   *            the package node to read the 'uninstalldate' attribute from
4703   * @return Date object representing uninstall date of the given package. Returns
4704   *         null in case the 'uninstalldate' attribute is not set.
4705   */
4706  function getProfilePackageUninstallDate(packageNode) {
4707      var uninstallDate = null;
4708      var packageUninstallDate = packageNode.getAttribute("uninstalldate");
4709      if (packageUninstallDate != null) {
4710          uninstallDate = parseISODate(packageUninstallDate, true);
4711      }
4712      return uninstallDate;
4713  }
4714  
4715  /**
4716   * Returns XML node which contains all profiles (profile database).
4717   */
4718  function getProfiles() {
4719      if(profiles == null) {
4720          var newProfiles = createProfiles();
4721          setProfiles(newProfiles);
4722      }
4723      return profiles;
4724  }
4725  
4726  /**
4727   * Returns an array of profile nodes that should be applied to the current
4728   * profile. This includes also all profile dependencies.
4729   * 
4730   * @return array of profiles (directly associated profiles and dependencies)
4731   */
4732  function getProfilesApplying() {
4733      dinfo("Getting profiles which apply to this node.");
4734      if (applyingProfilesAll == null) {
4735          // create cache entry
4736          var profilesApplying = new Array();
4737  
4738          // get list of applying profiles
4739          var profileList = getProfileList();
4740  
4741          for (var i=0; i<profileList.length; i++) {
4742              // receive profile node
4743              var profileNode = getProfileNode(profileList[i]);
4744  
4745              if (profileNode != null) {
4746                  dinfo("Applying profile: " + getProfileID(profileNode));
4747  
4748                  // Add the current profile's node as the first element in the
4749                  // array.
4750                  profilesApplying.push(profileNode);
4751  
4752                  appendProfileDependencies(profilesApplying, profileNode);
4753              } else {
4754                  error("Profile '" + profileList[i] + "' applies to this host but was not found!");
4755              }
4756          }
4757          applyingProfilesAll = profilesApplying;
4758      }
4759      return applyingProfilesAll;
4760  }
4761  
4762  /**
4763   * Returns the log level associated with a given profile.
4764   * 
4765   * @return merged log levels from all applying profiles. For example if one
4766   *         profile specifies info logging and a second profile specifies error.
4767   *         The resulting log level will be info+error. Returns null if no custom
4768   *         log level is specified for this profile.
4769   */
4770  function getProfilesLogLevel() {
4771      // set initial bitmask to 0x00;
4772      var logLevel = 0x00;
4773  
4774      // merge log levels
4775      try {
4776          var profileList = getProfileList();
4777          for (var i=0; i<profileList.length; i++) {
4778              var profileId = profileList[i];
4779              var profileNode = getProfileNode(profileId);
4780              if (profileNode != null) {
4781                  // add bitmask
4782                  logLevel = logLevel | profileNode.getAttribute("logLevel");
4783              }
4784          }
4785      } catch (e) {
4786          // Unable to read profile-specific log leve.
4787          // Maybe there is no profile found for this host.
4788          dinfo("No profile-specific log level found.");
4789      }
4790      if (logLevel > 0x00) {
4791          return logLevel;
4792      } else {
4793          return null;
4794      }
4795  }
4796  
4797  /**
4798   * Returns a list of variables from the Profile.
4799   * 
4800   * @param array
4801   *            Object of type Array to which the the variables are appended.
4802   *            In case null is supplied it returns a new Array object.
4803   * @return Object of type Scripting.Dictionary which contains all key/value
4804   *         pairs from the given profile including its dependencies
4805   */
4806  function getProfileVariables(array) {
4807      dinfo("Reading variables from profile[s]");
4808      if (profilesVariables == null) {
4809          profilesVariables = new Array();
4810          var profileArray = getProfilesApplying();
4811          // dinfo(profileArray.length + " profiles apply to this host.");
4812  
4813          /*
4814           * add each profile's variables to the array in reverse order reversing the order is done in order to allow
4815           * overwriting of variables from dependent profiles
4816           */
4817  
4818          for (var iProfiles=profileArray.length-1; iProfiles >= 0; iProfiles--) {
4819              var profileNode = profileArray[iProfiles];
4820              dinfo("Reading variables from profile " + getProfileID(profileNode));
4821  
4822              // Add variables from profile XML node.
4823              profilesVariables = getVariables(profileNode, profilesVariables);
4824          }
4825      }
4826  
4827      var concatenatedVariables = profilesVariables;
4828      if (array != null) {
4829          concatenatedVariables = profilesVariables.concat(array);
4830      }
4831  
4832      return concatenatedVariables;
4833  }
4834  
4835  /**
4836   * Returns current state of query mode.
4837   * @returns {String} Current query mode.
4838   */
4839  function getQueryMode() {
4840      return queryMode;
4841  }
4842  
4843  /**
4844   * Returns the corresponding package XML node from the settings database
4845   * (wpkg.xml). Returns null in case no such package is installed.
4846   * 
4847   * @param packageID
4848   *            ID of the package to be returned
4849   * @return returns package XML node as stored within the settings. Returns null
4850   *         if no such package exists.
4851   */
4852  function getSettingNode(packageID) {
4853      // get first node which matched the specified ID
4854      return getSettings().selectSingleNode("package[@id='" + packageID +"']");
4855  }
4856  
4857  
4858  /**
4859   * Tries to read host attributes from the settings database.
4860   * All host attributes found in the settings database will be used to override
4861   * attributes of the local host.
4862   */
4863  function getSettingHostAttributes() {
4864      // Fetch settings.
4865      var settings = getSettings();
4866      var attributes = settings.attributes;
4867  
4868      // Check whether attributes are defined.
4869      if (attributes.length > 0) {
4870  
4871          // Reset cache for host information.
4872          resetHostInformationCache();
4873          
4874          for (var iAttribute=0; iAttribute < attributes.length; iAttribute++) {
4875              var node = attributes.item(iAttribute);
4876              var attribute = node.nodeName;
4877  
4878              var value = node.nodeValue;
4879              switch (attribute) {
4880              case "hostname":
4881                  setHostname(value);
4882                  break;
4883  
4884              case "architecture":
4885                  setArchitecture(value);
4886                  break;
4887  
4888              case "os":
4889                  setHostOS(value);
4890                  break;
4891  
4892              case "ipaddresses":
4893                  var ipList = value.split(",");
4894                  setIPAddresses(ipList);
4895                  break;
4896  
4897              case "domainname":
4898                  setDomainName(value);
4899                  break;
4900  
4901              case "groups":
4902                  var hostGroupList = value.split(",");
4903                  setHostGroups(hostGroupList);
4904                  break;
4905  
4906              case "lcid":
4907                  setLocale(value);
4908                  break;
4909  
4910              case "lcidOS":
4911                  setLocaleOS(value);
4912                  break;
4913  
4914              default:
4915                  break;
4916              }
4917          }
4918      }
4919  }
4920  
4921  /**
4922   * Returns an array of all installed packages from the local wpkg.xml
4923   * 
4924   * @return Array of installed packages (XML nodes)
4925   */
4926  function getSettingNodes() {
4927      // retrieve packages
4928      var packageNodes = getSettings().selectNodes("package");
4929  
4930      // make sure a package ID exists only once
4931      // commented since the local database should not contain duplicated entries
4932      packageNodes = uniqueAttributeNodes(packageNodes, "id");
4933  
4934      // return this array
4935      return packageNodes;
4936  }
4937  
4938  /**
4939   * Returns current path to settings file.
4940   * 
4941   * @returns Settings file FS object.
4942   */
4943  function getSettingsPath() {
4944      if (settings_file == null || settings_file == "") {
4945          // Will be used for file operations.
4946          var fso = new ActiveXObject("Scripting.FileSystemObject");
4947  
4948          // Evaluate path.
4949          // Our default settings file is located in %SystemRoot%\system32.
4950          // If settings path was not specified via command line, then evaluate it
4951          // from the configuration file or fall back to default.
4952          if (settings_file_path == null) {
4953              var SystemFolder = 1;
4954              settings_file_path = fso.GetSpecialFolder(SystemFolder);
4955          }
4956          settings_file = settings_file_path + "\\" + settings_file_name;
4957          settings_file_processed = false;
4958      }
4959  
4960      if (!settings_file_processed) {
4961          // Check whether [PROFILE] epxression was used and repace it.
4962          var profileExp = new RegExp("\\[PROFILE\\]", "g");
4963          if (profileExp.test(settings_file) == true) {
4964              // This will throw an error if profile is not available yet.
4965              var profileList = getProfileList();
4966      
4967              // concatenate profile names or throw error if no names
4968              // available
4969              if (profileList.length > 0) {
4970                  var allProfiles = "";
4971                  for (var i=0; i<profileList.length; i++) {
4972                      if (allProfiles == "") {
4973                          allProfiles = profileList[i];
4974                      } else {
4975                          allProfiles += "-" + profileList[i];
4976                      }
4977                  }
4978                  settings_file = settings_file.replace(profileExp, allProfiles);
4979              } else {
4980                  throw new Error("Profile information not available.");
4981              }
4982          }
4983      
4984          // Check whether [HOSTNAME] expression was used and replace it.
4985          var hostnameExp = new RegExp("\\[HOSTNAME\\]", "g");
4986          if (hostnameExp.test(settings_file) == true) {
4987              settings_file = settings_file.replace(hostnameExp, getHostname());
4988          }
4989      }
4990  
4991      return settings_file;
4992  }
4993  
4994  /**
4995   * Returns XML node which contains all settings (local package database).
4996   */
4997  function getSettings() {
4998      if(settings == null) {
4999          var newSettings = createSettings();
5000          setSettings(newSettings, true);
5001      }
5002      return settings;
5003  }
5004  
5005  /**
5006   * Returns the checkResults node of the settings database.
5007   * 
5008   * @returns checkResults node of currently loaded settings database.
5009   */
5010  function getSettingsCheckResults() {
5011      var currentSettings = getSettings();
5012      var checkResults = currentSettings.selectSingleNode("checkResults");
5013      if (checkResults == null) {
5014          var xmlDoc = new ActiveXObject("Msxml2.DOMDocument.3.0");
5015          checkResults = xmlDoc.createElement("checkResults");
5016          currentSettings.appendChild(checkResults);
5017      }
5018      return checkResults;
5019  }
5020  
5021  /**
5022   * Adds the given check node to the checkResults list in the settings database.
5023   * 
5024   * @param checkNode Check XML node to be inserted.
5025   * @param result Result of the check on current node.
5026   */
5027  function addSettingsCheckResult(checkNode, result) {
5028      try {
5029          // Clone XML node to be added to settings.
5030          var settingsCheckNode = checkNode.cloneNode(false);
5031  
5032          // Check if there is already a check with the same attributes.
5033          var previousChecks = getSettingsCheck(settingsCheckNode);
5034  
5035          // Fetching checkResults node from settings.
5036          var checkResults = getSettingsCheckResults();
5037      
5038          // If a check was found then remove it from the results in order to avoid
5039          // duplicate entries. Checks might also be executed multiple times (with
5040          // potentially different results) and only the last result should be kept.
5041          if (previousChecks != null) {
5042              for (var i=0; i < previousChecks.length; i++) {
5043                  dinfo("Replacing check results of previous evaluation");
5044                  var previousCheck = previousChecks[i];
5045                  checkResults.removeChild(previousCheck);
5046              }
5047          }
5048      
5049          // Add result attribute.
5050          var resultValue = "false";
5051          if(result != null && result == true) {
5052              resultValue = "true";
5053          }
5054          settingsCheckNode.setAttribute("result", resultValue);
5055          
5056          // Add check results node.
5057          checkResults.appendChild(settingsCheckNode);
5058      
5059          // Save modified settings.
5060          saveSettings(false);
5061      } catch (e) {
5062          error("Unable to add result of check to settings: " + e.message);
5063      }
5064  }
5065  
5066  /**
5067   * Returns result of pre-evaluated check from settings node.
5068   * 
5069   * @param checkNode the check node for which to look in the settings
5070   * "checkResults" nodes to verify if the check has been executed already.
5071   * 
5072   * @returns result of already evaluated check. Returns null if the check has
5073   *          not been evaluated and saved to settings node before.
5074   */
5075  function getSettingsCheckResult(checkNode) {
5076      var result = null;
5077      var previousChecks = getSettingsCheck(checkNode);
5078      if (previousChecks != null) {
5079          // Get latest check result.
5080          var previousCheck = previousChecks[previousChecks.length-1];
5081          var checkResult = previousCheck.getAttribute("result");
5082          if (checkResult != null && checkResult == "true") {
5083              result = true;
5084          } else {
5085              result = false;
5086          }
5087          dinfo("Found previously executed check with result '" + result + "'.");
5088      }
5089      return result;
5090  }
5091  
5092  /**
5093   * Takes a check as a parameter and looks for the same check in the local
5094   * settings database. If an identical check with results is found, then this
5095   * check is returned in an array. Returns null if no identical check could be
5096   * found in the local settings database.
5097   * 
5098   * @param checkNode check to seek for in local settings databse.
5099   *  
5100   * @returns Array of matching checks; returns null if no check match.
5101   */
5102  function getSettingsCheck(checkNode) {
5103      if (checkNode == null) {
5104          return null;
5105      }
5106      var result = null;
5107      var currentSettings = getSettings();
5108  
5109      var checkResults = currentSettings.selectSingleNode("checkResults");
5110  
5111      if (checkResults != null) {
5112          var attributes = checkNode.attributes;
5113  
5114          // Check whether attributes are defined.
5115          if (attributes.length > 0) {
5116              var attributesClause = "";
5117              var checkMessage = "";
5118              for (var iAttribute=0; iAttribute < attributes.length; iAttribute++) {
5119                  if (attributesClause != "") {
5120                      attributesClause += " and ";
5121                      checkMessage += ", ";
5122                  }
5123                  var node = attributes.item(iAttribute);
5124                  var attribute = node.nodeName;
5125                  var value = node.nodeValue.replace(new RegExp("\\\\", "g"), "\\\\");
5126                  attributesClause += "@" + attribute + "=\"" + value + "\"";
5127                  checkMessage += attribute + "='" + value +"'";
5128              }
5129              // Get all nodes which match the attributes.
5130              dinfo("Searching for previously executed checks with attributes " + checkMessage);
5131              var xPathQuery = "check[" + attributesClause + "]";
5132              // dinfo("Query to find previously executed check: " + xPathQuery);
5133              var checkNodes = checkResults.selectNodes(xPathQuery);
5134  
5135              if (checkNodes != null) {
5136                  // Make sure the check nodes found do not contain any attributes
5137                  // not present in comparison node.
5138                  for (var iCheck=0; iCheck < checkNodes.length; iCheck++) {
5139                      var checkNode = checkNodes[iCheck];
5140                      // dinfo("Found previously executed check: " + checkNode.xml);
5141                      var checkAttributes = checkNode.attributes;
5142                      var allAttrFound = true;
5143                      
5144                      // Iterate over all attributes of the check node found and
5145                      // verify that the attribute is found in comparison node.
5146                      // (Except the result attribute)
5147                      for (var iAttr=0; iAttr < checkAttributes.length; iAttr++) {
5148                          var attrFound = false;
5149                          var checkAttrSettings = checkAttributes.item(iAttr).nodeName;
5150  
5151                          if (checkAttrSettings == "result") {
5152                              attrFound = true;
5153                          } else {
5154                              for (var iRefAttr=0; iRefAttr < attributes.length; iRefAttr++) {
5155                                  var checkAttrRef = attributes.item(iRefAttr).nodeName;
5156                                  if (checkAttrRef == checkAttrSettings) {
5157                                      attrFound = true;
5158                                      break;
5159                                  }
5160                              }
5161                          }
5162  
5163                          // If attribute has not been found in comparison node
5164                          // Then the node contains different checks.
5165                          if (attrFound == false) {
5166                              allAttrFound = false;
5167                              break;
5168                          }
5169                      }
5170                      
5171                      // If all attributes were found in original query node then
5172                      // the check is identical to the one in the settings DB.
5173                      if (allAttrFound) {
5174                          if (result == null) {
5175                              result = new Array();
5176                          }
5177                          result.push(checkNode);
5178                      }
5179                  }
5180              }
5181              if (result != null) {
5182                  dinfo("Found " + result.length + " previously executed checks.");
5183              } else {
5184                  dinfo("Unable to find any previously executed checks with these attributes.");
5185              }
5186          }
5187      }
5188      return result;
5189  }
5190  
5191  
5192  /**
5193   * Returns a list of package nodes (Array object) which have been scheduled for
5194   * removal but are not removed due to the /noremove flag.
5195   * 
5196   * @return Array of package nodes which would have been removed during this
5197   *         session
5198   */
5199  function getSkippedRemoveNodes() {
5200      if (skippedRemoveNodes == null) {
5201          skippedRemoveNodes = new Array();
5202      }
5203      return skippedRemoveNodes;
5204  }
5205  
5206  /**
5207   * Returns a list of key/value pairs representing all variable definitions from
5208   * the given XML node.
5209   * 
5210   * @param XMLNode
5211   *            The XML node to get the variables from
5212   * @param array
5213   *            Object of type Array to which the the variables are appended.
5214   *            In case null is supplied it returns a new Array object.
5215   *            Each array element is a dictionary object containing a key and
5216   *            a value. The key is the variable name and the value is the
5217   *            variable value to be assigned.
5218   * @return Object of type Scripting.Dictionary which contains all key/value
5219   *         pairs from the given XML node.
5220   */
5221  function getVariables(XMLNode, array) {
5222      // a new empty array of variables
5223      var variables = null;
5224  
5225      // make sure variables is either created or assigned
5226      if(array == null) {
5227          // variables = new ActiveXObject("Scripting.Dictionary");
5228          variables = new Array();
5229      } else {
5230          variables = array;
5231      }
5232  
5233      var variableNodes = XMLNode.selectNodes("variable");
5234  
5235      // Perform host matching on variables.
5236      variableNodes = filterConditionalNodes(variableNodes, true);
5237  
5238      for (var i=0; i < variableNodes.length; i++) {
5239          var variableName = variableNodes[i].getAttribute("name");
5240          var variableValue = variableNodes[i].getAttribute("value");
5241  
5242          if (variableName == null || variableValue == null) {
5243              error("Incomplete variable specification found. " +
5244                      "Variable name is '" + variableName + "' and variable value is '" +
5245                      variableValue + "'. Ignoring variable.");
5246              continue;
5247          }
5248          
5249          // Expand environment variables in value.
5250          // variableValue = shell.ExpandEnvironmentStrings(variableValue);
5251          dinfo("Got variable '" + variableName + "' of value '" + variableValue + "'");
5252  
5253          var variable = new ActiveXObject("Scripting.Dictionary");
5254          variable.Add(variableName, variableValue);
5255          
5256          // Add to variables list.
5257          variables.push(variable);
5258      }
5259  
5260      return variables;
5261  }
5262  
5263  /**
5264   * Installs the specified package node to the system. If an old package node is
5265   * supplied performs an upgrade. In case the old package node is null an
5266   * installation is performed.
5267   * 
5268   */
5269  function installPackage(packageNode) {
5270      // Initialize return value.
5271      var success = false;
5272      
5273      var packageName = getPackageName(packageNode);
5274      var packageID   = getPackageID(packageNode);
5275      var packageRev  = getPackageRevision(packageNode);
5276      var executeAttr = getPackageExecute(packageNode);
5277      var notifyAttr  = getPackageNotify(packageNode);
5278      var rebootAttr  = getPackageReboot(packageNode);
5279  
5280      // Get check policies.
5281      var installCheckPolicy = getPackagePrecheckPolicyInstall(packageNode);
5282      var upgradeCheckPolicy = getPackagePrecheckPolicyUpgrade(packageNode);
5283      var downgradeCheckPolicy = getPackagePrecheckPolicyDowngrade(packageNode);
5284  
5285      dinfo("Going to install package '" + packageName + "' (" + packageID +
5286          "), Revision " + packageRev + ", (execute flag is '" + executeAttr +
5287          "', notify flag is '" + notifyAttr + "').");
5288  
5289       // search for the package in the local settings
5290      var installedPackage = getSettingNode(packageID);
5291  
5292      // Check if package is manually installed.
5293      if (installedPackage != null) {
5294          var isManual = getPackageManualInstallation(installedPackage);
5295          if (isManual == true) {
5296              // Transfer manual install flag to new package.
5297              setPackageManualInstallation(packageNode, true);
5298          }
5299      }
5300      
5301      
5302      // if set then the package installation will be bypassed
5303      var bypass = false;
5304      // type of installation "install" or "upgrade"
5305      var typeInstall = "install";
5306      var typeUpgrade = "upgrade";
5307      var typeDowngrade = "downgrade";
5308      var installType = typeInstall;
5309  
5310      // string to print in events which identifies the package
5311      var packageMessage = "Package '" + packageName + "' (" + packageID + ")" +
5312                          ": ";
5313  
5314      // check if the package has been executed already
5315      if(searchArray(packagesInstalled, packageNode)) {
5316          // has been installed already during this session
5317          dinfo(packageMessage +
5318                  "Already installed once during this session.\n" +
5319                  "Checking if package is properly installed.");
5320          bypass=true;
5321  
5322          // check if installation of package node was successful
5323          if ((installedPackage != null) &&
5324              (versionCompare(getPackageRevision(installedPackage), packageRev) >= 0)) {
5325              // package successfully installed
5326              dinfo(packageMessage + "Verified; " +
5327                  "package successfully installed during this session.");
5328  
5329              success = true;
5330  
5331          } else {
5332              dinfo(packageMessage +
5333                  "Installation failed during this session.");
5334              // package installation must have been failed
5335  
5336              success = false;
5337  
5338          }
5339      } else {
5340          // mark package as processed
5341          packagesInstalled.push(packageNode);
5342  
5343          dinfo(packageMessage + "Not yet processed during this session.");
5344          bypass = false;
5345          // evaluate what do do with the package
5346  
5347          // Get action of package to be installed.
5348          var packageAction = getPackageInstallAction(packageNode);
5349          
5350          // Evaluate installation actions.
5351          switch (packageAction) {
5352          case "none":
5353              // No package actions shall be performed.
5354              dinfo(packageMessage + "Already installed.");
5355              installType = typeUpgrade;
5356              bypass = true;
5357              success = true;
5358              break;
5359  
5360          case "install":
5361              // Package needs to be installed.
5362              dinfo(packageMessage + "Prepared for installation.");
5363              installType = typeInstall;
5364              bypass = false;
5365              success = false;
5366  
5367              // If execute attribute is set to "always" just continue with installation.
5368              if (executeAttr == "always") {
5369                  break;
5370              }
5371              if (installCheckPolicy == "never") {
5372                  // Checks shall be bypassed and package is installed in any case.
5373                  dinfo(packageMessage + "Skipping checks whether package is already installed.");
5374              } else {
5375                  // Default is to execute checks first in order to evaluate if
5376                  // package is already installed.
5377                  if (isInstalled(packageNode)) {
5378                      info(packageMessage +
5379                          "Already installed (checks succeeded). Checking dependencies and chained packages.");
5380  
5381                      // append new node to local xml
5382                      addSettingsNode(packageNode, true);
5383  
5384                      // install all dependencies
5385                      var depSuccess = installPackageReferences(packageNode, "dependencies");
5386                      if (depSuccess) {
5387                          info(packageMessage +
5388                               "Package and all dependencies are already installed. Skipping.");
5389  
5390                      } else {
5391                          info(packageMessage +
5392                              "Installed but at least one dependency is missing.");
5393                      }
5394  
5395                      // install all chained packages
5396                      var chainedSuccess = installPackageReferences(packageNode, "chained");
5397                      if (chainedSuccess) {
5398                          info(packageMessage +
5399                               "Package and all chained packages are already installed. Skipping.");
5400  
5401                      } else {
5402                          info(packageMessage +
5403                          "Installed but at least one chained package is missing.");
5404                      }
5405  
5406                      // Bypass installation as installations seems to be done already.
5407                      bypass = true;
5408                      installType = typeInstall;
5409  
5410                      // Still set success to true since the package seems to be
5411                      // installed properly (check succeed).
5412                      success = true;
5413  
5414                  } else {
5415                      // Package not installed yet. Perform normal installation.
5416                      info(packageMessage +
5417                          "Not installed (checks failed). Preparing installation.");
5418                  }
5419              }
5420              break;
5421  
5422          case "upgrade":
5423              // Package needs to be upgraded.
5424              dinfo(packageMessage + "Prepared for upgrade.");
5425              installType = typeUpgrade;
5426              bypass = false;
5427              success = false;
5428  
5429              // If check policy is set to "always" then verify if the upgrade
5430              // might have been performed already.
5431              if (upgradeCheckPolicy == "always" && isInstalled(packageNode)) {
5432                  // Package marked for upgrade but upgrade is not necessary.
5433                  dinfo(packageMessage +
5434                          "Forced checks on upgrades succeeded. Package already up to date.");
5435  
5436                  // Update local package database.
5437                  addSettingsNode(packageNode, true);
5438  
5439                  // Package does not need to be upgraded.
5440                  bypass = true;
5441                  success = true;
5442              }
5443              break;
5444  
5445          case "downgrade":
5446              // Package needs to be downgraded.
5447              dinfo(packageMessage + "Prepared for downgrade.");
5448              installType = typeDowngrade;
5449              bypass = false;
5450              success = false;
5451  
5452              // If check policy is set to "always" then verify if the downgrade
5453              // might have been performed already.
5454              if (downgradeCheckPolicy == "always" && isInstalled(packageNode)) {
5455                  dinfo(packageMessage +
5456                      "Forced checks on downgrade succeeded. Package already downgraded.");
5457  
5458                  // Update local package database.
5459                  addSettingsNode(packageNode, true);
5460  
5461                  // Package does not need to be downgraded.
5462                  bypass = true;
5463                  success = true;
5464              }
5465              break;
5466  
5467          default:
5468              bypass = true;
5469              error("Unknown package action: " + packageAction);
5470              break;
5471          }
5472      }
5473  
5474      if (!bypass) {
5475          // Store current environment.
5476          var previousEnv = getEnv();
5477  
5478          try {
5479              // install dependencies
5480              var depInstallSuccess = installPackageReferences(packageNode, "dependencies");
5481  
5482              // abort installation in case dependencies could not be installed
5483              if (!depInstallSuccess) {
5484                  throw new Error("Installing dependencies failed");
5485              }
5486  
5487              // print event log entry
5488              info("Installing '" + packageName + "' (" + packageID + ")...");
5489              logStatus("Performing operation (" + installType + ") on '" + packageName + "' (" + packageID + ")");
5490  
5491              // stores if the package needs a reboot after installation
5492              var rebootRequired = false;
5493  
5494              // stores if the package needs a reboot after installing all
5495              // packages
5496              var rebootPostponed = false;
5497  
5498              // Generate the correct environment.
5499  
5500              // Set package specific environment.
5501              loadPackageEnv(packageNode);
5502  
5503              // Select command lines to install.
5504              var cmds;
5505              dinfo("Install type: " + installType);
5506              if (installType == typeUpgrade) {
5507                  // installation is an upgrade
5508                  cmds = getPackageCmdUpgrade(packageNode, null);
5509                  dinfo("Fetched " + cmds.length + " upgrade command(s).");
5510              } else if (installType == typeDowngrade) {
5511                  // prepare downgrade
5512                  cmds = getPackageCmdDowngrade(packageNode, null);
5513                  dinfo("Fetched " + cmds.length + " downgrade command(s).");
5514              }else {
5515                  // installation is default
5516                  cmds = getPackageCmdInstall(packageNode, null);
5517                  dinfo("Fetched " + cmds.length + " install command(s).");
5518              }
5519  
5520              // Get downloads from package node (if any).
5521              var downloadNodes = getDownloads(packageNode, null);
5522              // Append downloads from command node.
5523              for (var iCommands = 0; iCommands < cmds.length; iCommands++) {
5524                  var commandNode = cmds[iCommands ];
5525                  getDownloads(commandNode, downloadNodes);
5526              }
5527  
5528              // Download all specified downloads.
5529              var downloadResult = downloadAll(downloadNodes);
5530              if (downloadResult != true) {
5531                  var failureMessage = "Failed to download all files.";
5532                  if (isQuitOnError()) {
5533                      throw new Error(failureMessage);
5534                  } else {
5535                      error(failureMessage);
5536                  }
5537              }
5538  
5539              // execute each command line
5540              for (var iCmd = 0; iCmd < cmds.length; iCmd++) {
5541                  // execute commands
5542                  var cmdNode = cmds[iCmd];
5543                  var cmd = getCommandCmd(cmdNode);
5544                  if(cmd == null) {
5545                      error("Error: Command missing. Please fix the package. Ignoring command.");
5546                      continue;
5547                  }
5548                  var timeout = getCommandTimeout(cmdNode);
5549                  var workdir = getCommandWorkdir(cmdNode);
5550  
5551                  // mark system as changed (command execution in progress)
5552                  setSystemChanged();
5553                  if (notifyAttr) {
5554                      // notify user about start of installation
5555                      notifyUserStart();
5556                  }
5557  
5558                  var result = 0;
5559                  result = exec(cmd, timeout, workdir);
5560  
5561                  // search for exit code
5562                  var exitAction = getCommandExitCodeAction(cmdNode, result);
5563  
5564                  // check for special exit codes
5565                  if (exitAction != null) {
5566                      if (exitAction == "reboot") {
5567                          // This exit code forces a reboot.
5568                          info("Command in installation of " + packageName +
5569                              " returned exit code [" + result + "]. This " +
5570                              "exit code requires an immediate reboot.");
5571                          reboot();
5572                      } else if (exitAction == "delayedReboot") {
5573                          // This exit code schedules a reboot
5574                          info("Command in installation of " + packageName +
5575                              " returned exit code [" + result + "]. This " +
5576                              "exit code schedules a reboot.");
5577                          // schedule reboot
5578                          rebootRequired = true;
5579                          // proceed with next command
5580                          continue;
5581                      } else if (exitAction == "postponedReboot") {
5582                          info("Command in installation of " + packageName +
5583                              " returned exit code [" + result + "]. This " +
5584                              "exit code schedules a postponed reboot.");
5585                          rebootPostponed = true;
5586                          setPostponedReboot(rebootPostponed);
5587                          // execute next command
5588                          continue;
5589                      } else {
5590                          // this exit code is successful
5591                          info("Command in installation of " + packageName +
5592                              " returned exit code [" + result + "]. This " +
5593                              "exit code indicates success.");
5594                          // execute next command
5595                          continue;
5596                      }
5597                  } else if(result == 0) {
5598                      // if exit code is 0, return success
5599                      // execute next command
5600                      dinfo("Command in installation of " + packageName +
5601                          " returned exit code [" + result + "]. Success.");
5602                      continue;
5603                  } else {
5604                      // command did not succeed, throw an error
5605                      throw new Error("Exit code returned non-successful value (" +
5606                              result + ") on command '" + cmd + "'");
5607                  }
5608              }
5609  
5610              // packages with checks have to pass the isInstalled() test
5611              if (getChecks(packageNode).length > 0 && !isInstalled(packageNode)) {
5612                  // package failed for now
5613                  success = false;
5614  
5615                  // check if a delayed reboot has been scheduled
5616                  // if reboot is scheduled it might be OK if the package check
5617                  // fails
5618                  if (rebootRequired || rebootAttr == "true") {
5619                      warning("Package processing (" + installType + ") failed for package " +
5620                          packageName + ".\nHowever the package requires a reboot to complete. Rebooting.");
5621                      // reboot system without adding to local settings yet
5622                      reboot();
5623                  } else if (rebootPostponed || rebootAttr == "postponed") {
5624                      warning("Package processing (" + installType + ") failed for package " +
5625                          packageName + ".\nHowever the package schedules a postponed reboot.");
5626                  } else {
5627                      // package installation failed
5628                      var failMessage = "Could not process (" + installType + ") " + packageName + ".\n" +
5629                                  "Failed checking after installation.";
5630                      if (isQuitOnError()) {
5631                          throw new Error(failMessage);
5632                      } else {
5633                          error(failMessage);
5634                      }
5635                  }
5636              } else {
5637                  success = true;
5638                  // append new node to local xml
5639                  addSettingsNode(packageNode, true);
5640  
5641                  // install chained packages
5642                  var chainedStatus = installPackageReferences(packageNode, "chained");
5643                  if (chainedStatus) {
5644                      info(packageMessage +
5645                           "Package and all chained packages installed successfully.");
5646  
5647                  } else {
5648                      info(packageMessage +
5649                      "Package installed but at least one chained package failed to install.");
5650                  }
5651  
5652                  // Reboot the system if needed.
5653                  if (rebootRequired || rebootAttr == "true") {
5654                      info("Installation of " + packageName + " successful, system " +
5655                          "rebooting.");
5656                      reboot();
5657                  } else if (rebootPostponed || rebootAttr == "postponed") {
5658                      info("Installation of " + packageName + " successful, postponed reboot scheduled.");
5659                      setPostponedReboot(true);
5660                  } else {
5661                      info("Processing (" + installType + ") of " + packageName + " successful.");
5662                  }
5663              }
5664          } catch (err) {
5665              success = false;
5666              var errorMessage = "Could not process (" + installType + ") package '" +
5667                               packageName + "' (" + packageID + "):\n" + err.description + ".";
5668              if (isQuitOnError()) {
5669                  throw new Error(errorMessage);
5670              } else {
5671                  error(errorMessage);
5672              }
5673          } finally {
5674              // cleaning up temporary downloaded files
5675              dinfo("Cleaning up temporary downloaded files");
5676              // clean downloads
5677              downloadsClean(downloadNodes);
5678  
5679              //  PATCH SE3
5680              var packageID = getPackageID(packageNode);
5681              //var packageID = packageNode.getAttribute("id");
5682              var lnkDir = packageNode.getAttribute("lnk");
5683              var category = packageNode.getAttribute("category");
5684              if (lnkDir != null) {
5685                  if (category != null) {
5686                      info("Deplacement automatique des raccourcis de id=" + packageID + " depuis %AllUsersProfile%\\Menu Démarrer\\Programmes\\" + lnkDir + " dans la Category " + category + ".");
5687                      exec("%ComSpec% /C call %Z%\\wpkg\\AnalyseCategory.bat \"" + packageID + "\" \"" + lnkDir + "\"", timeout, workdir);
5688                  } else {
5689                      info("Pas de deplacement automatique des raccourcis car 'category' est absent dans " + packageID + ".");
5690                  }
5691              } else {
5692                  info("Pas de deplacement automatique des raccourcis car 'lnk' est absent dans " + packageID + ".");
5693              }
5694              log(4,"==============================================================="); // saut ligne entre les paquets à installer
5695              // FIN PATCH SE3
5696  
5697              // restore old environment
5698              dinfo("Restoring previous environment.");
5699              // restore previous environment
5700              loadEnv(previousEnv);
5701          }
5702      }
5703      return success;
5704  }
5705  
5706   /**
5707       * Installs all packages references of the selected type. Returns true in
5708       * case all references could be installed. Returns false if at least one
5709       * reference failed.
5710       * 
5711       * @param packageNode
5712       *            package to install the references of (XML node) NOTE: The
5713       *            package itself is not installed.
5714       * @param referenceType
5715       *            select "dependencies" or "chained". Defaults to
5716       *            "dependencies".
5717       * @return true=all dependencies installed successful; false=at least one
5718       *         dependency failed
5719       */
5720   function installPackageReferences(packageNode, referenceType) {
5721       var problemDesc = "";
5722       var refSuccess = true;
5723  
5724       // get references
5725       var type;
5726       var references = new Array();
5727       switch (referenceType) {
5728      case "chained":
5729          type = "chained";
5730           references = getPackageChained(packageNode, null);
5731          break;
5732  
5733      default:
5734          type = "dependencies";
5735          references = getPackageDependencies(packageNode, null);
5736          break;
5737      }
5738       if (references.length > 0) {
5739           info("Installing references (" + type + ") of '" +
5740                   getPackageName(packageNode) +
5741                   "' (" + getPackageID(packageNode) + ").");
5742       }
5743       for (var i=0; i < references.length; i++) {
5744           var refPackage = getPackageNodeFromAnywhere(references[i]);
5745           if (refPackage == null) {
5746               problemDesc += "Package references '" + references[i] +
5747                           "' but no such package exists";
5748               refSuccess = false;
5749               break;
5750           } else {
5751               // install this package
5752               var success = installPackage(refPackage);
5753               if (!success) {
5754                   problemDesc += "Installation of reference (" + type + ") package '"
5755                       + getPackageName(refPackage) + "' ("
5756                       + getPackageID(refPackage) + ") failed";
5757                   refSuccess = false;
5758                   // skip remaining references
5759                   break;
5760               }
5761           }
5762       }
5763       if (refSuccess) {
5764           var successMessage = "Installation of references (" + type + ") for '" +
5765                            getPackageName(packageNode) + "' (" +
5766                            getPackageID(packageNode) + ") successfully finished.";
5767           dinfo(successMessage);
5768       } else {
5769           var failMessage = "Installation of references (" + type + ") for '" +
5770                            getPackageName(packageNode) + "' (" +
5771                            getPackageID(packageNode) + ") failed. " + problemDesc;
5772           if (isQuitOnError()) {
5773               throw new Error(failMessage);
5774           } else {
5775               error(failMessage);
5776           }
5777       }
5778  
5779       return refSuccess;
5780   }
5781  
5782  
5783  /**
5784   * Installs a package by name.
5785   * 
5786   * @param name Package ID of package to be installed.
5787   * @param manualInstall Boolean value specifying whether the package is
5788   *        manually added. These packages are handled differently and not
5789   *        removed during synchronization. 
5790   */
5791  function installPackageName(name, manualInstall) {
5792      // Check package name.
5793      if (name == null || name == "") {
5794          info("Package ID missing!");
5795          return;
5796      }
5797  
5798      // Query manual installation flag.
5799      var isManual = false;
5800      if (manualInstall != null && manualInstall == true) {
5801          isManual = true;
5802      }
5803  
5804      // Query the package node.
5805      var node = getPackageNode(name);
5806  
5807      if (node == null) {
5808          info("Package " + name + " not found!");
5809          return;
5810      }
5811      
5812      // Set manual installation flag.
5813      if (isManual) {
5814          setPackageManualInstallation(node, true);
5815      }
5816  
5817       installPackage(node);
5818  }
5819  
5820  /**
5821   * Returns true if running on a 64-bit system. False if running on a 32-bit
5822   * system.
5823   * 
5824   * Please note that WPKG needs to be run from the local 64-bit cscript
5825   * instance in order to be able to access 64-bit directories and registry keys.
5826   * The 64-bit instance of cscript is located at %SystemRoot%\system32\. If
5827   * cscript from %SystemRoot%\SysWOW64\ is used (32-bit binary) then all reads to
5828   * %ProgramFiles% will be redirected to %ProgramFiles(x86). Hence it is not
5829   * possible for WPKG to access the "real" %ProgramFiles% folder with the 64-bit
5830   * binaries. The same applies for the registry. If 32-bit cscript is used all
5831   * reads to HKLM\Software\* are redirected to HKLM\Software\Wow6432Node\*.
5832   * 
5833   * WARNING: If cscript is invoked from a 32-bit application it is not possible
5834   * to run the 64-bit version of cscript since the real %SystemRoot%\System32
5835   * directory is not visible to 32-bit applications. So Windows will invoke the
5836   * 32-bit version even if the full path is specified!
5837   * 
5838   * A work-around is to copy the 64-bit cmd.exe from %SystemRoot%\System32
5839   * manually to a temporary folder and invoke it by using
5840   * c:\path\to\64-bit\cmd.exe /c \\path\to\wpkg.js
5841   * 
5842   * @return true in case the system is running on a 64-bit Windows version.
5843   *         otherwise false is returned.
5844   */
5845  function is64bit() {
5846      if (x64 == null) {
5847          x64 = false;
5848          var architecture = getArchitecture();
5849          if (architecture != "x86") {
5850              x64 = true;
5851          }
5852      }
5853      return x64;
5854  }
5855  
5856  /**
5857   * Returns the current setting of apply multiple configuration.
5858   * 
5859   * @returns Current state of apply multiple setting.
5860   */
5861  function isApplyMultiple() {
5862      return applyMultiple;
5863  }
5864  
5865  
5866  /**
5867   * returns current state of case sensitivity flag
5868   * 
5869   * @return true if case case sensitivity is enabled, false if it is disabled
5870   *         (boolean)
5871   */
5872  function isCaseSensitive() {
5873      return caseSensitivity;
5874  }
5875  
5876  /**
5877   * Returns current debug status.
5878   * 
5879   * @return true if debug state is on, false if debug is off
5880   */
5881  function isDebug() {
5882      return debug;
5883  }
5884  
5885  /**
5886   * Returns current dry run status.
5887   * 
5888   * @return true if dry run state is on, false if dry run is off
5889   */
5890  function isDryRun() {
5891      return dryrun;
5892  }
5893  
5894  /**
5895   * Returns current value of the force flag.
5896   * 
5897   * @return true if force is enabled, false if it is disabled (boolean).
5898   */
5899  function isForce() {
5900      return force;
5901  }
5902  
5903  /**
5904   * Returns current value of the forceinstall flag.
5905   * 
5906   * @return true if forced installation is enabled, false if it is disabled
5907   *         (boolean).
5908   */
5909  function isForceInstall() {
5910      return forceInstall;
5911  }
5912  
5913  /**
5914   * Returns if log should be appended or overwritten
5915   * 
5916   * @return true in case log should be appended. false if it should be
5917   *         overwritten (boolean).
5918   */
5919  function isLogAppend() {
5920      return logAppend;
5921  }
5922  
5923  /**
5924   * Check if package is installed.
5925   * 
5926   * @return returns true in case the package is installed, false otherwise
5927   * @throws Error
5928   *             in case checks could not be evaluated
5929   */
5930  function isInstalled(packageNode) {
5931      var packageName = getPackageName(packageNode);
5932      var result = true;
5933  
5934      dinfo ("Checking existence of package: " + packageName);
5935  
5936      // Get a list of checks to perform before installation.
5937      var checkNodes = getChecks(packageNode);
5938  
5939      // When there are no check conditions, say "not installed".
5940      if (checkNodes.length == 0) {
5941          return false;
5942      }
5943  
5944      // Save current environment.
5945      var previousEnv = getEnv();
5946  
5947      // load package specific environment
5948      loadPackageEnv(packageNode);
5949  
5950      // Verify checks
5951      result = checkAll(checkNodes);
5952  
5953      // restore environment
5954      loadEnv(previousEnv);
5955  
5956      return result;
5957  }
5958  
5959  /**
5960   * Returns current status of /noDownload parameter
5961   * 
5962   * @return true in case downloads shall be disabled, false if downloads are enabled
5963   */
5964  function isNoDownload() {
5965      return noDownload;
5966  }
5967  
5968  /**
5969   * Returns current status of /noforcedremove parameter
5970   * 
5971   * @return true in case forced remove is enabled, false if it is disabled
5972   */
5973  function isNoForcedRemove() {
5974      return noForcedRemove;
5975  }
5976  
5977  /**
5978   * Returns if the nonotify flag is set or not.
5979   * 
5980   * @return true if nonotify flag is set, false if nonotify is not set (boolean)
5981   */
5982  function isNoNotify() {
5983      return nonotify;
5984  }
5985  
5986  /**
5987   * Returns if the noreboot flag is set or not.
5988   * 
5989   * @return true if noreboot flag is set, false if noreboot is not set (boolean)
5990   */
5991  function isNoReboot() {
5992      return noreboot;
5993  }
5994  
5995  /**
5996   * Returns the current state (boolean) of the noremove flag.
5997   * 
5998   * @return true if noremove flag is set, false if noremove is not set (boolean)
5999   */
6000  function isNoRemove() {
6001      return noRemove;
6002  }
6003  
6004  /**
6005   * Returns if the noRunningState flag is set or not.
6006   * 
6007   * @return true if noRunningState flag is set, false if noRunningState is not
6008   *         set (boolean)
6009   */
6010  function isNoRunningState() {
6011      return noRunningState;
6012  }
6013  
6014  /**
6015   * Returns the current state of postponed reboots. If it returns true a reboot
6016   * is scheduled when the script exits (after completing all actions).
6017   * 
6018   * @return current status of postponed reboot (boolean)
6019   */
6020  function isPostponedReboot() {
6021      return postponedReboot;
6022  }
6023  
6024  /**
6025   * Returns current value of the sendStatus flag
6026   * 
6027   * @return true in case status should be sent, otherwise returns false
6028   */
6029  function isSendStatus() {
6030      return sendStatus;
6031  }
6032  
6033  /**
6034   * Returns true in case a package has been processed yet. Returns false if no
6035   * package has been processed yet at all.
6036   * 
6037   * @return true in case a package has been processed, false otherwise.
6038   */
6039  function isSystemChanged() {
6040      return systemChanged;
6041  }
6042  
6043  /**
6044   * Returns the current value of the upgrade-before-remove feature flag.
6045   * 
6046   * @return true in case upgrade-before-remove should be enabled, otherwise
6047   *         returns false.
6048   */
6049  function isUpgradeBeforeRemove() {
6050      return !noUpgradeBeforeRemove;
6051  }
6052  
6053  /**
6054   * Returns current value of skip event log setting.
6055   * 
6056   * @return true in case event log logging is enabled, false if it is disabled
6057   *         (boolean).
6058   */
6059  function isSkipEventLog() {
6060      return skipEventLog;
6061  }
6062  
6063  /**
6064   * Returns current state of event log fallback mode (logging to STDOUT instead
6065   * of event log.
6066   * 
6067   * @returns {Boolean} Current status of event log fallback mode.
6068   */
6069  function isEventLogFallback() {
6070      return eventLogFallback;
6071  }
6072  
6073  /**
6074   * Returns true if quiet mode is on. False otherwise.
6075   * 
6076   * @return true if quiet flag is set, false if it is unset (boolean)
6077   */
6078  function isQuiet() {
6079      return quietMode;
6080  }
6081  
6082  /**
6083   * Returns current value of quit on error setting (see '/quitonerror' parameter)
6084   * 
6085   * @return true in case quit on error is enabled, false if it is disabled
6086   *         (boolean).
6087   */
6088  function isQuitOnError() {
6089      return quitonerror;
6090  }
6091  
6092  /**
6093   * Checks if a package is a zombie package which means that it exists within the
6094   * locale package database (wpkg.xml) but not on server database (packages.xml).
6095   * 
6096   * @return true in case the package is a zombie, false otherwise
6097   */
6098  function isZombie(packageNode) {
6099      var packageName = getPackageID(packageNode);
6100      var allPackagesArray = getPackageNodes();
6101      var zombie = true;
6102      dinfo("Checking " + packageName + " zombie state.");
6103      for (var i=0; i < allPackagesArray.length; i++) {
6104          if (getPackageID(allPackagesArray[i]) == packageName) {
6105              zombie = false;
6106              break;
6107          }
6108      }
6109  
6110      // print message for zombie packages
6111      if (zombie) {
6112          var errorMessage = "Error while synchronizing package " + packageName +
6113          "\nZombie found: package installed but not in packages database.";
6114          if (isQuitOnError()) {
6115              errorMessage += " Aborting synchronization.";
6116              error(errorMessage);
6117              throw new Error(errorMessage);
6118          } else {
6119              errorMessage += " Removing package.";
6120              error(errorMessage);
6121          }
6122      }
6123  
6124      return zombie;
6125  }
6126  
6127  
6128  /**
6129   * Query and print local host information (read from the host where wpkg.js is
6130   * executed).
6131   */
6132  function queryHostInformation() {
6133      // Reset cache for host information.
6134      resetHostInformationCache();
6135  
6136      var hostInfoAttributes = getHostInformation();
6137      var hostInfo = hostInfoAttributes.keys().toArray();
6138      // Initialize output message.
6139      var message = "Host information attributes from local host:\n";
6140      // Fetch all host information attributes.
6141      for (var i=0; i < hostInfo.length; i++) {
6142          var hostInfoKey = hostInfo[i];
6143          message += "    " + hostInfoKey + ":";
6144  
6145          // Pad label to 20 characters (minus one for the colon ":").
6146          var padding = 19 - hostInfoKey.length;
6147          for (var iPadding=0; iPadding < padding; iPadding++) {
6148              message += " ";
6149          }
6150          message += hostInfoAttributes.Item(hostInfoKey) + "\n";
6151      }
6152      message += "\n\n";
6153  
6154      // If remote query mode is active reset host information.
6155      if (getQueryMode() == "remote") {
6156          dinfo("Query mode: remote");
6157          getSettingHostAttributes();
6158      }
6159      
6160      // Print message.
6161      alert(message);
6162  }
6163  
6164  
6165  /**
6166   * Query and print host information fread from settings file. This requires
6167   * that host information is available in settings file. You must have
6168   * settingsHostInfo enabled in your configuration.
6169   */
6170  function queryHostInformationFromSettings() {
6171      // Fetch settings.
6172      var settings = getSettings();
6173      var attributes = settings.attributes;
6174  
6175      // Initialize output message.
6176      var message = "Host information attributes from settings database:\n";
6177  
6178      // Check whether attributes are defined.
6179      if (attributes.length > 0) {
6180      
6181          for (var iAttribute=0; iAttribute < attributes.length; iAttribute++) {
6182              var node = attributes.item(iAttribute);
6183              var attribute = node.nodeName;
6184              var value = node.nodeValue;
6185  
6186              message += "    " + attribute + ":";
6187      
6188              // Pad label to 20 characters (minus one for the colon ":").
6189              var padding = 19 - attribute.length;
6190              for (var iPadding=0; iPadding < padding; iPadding++) {
6191                  message += " ";
6192              }
6193              message += value + "\n";
6194          }
6195      } else {
6196          message += "    No host attributes found in settings database.\n" +
6197              "Make sure \"settingsHostInfo\" is enabled in your configuration.\n";
6198      }
6199      message += "\n\n";
6200  
6201      // Print message.
6202      alert(message);
6203  }
6204  
6205  /**
6206   * Queries all available packages (from package database and local settings) and
6207   * prints a quick summary.
6208   */
6209  function queryAllPackages() {
6210      // Retrieve packages.
6211      var settingsNodes = getSettingNodes();
6212      var packagesNodes = getPackageNodes();
6213  
6214      // Concatenate both lists.
6215      var packageNodes = concatenateList(settingsNodes, packagesNodes);
6216      packageNodes = uniqueAttributeNodes(packageNodes, "id");
6217  
6218      // Create a string to append package descriptions to.
6219      var message = "All available packages (" + packageNodes.length + "):\n";
6220  
6221      // query all packages
6222      for (var i = 0; i < packageNodes.length; i++) {
6223          message += queryPackage(packageNodes[i], null) + "\n\n";
6224      }
6225  
6226      alert(message);
6227  }
6228  
6229  /**
6230   * Show the user a list of packages that are currently installed.
6231   */
6232  function queryInstalledPackages() {
6233      // Retrieve currently installed nodes.
6234      var packageNodes = getSettingNodes();
6235  
6236      // Create a string to append package descriptions to.
6237      var message = "Packages currently installed:\n";
6238  
6239      for (var i = 0; i < packageNodes.length; i++) {
6240          message += queryPackage(packageNodes[i], null) + "\n\n";
6241      }
6242  
6243      alert(message);
6244  }
6245  
6246  /**
6247   * Show the user information about a specific package.
6248   * 
6249   * @param packageNode
6250   *            The package node to print information of
6251   * @param packageAction
6252   *            Optional argument to include the action applied to the package
6253   *            in the package information. If set to null the package action
6254   *            information is omitted from the output.
6255   * @return string representing the package information
6256   */
6257  function queryPackage(packageNode, packageAction) {
6258      var message = "";
6259      if (packageNode != null) {
6260          var settingNode = getSettingNode(getPackageID(packageNode));
6261          var executeAttribute = getPackageExecute(packageNode);
6262          if (executeAttribute == null || executeAttribute == "") {
6263              executeAttribute = "-";
6264          }
6265  
6266          message = getPackageName(packageNode) + "\n";
6267          message += "    ID:                " + getPackageID(packageNode) + "\n";
6268          
6269          if (settingNode != null && packageAction != null) {
6270              var newPackageRevision = getPackageRevision(packageNode);
6271              var oldPackageRevision = getPackageRevision(settingNode);
6272              if (newPackageRevision != oldPackageRevision) {
6273                  message += "    Revision (new):    " + getPackageRevision(packageNode) + "\n";
6274                  message += "    Revision (old):    " + getPackageRevision(settingNode) + "\n";
6275              } else {
6276                  message += "    Revision:          " + getPackageRevision(packageNode) + "\n";
6277              }
6278          } else {
6279              message += "    Revision:          " + getPackageRevision(packageNode) + "\n";
6280          }
6281          
6282          // PATCH SE3
6283          //if (packageAction != null) {
6284          //    message += "    Action:            " + packageAction + "\n";
6285          //}
6286          // FIN PATCH SE3
6287  
6288          message += "    Reboot:            " + getPackageReboot(packageNode) + "\n";
6289          // PATCH SE3
6290          //message += "    Execute:           " + executeAttribute + "\n";
6291          //message += "    Priority:          " + getPackagePriority(packageNode) + "\n";
6292          // FIN PATCH SE3
6293          
6294          if (settingNode != null) {
6295              message += "    Status:            Installed\n";
6296          } else {
6297              message += "    Status:            Not Installed\n";
6298          }
6299          
6300      } else {
6301          message += "No such package\n";
6302      }
6303  
6304      return message;
6305  }
6306  
6307  /**
6308   * Shows the user a list of packages that are currently not installed.
6309   */
6310  function queryUninstalledPackages() {
6311      // Create a string to append package descriptions to.
6312      var message = "Packages not installed:\n";
6313  
6314      // Get list of all available packages from package database.
6315      var packageNodes = getPackageNodes();
6316  
6317      // Check for each package if it is installed.
6318      for (var i = 0; i < packageNodes.length; i++) {
6319          if (getSettingNode(getPackageID(packageNodes[i])) == null) {
6320              message += queryPackage(packageNodes[i], null) + "\n\n";
6321          }
6322      }
6323  
6324      alert(message);
6325  }
6326  
6327  /**
6328   * Query packages listed in the current profile.
6329   * @param listInstall List packages which are pending to be installed.
6330   * @param listUpgrade List packages which are pending to be upgraded.
6331   * @param listDowngrade List packages which are pending to be downgraded.
6332   * @param listRemove List packages which are pending to be removed.
6333   * @param listUnmodified List packages which are not modified during synchronization.
6334   */
6335  function queryProfilePackages(listInstall, listUpgrade, listDowngrade, listRemove, listUnmodified) {
6336      // Message to be shown as a result of query.
6337      var message = "Current profile packages:\n";
6338  
6339      // Message which is appended when system is modified (includes execute="change" packages).
6340      var messageOnChangeOnly = "";
6341  
6342      // Flag whether the system would be modified when WPKG is run.
6343      var systemModified = false;
6344  
6345      var profilePackageNodes = getProfilePackageNodes();
6346      // Read all packages applying to current profile.
6347      for (var i=0; i<profilePackageNodes.length; i++) {
6348          var packageNode = profilePackageNodes[i];
6349  
6350          // Check package action which would be applied during synchronization.
6351          var packageAction = getPackageInstallAction(packageNode);
6352  
6353          // "none" No action; package installed already
6354          // "install" Installation, package is new on the host
6355          // "upgrade" Upgrade package which already exists on the system
6356          // New version higher than installed version
6357          // "downgrade" Downgrade package which already exists on the system
6358          // New version lower than installed version
6359          var packageMessage = "";
6360          var changesSystem = false;
6361          switch (packageAction) {
6362          case "none":
6363              if (listUnmodified) {
6364                  packageMessage += queryPackage(packageNode, "None") + "\n\n";
6365              }
6366              break;
6367  
6368          case "install":
6369              if (listInstall) {
6370                  packageMessage += queryPackage(packageNode, "Installation pending") + "\n\n";
6371                  changesSystem = true;
6372              }
6373              break;
6374  
6375          case "upgrade":
6376              if (listUpgrade) {
6377                  packageMessage += queryPackage(packageNode, "Upgrade pending") + "\n\n";
6378                  changesSystem = true;
6379              }
6380              break;
6381  
6382          case "downgrade":
6383              if (listDowngrade) {
6384                  packageMessage += queryPackage(packageNode, "Downgrade pending") + "\n\n";
6385                  changesSystem = true;
6386              }
6387              break;
6388  
6389          default:
6390              break;
6391          }
6392          var executeAttribute = getPackageExecute(packageNode);
6393          if (executeAttribute == "changed") {
6394              messageOnChangeOnly += packageMessage;
6395          } else {
6396              message += packageMessage;
6397              // If the package modifies the system also packages which are
6398              // executed on change only shall be executed.
6399              if (changesSystem) {
6400                  systemModified = changesSystem;
6401              }
6402          }
6403      }
6404      if (systemModified) {
6405          message += messageOnChangeOnly;
6406      }
6407      
6408      // Print packages which are pending for removal.
6409      if (listRemove) {
6410          var removeList = getPackagesRemoved();
6411          for (var i=0; i<removeList.length; i++) {
6412              var packageNode = removeList[i];
6413              message += queryPackage(packageNode, "Remove pending") + "\n\n";
6414          }
6415      }
6416      alert(message);
6417  }
6418  
6419  /**
6420   * Removes the specified package node from the system. This function will remove
6421   * all packages which depend on the one to be removed prior to the package
6422   * itself. In case the /force parameter is set the function will even remove the
6423   * requested package if not all packages depending on it could be removed. Note
6424   * that these packages might probably not work any more in such case.
6425   * 
6426   * @param packageNode
6427   *            Package to be removed
6428   * @return True in case of successful remove of package and all packages
6429   *         depending on it. False in case of failed package uninstall of failed
6430   *         uninstall of package depending on it.
6431   */
6432  function removePackage(packageNode) {
6433      var packageName = getPackageName(packageNode);
6434      var packageID = getPackageID(packageNode);
6435      var notifyAttr = getPackageNotify(packageNode);
6436  
6437      // stores if the package needs a reboot after removing
6438      var rebootRequired = false;
6439      // stores if a postponed reboot should be scheduled
6440      var rebootPostponed = false;
6441  
6442      // Get package removal check policy.
6443      var checkPolicy = getPackagePrecheckPolicyRemove(packageNode);
6444  
6445      var success = true;
6446      var bypass = false;
6447  
6448      // string to print in events which identifies the package
6449      var packageMessage = "Package '" + packageName + "' (" + packageID + ")" +
6450                          ": ";
6451  
6452      // check if package has been processed already
6453      if(searchArray(packagesRemoved, packageNode)) {
6454          // package has been removed during this session already
6455          dinfo(packageMessage +
6456                  "Already removed once during this session.\n" +
6457                  "Checking if package has been removed properly.");
6458          bypass=true;
6459  
6460          // check if installation of package node was successful
6461          var installedPackage = getSettingNode(packageID);
6462          if (installedPackage == null) {
6463              // package successfully removed
6464              dinfo(packageMessage + "Verified; " +
6465                  "package successfully removed during this session.");
6466  
6467              success = true;
6468  
6469          } else {
6470              dinfo(packageMessage +
6471                  "Package removal failed during this session.");
6472              // package removal must have failed
6473  
6474              success = false;
6475          }
6476      } else {
6477          dinfo(packageMessage + "Not yet processed during this session.");
6478      }
6479  
6480      // Verify whether checks shall be used to verify if the package
6481      // has been removed already.
6482      if (checkPolicy == "always" && !isInstalled(packageNode)) {
6483          dinfo(packageMesseage + "Package already removed from system. Skipping removal.");
6484          // Package already removed. Skip removal.
6485          success = true;
6486  
6487          // Remove package node from local xml.
6488          removeSettingsNode(packageNode, true);
6489  
6490          // set package as processed in order to prevent processing multiple
6491          // times
6492          packagesRemoved.push(packageNode);
6493  
6494          // Cancel further removal processing.
6495          bypass = true;
6496      }
6497  
6498  
6499      if (!bypass) {
6500          // set package as processed in order to prevent processing multiple
6501          // times
6502          packagesRemoved.push(packageNode);
6503  
6504          if (isNoRemove()) {
6505              var message = "Package removal disabled: ";
6506              // check if the package is still installed
6507              if (isInstalled(packageNode)) {
6508                  // the package is installed - keep it and add to skipped nodes
6509                  dinfo(message + "Package " + packageName +  " (" + packageID +
6510                      ") will not be removed.");
6511                  addSkippedRemoveNodes(packageNode);
6512  
6513                  // package is not effectively removed
6514                  success = false;
6515              } else {
6516                  // Get a list of checks to perform before installation.
6517                  var checkNodes = getChecks(packageNode);
6518  
6519                  if (checkNodes.length != 0) {
6520                      // package not installed - remove from local settings file
6521                      dinfo(message + "Package " + packageName +  " (" + packageID +
6522                          ") will be removed from local settings because it is not installed.");
6523                      removeSettingsNode(packageNode, true);
6524                      success = true;
6525                  } else {
6526                      // unable to detect if the package is installed properly
6527                      // assume it's still installed
6528                      dinfo(message + "Package " + packageName +  " (" + packageID +
6529                              ") remains within local settings (no checks defined so WPKG " +
6530                              "cannot verify if the package is still installed properly).");
6531                      success = false;
6532                  }
6533              }
6534          } else {
6535              // remove dependent packages first
6536              var allSuccess = removePackagesDependent(packageNode);
6537              if (!allSuccess && !isForce()) {
6538                  // removing of at least one dependent package failed
6539                  var failedRemove = "Failed to remove package which depends on '"
6540                          + packageName + " (" + packageID + "), skipping removal.\n"
6541                          + "You might use the /force flag to force removal but "
6542                          + "remember that the package depending on this one might "
6543                          + "stop working.";
6544                  success = false;
6545  
6546                  if (isQuitOnError()) {
6547                      throw new Error(0, failedRemove);
6548                  } else {
6549                      error(failedRemove);
6550                  }
6551              } else {
6552                  // Save environment.
6553                  var previousEnv = getEnv();
6554                  
6555                  try {
6556                      info("Removing " + packageName + " (" + packageID + ")...");
6557  
6558                      // select command lines to remove
6559                      var cmds = getPackageCmdRemove(packageNode, null);
6560  
6561                      // set package specific environment
6562                      loadPackageEnv(packageNode);
6563  
6564                      // Get downloads from package node (if any).
6565                      var downloadNodes = getDownloads(packageNode, null);
6566                      // Append downloads from command node.
6567                      for (var iCommand = 0; iCommand < cmds.length; iCommand++) {
6568                          var commandNode = cmds[iCommand ];
6569                          getDownloads(commandNode, downloadNodes);
6570                      }
6571  
6572                      // Download all specified downloads.
6573                      var downloadResult = downloadAll(downloadNodes);
6574                      if (downloadResult != true) {
6575                          var failureMessage = "Failed to download all files.";
6576                          if (isQuitOnError()) {
6577                              throw new Error(failureMessage);
6578                          } else {
6579                              error(failureMessage);
6580                          }
6581                      }
6582                      
6583                      // execute all remove commands
6584                      for (var iCommand = 0; iCommand  < cmds.length; iCommand++) {
6585                          // execute commands
6586                          var cmdNode = cmds[iCommand ];
6587                          var cmd = getCommandCmd(cmdNode);
6588                          if(cmd == null) {
6589                              error("Error: Command missing. Please fix the package. Ignoring command.");
6590                              continue;
6591                          }
6592                          var timeout = getCommandTimeout(cmdNode);
6593                          var workdir = getCommandWorkdir(cmdNode);
6594  
6595                          // mark system as changed (command execution in
6596                          // progress)
6597                          setSystemChanged();
6598                          if(notifyAttr) {
6599                              notifyUserStart();
6600                          }
6601  
6602                          var result = exec(cmd, timeout, workdir);
6603  
6604                          dinfo("Command returned result: " + result);
6605  
6606                          // check if there is an exit code defined
6607                          var exitAction = getCommandExitCodeAction(cmdNode, result);
6608  
6609                          // Check for special exit codes.
6610                          if (exitAction != null) {
6611                              if (exitAction == "reboot") {
6612                                  // This exit code forces a reboot.
6613                                  info("Command in removal of " + packageName + " returned " +
6614                                      "exit code [" + result + "]. This exit code " +
6615                                      "requires an immediate reboot.");
6616  
6617                                  // Verify if the package is a zombie (not in package
6618                                  // database any more). If it is a zombie, and not referenced
6619                                  // in the profile then prevent endless reboots by removing
6620                                  // the package from local database.
6621                                  if(isZombie(packageNode)) {
6622                                      // check if still referenced within the profile
6623                                      var profilePackageArray = getProfilePackageNodes();
6624                                      var referenceFound = false;
6625                                      for (var iPackage = 0; iPackage < profilePackageArray.length; iPackage++) {
6626                                          if (packageID == getPackageID(profilePackageArray[iPackage])) {
6627                                              referenceFound = true;
6628                                              break;
6629                                          }
6630                                      }
6631                                      // if package is a zombie and not referenced
6632                                      // within the profile remove the settings entry
6633                                      if(!referenceFound && !isNoForcedRemove()) {
6634                                          removeSettingsNode(packageNode, true);
6635                                          info("Removed '" + packageName + "' ("
6636                                              + packageID + ") from local settings.\n" +
6637                                                  "Package initiated immediate reboot and is a zombie.");
6638                                      }
6639                                  }
6640  
6641                                  reboot();
6642                              } else if(exitAction == "delayedReboot") {
6643                                  // This exit code schedules a reboot
6644                                          info("Command in removal of " + packageName +
6645                                              " returned exit code [" + result + "]. This " +
6646                                              "exit code schedules a reboot.");
6647                                  // schedule reboot
6648                                  rebootRequired = true;
6649                                  // execute next command
6650                                  continue;
6651                              } else if(exitAction == "postponedReboot") {
6652                                  info("Command in removal of " + packageName +
6653                                      " returned exit code [" + result + "]. This " +
6654                                      "exit code schedules a postponed reboot.");
6655                                  rebootPostponed = true;
6656                                  setPostponedReboot(rebootPostponed);
6657                                  // execute next command
6658                                  continue;
6659                              } else {
6660                                  // This exit code is successful.
6661                                  info("Command in removal of " + packageName + " returned " +
6662                                      " exit code [" + result + "]. This exit code " +
6663                                      "indicates success.");
6664                                  continue;
6665                              }
6666                          } else if(result == 0) {
6667                              // if exit code is 0, return success
6668                              // execute next command
6669                              dinfo("Command in removal of " + packageName +
6670                                  " returned exit code [" + result + "]. Success.");
6671                              continue;
6672                          } else {
6673                              // command did not succeed, log error
6674                              var failedCmd = "Exit code returned non-successful value: " +
6675                                  result + "\nPackage: " + packageName + ".\nCommand:\n" + cmd;
6676                              // error occurred during remove
6677                              success = false;
6678  
6679                              if (isQuitOnError()) {
6680                                  throw new Error(0, failedCmd);
6681                              } else {
6682                                  error(failedCmd);
6683                              }
6684                          }
6685                      }
6686                  } catch (err) {
6687                      success = false;
6688                      var errorMessage = "Could not process (remove) package '" +
6689                                           packageName + "' (" + packageID + "):\n" + err.description + ".";
6690                      if (isQuitOnError()) {
6691                          throw new Error(errorMessage);
6692                      } else {
6693                          error(errorMessage);
6694                      }
6695                  } finally {
6696  
6697                      // PATCH SE3
6698                      var packageID = getPackageID(packageNode);
6699                      //var packageID = packageNode.getAttribute("id");
6700                      var lnkDir = packageNode.getAttribute("lnk");
6701                      var category = packageNode.getAttribute("category");
6702                      if (lnkDir != null) {
6703                          if (category != null) {
6704                              info("Suppression des raccourcis de id=" + packageID + " depuis %AllUsersProfile%\\Menu Démarrer\\Programmes\\" + category + "\\" + lnkDir);
6705                              exec("%ComSpec% /C call %Z%\\wpkg\\AnalyseCategory.bat \"" + packageID + "\" \"" + lnkDir + "\" remove", timeout, workdir);
6706                          } else {
6707                              info("Pas de suppression automatique des raccourcis car 'category' est absent dans " + packageID + ".");
6708                          }
6709                      } else {
6710                          info("Pas de suppression automatique des raccourcis car 'lnk' est absent dans " + packageID + ".");
6711                      }
6712                      // FIN PATCH SE3
6713  
6714                      // restore old environment
6715                      dinfo("Restoring previous environment.");
6716  
6717                      // restore previous environment
6718                      loadEnv(previousEnv);
6719                  }
6720              }
6721  
6722              // read reboot attribute
6723              var rebootAttr = getPackageReboot(packageNode);
6724  
6725              // Use package checks to prove if package has been removed.
6726              // Zombies are removed in any case (even if uninstall failed) except
6727              // if the
6728              // "/noforcedremove" parameter was set
6729              if (!isInstalled(packageNode)) {
6730                  // Remove package node from local xml.
6731                  removeSettingsNode(packageNode, true);
6732  
6733                  if (rebootRequired || rebootAttr == "true") {
6734                      info("Removal of " + packageName + " successful, system " +
6735                          "rebooting.");
6736                      reboot();
6737                  } else if (rebootPostponed || rebootAttr == "postponed") {
6738                      info("Removal of " + packageName + " successful, postponed reboot scheduled.");
6739                  } else {
6740                      info("Removal of " + packageName + " successful.");
6741                  }
6742              } else {
6743                  // Check if package is a zombie.
6744                  if(isZombie(packageNode)) {
6745                      // Check if still referenced within the profile.
6746                      var packageArray = getProfilePackageNodes();
6747                      var referenced = false;
6748                      for (var i=0; i < packageArray.length; i++) {
6749                          if (packageID == getPackageID(packageArray[i])) {
6750                              referenced = true;
6751                              break;
6752                          }
6753                      }
6754                      // If package is a zombie and not referenced within the profile
6755                      // remove the settings entry.
6756                      if(!referenced && !isNoForcedRemove()) {
6757                          removeSettingsNode(packageNode, true);
6758                          warning("Errors occurred while removing '" + packageName + "' ("
6759                              + packageID + ").\nPackage has been removed anyway because it was a zombie " +
6760                              "and not referenced within the profile.");
6761                      }
6762                  } else if (rebootRequired || rebootAttr == "true") {
6763                      warning("Package processing (remove) failed for package " +
6764                          packageName + ".\nHowever the package requires a reboot to complete. Rebooting.");
6765                      // reboot system without adding to local settings yet
6766                      reboot();
6767                  } else if (rebootPostponed || rebootAttr == "postponed") {
6768                      warning("Package processing (remove) failed for package " +
6769                          packageName + ".\nHowever the package schedules a postponed reboot.");
6770                  } else {
6771                      // package installation failed
6772                      success = false;
6773                      message = "Could not process (remove) " + packageName + ".\n" +
6774                                  "Package still installed.";
6775                      if (isQuitOnError()) {
6776                          throw new Error(message);
6777                      } else {
6778                          error(message);
6779                      }
6780                  }
6781              }
6782              log(4,"==============================================================="); // saut ligne entre les paquets à installer
6783          }
6784      }
6785  
6786      // return status
6787      return success;
6788  }
6789  
6790  /**
6791   * Removes a package by name.
6792   * 
6793   * @param name
6794   *            name of the package to be removed (package ID).
6795   * @return True in case of successful remove of package and all packages
6796   *         depending on it. False in case of failed package uninstall of failed
6797   *         uninstall of package depending on it.
6798   */
6799  function removePackageName(name) {
6800      // Query the package node.
6801      var node = getSettingNode(name);
6802  
6803      // return code
6804      var success = false;
6805  
6806      dinfo("Removing package '" + name + "'.");
6807  
6808      if (node == null) {
6809  
6810          // check if the package has been removed during this session
6811          var alreadyRemoved = false;
6812          for (var iRemovedPkg = 0; iRemovedPkg < packagesRemoved.length; iRemovedPkg++) {
6813              var removedPackage = packagesRemoved[iRemovedPkg];
6814              if (name == getPackageID(removedPackage)) {
6815                  alreadyRemoved = true;
6816                  break;
6817              }
6818          }
6819          if (alreadyRemoved) {
6820              dinfo("Package '" + name + "' already removed during this session.");
6821              success = true;
6822          } else {
6823              info("Package '" + name + "' currently not installed.");
6824              success = false;
6825          }
6826      } else {
6827          success = removePackage(node);
6828      }
6829      return success;
6830  }
6831  
6832  /**
6833   * Removes all packages which depends on the given package. Returns true in case
6834   * all packages could be removed. Returns false if at least one dependent
6835   * package failed to remove.
6836   * 
6837   * @param packageNode
6838   *            package to install the dependencies of (XML node) NOTE: The
6839   *            package itself is not installed.
6840   * @return true=all dependencies installed successful; false=at least one
6841   *         dependency failed
6842   */
6843  function removePackagesDependent(packageNode) {
6844      var packageID = getPackageID(packageNode);
6845      var packageName = getPackageName(packageNode);
6846  
6847      var problemDesc = "";
6848      // search for all packages which depend on the one to be removed
6849      var dependencies = new Array();
6850      var installedPackages = getSettingNodes();
6851      for (var iInstPkg = 0; iInstPkg<installedPackages.length; iInstPkg++) {
6852          // get dependencies of this package
6853          var pkgDeps = getPackageDependencies(installedPackages[iInstPkg]);
6854          for (var j=0; j<pkgDeps.length; j++) {
6855              if (pkgDeps[j] == packageID) {
6856                  dependencies.push(installedPackages[iInstPkg]);
6857                  break;
6858              }
6859          }
6860      }
6861      if (dependencies.length > 0) {
6862          info("Removing packages depending on '" + packageName +
6863              "' (" + packageID + ").");
6864      }
6865      var depSuccess = true;
6866      for (var iDependencies = 0; iDependencies < dependencies.length; iDependencies++) {
6867          var dependingPackage = dependencies[iDependencies];
6868          // install this package
6869          var success = removePackage(dependingPackage);
6870          if (!success) {
6871              problemDesc += "Removal of depending package '"
6872                  + getPackageName(dependingPackage) + "' ("
6873                  + getPackageID(dependingPackage) + ") failed";
6874              depSuccess = false;
6875              // skip remaining dependencies
6876              break;
6877          }
6878      }
6879  
6880      if (depSuccess) {
6881          dinfo("Removal of depending packages for '" +
6882                   packageName + "' (" +
6883                   packageID + ") successfully finished.");
6884      } else {
6885          var failMessage = "Removal of depending packages for '" +
6886                           packageName + "' (" +
6887                          packageID + ") failed. " + problemDesc;
6888          if (isQuitOnError()) {
6889              throw new Error(failMessage);
6890          } else {
6891              error(failMessage);
6892          }
6893      }
6894  
6895      return depSuccess;
6896  }
6897  
6898  /**
6899   * Removes a package node from the settings XML node
6900   * 
6901   * @param packageNode
6902   *            The package node to be removed from settings.
6903   * @param saveImmediately
6904   *            Set to true in order to save settings immediately after removing.
6905   *            Settings will not be saved immediately if value is false.
6906   * @return Returns true in case of success, returns false if no node could be
6907   *         removed
6908   */
6909  function removeSettingsNode(packageNode, saveImmediately) {
6910      // make sure the settings node is selected
6911      var packageID = getPackageID(packageNode);
6912      dinfo("Removing package id '" + packageID + "' from settings.");
6913      var settingsNode = getSettingNode(packageID);
6914      var success = false;
6915      if(settingsNode != null) {
6916          success = removeNode(getSettings(), settingsNode);
6917      }
6918      // save settings if remove was successful
6919      if (success && saveImmediately) {
6920          saveSettings(true);
6921      }
6922      return success;
6923  }
6924  
6925  /**
6926   * Erases host information cache to enforce re-reading of host information when
6927   * getter methods like getHostInformation(), getHostOS(), getLocale() etc are
6928   * executed. 
6929   */
6930  function resetHostInformationCache() {
6931      // Empty caches.
6932      hostName = null;
6933      hostOs = null;
6934      domainName = null;
6935      ipAddresses = null;
6936      hostGroups = null;
6937      hostArchitecture = null;
6938      hostAttributes = null;
6939  }
6940  
6941  
6942  /**
6943   * Sets state of multiple profile assignment.
6944   * 
6945   * @param newState
6946   *            new debug state
6947   */
6948  function setApplyMultiple(newState) {
6949      applyMultiple = newState;
6950  }
6951  
6952  /**
6953   * Set new architecture for this host.
6954   * @param newArchitecture Architecture to used for this host.
6955   */
6956  function setArchitecture(newArchitecture) {
6957      hostArchitecture = newArchitecture;
6958  }
6959  
6960  /**
6961   * Sets new status of the case-sensitive flag
6962   * 
6963   * @param newSensitivity
6964   *            true to enable case sensitivity, false to disable it (boolean)
6965   */
6966  function setCaseSensitivity(newSensitivity) {
6967      caseSensitivity = newSensitivity;
6968  }
6969  
6970  /**
6971   * Sets debug value to the given state.
6972   * 
6973   * @param newState
6974   *            new debug state
6975   */
6976  function setDebug(newState) {
6977      debug = newState;
6978  }
6979  
6980  /**
6981   * Sets domain name used by the script.
6982   * 
6983   * @param newDomainName
6984   *            new domain name
6985   */
6986  function setDomainName(newDomainName) {
6987      domainName = newDomainName;
6988  }
6989  
6990  /**
6991   * Sets dry run value to the given state.
6992   * 
6993   * @param newState
6994   *            new dry run state
6995   */
6996  function setDryRun(newState) {
6997      dryrun = newState;
6998  }
6999  
7000  /**
7001   * Sets a new value for the forceinstall flag.
7002   * 
7003   * @param newState
7004   *            new value for the forceinstall flag (boolean)
7005   */
7006  function setForce(newState) {
7007      force = newState;
7008  }
7009  
7010  /**
7011   * Sets a new value for the forceinstall flag.
7012   * 
7013   * @param newState
7014   *            new value for the forceinstall flag (boolean)
7015   */
7016  function setForceInstall(newState) {
7017      forceInstall = newState;
7018  }
7019  
7020  /**
7021   * Set new group names the host belongs to.
7022   * 
7023   * @param newGroupNames
7024   *            Array of group names the host belongs to.
7025   */
7026  function setHostGroups(newGroupNames) {
7027      hostGroups = newGroupNames;
7028  }
7029  
7030  /**
7031   * Set a new host name which will be used by the script. This is useful for
7032   * debugging purposes.
7033   * 
7034   * @param newHostname
7035   *            host name to be used
7036   */
7037  function setHostname(newHostname) {
7038      hostName = newHostname;
7039  }
7040  
7041  /**
7042   * Set new host OS variable overwriting automatically-detected value.
7043   * 
7044   * @param newHostOS
7045   *            host OS name
7046   */
7047  function setHostOS(newHostOS) {
7048      hostOs = newHostOS;
7049  }
7050  
7051  
7052  /**
7053   * Sets a new profile-id attribute to the given host XML node
7054   * 
7055   * @param hostNode
7056   *            the host XML node to modify
7057   * @param profileID
7058   *            the new profile ID to be written to this node
7059   */
7060  function setHostProfile(hostNode, profileID) {
7061      hostNode.setAttribute("profile-id", profileID);
7062  }
7063  
7064  /**
7065   * Set a new hosts node
7066   * 
7067   * @param newHosts
7068   *            the new hosts XML node to be used fro now on
7069   */
7070  function setHosts(newHosts) {
7071      hosts = newHosts;
7072  }
7073  
7074  /**
7075   * Set a new IP address list array.
7076   * 
7077   * @param newIPAdresses
7078   *            Array of IP addresses to be used by script.
7079   */
7080  function setIPAddresses(newIPAdresses) {
7081      ipAddresses = newIPAdresses;
7082  }
7083  
7084  /**
7085   * Set new value for log file pattern
7086   * 
7087   * @param pattern
7088   *            new pattern to be used
7089   * @return returns the pattern with expanded environment variables
7090   */
7091  function setLogfilePattern(pattern) {
7092      var wshShell = new ActiveXObject("WScript.Shell");
7093      logfilePattern = wshShell.ExpandEnvironmentStrings(pattern);
7094      return logfilePattern;
7095  }
7096  
7097  /**
7098   * Sets new value for the no-download flag.
7099   * 
7100   * @param newState
7101   *            new value for the no-download flag (boolean).
7102   *            If set to true then all downloads are disabled (just skipped).
7103   */
7104  function setNoDownload(newState) {
7105      noDownload = newState;
7106  }
7107  
7108  /**
7109   * Sets new value for the noforcedremove flag.
7110   * 
7111   * @param newState
7112   *            new value for the noforcedremove flag (boolean).
7113   */
7114  function setNoForcedRemove(newState) {
7115      noForcedRemove = newState;
7116  }
7117  
7118  /**
7119   * Sets new state for the noreboot flag.
7120   * 
7121   * @param newState
7122   *            new state of the noreboot flag (boolean)
7123   */
7124  function setNoReboot(newState) {
7125      noreboot = newState;
7126  }
7127  
7128  /**
7129   * Sets new state for the noremove flag.
7130   * 
7131   * @param newState
7132   *            new state of the noremove flag (boolean)
7133   */
7134  function setNoRemove(newState) {
7135      noRemove = newState;
7136  }
7137  
7138  /**
7139   * Sets new state for the noRunningState flag.
7140   * 
7141   * @param newState
7142   *            new state of the noreboot flag (boolean)
7143   */
7144  function setNoRunningState(newState) {
7145      noRunningState = newState;
7146  }
7147  
7148  /**
7149   * Sets a new package id-attribute to the given host XML node
7150   * 
7151   * @param packageNode
7152   *            the package XML node to modify
7153   * @param packageID
7154   *            the new package ID to be written to this node
7155   */
7156  function setPackageID(packageNode, packageID) {
7157      packageNode.setAttribute("id", packageID);
7158  }
7159  
7160  /**
7161   * Set a new value for the manual installation flag of the given package.
7162   * Manual installations are flagged only for packages which are installed via
7163   * command line directly and not via synchronization.
7164   * 
7165   * @param packageNode package to be modified.
7166   * @param manualInstall {Boolean} new value of package installation flag.
7167   */
7168  function setPackageManualInstallation(packageNode, manualInstall) {
7169      if (packageNode == null) {
7170          error("No package node specified. Cannot set manual installation flag.");
7171          return;
7172      }
7173      if (manualInstall == null) {
7174          error("No manual installation flag value specified.");
7175          return;
7176      }
7177      if (manualInstall == true) {
7178          packageNode.setAttribute("manualInstall", "true");
7179      }
7180  }
7181  
7182  /**
7183   * Set a new packages node.
7184   * 
7185   * @param newPackages
7186   *            the new packages XML node to be used fro now on
7187   */
7188  function setPackages(newPackages) {
7189      packages = newPackages;
7190      // iterate through all packages and set the package id to lower case
7191      // this allows XPath search for lowercase value later on (case-insensitive)
7192      if (packages != null && !isCaseSensitive()) {
7193          var packageNodes = getPackageNodes();
7194          for (var i=0; i<packageNodes.length; i++) {
7195              var packageNode = packageNodes[i];
7196              setPackageID(packageNode, getPackageID(packageNode).toLowerCase());
7197          }
7198      }
7199  }
7200  
7201  /**
7202   * Sets the status of postponed reboot. A postponed reboot schedules a system
7203   * reboot after finishing all actions (right before the script exits).
7204   * 
7205   * @param newState
7206   *            new state of postponed reboot
7207   */
7208  function setPostponedReboot(newState) {
7209      postponedReboot = newState;
7210  }
7211  
7212  /**
7213   * Sets a new profile id-attribute to the given profile XML node
7214   * 
7215   * @param profileNode
7216   *            the profile XML node to modify
7217   * @param profileID
7218   *            the new profile ID to be written to this node
7219   */
7220  function setProfileID(profileNode, profileID) {
7221      profileNode.setAttribute("id", profileID);
7222  }
7223  
7224  /**
7225   * Set a new profiles node
7226   * 
7227   * @param newProfiles
7228   *            the new profiles XML node to be used fro now on
7229   */
7230  function setProfiles(newProfiles) {
7231      profiles = newProfiles;
7232      // iterate through all profiles and set the profile id to lower case
7233      // this allows XPath search for lowercase value later on (case-insensitive)
7234      if (profiles != null && !isCaseSensitive()) {
7235          var profileNodes = getProfileNodes();
7236          for (var i=0; i<profileNodes.length; i++) {
7237              var profileNode = profileNodes[i];
7238              setProfileID(profileNode, getProfileID(profileNode).toLowerCase());
7239          }
7240      }
7241  }
7242  
7243  /**
7244   * Sets query mode to new state. Allowed states are "remote" and "local".
7245   * 
7246   * @param newState query mode value to be set.
7247   */
7248  function setQueryMode(newState) {
7249      if (newState != null && (newState == "remote" || newState == "local")) {
7250          queryMode = newState;
7251      }
7252  }
7253  
7254  /**
7255   * Sets new state of the quiet flag
7256   * 
7257   * @param newState
7258   *            new status of quiet flag (boolean)
7259   */
7260  function setQuiet(newState) {
7261      quietMode = newState;
7262  }
7263  
7264  /**
7265   * Sets a new value for the quit on error flag.
7266   * 
7267   * @param newState
7268   *            new value for the quit on error flag (boolean).
7269   */
7270  function setQuitOnError(newState) {
7271      quitonerror = newState;
7272  }
7273  
7274  /**
7275   * Sets new value for the reboot command (rebootCmd).
7276   * 
7277   * @param newCommand
7278   */
7279  function setRebootCmd(newCommand) {
7280      var wshShell = new ActiveXObject("WScript.Shell");
7281      rebootCmd = wshShell.ExpandEnvironmentStrings(newCommand);
7282  }
7283  
7284  /**
7285   * Set state of application so other applications can see that it is running by
7286   * reading from the registry.
7287   * 
7288   * @param statename
7289   *            String which is written to the registry as a value of the
7290   *            "running" key
7291   */
7292  function setRunningState(statename) {
7293      var WshShell = new ActiveXObject("WScript.Shell");
7294      var val;
7295  
7296      try {
7297          val = WshShell.RegWrite(sRegWPKG_Running, statename);
7298      } catch (e) {
7299          val = null;
7300      }
7301  
7302      return val;
7303  }
7304  
7305  /**
7306   * Sets new value for the sendStatus flag which defines if status messages are
7307   * sent to the calling program using STDOUT
7308   * 
7309   * @param newStatus
7310   *            new value for the sendStatus flag (boolean)
7311   */
7312  function setSendStatus(newStatus) {
7313      sendStatus = newStatus;
7314  }
7315  
7316  /**
7317   * Set a new settings node
7318   * 
7319   * @param newSettings
7320   *            the new settings XML node to be used fro now on
7321   */
7322  function setSettings(newSettings, saveImmediately) {
7323      settings = newSettings;
7324      // iterate through all packages and set the package id to lower case
7325      // this allows XPath search for lowercase value later on (case-insensitive)
7326      if (settings != null && !isCaseSensitive()) {
7327          var packageNodes = getSettingNodes();
7328          for (var i=0; i<packageNodes.length; i++) {
7329              var packageNode = packageNodes[i];
7330              setPackageID(packageNode, getPackageID(packageNode).toLowerCase());
7331          }
7332      }
7333      // save new settings
7334      if(saveImmediately) {
7335          saveSettings(true);
7336      }
7337  }
7338  
7339  /**
7340   * Set path to local settings file (locak package database).
7341   * The path might contain environment variables as well as the following
7342   * expressions:
7343   *     [HOSTNAME]  Replaced by the executing hostname.
7344   *     [PROFILE]   Replaced by the concatenated list of profiles applied.
7345   * @param path path to settings XML file.
7346   */
7347  function setSettingsPath(path) {
7348      if (path == null || path == "") {
7349          error("Path to settings is required");
7350          return;
7351      }
7352  
7353      var wshObject = new ActiveXObject("WScript.Shell");
7354      var expandedSettingsPath = wshObject.ExpandEnvironmentStrings(path);
7355  
7356      // Set global variable holding settings file path.
7357      settings_file = expandedSettingsPath;
7358  }
7359  
7360  
7361  /**
7362   * Sets the system changed attribute to true. Call this method to make WPKG
7363   * aware that a system change has been done.
7364   * 
7365   * @return returns current system change status (always true after this method
7366   *         has been called
7367   */
7368  function setSystemChanged() {
7369      systemChanged = true;
7370      return systemChanged;
7371  }
7372  
7373  /**
7374   * Set new value for the boolean flag to disable/enable event log logging.
7375   * 
7376   * @param newValue
7377   *            value to be used for the skip event log flag from now on.
7378   */
7379  function setSkipEventLog(newValue) {
7380      skipEventLog = newValue;
7381  }
7382  
7383  /**
7384   * Set event log fallback to new value (enabled/disabled).
7385   * 
7386   * @param newValue
7387   *           value to be used for the event log fallback flag.
7388   */
7389  function setEventLogFallback(newValue) {
7390      eventLogFallback = newValue;
7391  }
7392  
7393  /**
7394   * Sorts package nodes by priority flag.
7395   * 
7396   * @param packageNodes
7397   *            JScript Array containing package node entries
7398   * @param sortBy
7399   *            select the field to sort on. Supported Values are "PRIORITY" and
7400   *            "NAME"
7401   * @param sortOrder
7402   *            order in which the elements are sorted (integer) valid values:<br>1
7403   *            sort ascending (default)<br>2 sort descending
7404   * 
7405   * @return new Array containing the same package nodes in sorted order (sorted
7406   *         by priority)
7407   */
7408  function sortPackageNodes(packageNodes, sortBy, sortOrder) {
7409      // create array to do the sorting on
7410      var sortedPackages = new Array();
7411      for (var iPkgNodes = 0; iPkgNodes < packageNodes.length; iPkgNodes++) {
7412          sortedPackages.push(packageNodes[iPkgNodes]);
7413      }
7414      // Classic bubble-sort algorithm on selected attribute
7415      for (var iSortedPkg = 0; iSortedPkg < sortedPackages.length - 1; iSortedPkg++) {
7416          for (var j=0; j < sortedPackages.length - 1 - iSortedPkg; j++) {
7417              var prio1;
7418              var prio2;
7419              var priVal1 = null;
7420              var priVal2 = null;
7421  
7422              switch(sortBy) {
7423                  case "NAME":
7424                      priVal1 = getPackageName(sortedPackages[j]);
7425                      priVal2 = getPackageName(sortedPackages[j + 1]);
7426                      break;
7427                  default:
7428                      priVal1 = parseInt(getPackagePriority(sortedPackages[j]));
7429                      priVal2 = parseInt(getPackagePriority(sortedPackages[j + 1]));
7430                      break;
7431              }
7432              // If a priority is not set, we assume 0.
7433  
7434              if (priVal1 == null) {
7435                  prio1 = 0;
7436              } else {
7437                  prio1 = priVal1;
7438              }
7439  
7440              if (priVal2 == null) {
7441                  prio2 = 0;
7442              } else {
7443                  prio2 = priVal2;
7444              }
7445  
7446              var swapElements = false;
7447              switch (sortOrder) {
7448                  case 2:
7449                      if (prio1 < prio2) {
7450                          swapElements = true;
7451                      }
7452                      break;
7453                  default:
7454                      if (prio1 > prio2) {
7455                          swapElements = true;
7456                      }
7457                      break;
7458              }
7459              // If the priority of the first one in the list exceeds the second,
7460              // swap the packages.
7461              if (swapElements) {
7462                  var tmp = sortedPackages[j];
7463                  sortedPackages[j] = sortedPackages[j + 1];
7464                  sortedPackages[j + 1] = tmp;
7465              }
7466          }
7467      }
7468      return sortedPackages;
7469  }
7470  
7471  /**
7472   * Sorts the settings file by package name. Returns sorted package XML node.
7473   */
7474  function sortSettings() {
7475      // sort current setting nodes
7476      var sortedPackages = sortPackageNodes(getSettingNodes(), "NAME", 1);
7477  
7478      // Get setting checks.
7479      var settingsChecks = getSettingsCheckResults();
7480      
7481      // create new (empty) settings node
7482      var sortedSettings = createSettings();
7483      sortedSettings.appendChild(settingsChecks);
7484      
7485      // use this settings node
7486      setSettings(sortedSettings, false);
7487  
7488      // fill new settings node with sorted packages (same order)
7489      for (var i=0; i<sortedPackages.length; i++) {
7490          addSettingsNode(sortedPackages[i], false);
7491      }
7492  }
7493  
7494  /**
7495   * Synchronizes the current package state to that of the specified profile,
7496   * adding, removing or upgrading packages.
7497   */
7498  function synchronizeProfile() {
7499      // send message to client
7500      logStatus("Starting software synchronization");
7501  
7502      /**
7503       * Get package nodes referenced within the profile (and profile
7504       * dependencies). This includes package dependencies as well.
7505       */
7506      var profilePackageNodes = getProfilePackageNodes();
7507      dinfo("Synchronizing. Number of packages referenced by profile: " + profilePackageNodes.length + ".");
7508  
7509      var localPackages = getPackagesManuallyInstalled();
7510      if (localPackages.length > 0) {
7511          dinfo("Synchronizing. Locally installed packages: " + localPackages.length + ".");
7512          for(var i=0; i<localPackages.length; i++) {
7513              // Fetch latest package node to schedule installation/upgrade.
7514              var localPackage = localPackages[i];
7515              var latestVersion = getPackageNode(getPackageID(localPackage));
7516              if (latestVersion != null) {
7517                  profilePackageNodes.push(latestVersion);
7518              }
7519          }
7520      }
7521  
7522      // Get list of packages scheduled for removal.
7523      // This excludes manually installed packages except if they do not exist.
7524      var removablesArray = getPackagesRemoved();
7525  
7526      dinfo("Number of packages to remove: " + removablesArray.length);
7527      logStatus("Number of packages to be removed: " + removablesArray.length);
7528      /*
7529       * upgrade packages to be removed to latest version first. This allows system administrators to provide a fixed
7530       * version of the package which allows clean uninstall.
7531       * 
7532       * This was done to allow fixing a broken uninstall-procedure on server side. Without upgrading to the latest
7533       * version here it might happen that the package cannot be removed without the possibility to fix it. If you remove
7534       * the package completely from the package database it will be forced to be removed from the local settings file
7535       * even if uninstall fails.
7536       * 
7537       * NOTE: This is not done within the same loop as the removal (see below) in order to prevent re-installing already
7538       * removed dependencies.
7539       */
7540      // sort packages to upgrade the ones with highest priority first
7541      if (isUpgradeBeforeRemove()) {
7542          var sortedUpgradeList = sortPackageNodes(removablesArray, "PRIORITY", 2);
7543          for (var iSortedPkg = 0; iSortedPkg < sortedUpgradeList.length; iSortedPkg++) {
7544              var upgradePkgNode = sortedUpgradeList[iSortedPkg];
7545              // upgrade package if package is available on server database
7546              var serverPackage = getPackageNode(getPackageID(upgradePkgNode));
7547              if (serverPackage != null) {
7548                  logStatus("Remove: Checking status of '" + getPackageName(serverPackage) +
7549                          "' (" + (iSortedPkg+1) + "/" + sortedUpgradeList.length + ")");
7550                  // start upgrade first
7551                  installPackage(serverPackage);
7552              }
7553          }
7554      }
7555  
7556      // Remove packages which do not exist in package database or do not apply
7557      // to the profile
7558      // reverse-sort packages to remove the one with lowest priority first
7559      var sortedRemovablesArray = sortPackageNodes(removablesArray, "PRIORITY", 1);
7560      for (var iRemovables = 0; iRemovables < sortedRemovablesArray.length; iRemovables++) {
7561          var removePkgNode = sortedRemovablesArray[iRemovables];
7562          // remove package from system
7563          // the settings node might have been changed during update before
7564          // reload it.
7565          logStatus("Remove: Removing package '" + getPackageName(removePkgNode) +
7566                  "' (" + (iRemovables+1) + "/" + sortedRemovablesArray.length + ")");
7567          // removePackage(getSettingNode(getPackageID(removePkgNode)));
7568          removePackageName(getPackageID(removePkgNode));
7569      }
7570  
7571      // create array to do the sorting on
7572      var sortedPackages = sortPackageNodes(profilePackageNodes, "PRIORITY", 2);
7573  
7574      /*
7575       * Move packages with execute=changed attribute to independent array in order to allow them to be executed after the
7576       * other packages.
7577       */
7578      var packagesToInstall = new Array();
7579      var packagesAwaitingChange = new Array();
7580      // NOTE: This should not change the sort order of the packages.
7581      for (var iPkg = 0; iPkg < sortedPackages.length; iPkg++) {
7582          var packageNode = sortedPackages[iPkg];
7583          var executeAttribute = getPackageExecute(packageNode);
7584          if (executeAttribute == "changed") {
7585              packagesAwaitingChange.push(packageNode);
7586          } else {
7587              packagesToInstall.push(packageNode);
7588          }
7589      }
7590  
7591      /*
7592       * Loop over each available package and install it. No check required if package is already installed or not. The
7593       * install method will check by itself if the package needs to be installed/upgraded or no action is needed.
7594       */
7595      for (var iInstallPkg=0; iInstallPkg < packagesToInstall.length; iInstallPkg++) {
7596          // install/upgrade package
7597          logStatus("Install: Verifying package '" + getPackageName(packagesToInstall[iInstallPkg]) +
7598                  "' (" + (iInstallPkg + 1) + "/" + packagesToInstall.length + ")");
7599          installPackage(packagesToInstall[iInstallPkg]);
7600      }
7601  
7602      /*
7603       * Install packages which might have been postponed because no other change has been done to the system.
7604       */
7605      for(var iChangeAwait = 0; iChangeAwait < packagesAwaitingChange.length; iChangeAwait++) {
7606          // try applying this packages again now.
7607          if (isSystemChanged()) {
7608              logStatus("Install: Verifying package (system changed) '" + getPackageName(packagesAwaitingChange[iChangeAwait]) +
7609                      "' (" + (packagesToInstall.length + iChangeAwait + 1) + "/" + sortedPackages.length + ")");
7610  
7611              installPackage(packagesAwaitingChange[iChangeAwait]);
7612          } else {
7613              logStatus("Install: No system change, skipping '" + getPackageName(packagesAwaitingChange[iChangeAwait]) +
7614                      "' (" + (packagesToInstall.length + iChangeAwait + 1) + "/" + sortedPackages.length + ")");
7615          }
7616      }
7617  
7618      logStatus("Finished software synchronization");
7619  
7620      // If we had previously warned the user about an impending installation, let
7621      // them know that all action is complete.
7622      notifyUserStop();
7623  }
7624  
7625  /*******************************************************************************
7626   * XML handling
7627   * ****************************************************************************
7628   */
7629  
7630  /**
7631   * Saves the root element to the specified XML file.
7632   */
7633  function saveXml(root, path) {
7634      if (isDryRun()) {
7635          path += ".dryrun";
7636      }
7637      dinfo("Saving XML : " + path);
7638      var xmlDoc = new ActiveXObject("Msxml2.DOMDocument.3.0");
7639      var processing = xmlDoc.createProcessingInstruction("xml", "version='1.0' encoding='UTF-8'");
7640      xmlDoc.insertBefore(processing, xmlDoc.firstChild);
7641      xmlDoc.appendChild(root);
7642      if (xmlDoc.save(path)) {
7643          throw new Error(0, "Could not save XML document to " + path);
7644      }
7645  }
7646  
7647  /**
7648   * Creates a new root element of the specified name.
7649   * 
7650   * @param root
7651   *           Root element name to be created. Might be prefixed by a namespace.
7652   *           e.g. "packages" or "packages:packages"
7653   * @param rootNS
7654   *           Optionally specify a namespace.
7655   *           e.g. "http://www.wpkg.org/packages"
7656   */
7657  function createXml(root, rootNS) {
7658      // Verify root node name.
7659      if (root == null) {
7660          return null;
7661      }
7662      // Evaluate namespace.
7663      var nameSpace = rootNS;
7664      if (nameSpace == null) {
7665          nameSpace = "";
7666      }
7667  
7668      // Create XML document.
7669      var xmlDoc = new ActiveXObject("Msxml2.DOMDocument.3.0");
7670      xmlDoc.async = false;
7671  
7672      // Create root node.
7673      var rootNode = xmlDoc.createNode(1, root, nameSpace);
7674  
7675      return rootNode;
7676  }
7677  
7678  /**
7679   * Loads XML from the given path and/or directory. Returns null in case XML
7680   * could not be loaded.
7681   * 
7682   * @param xmlPath
7683   *            optional path to XML file to be loaded, specify null if you do not
7684   *            want to load from XML file
7685   * @param xmlDirectory
7686   *            optional path to directory where XML file(s) might can be found.
7687   *            Specify null if you do not want to read from a directory.
7688   * @param type
7689   *            optional, type of XML to be loaded. If type is specified some
7690   *            validation on XML structure is done like the verification of root
7691   *            and child node names. In addition correct namespace is inserted
7692   *            into generated XML document.
7693   *            Supported types:
7694   *            - settings (local WPKG database XML)
7695   *            - hosts (hosts database)
7696   *            - profiles (profile database)
7697   *            - packages (package database)
7698   *            - config (configuration file)
7699   * @return XML root node containing all nodes from the specified files.
7700   */
7701  function loadXml(xmlPath, xmlDirectory, type) {
7702      // Initialize return variable.
7703      var xmlDocument = new ActiveXObject("Msxml2.DOMDocument.3.0");
7704      
7705      // Validation variables.
7706      // Name of XML root node. If null it will not be verified.
7707      var rootNodeName = null;
7708  
7709      // Namespace of XML if it is to be created.
7710      var xmlNamespace = null;
7711  
7712      // Name of child elements to be read if multiple files are read from directory.
7713      var childElementNodeName = null;
7714      
7715      // Evaluate type.
7716      var xmlType = type;
7717      if (xmlType != null) {
7718          switch (xmlType) {
7719          case "settings":
7720              rootNodeName = "wpkg";
7721              // childElementNodeName = "package";
7722              // Multiple child nodes (packages and check results).
7723              childElementNodeName = null;
7724              xmlNamespace = namespaceSettings;
7725              break;
7726  
7727          case "hosts":
7728              rootNodeName = "wpkg";
7729              childElementNodeName = "host";
7730              xmlNamespace = namespaceHosts;
7731              break;
7732  
7733          case "profiles":
7734              rootNodeName = "profiles";
7735              childElementNodeName = "profile";
7736              xmlNamespace = namespaceProfiles;
7737              break;
7738  
7739          case "packages":
7740              rootNodeName = "packages";
7741              childElementNodeName = "package";
7742              xmlNamespace = namespacePackages;
7743              break;
7744  
7745          case "config":
7746              rootNodeName = "config";
7747              // Do not verify child nodes as there are multiple:
7748              // - param
7749              // - languages
7750              childElementNodeName = null;
7751              xmlNamespace = namespaceConfig;
7752              break;
7753  
7754          default:
7755              break;
7756          }
7757      }
7758      
7759      // create variable to return
7760      // var rootNodeName = "pkg:packages";
7761      // var rootNodeName = "packages";
7762      // source.setProperty("SelectionNamespaces", "xmlns:packages='http://www.wpkg.org/packages'");
7763      var filePaths = new Array();
7764      
7765      // Read data from specified XML directory (load all XML from folder).
7766      if (xmlDirectory != null) {
7767          dinfo("Trying to read XML files from directory: " + xmlDirectory);
7768          // check if directory exists
7769          var fso = new ActiveXObject("Scripting.FileSystemObject");
7770          if( fso.FolderExists( xmlDirectory ) ) {
7771              var folder = fso.GetFolder(xmlDirectory);
7772              var e = new Enumerator(folder.files);
7773  
7774              // read all files
7775              for( e.moveFirst(); ! e.atEnd(); e.moveNext() ) {
7776                  var file = e.item();
7777                  var filePath = xmlDirectory.replace( /\\/g, "/" ) + "/" + file.name;
7778  
7779                  // search for last "."
7780                  var dotLocation = file.name.toString().lastIndexOf('.');
7781                  var extension = file.name.toString().substr(dotLocation + 1, file.name.toString().length);
7782  
7783                  // make sure to read only .xml files
7784                  if(extension == "xml") {
7785                      // Add file to list of files to be read.
7786                      filePaths.push(filePath);
7787                  }
7788              }
7789              // Sort files by name (ASCII order).
7790              filePaths.sort(null);
7791          } else {
7792              dinfo("Specified XML directory does not exist: " + xmlDirectory);
7793          }
7794      }
7795  
7796      // Add XML single-file path to the list of files to be read.
7797      if (xmlPath != null) {
7798          filePaths.push(xmlPath.replace( /\\/g, "/" ));
7799      }
7800  
7801      for( var i=0; i < filePaths.length; i++) {
7802          var filePath = filePaths[i];
7803          dinfo("Reading XML file: " + filePath);
7804  
7805          // Read XML file from file system.
7806          var xsl = new ActiveXObject("Msxml2.DOMDocument.3.0");
7807          xsl.async = false;
7808          xsl.validateOnParse = false;
7809          /*
7810          var str = "<?xml version=\"1.0\"?>\r\n";
7811          str += "<xsl:stylesheet xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\" xmlns:wpkg=\"" + xmlNamespace + "\" version=\"1.0\">\r\n";
7812          str += "    <xsl:output encoding=\"utf-8\" indent=\"yes\" method=\"xml\" version=\"1.0\"/>\r\n";
7813          str += "    <xsl:template match=\"/\">\r\n";
7814          str += "        <" + "wpkg:" + rootNodeName + ">\r\n";
7815          str += "            <xsl:copy-of select=\"document('" +
7816                                  filePath +
7817                                  "')/wpkg:" + rootNodeName + "/child::*\"/>\r\n";
7818          str += "            <xsl:copy-of select=\"document('" +
7819                                  filePath +
7820                                  "')/" + rootNodeName + "/child::*\"/>\r\n";
7821          str += "        </" + "wpkg:" + rootNodeName + ">\r\n";
7822          str += "    </xsl:template>\r\n";
7823          str += "</xsl:stylesheet>\r\n";
7824  
7825          xsl.loadXML(str);
7826          */
7827          xsl.load(filePath);
7828          // dinfo("XSLT: " + xsl.xml);
7829  
7830          // Apply transforms.
7831          var source = new ActiveXObject("Msxml2.DOMDocument.3.0");
7832          source.async = false;
7833          source.validateOnParse = false;
7834          try {
7835              source.loadXML(source.transformNode(xsl));
7836          } catch (e) {
7837              var errorMessage = "Error parsing xml '" + filePath + "': " + e.description;
7838              if (isQuitOnError()) {
7839                  throw new Error(errorMessage);
7840              } else {
7841                  error(errorMessage);
7842              }
7843          }
7844          
7845          // check if there was an error when loading XML
7846          if (source.parseError.errorCode != 0) {
7847              var loadError = source.parseError;
7848              var errorMessage = "Error parsing xml '" + filePath + "': " + loadError.reason + "\n" +
7849                              "File      " + filePath + "\n" +
7850                              "Line      " + loadError.line + "\n" +
7851                              "Linepos   " + loadError.linepos + "\n" +
7852                              "Filepos   " + loadError.filepos + "\n" +
7853                              "srcText   " + loadError.srcText + "\n";
7854              if (isQuitOnError()) {
7855                  throw new Error(errorMessage);
7856              } else {
7857                  error(errorMessage);
7858              }
7859          } else {
7860              // dinfo("Loaded: " + source.xml);
7861  
7862              // Verify document structure.
7863              if (source.documentElement == null) {
7864                  var message = "No root element found in '" + filePath + "'.";
7865                  if (isQuitOnError()) {
7866                      throw new Error(message);
7867                  } else {
7868                      error(message);
7869                  }
7870                  continue;
7871              }
7872              var xmlRootNodeName = source.documentElement.tagName;
7873  
7874              // Check name spaces.
7875              var rootComponents = xmlRootNodeName.split(":");
7876              var rootElementName = xmlRootNodeName;
7877              if (rootComponents.length > 1) {
7878                  // nameSpace = rootComponents[0];
7879                  rootElementName = rootComponents[1];
7880              }
7881              
7882              // Verify if root element is correct.
7883              if (rootNodeName != null && rootNodeName != rootElementName) {
7884                  // Element does not match expected root element name.
7885                  var message = "Invalid XML structure found. Root element '" +
7886                          rootElementName + "' does not match expected element name of '" +
7887                          rootNodeName + "'.";
7888                  if (isQuitOnError()) {
7889                      throw new Error(message);
7890                  } else {
7891                      error(message);
7892                  }
7893                  continue;
7894              }
7895              
7896              // If this is the only document to read, then just return it.
7897              if (filePaths.length <= 1) {
7898                  xmlDocument = source;
7899                  break;
7900              } else {
7901                  // Merge document contents.
7902                  if (xmlDocument.documentElement == null) {
7903                      var rootName = rootElementName;
7904                      if (xmlNamespace != null) {
7905                          rootName = rootName + ":" + rootName;
7906                      }
7907                      var rootElement = createXml(rootName, xmlNamespace);
7908                      xmlDocument.appendChild(rootElement);
7909                  }
7910                  // Fetch all document nodes from loaded XML document.
7911                  var childPath;
7912                  if (childElementNodeName != null) {
7913                      childPath = childElementNodeName;
7914                  } else {
7915                      childPath = "*";
7916                  }
7917                  var documentNodes = source.documentElement.selectNodes(childPath);
7918  
7919                  // Add all nodes to XML document to be returned.
7920                  var xmlRoot = xmlDocument.documentElement;
7921                  for (var iDocumentNode=0; iDocumentNode < documentNodes.length; iDocumentNode++) {
7922                      xmlRoot.appendChild(documentNodes[iDocumentNode]);
7923                  }
7924              }
7925          }
7926      }
7927      // In local (non-remote) mode the settings database read shall be reset in
7928      // order to assure to re-build the cached check-results.
7929      if (xmlType != null && xmlType == "settings" && getQueryMode() != "remote" ) {
7930          var documentElement = xmlDocument.documentElement;
7931          if (documentElement != null) {
7932              var checkResultsNode = documentElement.selectSingleNode("checkResults");
7933              if (checkResultsNode != null) {
7934                  documentElement.removeChild(checkResultsNode);
7935              }
7936          }
7937      }
7938      return xmlDocument.documentElement;
7939  }
7940  
7941  /**
7942   * Removes a sub-node from the given XML node entry.
7943   * 
7944   * @param XMLNode
7945   *            the XML node to remove from (e.g. packages or settings)
7946   * @param subNode
7947   *            the node to be removed from the XMLNode (for example a package
7948   *            node)
7949   * @return Returns true in case of success, returns false if no node could be
7950   *         removed
7951   */
7952  function removeNode(XMLNode, subNode) {
7953      var returnvalue = false;
7954      var result = XMLNode.removeChild(subNode);
7955      if(result != null) {
7956          returnvalue = true;
7957      }
7958      return returnvalue;
7959  }
7960  
7961  /**
7962   * Returns a new array of XML nodes unique by the specified attribute.
7963   */
7964  function uniqueAttributeNodes(nodes, attribute) {
7965      // Hold unique nodes in a new array.
7966      var newNodes = new Array();
7967  
7968      // Loop over nodes provided nodes searching for duplicated entries.
7969      for (var i = 0; i < nodes.length; i++) {
7970          // Get node for this loop.
7971          var node = nodes[i];
7972  
7973          // Get attribute which should be unique
7974          var attributeValue = node.getAttribute(attribute);
7975  
7976          // Determine if node with attribute already exists.
7977          var found = false;
7978  
7979          // Loop over elements of new nodes array and look for pre-existing
7980          // element.
7981          for (var j = 0; j < newNodes.length; j++) {
7982              var newNodeAttribute = newNodes[j].getAttribute(attribute);
7983              if (attributeValue == newNodeAttribute) {
7984                  found = true;
7985                  break;
7986              }
7987          }
7988  
7989          // If it doesn't exist, add it.
7990          if (!found) {
7991              newNodes.push(node);
7992          }
7993      }
7994      return newNodes;
7995  }
7996  
7997  /*******************************************************************************
7998   * Initialization and cleanup
7999   * ****************************************************************************
8000   */
8001  
8002  /**
8003   * Clean up function called at the end. Writes all required files, closes
8004   * handlers and prints/writes log. Then exits with the given exit code.
8005   */
8006  function cleanup() {
8007      // write settings XML file
8008      // no need as we save on each settings modification now.
8009      // saveSettings();
8010  
8011      // If there is still something in the log buffer write it to a file.
8012      if (logBuffer != null) {
8013          initializeLog();
8014      }
8015  
8016      // close log file
8017      // do not close the file if reboot is in progress
8018      // this is done since there might still be some writes to the file
8019      // before the reboot actually takes place
8020      if (getLogLevel() > 0 && !rebooting && getLogFile() != null) {
8021          // close the log
8022          getLogFile().Close();
8023      }
8024  }
8025  
8026  /**
8027   * Ends program execution with the specified exit code.
8028   */
8029  function exit(exitCode) {
8030      // print packages which have not been removed
8031      var skippedPackages = getSkippedRemoveNodes();
8032      if (skippedPackages.length > 0) {
8033          var message = "Packages where removal has been aborted:\n";
8034          for (var i=0; i<skippedPackages.length; i++) {
8035              var packageNode = skippedPackages[i];
8036              message += getPackageName(packageNode) + " (" +
8037                      getPackageID(packageNode) + ")\n";
8038          }
8039          info(message);
8040      }
8041  
8042      // check if there is a postponed reboot scheduled
8043      // cleanup is done directly within the reboot function
8044      if (isPostponedReboot()) {
8045          // postponed reboot executed
8046          setPostponedReboot(false);
8047          reboot();
8048      }
8049  
8050      // run cleanup
8051      cleanup();
8052  
8053      // reset running state
8054      if (!isNoRunningState()) {
8055          // Reset running state.
8056          setRunningState("false");
8057      }
8058  
8059      WScript.Quit(exitCode);
8060  }
8061  
8062  /**
8063   * Initializes the system, all required variables...
8064   */
8065  function initialize() {
8066      // Initialize configuration (read and set values).
8067      initializeConfig();
8068  
8069      // Parse command-line parameters.
8070      parseArguments(getArgv());
8071  
8072      // Print version number.
8073      dinfo("WPKG " + WPKG_VERSION + " starting...");
8074  
8075      // Inform to which value reboot command is set.
8076      dinfo("Reboot-Cmd is " + getRebootCmd() + ".");
8077  
8078      // Set quiet mode to desired value.
8079      if (quiet != null) {
8080          setQuiet(quiet);
8081      } else {
8082          setQuiet(quietDefault);
8083      }
8084      
8085      // get argument list
8086      var argv = getArgv();
8087  
8088      // Will be used for file operations.
8089      var fso = new ActiveXObject("Scripting.FileSystemObject");
8090  
8091      var httpregex = new RegExp("^http");
8092  
8093      var isWeb = false;
8094      var base = "";
8095  
8096      if(httpregex.test(wpkg_base) == true) {
8097          isWeb = true;
8098          base = wpkg_base;
8099      } else {
8100          // Use the executing location of the script as the default base
8101          // path.
8102          isWeb = false;
8103          if (wpkg_base == "") {
8104              var path = WScript.ScriptFullName;
8105              base = fso.GetParentFolderName(path);
8106          } else {
8107              base = fso.GetAbsolutePathName(wpkg_base);
8108          }
8109      }
8110  
8111      dinfo("Base directory is '" + base + "'.");
8112      dinfo("Log level is " + getLogLevel());
8113  
8114      var packages_file;
8115      var profiles_file;
8116      var hosts_file;
8117      var nodes;
8118      if (!isWeb) {
8119          // Append the settings file names to the end of the base path.
8120          packages_file = fso.BuildPath(base, packages_file_name);
8121          var packages_folder = fso.BuildPath(base, "packages");
8122          profiles_file = fso.BuildPath(base, profiles_file_name);
8123          var profiles_folder = fso.BuildPath(base, "profiles");
8124          hosts_file = fso.BuildPath(base, hosts_file_name);
8125          var hosts_folder = fso.BuildPath(base, "hosts");
8126          nodes = loadXml(profiles_file, profiles_folder, "profiles");
8127          if (nodes == null) {
8128              // cannot continue without profiles (probably network error
8129              // occurred)
8130              throw new Error(10, "No profiles found. Aborting");
8131          }
8132          setProfiles(nodes);
8133          nodes = loadXml(hosts_file, hosts_folder, "hosts");
8134          if (nodes == null) {
8135              // cannot continue without hosts (probably network error occurred)
8136              throw new Error(10, "No hosts found. Aborting");
8137          }
8138          setHosts(nodes);
8139          // load packages
8140          setPackages(loadXml(packages_file, packages_folder, "packages"));
8141      } else {
8142          packages_file = base + "/" + web_packages_file_name;
8143          profiles_file = base + "/" + web_profiles_file_name;
8144          hosts_file = base + "/" + web_hosts_file_name;
8145          nodes = loadXml(profiles_file, null, "profiles");
8146          if (nodes == null) {
8147              // cannot continue without profiles (probably network error
8148              // occurred)
8149              throw new Error(10, "No profiles found. Aborting");
8150          }
8151          setProfiles(nodes);
8152          nodes = loadXml(hosts_file, null, "hosts");
8153          if (nodes == null) {
8154              // cannot continue without hosts (probably network error occurred)
8155              throw new Error(10, "No hosts found. Aborting");
8156          }
8157          setHosts(nodes);
8158          // load packages
8159          setPackages(loadXml(packages_file, null, "packages"));
8160      }
8161      
8162      // Load packages and profiles.
8163      if (isForce() && isArgSet(argv, "/synchronize")) {
8164          dinfo("Skipping current settings. Checking for actually installed packages.");
8165  
8166          setSettings(createSettings(), true);
8167  
8168          fillSettingsWithInstalled();
8169  
8170      } else {
8171          // Load or create settings file.
8172          if (!fso.FileExists(getSettingsPath())) {
8173              dinfo("Settings file does not exist. Creating a new file.");
8174  
8175              setSettings(createSettings(), true);
8176          } else {
8177              dinfo("Reading settings file: " + getSettingsPath());
8178              // No need to save immediately because there is no change yet.
8179              setSettings(createSettingsFromFile(getSettingsPath()), false);
8180          }
8181      }
8182  }
8183  
8184  /**
8185   * Initializes configuration file
8186   */
8187  function initializeConfig() {
8188      // get list of parameters (<param... /> nodes)
8189      var param = getConfigParamArray();
8190  
8191      // loop through all parameters
8192      for (var i=0; i < param.length; i++) {
8193          var name = param[i].getAttribute("name");
8194          var value= param[i].getAttribute("value");
8195          if (name == "volatileReleaseMarker") {
8196              volatileReleaseMarkers.push((param[i].getAttribute("value")).toLowerCase());
8197          } else if(value === "true" || value === "false" || value === "null") {
8198              // If value is boolean or null, we don't want " around it.
8199              // Otherwise it'll be assigned as a string.
8200  
8201              // Here is where the <param name='...' ... /> is used as the
8202              // variable name and assigned the
8203              // <param ... value='...' /> value from the config.xml file. We're
8204              // using eval to do variable
8205              // substitution for the variable name.
8206              eval ( name + " = " + value );
8207          } else {
8208              // Non-Boolean value, put " around it.
8209  
8210              // Here is where the <param name='...' ... /> is used as the
8211              // variable name and assigned the
8212              // <param ... value='...' /> value from the config.xml file. We're
8213              // using eval to do variable
8214              // substitution for the variable name.
8215              eval ( name + " = \"" + value + "\"" );
8216          }
8217      }
8218      // Expand environment variables.
8219      var wshShell = new ActiveXObject("WScript.Shell");
8220      if(rebootCmd != null) {
8221          rebootCmd = wshShell.ExpandEnvironmentStrings(rebootCmd);
8222      }
8223      if(logfilePattern != null) {
8224          logfilePattern = wshShell.ExpandEnvironmentStrings(logfilePattern);
8225      }
8226  
8227      // Check if log level shall be altered.
8228      if (logLevel != null) {
8229          setLogLevel(logLevel);
8230      } else {
8231          setLogLevel(logLevelDefault);
8232      }
8233  }
8234  
8235  /**
8236   * Initializes log file depending on information available. If log file path is
8237   * not set or unavailable creates logfile within %TEMP%. Sets log file handler
8238   * to null in case logging is disabled (logLevel=0)
8239   * @returns log file handler; returns null if no logfile was initialized.
8240   */
8241  function initializeLog() {
8242      // Abort initialization if initialization is already running.
8243      if (logInitializing) {
8244          return logfileHandler;
8245      }
8246      /*
8247       * Set initializing flag during initialization to prevent initialization loop when logs are written during
8248       * initialization.
8249       */
8250      logInitializing = true;
8251  
8252      // only initialize a log file if log level is greater than 0
8253      if (getLogLevel() <= 0) {
8254          if (logfileHandler != null) {
8255              logfileHandler.Close();
8256              logfileHandler = null;
8257          }
8258          logfilePath = null;
8259          return null;
8260      }
8261  
8262      /** stores the new filehandler created during this execution */
8263      var newLogfileHandler = null;
8264      var newLogfilePath = null;
8265      var newLogfileAppendMode = false;
8266  
8267      /** file system object */
8268      var fso = new ActiveXObject("Scripting.FileSystemObject");
8269  
8270      // try to initialize real log file
8271      try {
8272          // build log file name
8273          var today = new Date();
8274          var year = today.getFullYear();
8275          var month = today.getMonth() + 1;
8276          var day = today.getDate();
8277          var hour = today.getHours();
8278          var minute = today.getMinutes();
8279          var second = today.getSeconds();
8280          if (month < 10) {
8281              month = "0" + month;
8282          }
8283          if (day < 10) {
8284              day = "0" + day;
8285          }
8286          if (hour < 10) {
8287              hour = "0" + hour;
8288          }
8289          if (minute < 10) {
8290              minute = "0" + minute;
8291          }
8292          if (second < 10) {
8293              second = "0" + second;
8294          }
8295  
8296          var logFileName = getLogfilePattern().replace(new RegExp("\\[HOSTNAME\\]", "g"), getHostname());
8297          logFileName = logFileName.replace(new RegExp("\\[YYYY\\]", "g"), year);
8298          logFileName = logFileName.replace(new RegExp("\\[MM\\]", "g"), month);
8299          logFileName = logFileName.replace(new RegExp("\\[DD\\]", "g"), day);
8300          logFileName = logFileName.replace(new RegExp("\\[hh\\]", "g"), hour);
8301          logFileName = logFileName.replace(new RegExp("\\[mm\\]", "g"), minute);
8302          logFileName = logFileName.replace(new RegExp("\\[ss\\]", "g"), second);
8303          // only apply profile if required
8304          /*
8305           * NOTE: In case profiles.xml is not valid this will quit the script on getProfile() call while keeping the
8306           * temporary local log file handler. As a result errors at initialization will be logged to local log only. So
8307           * make sure not to use the [PROFILE] placeholder if you like to remote- initialization logs (e.g. missing XML
8308           * files).
8309           */
8310          var regularExp = new RegExp("\\[PROFILE\\]", "g");
8311          if (regularExp.test(logFileName) == true) {
8312              // this will throw an error if profile is not available yet
8313              var profileList = getProfileList();
8314              // concatenate profile names or throw error if no names
8315              // available
8316              if (profileList.length > 0) {
8317                  var allProfiles = "";
8318                  for (var i=0; i<profileList.length; i++) {
8319                      if (allProfiles == "") {
8320                          allProfiles = profileList[i];
8321                      } else {
8322                          allProfiles += "-" + profileList[i];
8323                      }
8324                  }
8325                  logFileName = logFileName.replace(regularExp, allProfiles);
8326              } else {
8327                  throw new Error("Profile information not available.");
8328              }
8329          }
8330  
8331          if (log_file_path == null || log_file_path == "") {
8332              log_file_path = "%TEMP%";
8333          }
8334  
8335          newLogfilePath = new ActiveXObject("WScript.Shell").ExpandEnvironmentStrings(log_file_path + "\\" + logFileName);
8336  
8337          // Just open the log file in case it is not opened already or append mode changed from false to true.
8338          // Do not support switching from append mode to overwrite mode if the
8339          // log file is not changed as this would erase log entries.
8340          if (logfilePath != newLogfilePath || (logfileAppendMode != isLogAppend() && isLogAppend() == true)) {
8341              var newLogMessage = "Initializing new log file: '" + newLogfilePath + "' in ";
8342              if (isLogAppend()) {
8343                  newLogMessage += "append";
8344              } else {
8345                  newLogMessage += "replace";
8346              }
8347              newLogMessage += " mode.";
8348  
8349              dinfo(newLogMessage);
8350              try {
8351                  // Evaluate append mode.
8352                  // 2=write (use 8 for append mode)
8353                  var openMode = 2;
8354                  if (isLogAppend()) {
8355                      openMode = 8;
8356                  }
8357  
8358                  // If new logfile path is identical to existing log file then just the append mode changed.
8359                  if (logfilePath == newLogfilePath) {
8360                      // Paths are identical, so mode must have been changed.
8361                      // Re-open the file with new file mode.
8362                      // NOTE: This should be handled as an atomic/synchronized
8363                      // operation in multi-threaded environment (not for WSH).
8364                      if(logfileHandler != null) {
8365                          // Close file first.
8366                          logfileHandler.Close();
8367                          // Replace handler.
8368                          logfileHandler = fso.OpenTextFile(newLogfilePath, openMode, true, -2);
8369                          logfileAppendMode = isLogAppend();
8370                      }
8371                  } else {
8372                      // Open mode:
8373                      // 2=write (use 8 for append mode)
8374                      // true=create if not exist
8375                      // 0=ASCII, -1=unicode, -2=system default
8376                      newLogfileHandler = fso.OpenTextFile(newLogfilePath, openMode, true, -2);
8377                  }
8378                  newLogfileAppendMode = isLogAppend();
8379              } catch (e) {
8380                  // Fall back to local temp folder.
8381                  newLogfilePath = new ActiveXObject("WScript.Shell").ExpandEnvironmentStrings("%TEMP%\\" + logFileName);
8382                  dinfo("Failed to open log file: " + e.description + "; falling back to local logging: " + newLogfilePath);
8383                  if (logfilePath != newLogfilePath) {
8384                      // Open mode:
8385                      // 2=write (use 8 for append mode)
8386                      // true=create if not exist
8387                      // 0=ASCII, -1=unicode, -2=system default
8388                      newLogfileHandler = fso.OpenTextFile(newLogfilePath, 2, true, -2);
8389                      newLogfileAppendMode = false;
8390                  }
8391              }
8392          }
8393      } catch (err) {
8394          dinfo("Cannot initialize log file (" + err.description + "), probably not all data available " +
8395                  "yet, stick with local log file. ");
8396          // Initialize local log file.
8397          var newLogfile = new ActiveXObject("WScript.Shell").ExpandEnvironmentStrings("%TEMP%\\wpkg-logInit.log");
8398          // create new temporary file - overwrite existing
8399          newLogfileHandler = fso.OpenTextFile(newLogfile, 8, true, -2);
8400          newLogfileAppendMode = true;
8401          dinfo("Initialized temporary local log file: " + logfilePath);
8402      }
8403  
8404  
8405      // In case a new log file handler was created switch handlers and move data
8406      // from old log file to new log file.
8407      if (newLogfileHandler != null) {
8408          // Switch to new log file handler.
8409          // NOTE: In multi-threaded environment this shall be synchronized.
8410          var oldLogfileHandler = logfileHandler;
8411          var oldLogfilePath = logfilePath;
8412          logfileHandler = newLogfileHandler;
8413          logfileAppendMode = newLogfileAppendMode;
8414          logfilePath = newLogfilePath;
8415  
8416          // Transfer all logs to the new logfile and close old log file.
8417          if (oldLogfileHandler != null) {
8418              oldLogfileHandler.Close();
8419          }
8420          if (oldLogfilePath != null) {
8421              // Read old log file and write contents to new log file.
8422              // Open read-only.
8423              var readerFile = fso.OpenTextFile(oldLogfilePath, 1, true, -2);
8424              while (!readerFile.AtEndOfStream) {
8425                  logfileHandler.WriteLine(readerFile.ReadLine());
8426              }
8427              readerFile.Close();
8428              // delete old logfile
8429              fso.DeleteFile(oldLogfilePath, true);
8430          }
8431          // Write log buffer to file and clean buffer.
8432          if (logBuffer != null) {
8433              logfileHandler.Write(logBuffer);
8434              logBuffer = null;
8435          }
8436      }
8437      // Initialization finished.
8438      logInitializing = false;
8439      return logfileHandler;
8440  }
8441  
8442  /**
8443   * Processes command line options and sets internal variables accordingly.
8444   */
8445  function parseArguments(argv) {
8446      // Initialize temporary log file
8447      // Note: this will be done automatically on first log output
8448      // initializeLog();
8449  
8450      // Parse bare arguments.
8451      // All which start with a "/" and do not have a ":" in them.
8452      for (var i=0; i < argv.length; i++) {
8453          var argument = argv.Item(i);
8454          switch (argument) {
8455          // Check for quiet mode.
8456          case "/quiet":
8457              quiet = true;
8458              break;
8459  
8460          // Check for log append flag.
8461          case "/logAppend":
8462              setLogAppend(true);
8463              break;
8464  
8465          // Check for dry run flag.
8466          case "/dryrun":
8467              setDryRun(true);
8468              setDebug(true);
8469              setNoReboot(true);
8470              break;
8471  
8472          // Check for debug flag.
8473          case "/debug":
8474          case "/verbose":
8475              setDebug(true);
8476              break;
8477  
8478          // Check for help flag.
8479          case "/help":
8480              showUsage();
8481              exit(0);
8482              break;
8483  
8484          // Check for nonotify flag.
8485          case "/nonotify":
8486              setNoNotify(true);
8487              break;
8488  
8489          // Check for noreboot flag.
8490          case "/noreboot":
8491              setNoReboot(true);
8492              break;
8493  
8494          // Check for noremove flag.
8495          case "/noremove":
8496              setNoRemove(true);
8497              break;
8498  
8499          // Check for force flag.
8500          case "/force":
8501              setForce(true);
8502              break;
8503  
8504          // Check for quot on error flag.
8505          case "/quitonerror":
8506              setQuitOnError(true);
8507              break;
8508  
8509          // Check if status messages should be sent.
8510          case "/sendStatus":
8511              setSendStatus(true);
8512              break;
8513  
8514          // Check if upgrade-before-remove feature should be enabled.
8515          case "/noUpgradeBeforeRemove":
8516              setUpgradeBeforeRemove(false);
8517              break;
8518  
8519          // Check if installation should be forced.
8520          case "/forceinstall":
8521              setForceInstall(true);
8522              break;
8523  
8524          // Check if forced remove shall be disabled.
8525          case "/noforcedremove":
8526              setNoForcedRemove(true);
8527              break;
8528  
8529          // Check if WPKG state shall be exported to registry.
8530          case "/norunningstate":
8531              setNoRunningState(true);
8532              break;
8533  
8534          // Check if WPKG shall work case-insensitive.
8535          case "/ignoreCase":
8536              setCaseSensitivity(false);
8537              break;
8538  
8539          // Check if multiple profiles shall be applied
8540          case "/applymultiple":
8541              setApplyMultiple(true);
8542              break;
8543  
8544          // Check if user likes to disable all downloads.
8545          case "/noDownload":
8546              setNoDownload(true);
8547              break;
8548  
8549          // Check if /synchronize parameter is set.
8550          case "/synchronize":
8551              // Do not do anything. The /synchronize parameter is handled by main() function.
8552              break;
8553              
8554          default:
8555              // Check if the argument is a named argument.
8556              var argument = argv.Item(i);
8557              if (argument.indexOf(":") < 0) {
8558                  dinfo("Unknown argument: " + argv.Item(i));
8559              }
8560          }
8561      }
8562  
8563      // Get special purpose argument lists.
8564      var argn = argv.Named;
8565  
8566      // Process quiet mode flag.
8567      var quietFlagValue = argn.Item("quiet");
8568      if (quietFlagValue != null) {
8569          if (quietFlagValue == "true") {
8570              quiet = true;
8571          } else if (quietFlagValue == "false"){
8572              quiet = false;
8573          }
8574      }
8575  
8576      // Process log append mode flag.
8577      var logAppendFlagValue = argn.Item("logAppend");
8578      if (logAppendFlagValue != null) {
8579          if (logAppendFlagValue == "true") {
8580              setLogAppend(true);
8581          } else if (logAppendFlagValue == "false"){
8582              setLogAppend(false);
8583          }
8584      }
8585  
8586      // Process dryrun mode flag.
8587      var dryrunFlagValue = argn.Item("dryrun");
8588      if (dryrunFlagValue != null) {
8589          if (dryrunFlagValue == "true") {
8590              setDryRun(true);
8591              setDebug(true);
8592              setNoReboot(true);
8593          } else if (dryrunFlagValue == "false"){
8594              setDryRun(false);
8595              setNoReboot(false);
8596          }
8597      }
8598  
8599      // Process verbose mode flag.
8600      var verboseFlagValue = argn.Item("verbose");
8601      if (verboseFlagValue != null) {
8602          if (verboseFlagValue == "true") {
8603              setDebug(true);
8604          } else if (verboseFlagValue == "false"){
8605              setDebug(false);
8606          }
8607      }
8608      
8609      // Process debug mode flag.
8610      var debugFlagValue = argn.Item("debug");
8611      if (debugFlagValue != null) {
8612          if (debugFlagValue == "true") {
8613              setDebug(true);
8614          } else if (debugFlagValue == "false"){
8615              setDebug(false);
8616          }
8617      }
8618  
8619      // Process nonotify mode flag.
8620      var nonotifyFlagValue = argn.Item("nonotify");
8621      if (nonotifyFlagValue != null) {
8622          if (nonotifyFlagValue == "true") {
8623              setNoNotify(true);
8624          } else if (nonotifyFlagValue == "false"){
8625              setNoNotify(false);
8626          }
8627      }
8628  
8629      // Process noreboot mode flag.
8630      var norebootFlagValue = argn.Item("noreboot");
8631      if (norebootFlagValue != null) {
8632          if (norebootFlagValue == "true") {
8633              setNoReboot(true);
8634          } else if (norebootFlagValue == "false"){
8635              setNoReboot(false);
8636          }
8637      }
8638  
8639      // Process noremove mode flag.
8640      var noremoveFlagValue = argn.Item("noremove");
8641      if (noremoveFlagValue != null) {
8642          if (noremoveFlagValue == "true") {
8643              setNoRemove(true);
8644          } else if (noremoveFlagValue == "false"){
8645              setNoRemove(false);
8646          }
8647      }
8648  
8649      // Process force mode flag.
8650      var forceFlagValue = argn.Item("force");
8651      if (forceFlagValue != null) {
8652          if (forceFlagValue == "true") {
8653              setForce(true);
8654          } else if (forceFlagValue == "false"){
8655              setForce(false);
8656          }
8657      }
8658  
8659      // Process quitonerror mode flag.
8660      var quitonerrorFlagValue = argn.Item("quitonerror");
8661      if (quitonerrorFlagValue != null) {
8662          if (quitonerrorFlagValue == "true") {
8663              setQuitOnError(true);
8664          } else if (quitonerrorFlagValue == "false"){
8665              setQuitOnError(false);
8666          }
8667      }
8668  
8669      // Process sendStatus mode flag.
8670      var sendStatusFlagValue = argn.Item("sendStatus");
8671      if (sendStatusFlagValue != null) {
8672          if (sendStatusFlagValue == "true") {
8673              setSendStatus(true);
8674          } else if (sendStatusFlagValue == "false"){
8675              setSendStatus(false);
8676          }
8677      }
8678  
8679      // Process noUpgradeBeforeRemove mode flag.
8680      var noUpgradeBeforeRemoveFlagValue = argn.Item("noUpgradeBeforeRemove");
8681      if (noUpgradeBeforeRemoveFlagValue != null) {
8682          if (noUpgradeBeforeRemoveFlagValue == "true") {
8683              setUpgradeBeforeRemove(false);
8684          } else if (noUpgradeBeforeRemoveFlagValue == "false"){
8685              setUpgradeBeforeRemove(true);
8686          }
8687      }
8688  
8689      // Process forceinstall mode flag.
8690      var forceInstallFlagValue = argn.Item("forceinstall");
8691      if (forceInstallFlagValue != null) {
8692          if (forceInstallFlagValue == "true") {
8693              setForceInstall(true);
8694          } else if (forceInstallFlagValue == "false"){
8695              setForceInstall(false);
8696          }
8697      }
8698  
8699      // Process noforcedremove mode flag.
8700      var noForcedRemoveFlagValue = argn.Item("noforcedremove");
8701      if (noForcedRemoveFlagValue != null) {
8702          if (noForcedRemoveFlagValue == "true") {
8703              setNoForcedRemove(true);
8704          } else if (noForcedRemoveFlagValue == "false"){
8705              setNoForcedRemove(false);
8706          }
8707      }
8708  
8709      // Process norunningstate mode flag.
8710      var noRunningStateFlagValue = argn.Item("norunningstate");
8711      if (noRunningStateFlagValue != null) {
8712          if (noRunningStateFlagValue == "true") {
8713              setNoRunningState(true);
8714          } else if (noRunningStateFlagValue == "false"){
8715              setNoRunningState(false);
8716          }
8717      }
8718  
8719      // Process ignoreCase mode flag.
8720      var ignoreCaseFlagValue = argn.Item("ignoreCase");
8721      if (ignoreCaseFlagValue != null) {
8722          if (ignoreCaseFlagValue == "true") {
8723              setCaseSensitivity(false);
8724          } else if (ignoreCaseFlagValue == "false"){
8725              setCaseSensitivity(true);
8726          }
8727      }
8728  
8729      // Process applymultiple mode flag.
8730      var applyMultipleFlagValue = argn.Item("applymultiple");
8731      if (applyMultipleFlagValue != null) {
8732          if (applyMultipleFlagValue == "true") {
8733              setApplyMultiple(true);
8734          } else if (applyMultipleFlagValue == "false"){
8735              setApplyMultiple(false);
8736          }
8737      }
8738  
8739      // Process noDownload mode flag.
8740      var noDownloadFlagValue = argn.Item("noDownload");
8741      if (noDownloadFlagValue != null) {
8742          if (noDownloadFlagValue == "true") {
8743              setNoDownload(true);
8744          } else if (noDownloadFlagValue == "false"){
8745              setNoDownload(false);
8746          }
8747      }
8748      
8749      // Parse parameters with string values.
8750  
8751      // Fetch base folder where to read XML files from.
8752      if (argn("base") != null) {
8753          wpkg_base = argn("base");
8754      }
8755      
8756      // Process log level.
8757      if (argn.Item("logLevel") != null) {
8758          setLogLevel(parseInt(argn.Item("logLevel")));
8759      }
8760      
8761      // Set the profile from either the command line or the hosts file.
8762      if (argn.Item("host") != null) {
8763          setHostname(argn("host"));
8764      }
8765  
8766      // Parse OS override setting.
8767      if (argn.Item("os") != null) {
8768          setHostOS(argn("os"));
8769      }
8770  
8771      // Parse IP address override setting.
8772      if (argn.Item("ip") != null) {
8773          var ipListParam = argn.Item("ip").split(",");
8774          setIPAddresses(ipListParam);
8775      }
8776  
8777      // Parse domain name override setting.
8778      if (argn.Item("domainname") != null) {
8779          setDomainName(argn.Item("domainname"));
8780      }
8781  
8782      // Parse group override setting.
8783      if (argn.Item("group") != null) {
8784          var hostGroupParam = argn.Item("group").split(",");
8785          setHostGroups(hostGroupParam);
8786      }
8787  
8788      // Process log file pattern.
8789      if (argn.Item("logfilePattern") != null) {
8790          setLogfilePattern(argn.Item("logfilePattern"));
8791      }
8792  
8793      // Process path to log file.
8794      if (argn.Item("log_file_path") != null) {
8795          log_file_path = argn.Item("log_file_path");
8796      }
8797  
8798      // Parse reboot command.
8799      if (argn.Item("rebootcmd") != null) {
8800          setRebootCmd(argn.Item("rebootcmd"));
8801      }
8802  
8803      // Parse path to settings file.
8804      if (argn.Item("settings") != null) {
8805          setSettingsPath(argn.Item("settings"));
8806      }
8807      
8808      // Evaluate query mode.
8809      if (argn.Item("query") != null) {
8810          // Read query mode.
8811          setQueryMode(argn.Item("queryMode"));
8812  
8813          if (getQueryMode() == "remote") {
8814              dinfo("Query mode: remote");
8815          }
8816      }
8817  }
8818  
8819  /**
8820   * Saves settings to file system. Optionally allows sorting of package nodes.
8821   * 
8822   * @param sort {Boolean} Set to true in order to sort package nodes.
8823   */
8824  function saveSettings(sort) {
8825      if (getQueryMode() == "remote") {
8826          // Do not save settings in remote qurey mode.
8827          dinfo("Skipping settings save: Remote query mode enabled.");
8828          return;
8829      }
8830  
8831      var sortPackages = true;
8832      if (sort != null && sort == false) {
8833          sortPackages = false;
8834      }
8835      
8836      if (sortPackages) {
8837          dinfo("Saving sorted settings to '" + getSettingsPath() + "'." + sort);
8838          sortSettings();
8839      } else {
8840          dinfo("Saving unsorted settings to '" + getSettingsPath() + "'." + sort);
8841      }
8842  
8843      // Do not save settings if settings are empty or in remote query mode.
8844      if (getSettingsPath() != null && settings != null) {
8845          saveXml(settings, getSettingsPath());
8846      } else {
8847          dinfo("Settings not saved! Either settings are empty or path is not set.");
8848      }
8849  }
8850  
8851  /*******************************************************************************
8852   * LOG FUNCTIONS
8853   * ****************************************************************************
8854   */
8855  
8856  /**
8857   * Echos text to the command line or a prompt depending on how the program is
8858   * run.
8859   */
8860  function alert(message) {
8861      WScript.Echo(message);
8862  }
8863  
8864  /**
8865   * Presents some debug output if debugging is enabled
8866   */
8867  function dinfo(stringInfo) {
8868      log(8, stringInfo);
8869  }
8870  
8871  /**
8872   * Logs or presents an error message depending on interactivity.
8873   */
8874  function error(message) {
8875      log(1, message);
8876  }
8877  
8878  /**
8879   * Returns log file handler. If logfile has not been initialized yet, starts
8880   * initialization and returns new filehandler.
8881   * 
8882   * Returns null in case logLevel is set to 0.
8883   * 
8884   * @return log file handler (returns null if log level is 0)
8885   */
8886  function getLogFile() {
8887      return logfileHandler;
8888  }
8889  
8890  /**
8891   * Creates a log line from a given string. The severity string is automatically
8892   * padded to a fixed length too to make the log entries easily readable.
8893   * 
8894   * @param severity
8895   *            string which represents log severity
8896   * @param message
8897   *            string which represents the message to be logged
8898   * @return log entry in its default format:<br>YYYY-MM-DD hh:mm:ss, SEVERITY:
8899   *         <message>
8900   */
8901  function getLogLine(severity, message) {
8902      var severityPadding = 7;
8903      // pad string with spaces
8904      for (var i = severity.length; i <= severityPadding; i++) {
8905          severity += " ";
8906      }
8907  
8908      // escape pipes (since they are used as new-line characters)
8909      var logLine = message.replace(new RegExp("\\|", "g"), "\\|");
8910      // replace new-lines by pipes
8911      logLine = logLine.replace(new RegExp("(\\r\\n)|(\\n\\r)|[\\r\\n]+", "g"), "|");
8912  
8913      // build date string
8914      var today = new Date();
8915      var year = today.getFullYear();
8916      var month = today.getMonth() + 1;
8917      var day = today.getDate();
8918      var hour = today.getHours();
8919      var minute = today.getMinutes();
8920      var second = today.getSeconds();
8921      if (month < 10) {
8922          month = "0" + month;
8923      }
8924      if (day < 10) {
8925          day = "0" + day;
8926      }
8927      if (hour < 10) {
8928          hour = "0" + hour;
8929      }
8930      if (minute < 10) {
8931          minute = "0" + minute;
8932      }
8933      if (second < 10) {
8934          second = "0" + second;
8935      }
8936  
8937      var tstamp = year + "-" + month + "-" + day + " " + hour + ":" + minute + ":" + second;
8938  
8939      // build log line
8940      logLine = tstamp + ", " + severity + ": " + logLine;
8941  
8942      return logLine;
8943  }
8944  
8945  /**
8946   * Returns the current log level:
8947   *
8948   * @return Log level<br>
8949   * 0: do not log anything, disables writing of a log-file<br>
8950   * 1: log errors only<br>
8951   * 2: log errors and warnings<br>
8952   * 4: log errors, warnings and information<br>
8953   */
8954  function getLogLevel() {
8955      return logLevelValue;
8956  }
8957  
8958  /**
8959   * Logs or presents an info message depending on interactivity.
8960   */
8961  function info(message) {
8962      log(4, message);
8963      // PATCH SE3 : on a modifié notify pour utiliser les tooltips déployés : on informe l'user de ce que fait wpkg-se3.js
8964      notify(message);
8965      // PATCH SE3
8966  }
8967  
8968  /**
8969   * Logs the specified event type and description in the Windows event log.
8970   *
8971   * Log types:
8972   * <pre>
8973   * 0    SUCCESS
8974   * 1    ERROR
8975   * 2    WARNING
8976   * 4    INFORMATION
8977   * 8    AUDIT_SUCCESS
8978   * 16   AUDIT_FAILURE
8979   * </pre>
8980   */
8981  function log(type, description) {
8982      // just log information level to event log or everything in case debug is
8983      // enabled.
8984      if (((type & 7) > 0 || isDebug()) && !isSkipEventLog()) {
8985          if(isQuiet() && !isEventLogFallback()) {
8986              try {
8987                  WshShell = WScript.CreateObject("WScript.Shell");
8988                  WshShell.logEvent(type, "" + description);
8989              } catch (e) {
8990                  // skip future event log entries and log an error
8991                  setEventLogFallback(true);
8992                  var message = "Error when writing to event log, falling back" +
8993                              " to standard output (STDOUT).\n" +
8994                              "Description: " + e.description + "\n" +
8995                              "Error number: " + hex(e.number) + "\n" +
8996                              "Stack: " + e.stack  + "\n" +
8997                              "Line: " + e.lineNumber + "\n";
8998                  error(message);
8999  
9000                  // write message to STDOUT to ensure it is not lost
9001                  alert(description);
9002              }
9003          } else {
9004              alert(description);
9005          }
9006      }
9007      if ((type & getLogLevel()) > 0) {
9008          // write to log file
9009          var logSeverity = "unspecified";
9010          switch(type) {
9011              case 0:
9012                  logSeverity = "SUCCESS";
9013                  break;
9014              case 1:
9015                  logSeverity = "ERROR";
9016                  break;
9017              case 2:
9018                  logSeverity = "WARNING";
9019                  break;
9020              case 4:
9021                  logSeverity = "INFO";
9022                  break;
9023              case 8:
9024                  logSeverity = "DEBUG";
9025                  break;
9026              case 16:
9027                  logSeverity = "DEBUG";
9028                  break;
9029          }
9030  
9031          var logFile = getLogFile();
9032          if (logFile != null) {
9033              // Write log to file.
9034              logFile.WriteLine(getLogLine(logSeverity, description));
9035          } else {
9036              // First write log line to buffer.
9037              if (logBuffer != null) {
9038                  // Write log entry to local buffer.
9039                  logBuffer += getLogLine(logSeverity, description) + "\r\n";
9040              } else {
9041                  // Create new log buffer.
9042                  logBuffer = getLogLine(logSeverity, description) + "\r\n";
9043              }
9044              if (logInitReady == true) {
9045                  // Log file not initialized but ready to be initialized
9046                  // If log is ready to be initialized, then initialize it.
9047                  initializeLog();
9048              }
9049          }
9050      }
9051  }
9052  
9053  /**
9054   * Logs status message which can be read by WPKG client to display messages to
9055   * the user
9056   * 
9057   * @param message
9058   *            the message to be sent to the client.
9059   */
9060  function logStatus(message) {
9061      if (isSendStatus()) {
9062          alert(getLogLine("STATUS", message));
9063      }
9064  }
9065  
9066  /**
9067   * Notifies the user/computer with a pop up message.
9068   */
9069  function notify(message) {
9070      if (!isNoNotify()) {
9071          // PATCH SE3 pour utilisation du msg.exe des tooltips.
9072          var msgPath = "%SystemRoot%\\wpkg-msg.exe";
9073          // FIN PATCH
9074          var netPath = "%SystemRoot%\\System32\\net.exe";
9075          var cmd = "";
9076          // check if msg.exe exists
9077          var fso = new ActiveXObject("Scripting.FileSystemObject");
9078          if(fso.FileExists(new ActiveXObject("WScript.Shell").ExpandEnvironmentStrings(msgPath))) {
9079              // try msg method
9080              // cmd += "%COMSPEC% /U /C chcp 65001 && echo " + message + " | " +
9081              // msgPath + " * /TIME:" + notificationDisplayTime;
9082              cmd += msgPath + " * /TIME:" + notificationDisplayTime + " \"" + message + "\"";
9083          } else {
9084              // try net send method
9085              cmd += netPath + " SEND ";
9086              cmd += getHostname();
9087              cmd += " \"" + message + "\"";
9088          }
9089          try {
9090              exec(cmd, 0, null);
9091          } catch (e) {
9092              var errorMessage = "Notification failed. " + e.description;
9093              if (isQuitOnError()) {
9094                  throw new Error(0, errorMessage);
9095              } else {
9096                  error(errorMessage);
9097              }
9098          }
9099      // PATCH SE3 : saturation dans le cas /nonotify.
9100      //} else {
9101      //    info("User notification suppressed. Message: " + message);
9102      // FIN PATCH SE3
9103      }
9104  }
9105  
9106  /**
9107   * Sends a message to the system console notifying the user that installation
9108   * failed.
9109   */
9110  function notifyUserFail() {
9111      // get localized message
9112      var msg = getLocalizedString("notifyUserFail");
9113      if (msg == null) {
9114          msg = "The software installation has failed.";
9115      }
9116  
9117      try {
9118          notify(msg);
9119      } catch (e) {
9120          error("Unable to notify the user that all action has been completed.");
9121      }
9122  }
9123  
9124  /**
9125   * Sends a message to the system console notifying of impending action.
9126   */
9127  function notifyUserStart() {
9128      if (!was_notified) {
9129          // get localized message
9130          var msg = getLocalizedString("notifyUserStart");
9131          if (msg == null) {
9132              msg = "";
9133              msg += "Automatic software deployment is currently updating your ";
9134              msg += "system. Please wait until the process has finished. Thank you.";
9135          }
9136  
9137          was_notified = true;
9138  
9139          try {
9140              notify(msg);
9141          } catch (e) {
9142              throw new Error(0, "Unable to notify user that the system was " +
9143                  "about to begin updating. " + e.description);
9144          }
9145      }
9146  }
9147  
9148  /**
9149   * Sends a message to the system console notifying them that all action is
9150   * complete.
9151   */
9152  function notifyUserStop() {
9153      if(was_notified) {
9154          // get localized message
9155          var msg = getLocalizedString("notifyUserStop");
9156          if (msg == null) {
9157              msg = "";
9158              msg += "The automated software installation utility has completed ";
9159              msg += "installing or updating software on your system. No reboot was ";
9160              msg += "necessary. All updates are complete.";
9161          }
9162  
9163          try {
9164              notify(msg);
9165          } catch (e) {
9166              error("Unable to notify the user that all actions have been completed.");
9167          }
9168      }
9169  }
9170  
9171  /**
9172   * Set new locale LCID for user.
9173   * 
9174   * @param newLocale new locale to be used starting from now.
9175   */
9176  function setLocale(newLocale) {
9177      if (newLocale != null) {
9178          LCID = newLocale;
9179      }
9180  }
9181  
9182  /**
9183   * Set new locale LCID for OS.
9184   * 
9185   * @param newLocale new locale to be used starting from now.
9186   */
9187  function setLocaleOS(newLocale) {
9188      if (newLocale != null) {
9189          LCIDOS = newLocale;
9190      }
9191  }
9192  
9193  /**
9194   * Sets new log append value.
9195   * 
9196   * @param append
9197   *            true if log should be appended, false otherwise (boolean)
9198   */
9199  function setLogAppend(append) {
9200      logAppend = append;
9201  }
9202  
9203  
9204  /**
9205   * Sets new logging level.
9206   *
9207   * @param newLevel new log level to be used:<br>
9208   * 0: do not log anything, disables writing of a log-file<br>
9209   * 1: log errors only<br>
9210   * 2: log errors and warnings<br>
9211   * 4: log errors, warnings and information
9212   */
9213  function setLogLevel(newLevel) {
9214      logLevelValue = parseInt(newLevel);
9215  }
9216  
9217  /**
9218   * Sets new state for the nonotify flag.
9219   * 
9220   * @param newState
9221   *            new state of the nonotify flag (boolean)
9222   */
9223  function setNoNotify(newState) {
9224      nonotify = newState;
9225  }
9226  
9227  /**
9228   * Sets new state for the upgrade-before-remove flag.
9229   * 
9230   * @param newState
9231   *            set to true if you want to enable the upgrade-before-remove
9232   *            feature. Otherwise set false.
9233   */
9234  function setUpgradeBeforeRemove(newState) {
9235      noUpgradeBeforeRemove = !newState;
9236  }
9237  
9238  /**
9239   * Logs or presents a warning message depending on interactivity.
9240   */
9241  function warning(message) {
9242      log(2, message);
9243  }
9244  
9245  /*******************************************************************************
9246   * SUPPLEMENTARY FUNCTIONS Not directly related to the application logic but
9247   * used by several functions to fulfill the task.
9248   * ****************************************************************************
9249   */
9250  
9251  
9252  /**
9253   * Combines one list and another list into a single array.
9254   */
9255  function concatenateList(list1, list2) {
9256      // Create a new array the size of the sum of both original lists.
9257      var list = new Array();
9258  
9259      for (var iList1 = 0; iList1 < list1.length; iList1++) {
9260          list.push(list1[iList1]);
9261      }
9262  
9263      for (var iList2 = 0; iList2 < list2.length; iList2++) {
9264          list.push(list2[iList2]);
9265      }
9266  
9267      return list;
9268  }
9269  
9270  /**
9271   * Concatenates two Dictionary object and returns concatenated list.
9272   * Does not modify any of the dictionaries passed as paramters. Returns new
9273   * Dictionary object instead.
9274   * If an element is listed in both dictionaries, then the value of the second
9275   * Dictionary is applied (overwrite).
9276   * Throws error in case an error occurs during dictionary append.
9277   * @param dictionary1 Dictionary to be used as a base.
9278   * @param dictionary2 Dictionary to be appended to dictionary1.
9279   * @returns Dictionary object containing values of dicitonary1 and dictionary2.
9280   */
9281  function concatenateDictionary(dictionary1, dictionary2) {
9282      // Return variable.
9283      var concatenatedDictionary = new ActiveXObject("Scripting.Dictionary");
9284  
9285      var dictionaries = new Array();
9286      dictionaries.push(dictionary1);
9287      dictionaries.push(dictionary2);
9288      
9289      // Concatenate
9290      for (var iDictionary=0; iDictionary<dictionaries.length; iDictionary++) {
9291          var dictionary = dictionaries[iDictionary];
9292          var dictKeys = dictionaries[iDictionary].keys().toArray();
9293  
9294          for (var iDictKey=0; iDictKey<dictKeys.length; iDictKey++) {
9295              var key = dictKeys[iDictKey];
9296              var value = dictionary.Item(key);
9297      
9298              // remove eventually existing variable
9299              // I don't like to use
9300              // variables.Item(variableName)=variableValue;
9301              // because my IDE/parser treats it as an error:
9302              // "The left-hand side of an assignment must be a variable"
9303              try {
9304                  concatenatedDictionary.Remove(key);
9305              } catch(e) {
9306                  // dinfo("Dictionary element '" + key + "' was not defined before. Creating now.");
9307              }
9308              try {
9309                  concatenatedDictionary.Add(key, value);
9310              } catch(e) {
9311                  var message = "Dictionary element '" + key + "' with value '" + value + "'" +
9312                      " could not be assigned to dictionary!";
9313                  if (isQuitOnError()) {
9314                      throw new Error(message);
9315                  }
9316                  error(message);
9317              }
9318          }
9319      }
9320  
9321      return concatenatedDictionary;
9322  }
9323  
9324  
9325  /**
9326   * Downloads a file by url, target directory and timeout
9327   * 
9328   * @param url
9329   *            full file URL to download (http://www.server.tld/path/file.msi)
9330   * @param target
9331   *            target directory do download to. This is specified relative to the
9332   *            downloadUrl path as specified within config.xml
9333   * @param timeout
9334   *            timeout in seconds
9335   * @return true in case of successful download, false in case of error
9336   */
9337  function downloadFile(url, target, timeout, expandURL) {
9338      if (url == null || url == "") {
9339          error("No URL specified for download!");
9340          return false;
9341      }
9342  
9343      // evaluate target directory
9344      if (target == null || target == "") {
9345          error("Invalid download target specified: " + target);
9346          return false;
9347      } else {
9348          target = downloadDir + "\\" + target;
9349      }
9350  
9351      try {
9352          // Get shell to expand environment.
9353          var shell = new ActiveXObject("WScript.Shell");
9354  
9355          // Expand environment on target.
9356          target = shell.ExpandEnvironmentStrings(target);
9357  
9358          // Expand environment on URL.
9359          if (expandURL) {
9360              url = shell.ExpandEnvironmentStrings(url);
9361          }
9362  
9363          var fso = new ActiveXObject("Scripting.FileSystemObject");
9364          var stream = new ActiveXObject("ADODB.Stream");
9365          var xmlHttp = new createXmlHttp();
9366  
9367          dinfo("Downloading '" + url + "' to '" + target + "'");
9368  
9369          // open HTTP connection
9370          xmlHttp.open("GET", url, true);
9371          xmlHttp.setRequestHeader("User-Agent", "XMLHTTP/1.0");
9372          xmlHttp.send();
9373  
9374          for (var t=0; t < timeout; t++) {
9375              if (xmlHttp.ReadyState == 4) {
9376                  break;
9377              }
9378              WScript.Sleep(1000);
9379          }
9380  
9381          // abort download if not finished yet
9382          if (xmlHttp.ReadyState != 4) {
9383              xmlHttp.abort();
9384              error("HTTP Timeout after " + timeout + " seconds.");
9385          }
9386  
9387          // check if download has been completed
9388          if (xmlHttp.status != 200) {
9389              error("HTTP Error: " + xmlHttp.status + ", " + xmlHttp.StatusText);
9390          }
9391  
9392          stream.open();
9393          stream.type = 1;
9394  
9395          stream.write(xmlHttp.responseBody);
9396          stream.position = 0;
9397  
9398          // delete temporary file if it already exists
9399          if (fso.FileExists(target)) {
9400              fso.DeleteFile(target);
9401          }
9402  
9403          // check if target folder exists, crate if required
9404          var folder = fso.getParentFolderName(target);
9405          var folderStructure = new Array();
9406  
9407          while (!fso.FolderExists(folder)) {
9408              folderStructure.push(folder);
9409              folder = fso.getParentFolderName(folder);
9410          }
9411          // create folders
9412          for (var i=folderStructure.length-1; i>=0; i--) {
9413              fso.createFolder(folderStructure[i]);
9414          }
9415  
9416          // write file
9417          stream.saveToFile(target);
9418          stream.close();
9419  
9420      } catch (e) {
9421          error("Download failed: " + e.description);
9422          return false;
9423      }
9424  
9425      return true;
9426  }
9427  
9428  /**
9429   * This method is used to return an XMLHTTP object. Depending on the MSXML
9430   * version used the factory is different.
9431   * 
9432   * @return XMLHTTP object
9433   */
9434  function createXmlHttp() {
9435      var xmlHttpFactories = [
9436          function () {return new XMLHttpRequest();},
9437          function () {return new ActiveXObject("Msxml2.XMLHTTP");},
9438          function () {return new ActiveXObject("Msxml3.XMLHTTP");},
9439          function () {return new ActiveXObject("Microsoft.XMLHTTP");}
9440      ];
9441  
9442      var xmlHttp = null;
9443      for (var i=0; i < xmlHttpFactories.length; i++) {
9444          try {
9445              xmlHttp = xmlHttpFactories[i]();
9446          } catch (e) {
9447              continue;
9448          }
9449          break;
9450      }
9451      return xmlHttp;
9452  }
9453  
9454  
9455  
9456  /**
9457   * Executes a shell command and blocks until it is completed, returns the
9458   * program's exit code. Command times out and is terminated after the specified
9459   * number of seconds.
9460   * 
9461   * @param cmd
9462   *            the command line to be executed
9463   * @param timeout
9464   *            timeout value in seconds (use value <= 0 for default timeout)
9465   * @param workdir
9466   *            working directory (optional). If set to null uses the current
9467   *            working directory of the script.
9468   * @return command exit code (or -1 in case of timeout)
9469   */
9470  function exec(cmd, timeout, workdir) {
9471      if (isDryRun()) {
9472          return 0;
9473      }
9474      // Create shell object for variable expansion.
9475      var shell = new ActiveXObject("WScript.Shell");
9476  
9477      // Expand command for better traceability in logs.
9478      var cmdExpanded = shell.ExpandEnvironmentStrings(cmd);
9479  
9480      // Initialize shell execute object.
9481      var shellExec = null;
9482  
9483      try {
9484  
9485          // Timeout after an hour by default.
9486          if (timeout <= 0) {
9487              timeout = 3600;
9488          }
9489  
9490          // set working directory (if supplied)
9491          if (workdir != null && workdir != "") {
9492              workdir = shell.ExpandEnvironmentStrings(workdir);
9493              dinfo("Switching to working directory: " + workdir);
9494              shell.CurrentDirectory = workdir;
9495          }
9496  
9497          var executeMessage = "Executing command: '" + cmd + "'";
9498          if (cmd != cmdExpanded) {
9499              executeMessage += " ('" + cmdExpanded + "')";
9500          }
9501          dinfo(executeMessage + ".");
9502          var shellExec = shell.exec(cmd);
9503          var startTime = (new Date()).getTime();
9504  
9505          // close STDIN channel as we won't write to it and some command like
9506          // PowerShell might wait for it to be closed on exit
9507          shellExec.StdIn.close();
9508  
9509          var timeUsed = 0;
9510          var timeoutMilliseconds = timeout * 1000;
9511          var increment = 10;
9512          var incrementMax = 1000;
9513          while (shellExec.status == 0) {
9514              /*
9515               * Unfortunately WSH is terribly broken when handling I/O streams from processes. AtEndOfStream blocks as
9516               * well as ReadAll(), Read(x) and ReadLine(). So it's impossible to fetch STDOUT/ STDERR without blocking
9517               * the main WPKG program. So either you can fetch the output or wait for the program to terminate, but not
9518               * both. For WPKG it's more important to handle a timeout in order to handle programs which do not terminate
9519               * properly or interactively ask for input. Unfortunately sub-processes seem to be blocked if they write
9520               * more than 4k of data to STDOUT and/or STDERR buffer. So make sure your commands do not print too much on
9521               * the console. If in doubt you might redirect STDOUT/STDERR to a file. For example by adding ">
9522               * %TEMP%\myprog-out.txt 2>&1" to the command line. See
9523               * <http://www.tech-archive.net/Archive/Scripting/microsoft.public.scripting.wsh/2004-10/0204.html> for a
9524               * discussion on this topic.
9525               */
9526              // Read and discard the output buffers to prevent process blocking
9527              /*
9528               * if (!shellExec.StdOut.AtEndOfStream) { dinfo("STDOUT: " + shellExec.StdOut.ReadAll()); } if
9529               * (!shellExec.StdErr.AtEndOfStream) { dinfo("STDERR: " + shellExec.StdErr.ReadAll()); }
9530               */
9531  
9532              for(var i=0; i < 10 && shellExec.status == 0 && timeUsed < timeoutMilliseconds; i++) {
9533                  WScript.Sleep(increment);
9534                  timeUsed += increment;
9535              }
9536  
9537              if (shellExec.status != 0) {
9538                  break;
9539              }
9540              increment = increment * 10;
9541              if(increment > incrementMax) {
9542                  increment = incrementMax;
9543              }
9544              // Update time used to get real time used
9545              timeUsed = (new Date()).getTime() - startTime;
9546              if (timeUsed >= timeoutMilliseconds) {
9547                  throw new Error("Timeout reached while executing.");
9548              }
9549          }
9550  
9551          return shellExec.exitCode;
9552      } catch (e) {
9553          // handle execution exception
9554          var message = "Command '" + cmd + "'";
9555          if (cmd != cmdExpanded) {
9556              message += " ('" + cmdExpanded + "')";
9557          }
9558          message += " was unsuccessful.\n" + e.description;
9559          if(isQuitOnError()) {
9560              throw new Error(message);
9561          } else {
9562              error(message);
9563              return -1;
9564          }
9565      } finally {
9566          // If process is not terminated then make sure it's terminated now.
9567          if (shellExec != null && shellExec.status == 0) {
9568              shellExec.Terminate();
9569          }
9570      }
9571  }
9572  
9573  /**
9574   * Returns script arguments
9575   */
9576  function getArgv() {
9577      return WScript.Arguments;
9578  }
9579  
9580  /**
9581   * Returns processor architecture as reported by Windows.
9582   * Currently returns the following architecture strings:
9583   * <pre>
9584   * String       Description
9585   * x86          Intel x86 compatible 32-bit architecture
9586   * x64          AMD64 compatible 64-bit architecture
9587   * ia64         Itanium compatible 64-bit IA64 instruction set
9588   * </pre>
9589   * 
9590   * Other architectures are currently not supported.
9591   * 
9592   * @returns Processor architecture string.
9593   */
9594  function getArchitecture() {
9595      if (hostArchitecture == null) {
9596          hostArchitecture = "x86";
9597          var wshObject = new ActiveXObject("WScript.Shell");
9598          // check if PROCESSOR_ARCHITECTURE is AMD64
9599          // NOTE: On 32-bit systems PROCESSOR_ARCHITECTURE is x86 even if the CPU is
9600          // actually a 64-bit CPU
9601          var architecture = wshObject.ExpandEnvironmentStrings("%PROCESSOR_ARCHITECTURE%");
9602          switch (architecture) {
9603              case "AMD64":
9604                  hostArchitecture = "x64";
9605                  break;
9606              case "IA64":
9607                  hostArchitecture = "ia64";
9608                  break;
9609          }
9610      }
9611      return hostArchitecture;
9612  }
9613  
9614  /**
9615   * This function retrieves the IP address from the registry.
9616   * 
9617   * @return array of IP address strings, array can be of length 0
9618   */
9619  function getIPAddresses() {
9620      if (ipAddresses == null) {
9621          ipAddresses = new Array();
9622  
9623          var netCards = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkCards\\";
9624          var netInterfaces = "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\";
9625  
9626          var subKeys = getRegistrySubkeys(netCards, 0);
9627          if (subKeys != null) {
9628              for (var i=0; i < subKeys.length; i++) {
9629                  // get service name entry
9630                  var service = getRegistryValue("HKLM\\" + netCards + subKeys[i] + "\\ServiceName");
9631                   if (service != null && service != "") {
9632                      dinfo("Found network service: " + service);
9633  
9634                      var regBase = "HKLM\\" + netInterfaces +  service + "\\";
9635                      var isInterface = getRegistryValue(regBase);
9636                      if (isInterface == null) {
9637                          dinfo("No TCP/IP Parameters for network service " + service);
9638                      } else {
9639                          // check if DHCP is enabled
9640                          var isDHCP = getRegistryValue(regBase + "EnableDHCP");
9641                          if (isDHCP != null && isDHCP > 0) {
9642                              dinfo("Reading DHCP address.");
9643                              // read DHCP address
9644                              var dhcpIP = getRegistryValue(regBase + "DhcpIPAddress");
9645                              if (dhcpIP != null && dhcpIP != "") {
9646                                  ipAddresses.push(dhcpIP);
9647                                  dinfo("Found DHCP address: " + dhcpIP);
9648                              }
9649                          } else {
9650                              // try reading fixed IP
9651                              dinfo("Reading fixed IP address(es).");
9652  
9653                              var fixedIPsRegs = getRegistryValue(regBase + "IPAddress");
9654                              if (fixedIPsRegs == null || fixedIPsRegs == "") {
9655                                  dinfo("Error reading fixed IP address(es).");
9656                              } else {
9657                                  var fixedIPs = fixedIPsRegs.toArray();
9658                                  if (fixedIPs != null) {
9659                                      for (var j=0; j < fixedIPs.length; j++) {
9660                                          if (fixedIPs[j] != null &&
9661                                          fixedIPs[j] != "" &&
9662                                          fixedIPs[j] != "0.0.0.0") {
9663                                          ipAddresses.push(fixedIPs[j]);
9664                                          dinfo("Found fixed IP address: " + fixedIPs[j]);
9665                                          }
9666                                      }
9667                                  }
9668                              }
9669                          }
9670                      }
9671                  }
9672              }
9673          }
9674      }
9675      return ipAddresses;
9676  }
9677  
9678  /**
9679   * Returns the Windows LCID configured for the current user.<br>
9680   * NOTE: The LCID is read from "HKCU\Control Panel\International\Locale"
9681   * This is the locale of the user under which WPKG is run. In case WPKG GUI is
9682   * used this might probably differ from the real locale of the user but at
9683   * least it will match the system default locale. A user working on an English
9684   * installation will most probably be able to understand English messages even
9685   * if the users locale might be set to German. I was yet unable to find any
9686   * other reliable way to read the locale.
9687   * 
9688   * @return LCID value corresponding to current locale. See
9689   *         http://www.microsoft.com/globaldev/reference/lcid-all.mspx for a list
9690   *         of possible values. Leading zeroes are stripped.
9691   */
9692  function getLocale() {
9693      if (LCID == null) {
9694          // set default to English - United States
9695          var defaultLocale = "409";
9696          var localePath = "HKCU\\Control Panel\\International\\Locale";
9697  
9698          // read the key
9699          var regLocale = getRegistryValue(localePath);
9700          if (regLocale != null) {
9701              // trim leading zeroes
9702              var locale = trimLeadingZeroes(regLocale).toLowerCase();
9703              dinfo("Found user locale: " + locale);
9704              LCID = locale;
9705          } else {
9706              LCID = defaultLocale;
9707              dinfo("Unable to locate user locale. Using default locale: " + defaultLocale);
9708          }
9709      }
9710  
9711      return LCID;
9712  }
9713  
9714  /**
9715   * Returns the Windows operating system install language LCID.<br>
9716   * NOTE: The LCID is read from the InstallLanguage value at
9717   * HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\Language\.
9718   * This is the locale under which the OS has been initially installed
9719   * regardless of the user locale settings.<br>
9720   * For example on an English Windows installation with the locale settings set
9721   * to German it will still return 409.
9722   * 
9723   * @returns LCID value corresponding to system install language. See
9724   *          http://www.microsoft.com/globaldev/reference/lcid-all.mspx for a list
9725   *          of possible values. Leading zeroes are stripped.
9726   */
9727  function getLocaleOS() {
9728      if (LCIDOS == null) {
9729          // set default to English - United States
9730          var defaultLocale = "409";
9731          var localePath = "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Nls\\Language\\InstallLanguage";
9732  
9733          // read the key
9734          var regLocale = getRegistryValue(localePath);
9735          if (regLocale != null) {
9736              // trim leading zeroes
9737              var locale = trimLeadingZeroes(regLocale).toLowerCase();
9738              dinfo("Found system locale: " + locale);
9739              LCIDOS = locale;
9740          } else {
9741              LCIDOS = defaultLocale;
9742              dinfo("Unable to locate system locale. Using default locale: " + defaultLocale);
9743          }
9744      }
9745  
9746      return LCIDOS;
9747  }
9748  
9749  /**
9750   * Returns the logfile pattern currently in use
9751   * 
9752   * @return current value of logfilePattern
9753   */
9754  function getLogfilePattern() {
9755      return logfilePattern;
9756  }
9757  
9758  /**
9759   * Returns the current value of the rebootCmd variable.
9760   * 
9761   * @return current value of rebootCmd
9762   */
9763  function getRebootCmd() {
9764      return rebootCmd;
9765  }
9766  
9767  /**
9768   * Returns a string array containing the names of the subkeys of the given
9769   * registry key. The parentKey parameter has to be specified without the leading
9770   * HKCU part.
9771   * 
9772   * @param parentKey
9773   *            key to read subkeys from (e.g. "SOFTWARE\\Microsoft"
9774   * @param subLevels
9775   *            number of sub-levels to parse. Set to 0 in order to parse only
9776   *            direct sub-keys of the given parent key. If set to 1 it will parse
9777   *            the subkeys of all direct child keys as well. Set to 2 to parse 2
9778   *            levels. Set to negative value (e.g. -1) to parse recursively
9779   *            without any recursion limit.
9780   * 
9781   * @return array containing a list of strings representing the subkey names
9782   *         returns null in case of error or empty array in case of no available
9783   *         subkeys.
9784   */
9785  function getRegistrySubkeys(parentKey, subLevels) {
9786      // dinfo("Getting registry subkeys from: " + parentKey);
9787  
9788      // get number of recursion levels
9789      if( subLevels == null ) {
9790          subLevels = 0;
9791      }
9792  
9793      // key representing HKEY_LOCAL_MACHINE
9794      var HKLM = 0x80000002;
9795  
9796      var returnArray = new Array();
9797  
9798      try {
9799          // Getting registry access object.
9800          var locator = new ActiveXObject("WbemScripting.SWbemLocator");
9801          var service = locator.ConnectServer(".", "root\\default");
9802          var regProvider = service.Get("StdRegProv");
9803  
9804          var enumKeyMethod = regProvider.Methods_.Item("EnumKey");
9805          var inputParameters = enumKeyMethod.InParameters.SpawnInstance_();
9806          inputParameters.hDefKey = HKLM;
9807          inputParameters.sSubKeyName = parentKey;
9808          var outputParam = regProvider.ExecMethod_(enumKeyMethod.Name, inputParameters);
9809  
9810          try {
9811              returnArray = outputParam.sNames.toArray();
9812  
9813              // if there is a sub key parse it as well if recursion is requested
9814              if (returnArray != null && ( subLevels >= 1 ) || subLevels < 0) {
9815                  for (var i = 0; i < returnArray.length; i++) {
9816                      var subKey = parentKey + "\\" + returnArray[i];
9817                      var subKeys = getRegistrySubkeys(subKey, subLevels - 1);
9818                      if (subKeys != null) {
9819                          for (var j = 0; j < subKeys.length; j++) {
9820                              returnArray.push(returnArray[i] + "\\" + subKeys[j]);
9821                          }
9822                      }
9823                  }
9824              }
9825          } catch (readError) {
9826              /*
9827               * a read error on outputParam.sNames typically means that there are no sub-keys available.
9828               */
9829          }
9830  
9831      } catch(err) {
9832          error("Error when searching registry sub-keys at 'HKLM\\" +
9833                   parentKey + "'\nCode: " + hex(err.number) + "; Descriptions: " +
9834                   err.description);
9835          returnArray = null;
9836      }
9837  
9838      return returnArray;
9839  }
9840  
9841  /**
9842   * Returns value of given key in registry. If a key is specified instead of a
9843   * registry value returns its "(default)" value. In case no default value is
9844   * assigned returns an empty string ("").
9845   * 
9846   * In case no such key or value exists within the registry, returns null
9847   * 
9848   * @return registry value, key default value (or "") or null if path does not
9849   *         exist. In case the read value is a REG_DWORD returns an integer. In
9850   *         case the value is of type REG_MULTI_SZ returns a VBArray of strings.
9851   *         In case value is of type REG_BINARY returns VBArray of integer.
9852   */
9853  function getRegistryValue(registryPath) {
9854      registryPath = trim(registryPath);
9855      var originalPath = registryPath;
9856  
9857      var WshShell = new ActiveXObject("WScript.Shell");
9858      var val = "";
9859      try {
9860          val = WshShell.RegRead(registryPath);
9861      } catch (e) {
9862          var readError = e.description;
9863          // dinfo("Error reading value at '" + registryPath + "', trying to read
9864          // it as a key");
9865  
9866          // supplied path is probably a key, test for key existence
9867          if (registryPath.match(new RegExp("\\\\$", "g")) == null) {
9868              // dinfo("String '" + registryPath + "' is not backslash " +
9869              // "terminated, adding trailing backslash and test for key
9870              // existence");
9871  
9872              registryPath = registryPath + "\\";
9873              try {
9874                  val = WshShell.RegRead(registryPath);
9875              } catch (keyErr) {
9876                  val = null;
9877                  // readError = keyErr.description;
9878                  // dinfo("Error reading key'" + registryPath + "': " +
9879                  // readError);
9880              }
9881          }
9882  
9883          // force error message to get returned error string
9884          // in case the key does not exist
9885          var noSuchKeyError = "";
9886          try {
9887              WshShell.RegRead("HKLM\\SOFTWARE\\NOSUCHKEY\\");
9888          } catch (noKeyError) {
9889              noSuchKeyError = noKeyError.description;
9890              // dinfo("Error when reading inexistent key: " + noSuchKeyError);
9891          }
9892          // check if the error message we got is the same
9893          if (noSuchKeyError.replace(new RegExp("HKLM\\\\SOFTWARE\\\\NOSUCHKEY\\\\"),
9894              registryPath) == readError) {
9895  
9896              // check if key exists for 32-bit applications in redirected path
9897              // (only if the path if not already pointing to the Wow6432Node key
9898              if (is64bit() &&
9899                  originalPath.match(new RegExp("^HKLM\\\\SOFTWARE", "i")) &&
9900                  !originalPath.match(new RegExp("^HKLM\\\\SOFTWARE\\\\Wow6432Node", "i"))) {
9901                  // dinfo("Searching for value at 32-bit redirection node.");
9902                  var redirectPath = originalPath.replace(new RegExp("^HKLM\\\\SOFTWARE", "i"),
9903                                                          "HKLM\\Software\\Wow6432Node");
9904                  val = getRegistryValue(redirectPath);
9905              } else {
9906                  // dinfo("No such key or value at '" + registryPath + "'
9907                  // returning null.");
9908                  // return null - not found
9909                  val = null;
9910              }
9911          } else {
9912              // dinfo("Key found at '" + registryPath + "'.");
9913          }
9914      }
9915  
9916      return val;
9917  }
9918  
9919  /**
9920   * User-defined function to format error codes. VBScript has a Hex() function
9921   * but JScript does not.
9922   */
9923  function hex(nmb) {
9924      if (nmb > 0) {
9925          return nmb.toString(16);
9926      } else {
9927          return (nmb + 0x100000000).toString(16);
9928      }
9929  }
9930  
9931  /**
9932   * Scans an argument vector for an argument "arg". Returns true if found, else
9933   * false.
9934   */
9935  function isArgSet(argv, arg) {
9936      // Loop over argument vector and return true if we hit it...
9937      for (var i = 0; i < argv.length; i++) {
9938          if (argv(i) == arg) {
9939              return true;
9940          }
9941      }
9942      // ...otherwise, return false.
9943      return false;
9944  }
9945  
9946  /**
9947   * Loads environment for the specified package (including applying hosts and
9948   * profile variables).
9949   * 
9950   * NOTE: You should invoke saveEnv() before loading the package environment.
9951   * This allows you to call loadEnv() after operations are done to restore
9952   * the previous environment.
9953   * 
9954   * <pre>
9955   * [...]
9956   * var previousEnv = getEnv();
9957   * loadPackageEnv(package);
9958   * // do some actions
9959   * loadEnv(previousEnv);
9960   * </pre>
9961   *
9962   * @param packageNode The package definition to load the environment from
9963   */
9964  function loadPackageEnv(packageNode) {
9965  
9966      // Array to store all variables found.
9967      var variables =  new Array();
9968  
9969      // Host variables first...
9970      variables = getHostsVariables(variables);
9971  
9972      // ...then profile variables...
9973      variables = getProfileVariables(variables);
9974  
9975      // ...and lastly package variables.
9976      variables = getPackageVariables(packageNode, variables);
9977  
9978      // Apply variables to environment.
9979      for (var iVariable=0; iVariable < variables.length; iVariable++) {
9980          var varDefinition = variables[iVariable];
9981          var variableKeys = varDefinition.keys().toArray();
9982          for (var iVarKey = 0; iVarKey < variableKeys.length; iVarKey++) {
9983              var key = variableKeys[iVarKey];
9984              var value = varDefinition.Item(key);
9985              dinfo("Setting variable: '" + key + "=" + value + "'.");
9986              setEnv(key, value);
9987          }
9988      }
9989  }
9990  
9991  /**
9992   * Restores environment using the given dictionary object.
9993   * Variables which are not defined within the dictionary object are unset.
9994   * Variables which are defined within the dictionary object are set to the
9995   * value defined in the dictionary object.
9996   * @param environment
9997   *           Optionally specify a Scripting.Dictionary object which contains
9998   *           the environment to load. If null is passed loads the environment
9999   *           previously saved with parameter-less saveEnv().
10000   * @return Always returns true.
10001   */
10002  function loadEnv(environment) {
10003      // dinfo("Loading environment");
10004      if (environment == null) {
10005          return true;
10006      }
10007      var success = true;
10008      var procEnv = new ActiveXObject("WScript.Shell").Environment("Process");
10009      for(var e = new Enumerator(procEnv); !e.atEnd(); e.moveNext()) {
10010          var env = e.item(e);
10011          var splitEnv = env.split("=", 1);
10012          var key = splitEnv[0];
10013          if (key != null && key != "") {
10014              if (environment.Exists(key)) {
10015                  // dinfo("Setting environment variable '" + key + "' to value '" + environment(key) + "'.");
10016                  procEnv(key) = environment(key);
10017                  // yields warning in my IDE:
10018                  // procEnv.Remove(key);
10019                  // procEnv.add(key, environment.Item(key));
10020              } else {
10021                  procEnv.Remove(key);
10022              }
10023          }
10024      }
10025      return success;
10026  }
10027  
10028  /**
10029   * Parses Date according to ISO 8601. See <http://www.w3.org/TR/NOTE-datetime>.
10030   * 
10031   * Generic format example:
10032   * 
10033   * <pre>
10034   *     "YYYY-MM-DD hh:mm:ss"
10035   * Valid date examples:
10036   *     (the following dates are all equal if ceil is set to false)
10037   *     "2007-11-23 22:00"            (22:00 local time)
10038   *     "2007-11-23T22:00"            (Both, "T" and space delimiter are allowed)
10039   *     "2007-11-23 22:00:00"        (specifies seconds which default to 0 above)
10040   *     "2007-11-23 22:00:00.000"    (specifies milliseconds which default to 0)
10041   * It is allowed to specify the timezone as well:
10042   *     "2007-11-23 22:00+01:00"    (22:00 CET)
10043   *     "2007-11-23 21:00Z"            (21:00 UTC/GMT = 22:00 CET)
10044   *     "2007-11-23 22:00+00:00"    (21:00 UTC/GMT = 22:00 CET)
10045   * </pre>
10046   *
10047   * If 'ceil' is set to true then unspecified date components do not fall back
10048   * to "floor" (basically 0) but will be extended to the next value.
10049   * This allows easy comparison if the current date is within a parsed "!ceil"
10050   * date and a parsed "ceil" date.
10051   * 
10052   * Examples:
10053   * <pre>
10054   * ceil=false:
10055   *     "2007-11-23"    => "2007-11-23 00:00:00"
10056   *     "2007-11"        => "2007-11-01 00:00:00"
10057   * ceil=true:
10058   *     "2007-11-23"    => "2007-11-24 00:00:00"
10059   *     "2007-11"        => "2007-12-01 00:00:00"
10060   * </pre>
10061   *
10062   * so you can specify a range in the following format
10063  * <pre>
10064   * if (parseISODate("2007-11", !ceil) >= currentDate &&
10065   *     parseISODate("2007-11", ceil) <= currentDate) {
10066   *         // this will be true for all dates within November 2007
10067   *         ...
10068   * }
10069   * </pre>
10070   *
10071   * TIMEZONES:
10072   *
10073   * As specified by ISO 8601 the date is parsed as local date in case no
10074   * time zone is specified. If you define a time zone then the specified time
10075   * is parsed as local time for the given time zone. So if you specify
10076   * "2007-11-23 22:00+05:00" this will be equal to "2007-11-23 18:00+01:00" while
10077   * "+01:00" is known as CET as well. The special identifier "Z" is equal to
10078   * "+00:00" time zone offset.
10079   * 
10080   * Specifying an empty string as dateString is allowed and will results in
10081   * returning the first of January 00:00 of the current year (ceil=false) or
10082   * first of January 0:00 of the next year (ceil=true).
10083   * 
10084   * @param dateString
10085   *            the string to be parsed as ISO 8601 date
10086   * @param ceil
10087   *            defines if missing date components are "rounded-up" or "rounded
10088   *            down", see above
10089   * @return Date object representing the specified date. Returns null if the
10090   *         date cannot be parsed.
10091   */
10092  function parseISODate(dateString, ceil) {
10093      // <YYYY>[-]<MM>[-]<DD>[T ]<hh>:<mm>:<ss>.<ms>[
10094      // make sure dateString is defined
10095      var now = new Date();
10096      var dateStringValue = dateString;
10097      if (dateStringValue == null) {
10098          dateStringValue = now.getFullYear() + "";
10099      }
10100  
10101      // http://www.w3.org/TR/NOTE-datetime
10102      var regexp = "([0-9]{4})(?:-?([0-9]{1,2})(?:-?([0-9]{1,2})" +
10103              "(?:[T ]([0-9]{1,2}):([0-9]{1,2})(?::([0-9]{1,2})(?:\\.([0-9]{1,3}))?)?" +
10104              "(?:(Z)|(?:([-+])([0-9]{1,2})(?::([0-9]{1,2}))?))?)?)?)?";
10105  
10106      // execute matching
10107      var matches = dateStringValue.match(new RegExp(regexp));
10108  
10109      var ceilValue = ceil;
10110      if (ceilValue == null) {
10111          ceilValue = false;
10112      }
10113  
10114      // create new date object using the year parsed
10115      var date = new Date(now.getFullYear(), 0, 1);
10116      if (matches[1]) {
10117          date.setFullYear(matches[1]);
10118      } else {
10119          dinfo("Date '" + dateString + "' could not be parsed.");
10120          return null;
10121      }
10122      /*
10123       else if (ceilValue) {
10124          date.setFullYear(date.getFullYear() + 1);
10125      } */
10126      // parse months
10127      if (matches[2]) {
10128          date.setMonth(matches[2] - 1);
10129      } else if (ceilValue) {
10130          // month not defined, advance to next year
10131          date.setFullYear(date.getFullYear() + 1);
10132          ceilValue = false;
10133      }
10134      // parse days (of the month)
10135      if (matches[3]) {
10136          date.setDate(matches[3]);
10137      } else if (ceilValue) {
10138          // date (day of the month) not defined, advance to next month
10139          date.setMonth(date.getMonth() + 1);
10140          ceilValue = false;
10141      }
10142      // parse hours
10143      if (matches[4]) {
10144          date.setHours(matches[4]);
10145      } else if (ceilValue) {
10146          // hours not defined, advance to next day
10147          date.setDate(date.getDate() + 1);
10148          ceilValue = false;
10149      }
10150      // parse minutes
10151      if (matches[5]) {
10152          date.setMinutes(matches[5]);
10153      } else if (ceilValue) {
10154          // minutes not defined, advance to next hour
10155          date.setHours(date.getHours() + 1);
10156          ceilValue = false;
10157      }
10158      // parse seconds
10159      if (matches[6]) {
10160          date.setSeconds(matches[6]);
10161      } else if (ceilValue) {
10162          // seconds not defined, advance to next minute
10163          date.setMinutes(date.getMinutes() + 1);
10164          ceilValue = false;
10165      }
10166      // parse milliseconds
10167      if (matches[7]) {
10168          date.setMilliseconds(Number(matches[7]));
10169      } else if (ceilValue) {
10170          // milliseconds not defined, advance to next second
10171          date.setSeconds(date.getSeconds() + 1);
10172          ceilValue = false;
10173      }
10174      // parse timezone offset
10175      var timeZoneSet = false;
10176      if (matches[8] == "Z") {
10177          matches[9] = 0;
10178          matches[10] = 0;
10179          timeZoneSet = true;
10180      }
10181      if (matches[9] || timeZoneSet) {
10182          // if offset is specified, translate time to local time
10183          var dateOffset = 0;
10184          if (matches[11]) {
10185              dateOffset = Number(matches[11]);
10186          }
10187          // convert to milliseconds
10188          dateOffset += Number(matches[10]) * 60;
10189  
10190          // evaluate prefix
10191          dateOffset *= (matches[9] == "+") ? 1 : -1;
10192  
10193          // calculate actual time
10194          // get UTC representation of the specified date in milliseconds
10195          time = Date.UTC(date.getFullYear(),
10196                          date.getMonth(),
10197                          date.getDate(),
10198                          date.getHours(),
10199                          date.getMinutes(),
10200                          date.getSeconds(),
10201                          date.getMilliseconds());
10202  
10203          // subtract specified offset to get UTC representation of specified date
10204          time -= dateOffset * 60 * 1000;
10205  
10206          // create new date object using the UTC time specified
10207          date = new Date(time);
10208      }
10209  
10210      return date;
10211  }
10212  
10213  /**
10214   * Reboots the system using tools\psshutdown.exe from the script execution
10215   * directory.
10216   */
10217  function psreboot() {
10218      if (!isNoReboot() ) {
10219          rebooting = true;
10220          // RFL prefers shutdown tool to this method: allows user to cancel
10221          // if required, but we loop for ever until they give in!
10222          // get localized message
10223          var msg = getLocalizedString("notifyUserReboot");
10224          if (msg == null) {
10225              msg="Rebooting to complete software installation. Please note that "+
10226                  "some software might not work until the machine is rebooted.";
10227          }
10228          // Overwrites global variable rebootcmd!
10229          var rebootCmd = "tools\\psshutdown.exe";
10230          var fso = new ActiveXObject("Scripting.FileSystemObject");
10231              if (!fso.FileExists(rebootCmd)) {
10232                  var path = WScript.ScriptFullName;
10233                  var psBase = fso.GetParentFolderName(path);
10234                  rebootCmd = fso.BuildPath(psBase, rebootCmd);
10235                  if (!fso.FileExists(rebootCmd)) {
10236                      throw new Error("Could not locate rebootCmd '" + rebootCmd + "'.");
10237                  }
10238              }
10239          var shutdown=rebootCmd + " -r -accepteula ";
10240  
10241          cleanup();
10242          for (var iCountdown1 = 60; iCountdown1 != 0; iCountdown1 = iCountdown1-1) {
10243              // This could be cancelled.
10244              var cmd1 = shutdown + " -c -m \"" + msg + "\" -t " + iCountdown1;
10245              info("Running a shutdown command: "+ cmd1);
10246              exec(cmd1, 0, null);
10247              WScript.Sleep(iCountdown1 * 1000);
10248          }
10249          // Hmm. We're still alive. Let's get more annoying.
10250          for (var iCountdown2 = 60; iCountdown2 != 0; iCountdown2 = iCountdown2 - 3) {
10251              var cmd2 = shutdown + " -m \"" + msg + "\" -t "+ iCountdown2;
10252              info("Running a shutdown command: " + cmd2);
10253              exec(cmd2, 0, null);
10254              WScript.Sleep(iCountdown2 * 1000);
10255          }
10256          // And if we're here, there's problem.
10257          notify("This machine needs to reboot.");
10258  
10259      } else {
10260          info("System reboot was initiated but overridden.");
10261      }
10262  
10263      exit(0);
10264  }
10265  
10266  /**
10267   * Reboots the system.
10268   */
10269  function reboot() {
10270      if (!isNoReboot() ) {
10271          // set global var that all functions know that a reboot is in progress
10272          rebooting = true;
10273          switch (getRebootCmd()) {
10274          case "standard":
10275              var wmi = GetObject("winmgmts:{(Shutdown)}//./root/cimv2");
10276              var win = wmi.ExecQuery("select * from Win32_OperatingSystem where Primary=true");
10277              var e = new Enumerator(win);
10278  
10279              info("System reboot in progress!");
10280  
10281              if (!isNoRunningState()) {
10282                  // Reset running state.
10283                  setRunningState("false");
10284              }
10285              // make sure files are written
10286              cleanup();
10287              for (; !e.atEnd(); e.moveNext()) {
10288                  var x = e.item();
10289                  x.win32Shutdown(6);
10290              }
10291              exit(3010);
10292              break;
10293          case "special":
10294              psreboot();
10295              break;
10296          default:
10297              var fso = new ActiveXObject("Scripting.FileSystemObject");
10298              if (!fso.FileExists(getRebootCmd())) {
10299                  var path = WScript.ScriptFullName;
10300                  var toolBase = fso.GetParentFolderName(path);
10301                  setRebootCmd(fso.BuildPath(toolBase, getRebootCmd()));
10302                  if (!fso.FileExists(getRebootCmd())) {
10303                      throw new Error("Could not locate rebootCmd '" + getRebootCmd() + "'.");
10304                  }
10305              }
10306              info("Running a shutdown command: " + getRebootCmd());
10307              // close files
10308              cleanup();
10309              // execute shutdown
10310              exec(getRebootCmd(), 0, null);
10311              exit(3010);
10312              break;
10313          }
10314      } else {
10315          info("System reboot was initiated but overridden.");
10316      }
10317  
10318      // exit with code "3010 << 8" (770560) which means 3010 shifted by 8 bits.
10319      // exiting with code 3010 will make WPKG client to initiate a reboot
10320      // which is unlikely to be expected because reboot command is overridden.
10321      exit(3010 << 8);
10322  }
10323  
10324  /**
10325   * Fetches current environment and returns Scripting.Dictionary object
10326   * containing current environment.
10327   * @returns {ActiveXObject} Dictionary representing current environment.
10328   */
10329  function getEnv() {
10330      // dinfo("Fetching environment");
10331      var currentEnvironment = new ActiveXObject("Scripting.Dictionary");
10332      var procEnv = new ActiveXObject("WScript.Shell").Environment("Process");
10333      for(var e=new Enumerator(procEnv); !e.atEnd(); e.moveNext()) {
10334          var env = e.item(e);
10335          var envKey = env.split("=", 1);
10336          var key = envKey[0];
10337          if (key != null && key != "") {
10338              var valueStartOffset = key.length + 1;
10339              currentEnvironment.add(envKey[0], env.substr(valueStartOffset));
10340          }
10341      }
10342      return currentEnvironment;
10343  }
10344  
10345  
10346  /**
10347   * Set an environment variable in the current script environment.
10348   * @param key Environment variable name.
10349   * @param value Value to assign to the variable.
10350   */
10351  function setEnv(key, value) {
10352      if (key == null) {
10353          dinfo("Cannot set environment variable: No key specified!");
10354          return;
10355      }
10356      if (value == null) {
10357          dinfo("Cannot set environment variable '" + key + "': No value specified!");
10358          return;
10359      }
10360  
10361      // Expand environment variables in variable definition.
10362      var shell = new ActiveXObject("WScript.Shell");
10363      // Somehow an empty string is not accepted as string in set instruction below.
10364      // So make sure value is of type string.
10365      var valueExpanded = shell.ExpandEnvironmentStrings(value) + "";
10366  
10367      // Fetch process environment.
10368      var procEnv = new ActiveXObject("WScript.Shell").Environment("Process");
10369  
10370      // Set environment.
10371      procEnv(key) = valueExpanded;
10372  
10373      /*
10374       if (procEnv.Exist(key)) {
10375           procEnv.Remove(key);
10376       }
10377       procEnv.add(key, value);
10378       */
10379  }
10380  
10381  
10382  /**
10383   * Scans uninstall list for given name. Uninstall list is placed in registry
10384   * under HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall Every
10385   * subkey represents package that can be uninstalled. Function checks each
10386   * subkey for containing value named DisplayName. If this value exists, function
10387   * returns true if nameSearched matches it.
10388   * 
10389   * @param nameSearched
10390   *            The uninstall string to look for (as it appears within control
10391   *            panel => add/remove software)
10392   * @return returns an array of registry paths to the uninstall entries found. An
10393   *         array is returned since the same software might be installed more
10394   *         than once (32-bit and 64-bit versions). Returns an empty array in
10395   *         case no uninstall entry could be located.
10396   */
10397  function scanUninstallKeys(nameSearched) {
10398      var uninstallPath = new Array();
10399      var scanKeys = new Array();
10400      scanKeys.push("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall");
10401      if (is64bit()) {
10402          // scan redirected path as well (assures that 32-bit applications are
10403          // found)
10404          scanKeys.push("SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall");
10405      }
10406  
10407      // try regular expression matching
10408      var regularExpression = true;
10409      for (var i=0; i < scanKeys.length; i++) {
10410          var regPath = scanKeys[i];
10411          /*
10412           * recursive registry reading is very slow with WSH. Therefore supporting Sub-keys in uninstall entries slows
10413           * down uninstall key scanning dramatically. So I leave it off for the moment. Please use registry key checks if
10414           * you need to check an uninstall key defined within a sub-key of the uninstall registry location
10415           */
10416          // var keyNames = getRegistrySubkeys(regPath, -1);
10417          var keyNames = getRegistrySubkeys(regPath, 0);
10418          /*
10419           * for (var k=0; k < keyNames.length; k++) { dinfo("Uninstall key: " + keyNames[k]); }
10420           */
10421  
10422          for (var j=0; j < keyNames.length; j++) {
10423              var registryPath = "HKLM\\" + regPath + "\\" + keyNames[j];
10424              var displayName = getRegistryValue(registryPath + "\\DisplayName");
10425  
10426              if (displayName != null) {
10427                  // first try direct 1:1 matching
10428                  if (displayName == nameSearched) {
10429                      dinfo("Uninstall entry '" + displayName +
10430                              "' matches string '" + nameSearched + "'.");
10431                      uninstallPath.push(registryPath);
10432                      break;
10433                  } else if(regularExpression) {
10434                      try {
10435                          // try regular-expression matching
10436                          var displayNameRegExp = new RegExp("^" + nameSearched + "$");
10437  
10438                          if (displayNameRegExp.test(displayName) == true) {
10439                              dinfo("Uninstall entry '" + displayName +
10440                                      "' matches expression '" + nameSearched+  "'.");
10441                              uninstallPath.push(registryPath);
10442                              break;
10443                          }
10444                      } catch (error) {
10445                          regularExpression = false;
10446                          dinfo("Unable to match uninstall key with regular expression. " +
10447                                  "Usually this means that the string '" + nameSearched +
10448                                  "'does not qualify as a regular expression: " +
10449                                  error.description);
10450                      }
10451                  }
10452              }
10453          }
10454      }
10455      return uninstallPath;
10456  }
10457  
10458  /**
10459   * Scans the specified array for the specified element and returns true if
10460   * found.
10461   */
10462  function searchArray(array, element) {
10463      for (var i=0; i < array.length; i++) {
10464          var e = array[i];
10465          if (element == e) {
10466              return true;
10467          }
10468      }
10469  
10470      return false;
10471  }
10472  
10473  /**
10474   * Removes leading / trailing spaces.
10475   */
10476  function trim(string) {
10477      if(string != null) {
10478          return(string.replace(new RegExp("(^\\s+)|(\\s+$)"),""));
10479      } else {
10480          return null;
10481      }
10482  }
10483  
10484  /**
10485   * Removes leading zeroes from a string (does not touch trailing zeroes)
10486   */
10487  function trimLeadingZeroes(string) {
10488      if(string != null) {
10489          return(string.replace(new RegExp("^[0]*"),""));
10490      } else {
10491          return null;
10492      }
10493  }
10494  
10495  /**
10496   * Remove duplicate items from an array.
10497   */
10498  function uniqueArray(array) {
10499      // Hold unique elements in a new array.
10500      var newArray = new Array();
10501  
10502      // Loop over elements.
10503      for (var i = 0; i < array.length; i++) {
10504          var found = false;
10505          for (var j = 0; j < newArray.length; j++) {
10506              if (array[i] == newArray[j]) {
10507                  found = true;
10508                  break;
10509              }
10510          }
10511  
10512          if (!found) {
10513              newArray.push(array[i]);
10514          }
10515      }
10516  
10517      return newArray;
10518  }
10519  
10520  /**
10521   * versionCompare - compare two version strings.
10522   *
10523   * The algorithm is supposed to deliver "human" results. It's not just
10524   * comparing numbers but also allows versions with characters.
10525   * 
10526   * Some version number contain appendices to the version string indicating
10527   * "volatile" versions like "pre releases". For example some packages use
10528   * versions like "1.0RC1" or "1.0alpha2". Usually a version like "1.0RC1" would
10529   * be considered to be newer than "1.0" by the algorithm but in case of "RC"
10530   * versions this would be wrong. To handle such cases a number of strings are
10531   * defined in order to define such volatile releases.
10532   * 
10533   * The list of prefixes is defined in the global volatileReleaseMarker array.
10534   *
10535   * Valid comparisons include:
10536   * <pre>
10537   * A        B              Result
10538   * "1"      "2"            B is newer
10539   * "1"      "15"           B is newer
10540   * "1.0"    "1.2.b"        B is newer
10541   * "1.35"   "1.35-2"       B is newer
10542   * "1.35-2" "1.36"         B is newer
10543   * "1.35R3" "1.36"         B is newer
10544   * "1"      "1.0.00.0000"  Versions are equal
10545   * "1"      "1.0"          Versions are equal
10546   * "1.35"   "1.35-2"       B is newer
10547   * "1.35-2" "1.35"         A is newer
10548   * "1.35R3" "1.36R4"       B is newer
10549   * "1.35-2" "1.35-2.0"     Versions are equal
10550   * "1.35.1" "1.35.1.0"     Versions are equal
10551   * "1.3RC2" "1.3"          B is newer (special case where A is an "RC" version)
10552   * "1.5"    "1.5I3656"     A is newer (B is an "I"/integration version)
10553   * "1.5"    "1.5M3656"     A is newer (B is an "M"/milestone version)
10554   * "1.5"    "1.5u3656"     B is newer (B is an update version)
10555   * </pre>
10556   *
10557   * @return  0 if equal,<br>
10558   *         -1 if a < b,<br>
10559   *         +1 if a > b
10560   */
10561  function versionCompare(a, b) {
10562      // first split the version into sub-versions separated by dots
10563      // eg. "1.00.1b20-R0" => 1, 00, 1b20-R0
10564      // constants defining the return values
10565      dinfo("Comparing version: '" + a + "' <=> '" + b + "'.");
10566      var BNEWER = -1;
10567      var ANEWER = 1;
10568      var EQUAL = 0;
10569  
10570      // small optimization, in most cases the strings will be equal.
10571      if (a == b) {
10572          return EQUAL;
10573      }
10574  
10575      var versionA = a.split(".");
10576      var versionB = b.split(".");
10577      var length = 0;
10578      versionA.length >= versionB.length ? length = versionA.length : length = versionB.length;
10579      var result = EQUAL;
10580  
10581      // split by sub-version-numbers
10582      // e.g. 1b20-R0" => 1b20, R0
10583      for (var i = 0; i < length; i++) {
10584          var versionPartsA = new Array();
10585          var versionPartsB = new Array();
10586          var partsSplitter = new RegExp("[^0-9a-zA-Z]");
10587          if( i < versionA.length ) {
10588              versionPartsA = versionA[i].split(partsSplitter);
10589          } else {
10590              // there is no such part on A side
10591              // assume 0
10592              versionPartsA.push(0);
10593          }
10594          if( i < versionB.length ) {
10595              versionPartsB = versionB[i].split(partsSplitter);
10596          } else {
10597              // there is no such part on B side
10598              // assume 0
10599              versionPartsB.push(0);
10600          }
10601          var versionParts = 0;
10602          versionPartsA.length > versionPartsB.length ? versionParts = versionPartsA.length : versionParts = versionPartsB.length;
10603  
10604          // split these parts into char/number fields
10605          // e.g "1b20" => 1, b, 20
10606          for (var j = 0; j < versionParts; j++) {
10607              // get A-side version part
10608              var versionPartA;
10609              if( j < versionPartsA.length ) {
10610                  versionPartA = "" + versionPartsA[j];
10611              } else {
10612                  // A does not have such a part, so B seems to be a higher
10613                  // revision
10614                  result = BNEWER;
10615                  break;
10616              }
10617              // get B-side version part
10618              var versionPartB;
10619              if( j < versionPartsB.length ) {
10620                  versionPartB = "" + versionPartsB[j];
10621              } else {
10622                  // B does not have such a part, so A seems to be a higher
10623                  // revision
10624                  result = ANEWER;
10625                  break;
10626              }
10627  
10628              // both versions have such a part, compare them
10629              dinfo("Comparing version fragments: '" + versionPartA + "' <=> '" + versionPartB + "'");
10630  
10631              // first split the part into number/character parts
10632              var numCharSplitter = new RegExp("([0-9]+)|([a-zA-Z]+)", "g");
10633              var numCharPartsA = versionPartA.match(numCharSplitter);
10634              var numCharPartsB = versionPartB.match(numCharSplitter);
10635              var numCharLength = 0;
10636              numCharPartsA.length > numCharPartsB.length ? numCharLength = numCharPartsA.length : numCharLength = numCharPartsB.length;
10637              // now start comparing the parts
10638              for (var k = 0; k < numCharLength; k++) {
10639                  var numCharPartA;
10640                  var numCharPartB;
10641                  // get A-side
10642                  if( k < numCharPartsA.length ) {
10643                      numCharPartA = numCharPartsA[k];
10644                  } else {
10645                      // A-side does not have such a part, so B seems to be either
10646                      // a higher revision or appends a volatile version
10647                      // identifier
10648                      var bSideString = numCharPartsB[k];
10649                      // check if it matches one from the volatile list
10650                      for (var vId = 0; vId < volatileReleaseMarkers.length; vId++) {
10651                          if (bSideString.toLowerCase() == volatileReleaseMarkers[vId]) {
10652                              dinfo("Special case: '" + a + "' is newer because '" + b + "' " +
10653                                      "is considered to have a volatile version appendix (" +
10654                                      volatileReleaseMarkers[vId] + ").");
10655                              result = ANEWER;
10656                              break;
10657                          }
10658                      }
10659                      if (result == EQUAL) {
10660                          // B is newer
10661                          result = BNEWER;
10662                      }
10663                      break;
10664                  }
10665                  if( k < numCharPartsB.length ) {
10666                      numCharPartB = numCharPartsB[k];
10667                  } else {
10668                      // B-side does not have such a part, so A seems to be either
10669                      // a higher revision or appends a volatile version
10670                      // identifier
10671                      var aSideString = numCharPartsA[k];
10672                      // check if it matches one from the volatile list
10673                      for (var volId = 0; volId < volatileReleaseMarkers.length; volId++) {
10674                          if (aSideString.toLowerCase() == volatileReleaseMarkers[volId]) {
10675                              dinfo("Special case: '" + a + "' is newer because '" + b + "' " +
10676                                      "is considered to have a volatile version appendix (" +
10677                                      volatileReleaseMarkers[volId] + ").");
10678                              result = BNEWER;
10679                              break;
10680                          }
10681                      }
10682                      if (result == EQUAL) {
10683                          result = ANEWER;
10684                      }
10685                      break;
10686                  }
10687  
10688                  // both versions have such a part, compare them
10689                  // strip off leading zeroes first
10690                  var stripExpression = new RegExp("^[0 \t]*(.+)$");
10691                  var strippedA = numCharPartA.match(stripExpression);
10692                  numCharPartA = strippedA[1];
10693  
10694                  var strippedB = numCharPartB.match(stripExpression);
10695                  numCharPartB = strippedB[1];
10696  
10697                  var numCharSplitA = numCharPartA.split("");
10698                  var numCharSplitB = numCharPartB.split("");
10699                  if (numCharSplitB.length > numCharSplitA.length) {
10700                      // version B seems to be higher
10701                      result = BNEWER;
10702                      break;
10703                  } else if (numCharSplitA.length > numCharSplitB.length) {
10704                      // version a seems to be higher
10705                      result = ANEWER;
10706                      break;
10707                  }
10708  
10709                  // both versions seem to have equal length, compare them
10710                  for (var l = 0; l < numCharSplitA.length; l++) {
10711                      var characterA = numCharSplitA[l];
10712                      var characterB = numCharSplitB[l];
10713                      if (characterB > characterA) {
10714                          // B seems to be newer
10715                          result = BNEWER;
10716                          break;
10717                      } else if( characterA > characterB) {
10718                          // A seems to be newer
10719                          result = ANEWER;
10720                          break;
10721                      }
10722                  }
10723  
10724                  // stop evaluating
10725                  if(result != EQUAL) {
10726                      break;
10727                  }
10728              }
10729  
10730              // stop evaluating
10731              if(result != EQUAL) {
10732                  break;
10733              }
10734          }
10735  
10736          // stop evaluating
10737          if(result != EQUAL) {
10738              break;
10739          }
10740      }
10741  
10742      return result;
10743  }


Generated: Tue Mar 17 22:47:18 2015 Cross-referenced by PHPXref 0.7.1