[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

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

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


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