[ 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