[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

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

   1  var WPKG_VERSION = "1.3.0";
   2  /*******************************************************************************
   3   * 
   4   * WPKG - Windows Packager pour SambaEdu : http://sambaedu.org
   5   * 
   6   * Copyright 2003 Jerry Haltom<br>
   7   * Copyright 2005 Aleksander Wysocki <papopypu (at) op . pl><br>
   8   * Copyright 2005-2006 Tomasz Chmielewski <mangoo (at) wpkg . org><br>
   9   * Copyright 2007-2011 Rainer Meier <r.meier (at) wpkg.org><br>
  10   * Copyright 2007 Jean Le Bail <jean.lebail (at) etab . ac-caen . fr>
  11   * 
  12   * Please report your issues to the list on http://wpkg.org/
  13   */
  14   
  15   /*******************************************************************************
  16   *  Version de wpkg.js modifiée pour SambaEdu3
  17   
  18   * Le support de la remontée des logs d'execution des scripts batch est suspendu suite a des blocages msiexec du wpkg-se3.js adapté en version 1.2 avec la fonction exec() de la version wpkg-se3.js 1.1 de Jean Le Bail.
  19   
  20   PATCH pour SE3 (Olivier Lacroix)
  21   Les modifs par rapport à wpkg.js officiels sont délimitées par // PATCH SE3 et // FIN PATCH SE3 :
  22   1. ajout de lignes dans installPackage() et removePackage() pour classement du menu démarrer
  23   2. modif de la function QueryPackage pour compatibilité avec l'interface se3 actuelle et la lecture des logs d'install 
  24   */
  25  
  26  /**
  27   * Displays command usage.
  28   */
  29  function showUsage() {
  30  var message = "" +
  31  "If you cannot read this since it is displayed within a dialog-window please \n" +
  32  "execute 'cscript wpkg.js /help' on the command line. This will print all \n" +
  33  "messages to the console. \n\n" +
  34  "Command Line Switches \n" +
  35  "===================== \n" +
  36  "Note: These command line switches overwrite parameters within config.xml. For \n" +
  37  "example the /quiet flag overwrites an eventually present quiet parameter within \n" +
  38  "config.xml. \n" +
  39  "Usually you should specify as few parameters as possible since changing \n" +
  40  "config.xml on the server might be much easier than changing all client-side \n" +
  41  "stored parameters. Typically you would use the following command-line in \n" +
  42  "production: \n" +
  43  "    wpkg.js /synchronize \n" +
  44  "\n" +
  45  "Frequently used parameters (package operations, you need to choose one): \n" +
  46  "======================================================================== \n" +
  47  "\n" +
  48  "/install:<package>[,package2[,package3,[...]]] \n" +
  49  "    Install the specified package(s) on the system. \n" +
  50  "\n" +
  51  "/query:<option> \n" +
  52  "    Display a list of packages matching the specified criteria. Valid \n" +
  53  "    options are: \n" +
  54  "\n" +
  55  "    a - Query all packages (includes installed packages and package database). \n" +
  56  "    x - List packages which are not installed but in package database. \n" +
  57  "    i - List all packages which are currently installed. \n" +
  58  "    I - List packages which are about to be installed during synchronization. \n" +
  59  "    u - List packages which are about to be upgraded during synchronization. \n" +
  60  "    d - List packages which are about to be downgraded during synchronization. \n" +
  61  "    r - List packages which are about to be removed during synchronization. \n" +
  62  "    m - List all modifications which would apply during synchronization \n" +
  63  "        (equal to Iudr) \n" +
  64  "    n - List packages which belong to the profile but are not modified during \n" +
  65  "        synchronization. \n" +
  66  "    s - List host attributes from settings (wpkg.xml). \n" +
  67  "    l - List host attributes read from local host. \n" +
  68  "\n" +
  69  "/remove:<package>[,package2[,package3,[...]]] \n" +
  70  "    Remove the specified package(s) from the system. \n" +
  71  "\n" +
  72  "/show:<package> \n" +
  73  "    Display a summary of the specified package, including it's state. \n" +
  74  "\n" +
  75  "/upgrade:<package>[,package2[,package3,[...]]] \n" +
  76  "    Upgrade the already installed package(s) on the system. \n" +
  77  "\n" +
  78  "/synchronize \n" +
  79  "    Synchronize the current program state with the suggested program state \n" +
  80  "    of the specified profile. This is the action that should be called at \n" +
  81  "    system boot time for this program to be useful. \n" +
  82  "\n" +
  83  "/help \n" +
  84  "    Show this message. \n" +
  85  "\n" +
  86  "\n" +
  87  "Optional parameters (usually defined within config.xml): \n" +
  88  "======================================================== \n" +
  89  "\n" +
  90  "/base:<path> \n" +
  91  "    Set the local or remote path to find the xml input files. \n" +
  92  "    Can also be set to a web URL for direct XML retrieval from wpkg_web. \n" +
  93  "\n" +
  94  "/dryrun[:<true>|<false>] \n" +
  95  "    Do not execute any actions. Implies debug mode. \n" +
  96  "\n" +
  97  "/quiet[:<true>|<false>] \n" +
  98  "    Use the event log to record all error/status messages. Use this when \n" +
  99  "    running unattended. \n" +
 100  "\n" +
 101  "/nonotify[:<true>|<false>] \n" +
 102  "    Logged in users are not notified about impending updates. \n" +
 103  "\n" +
 104  "/noreboot[:<true>|<false>] \n" +
 105  "    System does not reboot regardless of need. \n" +
 106  "\n" +
 107  "/quitonerror[:<true>|<false>] \n" +
 108  "    Quit execution if the installation of any package was unsuccessful \n" +
 109  "    (default: install next package and show the error summary). \n" +
 110  "\n" +
 111  "/sendStatus[:<true>|<false>] \n" +
 112  "    Send status messages on STDOUT which can be parsed by calling program to \n" +
 113  "    display status information to the user. \n" +
 114  "\n" +
 115  "/noUpgradeBeforeRemove[:<true>|<false>] \n" +
 116  "    Usually WPKG upgrades a package to the latest available version before it \n" +
 117  "    removes the package. This allows administrators to fix bugs in the package \n" +
 118  "    and assure proper removal.\n" +
 119  "\n" +
 120  "/applymultiple[:<true>|<false>] \n" +
 121  "    Apply profiles of all host nodes with matching attributes. \n" +
 122  "    Only first matching host node is returned if not switched on. \n" +
 123  "    This parameter must be used with caution, it can break existing setup. \n" +
 124  "\n" +
 125  "Rarely used parameters (mainly for testing): \n" +
 126  "============================================ \n" +
 127  "\n" +
 128  "/config:<path> \n" +
 129  "    Path to the configuration file to be used. The path might be absolute \n" +
 130  "    or relative but including the XML file name. This parameter is entirely \n" +
 131  "    OPTIONAL and should normally not be specified at all. \n" +
 132  "    If not specified the configuration will be searched at: \n" +
 133  "    <script-path>\\config.xml \n" +
 134  "    where <script-path> is the directory from which the script is executed. \n" +
 135  "        e.g. '\\\\server\\share\\directory\\config.xml'. \n" +
 136  "        e.g. 'directory\\config.xml'. \n" +
 137  "    You can use environment variables as well as the following expressions: \n" +
 138  "     [HOSTNAME]  Replaced by the executing hostname. \n" +
 139  "     [PROFILE]   Replaced by the concatenated list of profiles applied. \n" +
 140  "\n" +
 141  "/settings:<path> \n" +
 142  "    Path to the settings (local WPKG database) file to be used. The path might \n" +
 143  "    be absolute or relative but including the XML file name. This parameter is \n" +
 144  "    entirely OPTIONAL and should normally not be specified at all. \n" +
 145  "    If not specified the settings file will be searched at: \n" +
 146  "    %SystemRoot%\\system32\\wpkg.xml \n" +
 147  "        e.g. '%SystemRoot%\\system32\\wpkg-custom.xml'. \n" +
 148  "        e.g. '\\\\server\share\directory\\[HOSTNAME].xml'. \n" +
 149  "    If you store the settings file on a share make sure the names is unique! \n" +
 150  "    You can use environment variables as well as the following expressions: \n" +
 151  "     [HOSTNAME]  Replaced by the executing hostname. \n" +
 152  "     [PROFILE]   Replaced by the concatenated list of profiles applied. \n" +
 153  "\n" +
 154  "/queryMode:<mode> \n" +
 155  "    Allows to switch to remote mode where package verification is skipped. \n" +
 156  "     remote: Disable package checks and assume that packages in settings \n" +
 157  "             database are still correctly installed. In remote mode also the \n" +
 158  "             host information is read from the local settings database. \n" +
 159  "     local:  Default setting. Query verifies package status using all checks. \n" +
 160  "    Note: Query mode can only be used with the query switch. \n" +
 161  "\n" +
 162  "/profile:<profile> \n" +
 163  "    Force the name of the profile to use. If not specified, the profile to use \n" +
 164  "    is looked up in hosts.xml. \n" +
 165  "\n" +
 166  "/debug[:<true>|<false>] or /verbose[:<true>|<false>] \n" +
 167  "    Enable debug output. Please note that this parameter only influences \n" +
 168  "    notification and event log output. It does not affect the logging level. \n" +
 169  "    It is possible to get debug-level output even without using this \n" +
 170  "    switch. \n" +
 171  "\n" +
 172  "/force[:<true>|<false>] \n" +
 173  "    When used in conjunction with /synchronize WPKG will ignore the local \n" +
 174  "    settings file (wpkg.xml) and re-build the database with installed \n" +
 175  "    packages. \n" +
 176  "    When used in conjunction with /remove forces removal of the specified \n" +
 177  "    package even if not all packages which depend on the one to be removed \n" +
 178  "    could be removed. \n" +
 179  "\n" +
 180  "/forceinstall[:<true>|<false>] \n" +
 181  "    Force installation over existing packages. \n" +
 182  "\n" +
 183  "/host:<hostname> \n" +
 184  "    Use the specified hostname instead of reading it from the executing host. \n" +
 185  "\n" +
 186  "/os:<hostos> \n" +
 187  "    Use the specified operating system string instead of reading it from the \n" +
 188  "    executing host. \n" +
 189  "\n" +
 190  "/ip:<ip-address-1,ip-address-2,...,ip-address-n> \n" +
 191  "    Use the specified ipaddresses instead of reading it from the executing host. \n" +
 192  "\n" +
 193  "/domainname:<domain> \n" +
 194  "    Name of the windows domain the computer belongs to. \n" +
 195  "    This permit to use group membership even on a non-member workstation. \n" +
 196  "\n" +
 197  "/group:<group-name-1,group-name-2,...,group-name-n>\n" +
 198  "    Name of the group the computer must belongs to instead of reading it from \n" +
 199  "    the executing host. \n" +
 200  "\n" +
 201  "/ignoreCase[:<true>|<false>] \n" +
 202  "    Disable case sensitivity of packages and profiles. Therefore you can \n" +
 203  "    assign the package 'myapp' to a profile while only a package 'MyApp' is \n" +
 204  "    defined within the packages. \n" +
 205  "    Note that this change requires modification of the package/profile/host nodes \n" +
 206  "    read from the XML files. All IDs are converted to lowercase. \n" +
 207  "    Note: This requires converting all profile/package IDs to lowercase. \n" +
 208  "          Therefore you will only see lowercase entries within the log files \n" +
 209  "          and also within the local package database. \n" +
 210  "\n" +
 211  "/logAppend[:<true>|<false>] \n" +
 212  "    Append log files instead of overwriting existing files. \n" +
 213  "    NOTE: You can specify a log file pattern which will create a new file on \n" +
 214  "    each run. Appending logs might cause problems with some log rotation \n" +
 215  "    scripts as well. So use it with caution. \n" +
 216  "\n" +
 217  "/logfilePattern:<pattern> \n" +
 218  "    Pattern for log file naming: \n" +
 219  "    Recognized patterns: \n" +
 220  "     [HOSTNAME]  Replaced by the executing hostname. \n" +
 221  "     [PROFILE]   Replaced by the concatenated list of profiles applied. \n" +
 222  "     [YYYY]      Replaced by year (4 digits). \n" +
 223  "     [MM]        Replaced by month number (2 digits). \n" +
 224  "     [DD]        Replaced by the day of the month (2 digits). \n" +
 225  "     [hh]        Replaced by hour of the day (24h format, 2 digits). \n" +
 226  "     [mm]        Replaced by minutes (2 digits). \n" +
 227  "     [ss]        Replaced by seconds (2 digits). \n" +
 228  "\n" +
 229  "     Examples: \n" +
 230  "        'wpkg-[YYYY]-[MM]-[DD]-[HOSTNAME].log' \n" +
 231  "             results in a name like 'wpkg-2007-11-04-myhost.log' \n" +
 232  "    NOTE: Using [PROFILE] causes all log messages before reading profiles.xml \n" +
 233  "            to be temporarily written to local %TEMP% folder. So they might \n" +
 234  "            appear on the final log file with some delay. \n" +
 235  "\n" +
 236  "/logLevel:[0-31] \n" +
 237  "    Level of detail for log file: \n" +
 238  "    use the following values: \n" +
 239  "    Log level is defined as a bitmask. Just sum up the values of each log \n" +
 240  "    severity you would like to include within the log file and add this value \n" +
 241  "    to your config.xml or specify it at /logLevel:<#>. \n" +
 242  "    Specify 0 to disable logging. \n" +
 243  "      1: log errors only \n" +
 244  "      2: log warnings \n" +
 245  "      4: log information \n" +
 246  "      8: log audit success \n" +
 247  "     16: log audit failure \n" +
 248  "    Example: \n" +
 249  "     31 log everything (1+2+4+8+16=31) \n" +
 250  "     13 log errors, information and audit success (1+4+8=13) \n" +
 251  "      3 log errors and warnings only (1+2=3) \n" +
 252  "    Default is 0 which will suppress all messages printed before log level is \n" +
 253  "    properly initialized by config.xml or by /logLevel:<#> parameter. \n" +
 254  "\n" +
 255  "/log_file_path:<path> \n" +
 256  "    Path where the log files will be stored. Also allows specifying an UNC \n" +
 257  "    path (e.g. '\\server\share\directory'). Make sure the path exists and \n" +
 258  "    that the executing user has write access to it. \n" +
 259  "    NOTE: If you set this parameter within config.xml please note that you \n" +
 260  "            need to escape backslashes: \n" +
 261  "            e.g. '\\\\server\\share\\directory'. \n" +
 262  "\n" +
 263  "/noforcedremove[:<true>|<false>] \n" +
 264  "    Do not remove packages from local package database if remove fails even \n" +
 265  "    if the package does not exist in the package database on the server and \n" +
 266  "    is not referenced within the profile. \n" +
 267  "    By default packages which have been removed from the server package \n" +
 268  "    database and the profile will be uninstalled and then removed \n" +
 269  "    from the local package database even if uninstall failed. \n" +
 270  "    This has been introduced to prevent a package whose uninstall script \n" +
 271  "    fails to repeat its uninstall procedure on each execution without the \n" +
 272  "    possibility to fix the problem since the package (including its \n" +
 273  "    uninstall string) is available on the local machine only. \n" +
 274  "    HINT: If you like the package to stay in the local database (including \n" +
 275  "    uninstall-retry on next boot) just remove it from the profile but do not \n" +
 276  "    completely delete it from the package database. \n" +
 277  "\n" +
 278  "/noremove[:<true>|<false>] \n" +
 279  "    Disable the removal of all packages. If used in conjunction with the \n" +
 280  "    /synchronize parameter it will just add packages but never remove them. \n" +
 281  "    Instead of removing a list of packages which would have been removed \n" +
 282  "    during that session is printed on exit. Packages are not removed from \n" +
 283  "    the local settings database (wpkg.xml). Therefore it will contain a list \n" +
 284  "    of all packages ever installed. \n" +
 285  "    Note that each package which is requested to be removed (manually or by \n" +
 286  "    a synchronization request) will be checked for its state by executing its \n" +
 287  "    included package checks. If the package has been removed manually it will \n" +
 288  "    also be removed from the settings database. This does not apply to packages \n" +
 289  "    which do not specify any checks. Such packages will remain in the local \n" +
 290  "    settings database even if the software has been removed manually. \n" +
 291  "\n" +
 292  "/noDownload[:<true>|<false>] \n" +
 293  "    Ignore all download nodes in packages. \n" +
 294  "    Useful for testing and in case your download targets already exist. \n" +
 295  "\n" +
 296  "/norunningstate[:<true>|<false>] \n" +
 297  "    Do not export the running state to the registry. \n" +
 298  "\n" +
 299  "/rebootcmd:<option> \n" +
 300  "    Use the specified boot command, either with full path or \n" +
 301  "    relative to the location of wpkg.js \n" +
 302  "    Specifying 'special' as option uses tools\psshutdown.exe \n" +
 303  "    from www.sysinternals.com - if it exists - and a notification loop \n";
 304  
 305      alert(message);
 306  }
 307  
 308  /*******************************************************************************
 309   * 
 310   * Global variables
 311   * 
 312   ******************************************************************************/
 313  /** base where to find the XML input files */
 314  var wpkg_base = "";
 315  
 316  /** forces to check for package existence but ignores wpkg.xml */
 317  var force = false;
 318  
 319  /** force installation */
 320  var forceInstall = false;
 321  
 322  /**
 323   * Forced remove of non-existing packages from wpkg.xml even if uninstall
 324   * command fails.
 325   */
 326  var noForcedRemove = false;
 327  
 328  /** defined if script should quit on error */
 329  var quitonerror = false;
 330  
 331  /** Debug output. */
 332  var debug = false;
 333  
 334  /** Dry run */
 335  var dryrun = false;
 336  
 337  /** notify user using net send? */
 338  var nonotify = false;
 339  
 340  /** timeout for user notifications. Works only if msg.exe is available */
 341  var notificationDisplayTime = 10;
 342  
 343  /** set to true to prevent reboot */
 344  var noreboot = false;
 345  
 346  /** stores if package removal should be skipped - see /noremove flag */
 347  var noRemove = false;
 348  
 349  /** allows disabling/enabling of running state export to registry */
 350  var noRunningState = false;
 351  
 352  /** type of reboot command */
 353  var rebootCmd = "standard";
 354  
 355  /** set to true for quiet mode */
 356  var quietDefault = false;
 357  
 358  /** registry path where WPKG stores its running state */
 359  var sRegWPKG_Running = "HKLM\\Software\\WPKG\\running";
 360  
 361  /** configuration file to hold the settings for the script */
 362  var config_file_name = "config.xml";
 363  
 364  /** name of package database file */
 365  var packages_file_name = "packages.xml";
 366  /** name of profiles database file */
 367  var profiles_file_name = "profiles.xml";
 368  /** name of hosts definition database file */
 369  var hosts_file_name = "hosts.xml";
 370  
 371  /**
 372   * specify if manually installed packages should be kept during synchronization
 373   * true: keep manually installed packages false: remove any manually installed
 374   * package which does not belong to the profile
 375   */
 376  var keepManual = true;
 377  
 378  /**
 379   * path where log-files are stored.<br>
 380   * Defaults to "%TEMP%" if empty.
 381   */
 382  var log_file_path = "%TEMP%";
 383  
 384  /** path where downloads are stored, defaults to %TEMP% if not defined */
 385  var downloadDir = "%TEMP%";
 386  
 387  /** timeout for downloads */
 388  var downloadTimeout = 7200;
 389  
 390  /** if set to true logfiles will be appended, otherwise they are overwritten */
 391  var logAppend = false;
 392  
 393  /**
 394   * set to true to enable sending of status messages to STDOUT, regardless of the
 395   * status of /debug
 396   */
 397  var sendStatus = false;
 398  
 399  /**
 400   * Set to true to disable upgrade-before-remove feature by default
 401   */
 402  var noUpgradeBeforeRemove = false;
 403  
 404  /**
 405   * use the following values: Log level is defined as a bitmask. Just add sum up
 406   * the values of each log severity you would like to include within the log file
 407   * and add this value to your config.xml or specify it at /logLevel:<num>.
 408   *
 409   * Specify 0 to disable logging.
 410   * 
 411   * <pre>
 412   * 1: log errors only
 413   * 2 : log warnings
 414   * 4 : log information
 415   * 8 : log audit success
 416   * </pre>
 417   * 
 418   * Example:
 419   * 
 420   * <pre>
 421   * 31 log everything (1+2+4+8+16=32)
 422   * 13 logs errors, information and audit success (1+4+8=13)
 423   *  3 logs errors and warnings only (1+2=3)
 424   * </pre>
 425   * 
 426   * Default is 0 which will suppress all messages printed before log level is
 427   * properly initialized by config.xml or by /logLevel:<#> parameter.
 428   */
 429  var logLevelDefault = 0xFF;
 430  
 431  /**
 432   * var logfile pattern Recognized patterns:
 433   * 
 434   * <pre>
 435   * [HOSTNAME]    replaced by the executing hostname
 436   * [PROFILE]    replaced by the  name
 437   * [YYYY]        replaced by year (4 digits)
 438   * [MM]            replaced by month number (2 digits)
 439   * [DD]            replaced by the day of the month (2 digits)
 440   * [HH]            replaced by hour of the day (24h format, 2 digits)
 441   * [mm]            replaced by minute (2 digits)
 442   * </pre>
 443   * 
 444   * Examples:
 445   * 
 446   * <pre>
 447   * wpkg-[YYYY]-[MM]-[DD]-[HOSTNAME].log
 448   * </pre>
 449   * 
 450   * results in a name like "wpkg-2007-11-04-myhost.log"
 451   */
 452  var logfilePattern = "wpkg-[HOSTNAME].log";
 453  
 454  /** web file name of package database if base is an http url */
 455  var web_packages_file_name = "packages_xml_out.php";
 456  /** web file name of profile database if base is an http url */
 457  var web_profiles_file_name = "profiles_xml_out.php";
 458  /** web file name of hosts database if base is an http url */
 459  var web_hosts_file_name = "hosts_xml_out.php";
 460  
 461  /** name of local settings file */
 462  var settings_file_name = "wpkg.xml";
 463  
 464  /** path to settings file, defaults to system folder if set to null */
 465  var settings_file_path = null;
 466  
 467  /** defines if package/profile IDs are handled case sensitively */
 468  var caseSensitivity = true;
 469  
 470  /** set to true to want to apply profiles of all matching host nodes */
 471  var applyMultiple = false;
 472  
 473  /** globally disable any downloads */
 474  var noDownload = false;
 475  
 476  /**
 477   * Allows to disable insert of host attributes to local settings DB. This is
 478   * handy for testing as the current test-suite compares the local wpkg.xml
 479   * database and the file will look different on all test machines if these
 480   * attribute are present. This setting might be removed if all test-cases
 481   * are adapted.
 482   */
 483  var settingsHostInfo = true;
 484  
 485  /**
 486   * Query mode. In order to "simulate" the result of a query on a system on
 487   * anohter (remote-) system you can set queryMode to "remote". This will
 488   * disable package checks because they will not return the same results on a
 489   * remote system.
 490   */
 491  var queryMode = "local";
 492  
 493  /**
 494   * XML namespaces.
 495   */
 496  var namespaceSettings = "http://www.wpkg.org/settings";
 497  var namespaceHosts = "http://www.wpkg.org/hosts";
 498  var namespaceProfiles = "http://www.wpkg.org/profiles";
 499  var namespacePackages = "http://www.wpkg.org/packages";
 500  var namespaceConfig = "http://www.wpkg.org/config";
 501  
 502  /*******************************************************************************
 503   * 
 504   * Caching variables - used by internal functions.
 505   * 
 506   ******************************************************************************/
 507  
 508  /** file to which the log is written to */
 509  var logfileHandler = null;
 510  
 511  /** path to the log file (corresponds to logfileHandler) */
 512  var logfilePath = null;
 513  
 514  /** store whether log file was opened in append mode */
 515  var logfileAppendMode = logAppend;
 516  
 517  /** stores if the user was notified about start of actions */
 518  var was_notified = false;
 519  
 520  /**
 521   * holds a list of packages which have been installed during this execution this
 522   * is used to prevent dependency packages without checks and always execution to
 523   * be executed several times as well as preventing infinite- loops on recursive
 524   * package installation.
 525   */
 526  var packagesInstalled = new Array();
 527  
 528  /**
 529   * holds a list of packages which have been removed during this execution This
 530   * is used to prevent removing packages multiple times during a session because
 531   * they are referenced as dependencies by multiple other packages.
 532   */
 533  var packagesRemoved = new Array();
 534  
 535  /** host properties used within script */
 536  var hostName = null;
 537  var hostOs = null;
 538  var domainName = null;
 539  var ipAddresses = null;
 540  var hostGroups = null;
 541  var hostArchitecture = null;
 542  var hostAttributes = null;
 543  
 544  /** String representing path where the settings are stored */
 545  var settings_file = null;
 546  
 547  /** Flag whether settings path was processed to replace parameters */
 548  var settings_file_processed = false;
 549  
 550  /** declare variables for data storage */
 551  var packages = null;
 552  var profiles = null;
 553  var hosts = null;
 554  var settings = null;
 555  var config = null;
 556  
 557  /** List of profiles assigned directly to current host */
 558  var applyingProfilesDirect = null;
 559  
 560  /** profiles applying to the current host (including dependencies) */
 561  var applyingProfilesAll = null;
 562  
 563  /** caches the host node entries applying to the current host */
 564  var applyingHostNodes = null;
 565  
 566  /** Packages belonging to current host (package nodes) */
 567  var profilePackageNodes = null;
 568  
 569  /** stores the locale ID (LCID) which applies for the local executing user */
 570  var LCID = null;
 571  
 572  /** stores the locale ID (LCID) which applies for the local machine */
 573  var LCIDOS = null;
 574  
 575  /** caches the language node applying to the current system locale */
 576  var languageNode = null;
 577  
 578  /** declare log level variable */
 579  var logLevel = null;
 580  
 581  /** actual value for log level */
 582  var logLevelValue = 0x03;
 583  
 584  /** buffer to store log entries while no real log file is available */
 585  var logBuffer = null;
 586  
 587  /** flag which is true if log is ready to be initialize */
 588  var logInitReady = false;
 589  
 590  /** flag which is set to true internally during log initialization */
 591  var logInitializing = false;
 592  
 593  /** declare quiet mode variable */
 594  var quiet = null;
 595  
 596  /** current value of quiet operation flag */
 597  var quietMode = quietDefault;
 598  
 599  /** stores if a postponed reboot is scheduled */
 600  var postponedReboot = false;
 601  
 602  /** set to true when a reboot is in progress */
 603  var rebooting = false;
 604  
 605  /** set to true to skip write attempts to event log */
 606  var skipEventLog = false;
 607  
 608  /** set to true to log event log entries to STDOUT (fallback mode) */
 609  var eventLogFallback = false;
 610  
 611  /** holds an array of packages which were not removed due to the /noremove flag */
 612  var skippedRemoveNodes = null;
 613  
 614  /**
 615   * holds status of change: true: System has been changed (package
 616   * installed/removed/updated/downgraded... false: System has not been touched
 617   * (yet)
 618   */
 619  var systemChanged = false;
 620  
 621  /**
 622   * Holds a list of packages which have been manually installed.
 623   */
 624  var manuallyInstalled = null;
 625  
 626  /**
 627   * Marks volatile releases and "inverts" the algorithm that a longer version
 628   * number is newer. For example 1.0RC2 would be newer than 1.0 because it
 629   * appends characters to the version. Using "rc" as a volatile release marker
 630   * the algorithm ignores the appendix and assumes that the string which omits
 631   * the marker is newer.
 632   *
 633   * Resulting in 1.0 to be treated as newer than 1.0RC2.
 634   *
 635   * NOTE: The strings are compared as lower-case. So use lower-case definitions
 636   * only!
 637   */
 638  var volatileReleaseMarkers = ["rc", "i", "m", "alpha", "beta", "pre", "prerelease"];
 639  
 640  /** stores if system is running on a 64-bit OS */
 641  var x64 = null;
 642  
 643  /** Stores variables assigned to host definitions applying to current host */
 644  var hostsVariables = null;
 645  
 646  /** Stores variables from profiles assigned to current hsot */
 647  var profilesVariables = null;
 648  
 649  /***********************************************************************************************************************
 650   * 
 651   * Program execution
 652   * 
 653   **********************************************************************************************************************/
 654  
 655  /**
 656   * Call the main function with arguments while catching all errors and
 657   * forwarding them to the error output.
 658   */
 659  try {
 660      main();
 661  } catch (e) {
 662      // Log error message.
 663      error("Message:      " + e.message + "\n" +
 664              "Description:  " + e.description + "\n" +
 665              "Error number: " + hex(e.number) + "\n" +
 666              "Stack:        " + e.stack  + "\n" +
 667              "Line:         " + e.lineNumber + "\n"
 668              );
 669      notifyUserFail();
 670      // Make sure log is initialized to write the output.
 671      if (logBuffer != null) {
 672          initializeLog();
 673      }
 674      exit(2);
 675  }
 676  
 677  /**
 678   * Main execution method. Actually runs the script
 679   */
 680  function main() {
 681      // Do not open pop-up window while initializing.
 682      setQuiet(true);
 683      
 684      // PATCH SE3 : oubli dans le script officiel.
 685      if (!isNoRunningState()) {
 686          setRunningState("true");
 687      }
 688      // FIN PATCH SE3.
 689  
 690      // Initialize WPKG internals.
 691      initialize();
 692  
 693      // Process command line arguments to determine course of action.
 694      // Get special purpose argument lists.
 695      var argv = getArgv();
 696      var argn = argv.Named;
 697      // var argu = argv.Unnamed;
 698      if (argn.Item("query") != null) {
 699          // Do not log to event log during query.
 700          var eventLogStatus = isSkipEventLog();
 701          setSkipEventLog(true);
 702  
 703          if (getQueryMode() == "remote") {
 704              getSettingHostAttributes();
 705          }
 706          
 707          // Now all parameters are set to build the final log file name
 708          // even if [PROFILE] is used.
 709          // WScript.Echo("Initializing Log");
 710          // WScript.Echo("Buffer: " + logBuffer);
 711          // Flag log file ready for initialization.
 712          logInitReady = true;
 713  
 714          // Do not log to log file during query.
 715          var logValue = getLogLevel();
 716          // setLogLevel(0);
 717  
 718          // Read query argument characters.
 719          var queryRequest = argn.Item("query").split("");
 720          
 721          // Supported arguments:
 722          // a Query all packages.
 723          // x List packages which are not installed but in package database.
 724          // i List all packages which are currently installed.
 725          // I List packages which are about to be installed during synchronization.
 726          // u List packages which are about to be upgraded during synchronization.
 727          // d List packages which are about to be downgraded during synchronization.
 728          // r List packages which are about to be removed during synchronization.
 729          // m List all modifications which would apply during synchronization (equal to Iudr)
 730          // n List packages which belong to the profile but are not modified during synchronization.
 731          // s List host attributes from settings (wpkg.xml).
 732          // l List host attributes read from local host.
 733          var listSyncInstall = false;
 734          var listSyncUpgrade = false;
 735          var listSyncDowngrade = false;
 736          var listSyncRemove = false;
 737          var listSyncUnmodified = false;
 738          for (var i=0; i<queryRequest.length; i++) {
 739              var requestCharacter = queryRequest[i];
 740              switch (requestCharacter) {
 741              case "a":
 742                  queryAllPackages();
 743                  break;
 744  
 745              case "x":
 746                  queryUninstalledPackages();
 747                  break;
 748  
 749              case "i":
 750                  queryInstalledPackages();
 751                  break;
 752  
 753              case "I":
 754                  listSyncInstall = true;
 755                  break;
 756  
 757              case "u":
 758                  listSyncUpgrade = true;
 759                  break;
 760  
 761              case "d":
 762                  listSyncDowngrade = true;
 763                  break;
 764  
 765              case "r":
 766                  listSyncRemove = true;
 767                  break;
 768  
 769              case "n":
 770                  listSyncUnmodified = true;
 771                  break;
 772  
 773              case "m":
 774                  listSyncInstall = true;
 775                  listSyncUpgrade = true;
 776                  listSyncDowngrade = true;
 777                  listSyncRemove = true;
 778                  break;
 779  
 780              case "s":
 781                  queryHostInformationFromSettings();
 782                  break;
 783  
 784              case "l":
 785                  queryHostInformation();
 786                  break;
 787  
 788              default:
 789                  break;
 790              }
 791          }
 792          // Print requested output.
 793          if (listSyncInstall || listSyncUpgrade || listSyncDowngrade || listSyncRemove || listSyncUnmodified) {
 794              queryProfilePackages(listSyncInstall, listSyncUpgrade, listSyncDowngrade, listSyncRemove, listSyncUnmodified);
 795          }
 796  
 797          setSkipEventLog(eventLogStatus);
 798          setLogLevel(logValue);
 799      } else {
 800  
 801          // set profile-based log level (if available)
 802          var profileLogLevel = getProfilesLogLevel();
 803          if (profileLogLevel != null) {
 804              setLogLevel(profileLogLevel);
 805          }
 806  
 807          // Now all parameters are set to build the final log file name
 808          // even if [PROFILE] is used.
 809          // WScript.Echo("Initializing Log");
 810          // WScript.Echo("Buffer: " + logBuffer);
 811          // Flag log file ready for initialization.
 812          logInitReady = true;
 813  
 814          var message;
 815          if(isDebug()) {
 816              var hst = getHostNodes();
 817              message = "Hosts file contains " + hst.length + " hosts:";
 818              for (var iHost = 0; iHost < hst.length; iHost++ ) {
 819                  message += "\n'" + getHostNodeDescription(hst[iHost]) + "'";
 820              }
 821              dinfo(message);
 822  
 823              var settingsPkg = getSettingNodes();
 824              message = "Settings file contains " + settingsPkg.length + " packages:";
 825              for (var iSettings = 0; iSettings < settingsPkg.length; iSettings++) {
 826                  if (settingsPkg[iSettings] != null) {
 827                       message += "\n'" + getPackageID(settingsPkg[iSettings]) + "'";
 828                  }
 829              }
 830              dinfo(message);
 831  
 832              var packageNodes = getPackageNodes();
 833              message = "Packages file contains " + packageNodes.length + " packages:";
 834              for (var iPackage = 0; iPackage < packageNodes.length; iPackage++) {
 835                  if (packageNodes[iPackage] != null) {
 836                       message += "\n'" + getPackageID(packageNodes[iPackage]) + "'";
 837                  }
 838              }
 839              dinfo(message);
 840  
 841              var profileNodes = getProfileNodes();
 842              message = "Profile file contains " + profileNodes.length + " profiles:";
 843              for (var iProfile = 0; iProfile < profileNodes.length; iProfile++) {
 844                  if (profileNodes[iProfile] != null) {
 845                       message += "\n'" + getProfileID(profileNodes[iProfile]) + "'";
 846                  }
 847              }
 848              dinfo(message);
 849  
 850              // Get list of profiles directly assigned to host.
 851              var profiles = getProfileList();
 852              message = "Using profile(s): ";
 853              for (var i=0; i < profiles.length; i++) {
 854                  message += "\n'" + profiles[i] + "'";
 855              }
 856              dinfo(message);
 857          }
 858  
 859          // Check if all referenced profiles are available.
 860          var profileList = getProfileList();
 861          var error = false;
 862          message = "Could not locate referenced profile(s):\n";
 863          for (var iProf = 0; iProf < profileList.length; iProf++) {
 864              var currentProfile = getProfileNode(profileList[iProf]);
 865              if (currentProfile == null) {
 866                  error = true;
 867                  message += profileList[iProf] + "\n";
 868              }
 869          }
 870          if (error) {
 871              throw new Error(message);
 872          }
 873          
 874          if (argn.Item("show") != null) {
 875              var requestedPackageName = argn.Item("show");
 876              // if case sensitive mode is disabled convert package name to lower case
 877              if (!isCaseSensitive()) {
 878                  requestedPackageName = requestedPackageName.toLowerCase();
 879              }
 880              var message = queryPackage(getPackageNodeFromAnywhere(requestedPackageName), null);
 881              info(message);
 882          } else if (argn.Item("install") != null) {
 883              var packageList = argn.Item("install").split(",");
 884              for (var iPackage=0; iPackage < packageList.length; iPackage++) {
 885                  installPackageName(packageList[iPackage], true);
 886              }
 887          } else if (argn.Item("remove") != null) {
 888              var packageList = argn.Item("remove").split(",");
 889              for (var iPackage=0; iPackage < packageList.length; iPackage++) {
 890                  removePackageName(packageList[iPackage]);
 891              }
 892          } else if (argn.Item("upgrade") != null) {
 893              var packageList = argn.Item("upgrade").split(",");
 894              for (var iPackage=0; iPackage < packageList.length; iPackage++) {
 895                  installPackageName(packageList[iPackage], false);
 896              }
 897          } else if (isArgSet(argv, "/synchronize")) {
 898              synchronizeProfile();
 899          } else {
 900              showUsage();
 901              throw new Error("No action specified.");
 902          }
 903      }
 904      exit(0);
 905  }
 906  
 907  
 908  /**
 909   * Adds a sub-node for the given XML node entry.
 910   * 
 911   * @param XMLNode
 912   *            the XML node to add to (e.g. packages or settings)
 913   * @param subNode
 914   *            the node to be added to the XMLNode (for example a package node)
 915   *            NOTE: The node will be cloned before add
 916   * @return Returns true in case of success, returns false if no node could be
 917   *         added
 918   */
 919  function addNode(XMLNode, subNode) {
 920      var returnvalue = false;
 921      var result = XMLNode.appendChild(subNode.cloneNode(true));
 922      if(result != null) {
 923          returnvalue = true;
 924      }
 925      return returnvalue;
 926  }
 927  
 928  
 929  /**
 930   * Adds a package node to the settings XML node. In case a package with the same
 931   * ID already exists it will be replaced.
 932   * 
 933   * @param packageNode
 934   *            the package XML node to add.
 935   * @param saveImmediately
 936   *            Set to true in order to save settings immediately after adding.
 937   *            Settings will not be saved immediately if value is false.
 938   * @return true in case of success, otherwise returns false
 939   */
 940  function addSettingsNode(packageNode, saveImmediately) {
 941      // first remove entry if one already exists
 942  
 943      // get current settings node
 944      var packageID = getPackageID(packageNode);
 945      var settingsNode = getSettingNode(packageID);
 946  
 947      if (settingsNode != null) {
 948          dinfo("Removing currently existing settings node first: '" +
 949                  getPackageName(settingsNode) + "' (" + getPackageID(settingsNode) +
 950                  "), Revision " + getPackageRevision(settingsNode) + ".");
 951          removeSettingsNode(settingsNode, false);
 952      }
 953  
 954      dinfo("Adding settings node: '" +
 955               getPackageName(packageNode) + "' (" + getPackageID(packageNode) +
 956               "), Revision " + getPackageRevision(packageNode) + ".");
 957  
 958      var success = addNode(getSettings(), packageNode);
 959      // save settings if remove was successful
 960      if (success && saveImmediately) {
 961          saveSettings(true);
 962      }
 963      return success;
 964  }
 965  
 966  /**
 967   * Adds a package node to the list of skipped packages during removal process.
 968   * 
 969   * @param packageNode
 970   *            the node which has been skipped during removal
 971   */
 972  function addSkippedRemoveNodes(packageNode) {
 973      var skippedNodes = getSkippedRemoveNodes();
 974      skippedNodes.push(packageNode);
 975  }
 976  
 977  /**
 978   * Appends dependent profile nodes of the specified profile to the specified
 979   * array. Recurses into self to get an entire dependency tree.
 980   */
 981  function appendProfileDependencies(profileArray, profileNode) {
 982      var profileNodes = getProfileDependencies(profileNode);
 983  
 984      // add nodes if they are not yet part of the array
 985      for (var i=0; i < profileNodes.length; i++) {
 986          var currentNode = profileNodes[i];
 987          if(!searchArray(profileArray, currentNode)) {
 988              dinfo("Adding profile dependencies of profile '" +
 989                      getProfileID(profileNode) + "': '" +
 990                      getProfileID(currentNode) + "'");
 991              profileArray.push(currentNode);
 992  
 993              // add dependencies of these profiles as well
 994              appendProfileDependencies(profileArray, currentNode);
 995          } else {
 996              dinfo("Profile '" +
 997                      getProfileID(currentNode) + "' " +
 998                      "already exists in profile dependency tree. Skipping.");
 999          }
1000      }
1001  }
1002  
1003  /**
1004   * Evaluates all checks in the check nodes array and returns its result.
1005   * @param checkNodes Array of XML <check /> nodes to be evaluated.
1006   * @returns {Boolean} true if all checks are true. False if at least one failed.
1007   */
1008  function checkAll(checkNodes) {
1009      if (checkNodes == null) {
1010          return true;
1011      }
1012  
1013      // Initialize return value.
1014      var result = true;
1015  
1016      // Save environment.
1017      var previousEnv = getEnv();
1018      
1019      // Loop over every condition check.
1020      // If all are successful, we consider package as installed.
1021      for (var i = 0; i < checkNodes.length; i++) {
1022          try {
1023              if (!checkCondition(checkNodes[i])) {
1024                  result = false;
1025                  break;
1026              }
1027          } catch (err) {
1028              message = "Error evaluating check: " + err.description;
1029              if (isQuitOnError()) {
1030                  throw new Error(message);
1031              } else {
1032                  error(message);
1033                  result = false;
1034                  break;
1035              }
1036          } finally {
1037              // Restore environment.
1038              loadEnv(previousEnv);
1039          }
1040      }
1041      return result;
1042  }
1043  
1044  /**
1045   * Checks for the success of a check condition for a package.
1046   * 
1047   * @param checkNode
1048   *            XML check node to be evaluated
1049   * @throws Error
1050   *             Throws error in case of invalid XML node definition
1051   */
1052  function checkCondition(checkNode) {
1053      var shell = new ActiveXObject("WScript.Shell");
1054  
1055      // get attributes of check
1056      var checkType = checkNode.getAttribute("type");
1057      var checkCond = checkNode.getAttribute("condition");
1058      var checkPath = checkNode.getAttribute("path");
1059      var checkValue = checkNode.getAttribute("value");
1060  
1061      // In remote mode try to verify the check using cached check results in
1062      // settings database.
1063      if (getQueryMode() == "remote") {
1064          // Logical checks shall be evaluated as usual.
1065          // Only look for previous check results for other types of checks.
1066          if (checkType != "logical") {
1067              var result = getSettingsCheckResult(checkNode);
1068              if (result == null) {
1069                  error("Result of check of type '" + checkType + "' with condition '" +
1070                          checkCond + "', path '" + checkPath + "' and value '" +
1071                          checkValue + "' is missing in settings database. " +
1072                          "Trying to evaluate locally. Results might be inaccurate");
1073              } else {
1074                  return result;
1075              }
1076          }
1077      }
1078      
1079      // Sanity check: must have Type set here.
1080      if (checkType == null) {
1081          throw new Error("Check Type is null - this is not permitted. Perhaps a typo? " +
1082                          "To help find it, here are the other pieces of information: " +
1083                          "condition='" + checkCond + "', path='" + checkPath +
1084                          "', value='" + checkValue + "'.");
1085      }
1086  
1087      // Initialize return value;
1088      var returnValue = false;
1089      
1090      // get expanded values for path and value used by some checks
1091      var checkPathExpanded = null;
1092      if (checkPath != null) {
1093          checkPathExpanded = shell.ExpandEnvironmentStrings(checkPath);
1094      }
1095      var checkValueExpanded = null;
1096      if (checkValue != null) {
1097          checkValueExpanded = shell.ExpandEnvironmentStrings(checkValue);
1098      }
1099  
1100      switch(checkType) {
1101      // check type: registry
1102      case "registry":
1103          // Sanity check: must have Cond and Path set for all registry checks.
1104          if ((checkCond == null) || (checkPath == null)) {
1105              throw new Error("Condition and / or path is null for a registry check. Perhaps " +
1106                              "a typo? To help find it, here are the other pieces of information: " +
1107                              "condition='" + checkCond + "', path='" + checkPath +
1108                              "', value='" + checkValue + "'.");
1109          }
1110  
1111          // branch on check condition
1112          switch (checkCond) {
1113          case "exists":
1114              if (getRegistryValue(checkPath) != null) {
1115                  // Some debugging information.
1116                  dinfo("The registry path '" + checkPath + "' exists: the check was successful.");
1117                  returnValue = true;
1118              } else if (getRegistryValue(checkPathExpanded) != null) {
1119                  dinfo("The expanded registry path '" + checkPathExpanded + "' exists: the check was successful.");
1120                  returnValue = true;
1121              } else {
1122                  // path does not exist
1123                  dinfo("Neither the registry path '" + checkPath + "' nor its expanded value of '" +
1124                          checkPathExpanded + "' exist: the check failed.");
1125                  returnValue = false;
1126              }
1127              break;
1128  
1129          case "equals":
1130              // read registry value and convert it to string in order to compare
1131              // to supplied
1132              // string within the 'value' attribute
1133              var readValue = getRegistryValue(checkPath);
1134  
1135              // check if value is eventually null (non-existing)
1136              if (readValue == null) {
1137                  // the path might have to be expanded
1138                  readValue = getRegistryValue(checkPathExpanded);
1139                  if (readValue == null) {
1140                      dinfo("The registry path '" + checkPath + "' did not exist. Check failed.");
1141                      returnValue = false;
1142                      break;
1143                  }
1144                  dinfo("The expanded registry path '" + checkPathExpanded + "' could be read.");
1145              } else {
1146                  dinfo("The registry path '" + checkPath+ "' could be read.");
1147              }
1148  
1149              // try treating the value as array
1150              var registyValue = "";
1151              try {
1152                  var readArray = readValue.toArray();
1153                  dinfo("The registry value received is an array, concatenating values for comparison.");
1154                  for (var iRegKey=0; iRegKey<readArray.length; iRegKey++) {
1155                      registyValue = registyValue + readArray[iRegKey] + "";
1156                      if ( (iRegKey+1) < readArray.length) {
1157                          registyValue += "\n";
1158                      }
1159                  }
1160              } catch(notAnArray) {
1161                  dinfo("The registry value received is a scalar value.");
1162                  registyValue = readValue + "";
1163              }
1164  
1165              if (registyValue == checkValue) {
1166                  // Some debugging information.
1167                  dinfo("The registry path '" + checkPath + "' contained the correct value: '" +
1168                          checkValue + "'. The check was successful.");
1169                  returnValue = true;
1170              } else {
1171                  // Try if expanded value matches (case-insensitive).
1172                  if (registyValue.toLowerCase() == checkValueExpanded.toLowerCase()) {
1173                      dinfo("The registry path '" + checkPath + "' contained the expanded value: '" +
1174                              checkValueExpanded + "'. The check was successful.");
1175                      returnValue = true;
1176                  } else {
1177                      dinfo("The registry path '" + checkPath + "' did not contain the value: '" +
1178                               checkValue + "'. Instead it contained '" + registyValue + "'. the check failed.");
1179                      returnValue = false;
1180                  }
1181              }
1182              break;
1183  
1184          default:
1185              throw new Error("Check condition " + checkCond + " unknown " +
1186                              "for type registry.");
1187              break;
1188          }
1189          
1190          // The result of Registry checks shall be stored in local settings node.
1191          addSettingsCheckResult(checkNode, returnValue);
1192          
1193          break;
1194  
1195      // check type: file
1196      case "file":
1197          // Sanity check: must have Cond and Path set for all file checks.
1198          if ((checkCond == null) ||
1199              (checkPath == null)) {
1200              throw new Error("Condition and / or path is null for a file check. Perhaps " +
1201                              "a typo? To help find it, here are the other pieces of information: " +
1202                              "condition='" + checkCond + "', path='" + checkPath +
1203                              "', value='" + checkValue + "'");
1204          }
1205  
1206          // expand environment variables
1207          // use only expanded value here
1208          checkPath = checkPathExpanded;
1209  
1210          if (checkCond == "exists") {
1211              var fso = new ActiveXObject("Scripting.FileSystemObject");
1212              if (fso.FileExists(checkPath)) {
1213                  // Some debugging information.
1214                  dinfo("The path '" + checkPath + "' exists and is a file: the test was successful.");
1215                  returnValue = true;
1216              } else if (fso.FolderExists(checkPath)) {
1217                  // Some debugging information.
1218                  dinfo("The path '" + checkPath + "' exists and is a folder: the test was successful.");
1219                  returnValue = true;
1220              } else {
1221                  // Some debugging information.
1222                  dinfo("The path '" + checkPath + "' does not exist: the test failed.");
1223                  returnValue = false;
1224              }
1225  
1226          } else if (checkCond == "sizeequals") {
1227              // sanity check: must have Value set for a size check.
1228              if (checkValue == null) {
1229                  throw new Error("Value is null for a file sizeequals check. Perhaps " +
1230                                  "a typo? To help find it, here are the other pieces of information: " +
1231                                  "condition='" + checkCond +
1232                                  "', path='" + checkPath +
1233                                  "', value='" + checkValue + "'.");
1234              }
1235  
1236              var filesize = getFileSize(checkPath);
1237              if (filesize == checkValueExpanded) {
1238                  dinfo("The file '" + checkPath + "' has size " + filesize + ": the test was successful.");
1239                  returnValue = true;
1240              } else {
1241                  dinfo("The file '" + checkPath + "' has size " + filesize + " - wanted " +
1242                          checkValueExpanded + ": the test fails.");
1243                  returnValue = false;
1244              }
1245          } else if (checkCond.substring(0,7) == "version") {
1246              // Sanity check: Must have a value set for version check.
1247              if (checkValue == null) {
1248                  throw new Error("Value is null for a file version check. Perhaps " +
1249                                  "a type? To help find it, here are the other pieces of information: " +
1250                                  "condition='" + checkCond + "', path='" + checkPath +
1251                                  "', value='" + checkValue + "'.");
1252              } // if checkValue == null
1253  
1254              var fileVersion = getFileVersion(checkPath);
1255  
1256              if (fileVersion == null || fileVersion == "") {
1257                   // no file version could be obtained
1258                   dinfo("Unable to find the file version for '" + checkPath + "'.");
1259                   returnValue = false;
1260              } else {
1261  
1262                  var fileVersionCompare = versionCompare(fileVersion, checkValueExpanded);
1263                  dinfo ("Checking file version " + fileVersion + " is " + checkCond +
1264                           " (than) " + checkValueExpanded + " - got result " + fileVersionCompare + ".");
1265      
1266                  var fileVersionCompResult = false;
1267                  switch (checkCond) {
1268                      case "versionsmallerthan":
1269                          if (fileVersionCompare < 0) {
1270                              fileVersionCompResult = true;
1271                          }
1272                          break;
1273                      case "versionlessorequal":
1274                          if (fileVersionCompare <= 0) {
1275                              fileVersionCompResult = true;
1276                          }
1277                          break;
1278                      case "versionequalto":
1279                          if (fileVersionCompare == 0) {
1280                              fileVersionCompResult = true;
1281                          }
1282                          break;
1283                      case "versiongreaterorequal":
1284                          if (fileVersionCompare >= 0) {
1285                              fileVersionCompResult = true;
1286                          }
1287                          break;
1288                      case "versiongreaterthan":
1289                          if (fileVersionCompare > 0) {
1290                              fileVersionCompResult = true;
1291                          }
1292                          break;
1293                      default:
1294                          error("Unknown operation on file versions : " + checkCond);
1295                          fileVersionCompResult = false;
1296                          break;
1297                  }
1298      
1299                  dinfo("File version check for file '" + checkPath + "' returned " +
1300                      fileVersionCompResult + " for operation type " + checkCond + ".");
1301                  returnValue = fileVersionCompResult;
1302              }
1303  
1304          } else if (checkCond.substring(0,4) == "date") {
1305              var fileDate = null;
1306              var comparisonDesc = "";
1307              var dateType = checkCond.substring(4,10);
1308              // Evaluate if modification date shall be checked.
1309              if (dateType == "modify") {
1310                  dinfo("Checking file modification date.");
1311                  // Evaluate file modification date.
1312                  fileDate = getFileDateModification(checkPath);
1313                  comparisonDesc = "Modification";
1314              } else if (dateType == "create") {
1315                  dinfo("Checking file creation date.");
1316                  // Evaluate file creation date.
1317                  fileDate = getFileDateCreation(checkPath);
1318                  comparisonDesc = "Creation";
1319              } else if (dateType == "access") {
1320                  dinfo("Checking file access date.");
1321                  // Evaluate file access date.
1322                  fileDate = getFileDateLastAccess(checkPath);
1323                  comparisonDesc = "Access";
1324              } else {
1325                  throw new Error ("Invalid file date comparison type: " + checkCond + ".");
1326              }
1327  
1328              // If file date could not be read: Comparison failed.
1329              if (fileDate == null) {
1330                  dinfo("File modification date could not be read, check failed.");
1331                  returnValue = false;
1332                  break;
1333              }
1334              // Make sure file date is in Date() format.
1335              fileDate = new Date(fileDate);
1336              
1337              // Parse comparison date.
1338              var firstChar = checkValueExpanded.substring(0,1);
1339              var comparisonDate = null;
1340              var comparisonType = "string";
1341              if (firstChar == "+" || firstChar == "-") {
1342                  // Relative date. Create time offset in minutes.
1343                  dinfo("Reading relative comparison date: " + checkValueExpanded + " minutes.");
1344                  var timeOffset = parseInt(checkValueExpanded) * 1000 * 60;
1345                  var now = new Date();
1346                  comparisonDate = new Date(now.getTime() + timeOffset);
1347              } else if (firstChar == "@" ) {
1348                  // Remember type of comparison.
1349                  comparisonType = "file";
1350                  // Evaluate date of reference file.
1351                  var filePath = checkValueExpanded.substring(1);
1352                  if (dateType == "modify") {
1353                      dinfo("Reading file modification date of reference file '" + filePath + "'.");
1354                      // Evaluate file modification date.
1355                      comparisonDate = getFileDateModification(filePath);
1356                  } else if (dateType == "create") {
1357                      dinfo("Reading file creation date of reference file '" + filePath + "'.");
1358                      // Evaluate file creation date.
1359                      comparisonDate = getFileDateCreation(filePath);
1360                  } else if (dateType == "access") {
1361                      dinfo("Reading file access date of reference file '" + filePath + "'.");
1362                      // Evaluate file access date.
1363                      comparisonDate = getFileDateLastAccess(filePath);
1364                  }
1365                  // If comparison date could not be read then comparison failed.
1366                  if (comparisonDate == null) {
1367                      dinfo("File comparison date could not be read, check failed.");
1368                      returnValue = false;
1369                      break;
1370                  }
1371                  // Make sure comparison date is in Date() format.
1372                  comparisonDate = new Date(comparisonDate);
1373  
1374              } else {
1375                  dinfo("Reading comparison date: " + checkValueExpanded + ".");
1376                  switch (checkValueExpanded) {
1377                  case "yesterday":
1378                      // Relative date. Create time offset of one day.
1379                      var timeOffset = -1000 * 60 * 60 * 24;
1380                      var now = new Date();
1381                      comparisonDate = new Date(now.getTime() + timeOffset);
1382                      break;
1383  
1384                  case "last-week":
1385                      // Relative date. Create time offset of one week ago.
1386                      var timeOffset = -1000 * 60 * 60 * 24 * 7;
1387                      var now = new Date();
1388                      comparisonDate = new Date(now.getTime() + timeOffset);
1389                      break;
1390  
1391                  case "last-month":
1392                      // Relative date. Create time offset of one month ago.
1393                      var timeOffset = -1000 * 60 * 60 * 24 * 30;
1394                      var now = new Date();
1395                      comparisonDate = new Date(now.getTime() + timeOffset);
1396                      break;
1397  
1398                  case "last-year":
1399                      // Relative date. Create time offset of one year ago.
1400                      var timeOffset = -1000 * 60 * 60 * 24 * 365;
1401                      var now = new Date();
1402                      comparisonDate = new Date(now.getTime() + timeOffset);
1403                      break;
1404  
1405                  default:
1406                      // Date is supposed to be in ISO format.
1407                      comparisonDate = parseISODate(checkValueExpanded, false);
1408                      break;
1409                  }
1410              }
1411              // Check whether comparison date has been evaluated properly.
1412              if (comparisonDate == null) {
1413                  throw new Error ("Unable to evaluate date from value '" + checkValueExpanded + "'.");
1414              }
1415  
1416              var success = false;
1417  
1418              // Get file date of file specified in path.
1419              var comparison = checkCond.substring(10);
1420  
1421              var comparisonCond = "";
1422              switch (comparison) {
1423              case "olderthan":
1424                  comparisonCond = "older than";
1425                  if (fileDate.getTime() < comparisonDate.getTime()) {
1426                      success =  true;
1427                  } else {
1428                      success = false;
1429                  }
1430                  break;
1431  
1432              case "equalto":
1433                  var fileDateCompare = new Date(fileDate);
1434                  // Reduce accuracy to milliseconds for equal comparison when comparing to user string.
1435                  if (comparisonType != "file") {
1436                      fileDateCompare.setMilliseconds(0);
1437                      comparisonDate.setMilliseconds(0);
1438                  }
1439                  comparisonCond = "equal to";
1440                  if (fileDateCompare.getTime() == comparisonDate.getTime()) {
1441                      success =  true;
1442                  } else {
1443                      success = false;
1444                  }
1445                  break;
1446  
1447              case "newerthan":
1448                  comparisonCond = "newer than";
1449                  if (fileDate.getTime() > comparisonDate.getTime()) {
1450                      success =  true;
1451                  } else {
1452                      success = false;
1453                  }
1454                  break;
1455                  
1456              default:
1457                  throw new Error ("Invalid file date comparison parameter: '" + checkCond + "'.");
1458                  break;
1459              }
1460  
1461              if (success) {
1462                  dinfo(comparisonDesc + " date of file '" + checkPath + "' is " + fileDate +
1463                          " which is " + comparisonCond + " the comparison date " +
1464                          comparisonDate + " check succeeded.");
1465              } else {
1466                  dinfo(comparisonDesc + " date of file '" + checkPath + "' is " + fileDate +
1467                          " which isn't " + comparisonCond + " the comparison date " +
1468                          comparisonDate + " check failed.");
1469              }
1470              returnValue = success;
1471          } else {
1472              throw new Error("Check condition " + checkCond + " unknown for " +
1473                              "type file.");
1474          }
1475  
1476          // The result of Registry checks shall be stored in local settings node.
1477          addSettingsCheckResult(checkNode, returnValue);
1478          
1479          break;
1480  
1481      // check type: uninstall
1482      case "uninstall":
1483          // Sanity check: must have Cond and Path set for all uninstall checks.
1484          if ((checkCond == null) ||
1485              (checkPath == null)) {
1486              throw new Error("Condition and / or path is null for an uninstall check. Perhaps " +
1487                              "a typo? To help find it, here are the other pieces of information: " +
1488                              "condition='" + checkCond +
1489                              "', path='" + checkPath + "'.");
1490          }
1491          var uninstallLocations = scanUninstallKeys(checkPath);
1492          // If expanded path is different to path read these keys too.
1493          if (checkPath != checkPathExpanded) {
1494              var uninstallLocationsExpanded = scanUninstallKeys(checkPathExpanded);
1495              for (var i=0; i < uninstallLocationsExpanded.length; i++) {
1496                  uninstallLocations.push(uninstallLocationsExpanded[i]);
1497              }
1498          }
1499  
1500          if (checkCond == "exists") {
1501              if (uninstallLocations.length > 0) {
1502                  dinfo("Uninstall entry for " + checkPath + " was found: test successful.");
1503                  returnValue = true;
1504              } else {
1505                  dinfo("Uninstall entry for " + checkPath + " missing: test failed.");
1506                  returnValue = false;
1507              }
1508          } else if (checkCond.substring(0,7) == "version") {
1509              // check versions of all installed instances
1510              // for version checks we need a value
1511              if (checkValue == null) {
1512                  throw new Error ("Uninstall entry version check has been specified but no" +
1513                          "'value' is defined. Please add a 'value=<version>' attribute.");
1514              }
1515  
1516              if (uninstallLocations.length <= 0) {
1517                  dinfo("No uninstall entry for '" + checkPath + "' found. " +
1518                          "Version comparison check failed.");
1519                  returnValue = false;
1520              } else {
1521      
1522                  var uninstallCheckResult = true;
1523                  for (var iUninstKey=0; iUninstKey < uninstallLocations.length; iUninstKey++) {
1524                      var uninstallValue = getRegistryValue(uninstallLocations[iUninstKey] + "\\DisplayVersion");
1525      
1526                      dinfo("Found version of '" + checkPath + "' at " + uninstallLocations[iUninstKey] +
1527                          ": " + uninstallValue + "\n" + "Comparing to expected version: " + checkValue + ".");
1528      
1529                      // check if valid version value was returned
1530                      if (uninstallValue == null || uninstallValue == "") {
1531                          error("Check condition '" + checkCond + "' cannot be executed" +
1532                              " since no version information is available for '" + checkPath + "'" +
1533                              " at " + uninstallLocations[iUninstKey] + ".");
1534                          uninstallCheckResult = false;
1535                          break;
1536                      } else {
1537      
1538                          var uninstallVersionCompare = versionCompare(uninstallValue, checkValueExpanded);
1539                          dinfo ("Comparing uninstall version '" + uninstallValue + "' to expected version '" +
1540                                  checkValueExpanded + "' using condition '" + checkCond  + "' returned " + uninstallVersionCompare + ".");
1541          
1542                          var uninstallVersionCompResult = false;
1543                          switch (checkCond) {
1544                              case "versionsmallerthan":
1545                                  if (uninstallVersionCompare < 0) {
1546                                      uninstallVersionCompResult = true;
1547                                  }
1548                                  break;
1549                              case "versionlessorequal":
1550                                  if (uninstallVersionCompare <= 0) {
1551                                      uninstallVersionCompResult = true;
1552                                  }
1553                                  break;
1554                               case "versionequalto":
1555                                  if (uninstallVersionCompare == 0) {
1556                                      uninstallVersionCompResult = true;
1557                                  }
1558                                  break;
1559                              case "versiongreaterorequal":
1560                                  if (uninstallVersionCompare >= 0) {
1561                                      uninstallVersionCompResult = true;
1562                                  }
1563                                  break;
1564                               case "versiongreaterthan":
1565                                  if (uninstallVersionCompare > 0) {
1566                                      uninstallVersionCompResult = true;
1567                                  }
1568                                  break;
1569                              default:
1570                                  error("Unknown operation on uninstall version check: " + checkCond + ".");
1571                                  uninstallVersionCompResult = false;
1572                                  break;
1573                          }
1574          
1575                          dinfo("Uninstall version check for package '" + checkPath + "' returned " +
1576                              uninstallVersionCompResult + " for operation type " + checkCond + ".");
1577          
1578                          // in case the current entry does not match the condition,
1579                          // immediately return
1580                          // else the next uninstall entry might be checked
1581                          if (uninstallVersionCompResult == false) {
1582                              uninstallCheckResult = false;
1583                              break;
1584                          }
1585                      }
1586                  }
1587                  // If all checks succeeded, set return value to true.
1588                  if (uninstallCheckResult) {
1589                      returnValue = true;
1590                  }
1591              }
1592          } else {
1593              throw new Error("Check condition " + checkCond + " unknown for " +
1594                              "type uninstall.");
1595          }
1596  
1597          // The result of Registry checks shall be stored in local settings node.
1598          addSettingsCheckResult(checkNode, returnValue);
1599          
1600          break;
1601  
1602      // check type: execution
1603      case "execute":
1604          // check if path to script is given
1605          if (checkPath == null) {
1606              throw new Error("No path is specified for execute check!");
1607          }
1608          if (checkCond == null) {
1609              dinfo("No execute condition specified, assuming 'exitcodeequalto'.");
1610              checkCond = "exitcodeequalto";
1611          }
1612          if (checkValueExpanded == null || checkValueExpanded == "") {
1613              dinfo("No execute value specified, assuming '0'.");
1614              checkValueExpanded = 0;
1615          } else {
1616              checkValueExpanded = parseInt(checkValueExpanded);
1617              if(isNaN(checkValueExpanded) == true) {
1618                  checkValueExpanded = 0;
1619              }
1620          }
1621  
1622          // use expanded path only
1623          checkPath = checkPathExpanded;
1624          // execute and catch return code
1625          var exitCode = exec(checkPath, 3600, null);
1626  
1627          var executeResult = false;
1628          switch (checkCond) {
1629              case "exitcodesmallerthan":
1630                  if (exitCode < checkValueExpanded) {
1631                      executeResult = true;
1632                  }
1633                  break;
1634              case "exitcodelessorequal":
1635                  if (exitCode <= checkValueExpanded) {
1636                      executeResult = true;
1637                  }
1638                  break;
1639               case "exitcodeequalto":
1640                  if (exitCode == checkValueExpanded) {
1641                      executeResult = true;
1642                  }
1643                  break;
1644              case "exitcodegreaterorequal":
1645                  if (exitCode >= checkValueExpanded) {
1646                      executeResult = true;
1647                  }
1648                  break;
1649               case "exitcodegreaterthan":
1650                  if (exitCode > checkValueExpanded) {
1651                      executeResult = true;
1652                  }
1653                  break;
1654              default:
1655                  dinfo("Invalid execute condition specified '" + checkCond
1656                      + "', check failed.");
1657                  executeResult = false;
1658                  break;
1659          }
1660  
1661          dinfo("Execute check for program '" + checkPath + "' returned '" +
1662                  exitCode + "'. Evaluating condition '" + checkCond +
1663                  "' revealed " + executeResult + " when comparing to expected" +
1664                  " value of '" + checkValueExpanded + "'.");
1665          returnValue = executeResult;
1666          break;
1667  
1668      // check type: logical
1669      case "logical":
1670  
1671          // check if logical condition is set
1672          if (checkCond == null) {
1673              throw new Error("Condition is null for a logical check.");
1674          }
1675  
1676          var subcheckNodes = getChecks(checkNode);
1677  
1678          switch (checkCond) {
1679          case "not":
1680              var checkResult = false;
1681              for (var iNotNodes=0; iNotNodes < subcheckNodes.length; iNotNodes++) {
1682                  // check if one of the subchecks return false
1683                  if (!checkCondition(subcheckNodes[iNotNodes])) {
1684                      checkResult = true;
1685                      break;
1686                  }
1687              }
1688              if (checkResult) {
1689                  dinfo("Result of logical 'NOT' check is true.");
1690              } else {
1691                  dinfo("Result of logical 'NOT' check is false.");
1692              }
1693              returnValue = checkResult;
1694              break;
1695  
1696          case "and":
1697              var checkResult = true;
1698              for (var iAndNodes = 0; iAndNodes < subcheckNodes.length; iAndNodes++) {
1699                  // check if one of the subchecks return false
1700                  if (!checkCondition(subcheckNodes[iAndNodes])) {
1701                      checkResult = false;
1702                      break;
1703                  }
1704              }
1705              if (checkResult) {
1706                  dinfo("Result of logical 'AND' check is true.");
1707              } else {
1708                  dinfo("Result of logical 'AND' check is false.");
1709              }
1710              returnValue = checkResult;
1711              break;
1712  
1713          case "or":
1714              // check if one of the sub-checks returns true
1715              var checkResult = false;
1716              for (var iOrNodes = 0; iOrNodes < subcheckNodes.length; iOrNodes++) {
1717                  if (checkCondition(subcheckNodes[iOrNodes])) {
1718                      checkResult = true;
1719                      break;
1720                  }
1721              }
1722              if (checkResult) {
1723                  dinfo("Result of logical 'OR' check is true.");
1724              } else {
1725                  dinfo("Result of logical 'OR' check is false");
1726              }
1727              returnValue = checkResult;
1728              break;
1729  
1730          case "atleast":
1731              if (checkValue == null) {
1732                  throw new Error("Check condition logical 'atleast' requires a value.");
1733              }
1734  
1735              // count number of checks which return true
1736              var numAtLeastNodes=0;
1737              var checkResult = false;
1738              for (var iAtLeastNodes = 0; iAtLeastNodes < subcheckNodes.length; iAtLeastNodes++) {
1739                  if (checkCondition(subcheckNodes[iAtLeastNodes])) {
1740                      numAtLeastNodes++;
1741                  }
1742                  // check if at least x checks revealed true
1743                  if (numAtLeastNodes >= checkValue) {
1744                      checkResult = true;
1745                      break;
1746                  }
1747              }
1748              if (checkResult) {
1749                  dinfo("Result of logical 'AT LEAST' check is true.");
1750              } else {
1751                  dinfo("Result of logical 'AT LEAST' check is false.");
1752              }
1753              returnValue = checkResult;
1754              break;
1755  
1756          case "atmost":
1757              // check if maximum x checks return true
1758              var checkResult = true;
1759              var numAtMostNodes = 0;
1760              for (var iAtMostNodes = 0; iAtMostNodes < subcheckNodes.length; iAtMostNodes++) {
1761                  if (checkCondition(subcheckNodes[iAtMostNodes])) {
1762                      numAtMostNodes++;
1763                  }
1764                  if (numAtMostNodes > checkValue) {
1765                      checkResult = false;
1766                      break;
1767                  }
1768              }
1769              if (checkResult) {
1770                  dinfo("Result of logical 'AT MOST' check is true.");
1771              } else {
1772                  dinfo("Result of logical 'AT MOST' check is false.");
1773              }
1774              returnValue = checkResult;
1775              break;
1776  
1777          default:
1778              throw new Error("Check condition " + checkCond + " unknown for " +
1779              "type logical.");
1780              break;
1781          }
1782          
1783          // Logical checks shall not be added to local settings node.
1784          break;
1785  
1786      // Check type: host
1787      case "host":
1788          // check if logical condition is set
1789          if (checkCond == null) {
1790              throw new Error("Condition is null for a host check.");
1791          }
1792          if (checkValueExpanded == null) {
1793              throw new Error("Value is null for a host check.");
1794          }
1795  
1796          // Verify if the host check matches current host.
1797          returnValue = checkHostAttribute(checkCond, checkValueExpanded);
1798  
1799          // The result of Registry checks shall be stored in local settings node.
1800          addSettingsCheckResult(checkNode, returnValue);
1801  
1802          break;
1803  
1804      // no such check type
1805      default:
1806          throw new Error("Check condition type " + checkType + " unknown.");
1807          break;
1808      }
1809  
1810      return returnValue;
1811  }
1812  
1813  /**
1814   * Checks whether the specified host attribute matches the expression passed as
1815   * argument.
1816   * 
1817   * @param attributeName
1818   *              Name of host attribute to match. See getHostInformation()
1819   *              function for valid host attributes.
1820   * @param expression
1821   *              Regular expression (or list for certain attributes) to use for
1822   *              matching.
1823   * @returns {Boolean} True if attribute matches the expression.
1824   */
1825  function checkHostAttribute(attributeName, expression) {
1826      // Terminate if attribute name is not specified.
1827      if (attributeName == null) {
1828          error("Host attribute matching failed. No attribute name specified.");
1829          return false;
1830      }
1831      var hostAttribute = attributeName;
1832  
1833      // Terminate if expression is not specified.
1834      if (expression == null) {
1835          error("Host attribute matching for attribute '" + hostAttribute + "' failed. No expression specified.");
1836          return false;
1837      }
1838      // Expand environment variables in expressions.
1839      var checkExpression = new ActiveXObject("WScript.Shell").ExpandEnvironmentStrings(expression);
1840  
1841      // Initialize return value.
1842      var returnValue = false;
1843  
1844      // Fetch current host attributes.
1845      var globalHostInformation = getHostInformation();
1846  
1847      // Add "environment" key since we want to support environment matching too.
1848      var hostInformation = new ActiveXObject("Scripting.Dictionary");
1849      var keys = globalHostInformation.keys().toArray();
1850      for (var i=0; i<keys.length; i++) {
1851          hostInformation.Add(keys[i], globalHostInformation.Item(keys[i]));
1852      }
1853      hostInformation.Add("environment", "");
1854  
1855      // First verify if the requested host information attribute exists.
1856      var hostInfoValue = hostInformation.Item(hostAttribute);
1857      if (hostInfoValue == null || (typeof(hostInfoValue) == "object" && hostInfoValue.length <= 0) ) {
1858          dinfo("Host match requires attribute '" + hostAttribute + "' "
1859                  + "which is not defined for current host. No match found."); 
1860          return false;
1861      }
1862      
1863      var attrMatchExpression = new RegExp(checkExpression, "i");
1864      // First try to match array objects.
1865      if (typeof(hostInfoValue) == "object" && hostInfoValue.length > 0) {
1866          for (var iHostInfo=0; iHostInfo < hostInfoValue.length; iHostInfo++) {
1867              // Get value from attribute array
1868              var hostInfoElement = hostInfoValue[iHostInfo];
1869              dinfo("Comparing multi-valued attribute '" + hostAttribute + "' with value '" +
1870                      hostInfoElement + "' using expression '" + checkExpression + "'.");
1871  
1872              // Compare attribute array element with expected
1873              // value.
1874              if (attrMatchExpression.test(hostInfoElement) == true) {
1875                  dinfo("Match for attribute '" + hostAttribute + "' with value '" + hostInfoElement + "' found.");
1876                  returnValue = true;
1877                  break;
1878              }
1879          }
1880      // } else if (typeof(host[hostNodeAttrName]) != "object") {
1881      } else {
1882          // Match simple attributes.
1883          switch (hostAttribute) {
1884              case "environment":
1885                  // Match environment condition to actual environment variable.
1886  
1887                  // Get condition value from from parameter, could be multiple, separated by '|'.
1888                  var environmentConditions = checkExpression.split('|');
1889                  returnValue = true;
1890                  for (var iEnv=0; iEnv < environmentConditions.length; iEnv++) {
1891                      var environmentCondition = environmentConditions[iEnv];
1892                      // Split environment conditions into key and value pairs.
1893                      var envConditionSplit = environmentCondition.split("=");
1894                      // Need at least the key and value. If there are less components, then skip it.
1895                      if (envConditionSplit.length >= 2) {
1896                          // The first value is the key.
1897                          var envKey = envConditionSplit[0];
1898                          if (envKey == "") {
1899                              dinfo("Invalid empty environment variable name.");
1900                              returnValue = false;
1901                              break;
1902                          }
1903  
1904                          // Fetch environment value.
1905                          var expandString = "%" + envKey + "%";
1906                          var envValueRead = new ActiveXObject("WScript.Shell").ExpandEnvironmentStrings(expandString);
1907                          
1908                          if (envValueRead == expandString) {
1909                              // Environment variable is not defined, match failed.
1910                              dinfo("Required environment not matched. Environment variable '" + envKey + "' not defined.");
1911                              returnValue = false;
1912                              break;
1913                          }
1914  
1915                          // All following values are belonging to the value.
1916                          /*
1917                          var valueParts = new Array();
1918                          for (var iValues=1; iValues < envConditionSplit.length; iValues++) {
1919                              valueParts.push(envConditionSplit[iValues]);
1920                          }
1921                          // Join values to re-assemble the value specified.
1922                          var envValue = valueParts.join("");
1923                          */
1924  
1925                          // Re-assemble value.
1926                          var valueStartOffset = envKey.length + 1;
1927                          var envValue = environmentCondition.substr(valueStartOffset);
1928  
1929                          // Check environment using regular expression match.
1930                          var envMatchExpression = new RegExp(envValue, "i");
1931                          if (envMatchExpression.test(envValueRead) == true) {
1932                              dinfo("Required environment matched. Environment variable '" + envKey +
1933                                      "' with value '" + envValueRead + "' matches '" + envValue + "'.");
1934                              // Check next value. All of them need to be true.
1935                              continue;
1936                          } else {
1937                              dinfo("Required environment dit not match. Environment variable '" + envKey +
1938                                      "' with value '" + envValueRead + "' does not match '" + envValue + "'.");
1939                              returnValue = false;
1940                              break;
1941                          }
1942                      } else {
1943                          error("Invalid environment match expression '" + environmentCondition + "'. Match skipped.");
1944                      }
1945                  }
1946                  break;
1947  
1948              case "lcid":
1949                  // Check whether any LCID matches the current host executing user LCID.
1950                  var attributeLCIDs = checkExpression.split(",");
1951                  for (var iLCID=0; iLCID < attributeLCIDs.length; iLCID++) {
1952                      // check if it corresponds to the system LCID
1953                      var currentLcid = trimLeadingZeroes(trim(attributeLCIDs[iLCID]));
1954                      if (currentLcid == hostInfoValue) {
1955                          dinfo("Required LCID match found. LCID '" + currentLcid + "' matches current user LCID.");
1956                          returnValue = true;
1957                          break;
1958                      }
1959                  }
1960                  if (!returnValue) {
1961                      dinfo("None of the required LCID values (" + checkExpression +
1962                              ") matched the current host LCID of '" + hostInfoValue + "'.");
1963                  }
1964                  break;
1965  
1966              case "lcidOS":
1967                  // Check whether any LCID matches the current host OS LCID.
1968                  var attributeLCIDs = checkExpression.split(",");
1969                  for (var iLCIDOS=0; iLCIDOS < attributeLCIDs.length; iLCIDOS++) {
1970                      // check if it corresponds to the system LCID
1971                      var currentLcid = trimLeadingZeroes(trim(attributeLCIDs[iLCIDOS]));
1972                      if (currentLcid == hostInfoValue) {
1973                          dinfo("Required OS LCID match found. LCID '" + currentLcid + "' matches current host LCID.");
1974                          returnValue = true;
1975                          break;
1976                      }
1977                  }
1978                  if (!returnValue) {
1979                      // Check if any LCID matched the current host.
1980                      dinfo("None of the required LCID values (" + checkExpression +
1981                              ") matched the current host LCID of '" + hostInfoValue + "'.");
1982                  }
1983                  break;
1984                  
1985              default:
1986                  // perform simple regular expression match of
1987                  // attribute
1988                  if (attrMatchExpression.test(hostInfoValue) == true) {
1989                      dinfo("Host attribute '" + hostAttribute + "' with value '" +
1990                              hostInfoValue + "' matches expression '" + checkExpression + "'.");
1991                      returnValue = true;
1992                  } else {
1993                      dinfo("Host attribute '" + hostAttribute + "' with value '" +
1994                              hostInfoValue + "' does not match expression '" + checkExpression + "'.");
1995                      returnValue = false;
1996                  }
1997                  break;
1998          }
1999      }
2000      return returnValue;
2001  }
2002  
2003  
2004  /**
2005   * Creates a new hosts XML root-node and returns it
2006   * 
2007   * @return new hosts node
2008   */
2009  function createHosts() {
2010      var newHosts = createXml("wpkg:wpkg", namespaceHosts);
2011      return newHosts;
2012  }
2013  
2014  /**
2015   * Creates a new packages XML root-node and returns it
2016   * 
2017   * @return new profiles node
2018   */
2019  function createPackages() {
2020      var newPackages = createXml("packages:package", namespacePackages);
2021      return newPackages;
2022  }
2023  
2024  /**
2025   * Creates a new profiles XML root-node and returns it
2026   * 
2027   * @return new profiles node
2028   */
2029  function createProfiles() {
2030      var newProfiles = createXml("profiles:profiles", namespaceProfiles);
2031      return newProfiles;
2032  }
2033  
2034  /**
2035   * Creates a new settings XML root-node and returns it
2036   * 
2037   * @return new settings node
2038   */
2039  function createSettings() {
2040      var newSettings = createXml("wpkg:wpkg", namespaceSettings);
2041      if (settingsHostInfo) {
2042          // Add host attributes.
2043          // NOTE: These attributes are currently not used by WPKG but might be
2044          // useful if wpkg.xml is copied to an external system so wpkg.xml
2045          // will include some host information.
2046          var hostInformation = getHostInformation();
2047          var attributes = hostInformation.keys().toArray();
2048          for (var i=0; i<attributes.length; i++) {
2049              var value = hostInformation.Item(attributes[i]);
2050              newSettings.setAttribute(attributes[i], value);
2051          }
2052      }
2053      return newSettings;
2054  }
2055  
2056  /**
2057   * Create a new settings XML root-node by reading a file and returns it
2058   * 
2059   * @param fileName String pointing to the settings file to be created
2060   *                 (full path).
2061   * @return settings root node as stored within the file
2062   */
2063  function createSettingsFromFile(fileName) {
2064      var newSettings = loadXml(fileName, null, "settings");
2065      return newSettings;
2066  }
2067  
2068  /**
2069   * Downloads a file as specified within a download node.
2070   * 
2071   * @param downloadNode
2072   *            XML 'download' node to be used
2073   * @return true in case of successful download, false in case of error
2074   */
2075  function download(downloadNode) {
2076      // get attributes
2077      var url = getDownloadUrl(downloadNode);
2078      var target = getDownloadTarget(downloadNode);
2079      var timeout = getDownloadTimeout(downloadNode);
2080      var expandURL = getDownloadExandURL(downloadNode);
2081  
2082      // initiate download
2083      return downloadFile(url, target, timeout, expandURL);
2084  }
2085  
2086  /**
2087   * Downloads all files from the given array of download XML nodes
2088   * 
2089   * @param downloadNodes
2090   *            Array of download XML nodes to be downloaded
2091   * @return true in case of successful download, false in case of error
2092   */
2093  function downloadAll(downloadNodes) {
2094      var returnValue = true;
2095      if (downloadNodes != null) {
2096          for (var i=0; i<downloadNodes.length; i++) {
2097              var result = download(downloadNodes[i]);
2098              // stop downloading if
2099              if (result != true) {
2100                  returnValue = false;
2101              }
2102          }
2103      }
2104      return returnValue;
2105  }
2106  
2107  /**
2108   * Removes eventually existing temporary downloads of the specified XML node
2109   * 
2110   * @param downloadNode
2111   *            XML node which contains the download definition to clean
2112   */
2113  function downloadClean(downloadNode) {
2114      // get download attributes
2115      var target = getDownloadTarget(downloadNode);
2116  
2117      // evaluate target directory
2118      if (target == null || target == "") {
2119              error("Invalid download target specified: " + target);
2120          target = downloadDir;
2121      } else {
2122          target = downloadDir + "\\" + target;
2123      }
2124      target = new ActiveXObject("WScript.Shell").ExpandEnvironmentStrings(target);
2125      var fso = new ActiveXObject("Scripting.FileSystemObject");
2126      // delete temporary file if it already exists
2127      if (fso.FileExists(target)) {
2128          fso.DeleteFile(target);
2129      }
2130  }
2131  
2132  
2133  /**
2134   * Cleans all temporary files belonging to the download XML nodes within the
2135   * passed array of download XML nodes
2136   * 
2137   * @param downloadNodes
2138   *            Array of download XML nodes
2139   */
2140  function downloadsClean(downloadNodes) {
2141      if (downloadNodes != null) {
2142          for (var i=0; i<downloadNodes.length; i++) {
2143              downloadClean(downloadNodes[i]);
2144          }
2145      }
2146  }
2147  
2148  
2149  /**
2150   * Builds settings document tree containing actually installed packages. Tests
2151   * all packages from given doc tree for "check" conditions. If given conditions
2152   * are positive, package is considered as installed.
2153   */
2154  function fillSettingsWithInstalled() {
2155  
2156      var packagesNodes = getPackageNodes();
2157  
2158      // check each available package
2159      var foundPackage = false;
2160      for (var i = 0; i < packagesNodes.length; i++) {
2161          var packNode = packagesNodes[i];
2162  
2163          // add package node to settings if it is installed
2164          if (isInstalled(packNode)) {
2165              addSettingsNode(packNode, true);
2166              foundPackage = true;
2167          }
2168      }
2169      if (foundPackage) {
2170          saveSettings(true);
2171      }
2172  }
2173  
2174  /**
2175   * Returns the command line argument for this command node. A command node can
2176   * be an <install/>, <upgrade/> or <remove/> node.
2177   * 
2178   * @param cmdNode
2179   *            cmd XML node to read from
2180   * @return command defined within the given cmd XML node, returns null
2181   *         if no command is defined.
2182   */
2183  function getCommandCmd(cmdNode) {
2184      return cmdNode.getAttribute("cmd");
2185  }
2186  
2187  /**
2188   * Returns the value of an exit code node within the given command node. A
2189   * command node can be an <install/>, <upgrade/> or <remove/> node. In case no
2190   * such exit code was defined null will be returned. In case the code is defined
2191   * the string "success" is returned. In case the exit code specifies an
2192   * immediate reboot then the string "reboot" is returned.
2193   * 
2194   * @return returns string "reboot" in case a reboot is required.<br>
2195   *         returns string "delayedReboot" in case a reboot should be scheduled
2196   *         as soon as possible<br>
2197   *         returns string "postponedReboot" in case a reboot after installing
2198   *         all packages is required<br>
2199   *         returns string "success" in case exit code specifies successful
2200   *         installation.<br>
2201   *         returns null in case the exit code is not defined.
2202   */
2203  function getCommandExitCodeAction(cmdNode, exitCode) {
2204      var returnValue = null;
2205      var exitNode = cmdNode.selectSingleNode("exit[@code='" + exitCode + "']");
2206      if (exitNode == null) {
2207          exitNode = cmdNode.selectSingleNode("exit[@code='any']");
2208      }
2209      if (exitNode == null) {
2210          exitNode = cmdNode.selectSingleNode("exit[@code='*']");
2211      }
2212      if (exitNode != null) {
2213          if (exitNode.getAttribute("reboot") == "true") {
2214              // This exit code forces a reboot.
2215              info("Command '" + getCommandCmd(cmdNode) + "' returned " +
2216                  " exit code [" + exitCode + "]. This exit code " +
2217                  "requires an immediate reboot.");
2218              returnValue = "reboot";
2219          } else if (exitNode.getAttribute("reboot") == "delayed")  {
2220              info("Command '" + getCommandCmd(cmdNode) + "' returned " +
2221                  " exit code [" + exitCode + "]. This exit code " +
2222                  "schedules a reboot after execution of all commands.");
2223              returnValue = "delayedReboot";
2224          } else if (exitNode.getAttribute("reboot") == "postponed")  {
2225              info("Command '" + getCommandCmd(cmdNode) + "' returned " +
2226                  " exit code [" + exitCode + "]. This exit code " +
2227                  "schedules a reboot after execution of all packages.");
2228              returnValue = "postponedReboot";
2229          } else {
2230              // This exit code is successful.
2231              info("Command '" + getCommandCmd(cmdNode) + "' returned " +
2232                  " exit code [" + exitCode + "]. This exit code " +
2233                  "indicates success.");
2234              returnValue = "success";
2235          }
2236      }
2237      return returnValue;
2238  }
2239  
2240  
2241  /**
2242   * Return value of include attribute of the given cmd node.
2243   * Returns null if include attribute is not set.
2244   * 
2245   * @param cmdNode
2246   *         The command node to read the include attribute from.
2247   * 
2248   * @returns Value of include attribute, returns null if attribute is undefined.
2249   */
2250  function getCommandInclude(cmdNode) {
2251      return cmdNode.getAttribute("include");
2252  }
2253  
2254  
2255  /**
2256   * Returns the timeout value for this command node. A command node can be an
2257   * <install/>, <upgrade/> or <remove/> node.
2258   * 
2259   * @param cmdNode
2260   *            cmd XML node to read from.
2261   * @return the timeout for the given cmd XML node - returns 0 if no timeout is
2262   *         defined
2263   */
2264  function getCommandTimeout(cmdNode) {
2265      var timeout = cmdNode.getAttribute("timeout");
2266      if (timeout == null) {
2267          timeout = 0;
2268      }
2269      return parseInt(timeout);
2270  }
2271  
2272  /**
2273   * Returns the value of the workdir attribute of the given cmd XML node.
2274   * 
2275   * @param cmdNode
2276   *            cmd XML node to read from
2277   * @return the workdir attribute value. Returns null in case value is not
2278   *         defined.
2279   */
2280  function getCommandWorkdir(cmdNode) {
2281      var workdir = cmdNode.getAttribute("workdir");
2282      return workdir;
2283  }
2284  
2285  /**
2286   * Returns condition node of a given XML node. Returns null if there is no
2287   * condition node specified.
2288   * 
2289   * @param xmlNode XML node which is supposed to have a <condition /> sub-node.
2290   * @returns Array of condition XML-nodes, might be null if no condition is specified
2291   */
2292  function getConditions(xmlNode) {
2293      // Read condition nodes (might be 0, 1 or any number)
2294      var conditionNodes = xmlNode.selectNodes("condition");
2295  
2296      /*
2297      var conditionNodes = xmlNode.selectNodes("wpkg:condition");
2298      if (conditionNodes.length <= 0) {
2299          // Maybe namespace has not been specified correctly.
2300          // Try reading from default namespace.
2301          conditionNodes = xmlNode.selectNodes("condition");
2302      }
2303      */
2304  
2305      // Per specification only one single condition node shall be specified
2306      /*
2307      if (conditionNodes != null && conditionNodes.length > 1) {
2308          error("More than one condition node specified. Ignoring all but the first condition.");
2309      }
2310      */
2311  
2312      // Return condition node.
2313      return conditionNodes;
2314  }
2315  
2316  /**
2317   * Returns XML node which contains the configuration
2318   */
2319  function getConfig() {
2320      if (config == null) {
2321          // load config
2322  
2323          // get argument list
2324          var argv = getArgv();
2325          // Get special purpose argument lists.
2326          var argn = argv.Named;
2327  
2328          // if set to true it will throw an error to quit in case of
2329          // file-not-found
2330          var exitIfNotFound = false;
2331  
2332          // stores config file path
2333          var config_file = null;
2334  
2335          // will be used for file operations
2336          var fso = new ActiveXObject("Scripting.FileSystemObject");
2337  
2338          if (argn("config") != null) {
2339              var configPath = argn("config");
2340              var wshObject = new ActiveXObject("WScript.Shell");
2341              var expConfigPath = wshObject.ExpandEnvironmentStrings(configPath);
2342              config_file = fso.GetAbsolutePathName(expConfigPath);
2343              // config was explicitly specified - I think we should quit if it
2344              // is not available
2345              exitIfNotFound = true;
2346          } else {
2347              // if config_file_name (config.xml) exists, use it
2348              var fullScriptPATH = WScript.ScriptFullName;
2349              var base = fso.GetParentFolderName(fullScriptPATH);
2350              config_file = fso.BuildPath(base, config_file_name);
2351              // config is optional in this case
2352              exitIfNotFound = false;
2353          }
2354  
2355          if (fso.FileExists(config_file)) {
2356              try {
2357                  // Read in config.xml.
2358                  config = loadXml(config_file, null, "config");
2359                  if (config == null) {
2360                      throw new Error("Unable to parse config file!");
2361                  }
2362              } catch (e) {
2363                  // There was an error processing the config.xml file. Alert the
2364                  // user
2365                  error("Error reading "+ config_file + ": " + e.description);
2366                  exit(99); // Exit code 99 means config.xml read error.
2367              }
2368          } else {
2369              var message = config_file + " could not be found.";
2370              if (exitIfNotFound) {
2371                  error(message);
2372                  exit(99); // Exit code 99 means config.xml read error.
2373              } else {
2374                  dinfo(message);
2375              }
2376          }
2377          // create empty config if no config could be read
2378          if (config == null) {
2379              config = createXml("config");
2380          }
2381      }
2382      return config;
2383  }
2384  
2385  /**
2386   * Returns array of <param> nodes from the configuration. Returns array of size
2387   * 0 in case no parameter is defined.
2388   * 
2389   * @return <param> nodes
2390   */
2391  function getConfigParamArray() {
2392      return getConfig().selectNodes("param");
2393  }
2394  
2395  /**
2396   * Returns download XML node array on a given XML node
2397   * 
2398   * @param xmlNode
2399   *            the xml node to read child-nodes of type download from
2400   * @param downloadsArray
2401   *            array of downloads to be extended with the ones from the given XML
2402   *            node, specify null to return a new array.
2403   * @return XML node array on a given package XML node containing all package
2404   *         downloads. returns empty array if no downloads are defined
2405   */
2406  function getDownloads(xmlNode, downloadsArray) {
2407      var downloadsArrayRef = downloadsArray;
2408      if (downloadsArrayRef == null) {
2409          downloadsArrayRef = new Array();
2410      }
2411      // Only fetch download nodes if downloads are not disabled.
2412      // Just hide download nodes in case downloads are disabled.
2413      if (!isNoDownload()) {
2414          var downloads = xmlNode.selectNodes("download");
2415          if (downloads != null) {
2416              var filteredDownloads = filterConditionalNodes(downloads, true);
2417              for(var i=0; i<filteredDownloads.length; i++) {
2418                  downloadsArrayRef.push(filteredDownloads[i]);
2419              }
2420          }
2421      }
2422      return downloadsArrayRef;
2423  }
2424  
2425  /**
2426   * Returns 'target' attribute from the given download XML node
2427   * 
2428   * @param downloadNode
2429   *            download XML node
2430   * @return value of 'target' attribute, null if attribute is not defined
2431   */
2432  function getDownloadTarget(downloadNode){
2433      return downloadNode.getAttribute("target");
2434  }
2435  
2436  /**
2437   * Returns 'timeout' attribute from the given download XML node
2438   * 
2439   * @param downloadNode
2440   *            download XML node
2441   * @return {Number} Value of 'timeout' attribute, returns value of downloadTimeout if no
2442   *         timeout value exists or it cannot be parsed. Returns integer.
2443   */
2444  function getDownloadTimeout(downloadNode) {
2445      var returnValue = downloadTimeout;
2446      var timeout = downloadNode.getAttribute("timeout");
2447      if (timeout != null) {
2448          try {
2449              returnValue = parseInt(timeout);
2450          } catch(e) {
2451              error("Error parsing timeout attribute: " + e.description);
2452          }
2453      }
2454  
2455      return returnValue;
2456  }
2457  
2458  /**
2459   * Returns value of expandURL attribute from a download node.
2460   * @param downloadNode The download XML node.
2461   * @returns true if variables shall be expanded in URL attribute,
2462   *         false if they should not be expanded. Defaults to true if attribute is undefined.
2463   */
2464  function getDownloadExandURL(downloadNode) {
2465      var returnValue = true;
2466      var attributeValue = downloadNode.getAttribute("expandURL");
2467      if (attributeValue != null && attributeValue == "false") {
2468          returnValue = false;
2469      }
2470      return returnValue;
2471  }
2472  
2473  /**
2474   * Returns 'url' attribute from the given download XML node
2475   * 
2476   * @param downloadNode
2477   *            download XML node
2478   * @return value of 'url' attribute, null if attribute is not defined
2479   */
2480  function getDownloadUrl(downloadNode) {
2481      return downloadNode.getAttribute("url");
2482  }
2483  
2484  /**
2485   * Gets the size of a file (in Bytes). The path is allowed to contain
2486   * environment variables like "%TEMP%\somefile.txt".
2487   * 
2488   * @param file
2489   *          path to the file whose size has to be returned
2490   * @return size of the file (in Bytes), returns -1 if file size could not be
2491   *         determined
2492   */
2493  function getFileSize (file) {
2494      var size = -1;
2495      try {
2496          dinfo ("Finding size of '" + file + "'\n");
2497          var expandedPath = new ActiveXObject("WScript.Shell").ExpandEnvironmentStrings(file);
2498          var FSO = new ActiveXObject("Scripting.FileSystemObject");
2499          var fsof = FSO.GetFile(expandedPath);
2500          size = fsof.Size;
2501      } catch (e) {
2502          size = -1;
2503          dinfo("Unable to get file size for '" + file + "': " +
2504                   e.description);
2505      }
2506      dinfo ("Leaving getFileSize with size " + size);
2507      return size;
2508  }
2509  
2510  /**
2511   * Gets the creation date of a file.
2512   * 
2513   * @param file
2514   *          Path to the file from which to read the creation date.
2515   * @returns Date when the file has been created.
2516   * Returns null if file date could not be read.
2517   */
2518  function getFileDateCreation(file) {
2519      var fileDate = null; // new Date();
2520      try {
2521          dinfo ("Reading creation date of '" + file + "'.");
2522          var expandedPath = new ActiveXObject("WScript.Shell").ExpandEnvironmentStrings(file);
2523          var FSO = new ActiveXObject("Scripting.FileSystemObject");
2524          var fsof = FSO.GetFile(expandedPath);
2525          fileDate = fsof.DateCreated;
2526      } catch (e) {
2527          fileDate = null;
2528          dinfo("Unable to get file creation date for '" + file + "': " +
2529                   e.description);
2530      }
2531      return fileDate;
2532  }
2533  
2534  /**
2535   * Gets the last modified date of a file.
2536   * 
2537   * @param file
2538   *          Path to the file from which to read the last modification date.
2539   * @returns Date when the file has been last modified.
2540   * Returns null if file date could not be read.
2541   */
2542  function getFileDateModification(file) {
2543      var fileDate = null; // new Date();
2544      try {
2545          dinfo ("Reading last modification date of '" + file + "'.");
2546          var expandedPath = new ActiveXObject("WScript.Shell").ExpandEnvironmentStrings(file);
2547          var FSO = new ActiveXObject("Scripting.FileSystemObject");
2548          var fsof = FSO.GetFile(expandedPath);
2549          fileDate = fsof.DateLastModified;
2550      } catch (e) {
2551          fileDate = null;
2552          dinfo("Unable to get file last modification date for '" + file + "': " +
2553                   e.description);
2554      }
2555      return fileDate;
2556  }
2557  
2558  /**
2559   * Gets the last access date of a file.
2560   * 
2561   * @param file
2562   *          Path to the file from which to read the last access date.
2563   * @returns Date when the file has been last accessed.
2564   * Returns null if file date could not be read.
2565   */
2566  function getFileDateLastAccess(file) {
2567      var fileDate = null; // new Date();
2568      try {
2569          dinfo ("Reading last access date of '" + file + "'.");
2570          var expandedPath = new ActiveXObject("WScript.Shell").ExpandEnvironmentStrings(file);
2571          var FSO = new ActiveXObject("Scripting.FileSystemObject");
2572          var fsof = FSO.GetFile(expandedPath);
2573          fileDate = fsof.DateLastAccessed;
2574      } catch (e) {
2575          fileDate = null;
2576          dinfo("Unable to get file last accessed date for '" + file + "': " +
2577                   e.description);
2578      }
2579      return fileDate;
2580  }
2581  
2582  /**
2583   * Returns the version of a file.
2584   * 
2585   * @return string representation of version, null in case no version could be
2586   *         read.
2587   */
2588  function getFileVersion (file) {
2589      var version = null;
2590      try {
2591          dinfo ("Trying to find version of " + file);
2592          var FSO = new ActiveXObject("Scripting.FileSystemObject");
2593          version = FSO.GetFileVersion(file);
2594          dinfo ("Obtained version '" + version + "'.");
2595      } catch (e) {
2596          version = null;
2597          dinfo("Unable to find file version for " + file + " : " +
2598              e.description);
2599      }
2600      return version;
2601  }
2602  
2603  /**
2604   * Returns the hostname of the machine running this script. The hostname might
2605   * be overwritten by the /host:<hostname> switch.
2606   */
2607  function getHostname() {
2608      if (hostName == null) {
2609          var WshNetwork = WScript.CreateObject("WScript.Network");
2610          setHostname(WshNetwork.ComputerName.toLowerCase());
2611      }
2612      return hostName;
2613  }
2614  
2615  /**
2616   * Returns a string representing the regular expression associated to the host
2617   * definition in hosts.xml.
2618   */
2619  function getHostNameAttribute(hostNode) {
2620      return hostNode.getAttribute("name");
2621  }
2622  
2623  /**
2624   * Returns the operating system of the machine running this script. The return
2625   * format is:
2626   * 
2627   * <pre>
2628   * <OS-caption>, <OS-description>, <CSD-version>, <OS-version>
2629   * example output:
2630   * microsoft windows 7 professional, , sp1, 6.1.7601
2631   * </pre>
2632   * 
2633   * It might be overwritten by the /os:<hostos> switch.
2634   * 
2635   * Note: Some values might be empty.
2636   * 
2637   * @returns Host operating system specification as a plain string converted to
2638   *          lower case letters to ease parsing
2639   */
2640  function getHostOS() {
2641      if (hostOs == null) {
2642          var strComputer = ".";
2643          var strQuery = "Select * from Win32_OperatingSystem";
2644              try {
2645                  var objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\\\" +
2646                                                  strComputer + "\\root\\cimv2");
2647                  var colOSes = objWMIService.ExecQuery(strQuery,"WQL",48);
2648                  var osEnum = new Enumerator(colOSes);
2649                  for (; !osEnum.atEnd(); osEnum.moveNext()) {
2650                      var osItem = osEnum.item();
2651                      var OtherTypeDescription = "";
2652                      var CSDVersion = "";
2653                          if (osItem.OtherTypeDescription != null) {
2654                              OtherTypeDescription = osItem.OtherTypeDescription;
2655                          }
2656                          if (osItem.CSDVersion != null) {
2657                              CSDVersion = osItem.CSDVersion.replace(/Service Pack /i,"SP");
2658                          }
2659                          var strSystem = trim(osItem.Caption) + ", "
2660                                  + OtherTypeDescription + ", "
2661                                  + CSDVersion + ", "
2662                                  + osItem.Version;
2663                          hostOs = strSystem.toLowerCase();
2664                          dinfo("Host operating system: " + hostOs);
2665                  }
2666              } catch (e) {
2667                  dinfo("Warning: unable to get operating system information.");
2668              }
2669      }
2670      return hostOs;
2671  }
2672  
2673  /**
2674   * Returns name of domain on which the executing host is member of.
2675   * 
2676   * @returns Returns domain name string.
2677   */
2678  function getDomainName() {
2679      if (domainName == null) {
2680          try {
2681              var strComputer = "." ;
2682  
2683              // Get WMI object to read information from.
2684              var WMIServiceStr = "winmgmts:{impersonationLevel=impersonate}!\\\\"
2685                                  + strComputer + "\\root\\cimv2";
2686              var objWMIService = GetObject(WMIServiceStr) ;
2687  
2688              // Query domain name from WMI.
2689              var QueryRes = objWMIService.ExecQuery("Select * from Win32_ComputerSystem where PartOfDomain=True ");
2690              var items=new Enumerator(QueryRes);
2691              items.moveFirst();
2692              if (items.atEnd() == true) {
2693                  // Not a domain member
2694                  dinfo("Not a domain member.");
2695                  // set
2696                  domainName = "";
2697              } else {
2698                  var First = items.item();
2699                  domainName = First.Domain.toLowerCase();
2700                  dinfo("Domain Name: " + domainName);
2701              }
2702          } catch (e) {
2703              dinfo("Message: Unable to get domain information.");
2704          }
2705      }
2706      return domainName;
2707  }
2708  
2709  /**
2710   * Returns array of group names where the executing host is member of.
2711   * 
2712   * @returns Returns list of membership groups.
2713   */
2714  function getHostGroups() {
2715      if (hostGroups == null) {
2716          hostGroups = new Array();
2717          try {
2718              var hostName = getHostname();
2719              var domainName = getDomainName();
2720              var obj = GetObject("WinNT://" + domainName + "/" + hostName + "$,user") ;
2721              var groups = obj.Groups();
2722              for (var item =new Enumerator(groups); !item.atEnd(); item.moveNext() ) {
2723                  var group = item.item();
2724                  dinfo("Found computer group: " + group.Name);
2725                  hostGroups.push(group.Name);
2726              }
2727          } catch (e) {
2728              dinfo("Message: Unable to fetch computer membership groups. Probably not a domain member.");
2729          }
2730      }
2731      return hostGroups;
2732  }
2733  
2734  /**
2735   * Returns a list of attribute/value pair associated to the host
2736   * definition in hosts.xml.
2737   *
2738   * @param hostNode XML node of the host definition
2739   * @return dictionary of attribute/value pair.
2740   */
2741  function getHostAttributes(hostNode) {
2742      var hostAttributes = new  ActiveXObject("Scripting.Dictionary");
2743  
2744      if(hostNode.attributes != null) {
2745          for (var i=0; i<hostNode.attributes.length; i++) {
2746              if (hostNode.attributes[i].value != null) {
2747                  hostAttributes.Add(hostNode.attributes[i].name, hostNode.attributes[i].value);
2748              }
2749          }
2750      }
2751      return  hostAttributes;
2752  }
2753  
2754  /**
2755   * Returns a string identifying a host node including all attributes.
2756   * 
2757   * @param hostNode
2758   *            XML node of the host definition
2759   * @return a string of concatenate 'attribute=value'
2760   */
2761  function getHostNodeDescription(hostNode) {
2762      // Get dictionary object of all attributes.
2763      var hostNodeAttrs = getHostAttributes(hostNode);
2764  
2765      // Fill all attributes into array.
2766      var attrsKeys = hostNodeAttrs.keys().toArray();
2767      var attrDesc = new Array();
2768      for (var i=0; i<attrsKeys.length; i++) {
2769          var attrName = attrsKeys[i];
2770          var attrValue = hostNodeAttrs.Item(attrName);
2771          attrDesc.push(attrName + "='" + attrValue + "'");
2772      }
2773      // Convert array to comma-separated list
2774      // attr1='value1',attr2='value2'
2775      return attrDesc.join(",");
2776  }
2777  
2778  
2779  /**
2780   * Collects information from local host and stores it into a scripting
2781   * dictionary object.
2782   * 
2783   * @returns host attributes stored within a dictionary object. This currently
2784   *          includes the following attributes: name, architecture, os,
2785   *          ipaddresses, domainname, groups, lcid
2786   */
2787  function getHostInformation() {
2788      // Fetch host information if not already collected.
2789      // This information is supposed to be static during execution and
2790      // therefore it will be cached.
2791      if (hostAttributes == null) {
2792          hostAttributes = new ActiveXObject("Scripting.Dictionary");
2793          hostAttributes.Add("hostname", getHostname());
2794          hostAttributes.Add("architecture", getArchitecture());
2795          hostAttributes.Add("os", getHostOS());
2796          hostAttributes.Add("ipaddresses", getIPAddresses());
2797          hostAttributes.Add("domainname", getDomainName());
2798          hostAttributes.Add("groups", getHostGroups());
2799          hostAttributes.Add("lcid", getLocale());
2800          hostAttributes.Add("lcidOS", getLocaleOS());
2801  
2802          // Print information found for debug purposes.
2803          dinfo("Host properties: "
2804              + "hostname='" + hostAttributes.Item("hostname") + "'\n"
2805              + "architecture='" + hostAttributes.Item("architecture") + "'\n"
2806              + "os='" + hostAttributes.Item("os") + "'\n"
2807              + "ipaddresses='" + hostAttributes.Item("ipaddresses").join(",") + "'\n"
2808              + "domain name='" + hostAttributes.Item("domainname") + "'\n"
2809              + "groups='" + hostAttributes.Item("groups").join(",") + "'\n"
2810              + "lcid='" + hostAttributes.Item("lcid") + "'\n"
2811              + "lcidOS='" + hostAttributes.Item("lcidOS") + "'"
2812          );
2813      }
2814      return hostAttributes;
2815  }
2816  
2817  /**
2818   * Accepts a list of XML nodes (Array of XML nodes) which is then filtered for
2819   * XML nodes which either do not specify specific host matches or all specified
2820   * attributes match the current host. For example the following XML nodes would
2821   * match:
2822   * 
2823   * E.g.
2824   * 
2825   * <pre>
2826   * <host name="nodename"; os="windows"; attributeX="value" profile-id="default" />
2827   * <host name="nodename" profile-id="default" />
2828   * <package os="windows" package-id="value" ipaddresses="192\.168\.1\..*" />
2829   * <package package-id="value" />
2830   * </pre>
2831   * 
2832   * The last example matches since there is no limitation to host attributes in the definition.
2833   * 
2834   * The return value will be an Array object listing only the XML nodes which
2835   * match.
2836   * 
2837   * @param xmlNodes
2838   *            Array of XML nodes which shall be verified for current host match.
2839   * @param getAllMatches
2840   *            If set to true returns all matches. If set to false just returns the first matching node from xmlNodes. In this case the return array will contain only one element (or 0 if no match was found).
2841   * @returns Array of XML nodes which match the current host.
2842   */
2843  function filterConditionalNodes(xmlNodes, getAllMatches) {
2844      // Create array to store the XML nodes which match this host.
2845      var applyingNodes = new Array();
2846  
2847      if(getAllMatches == null) {
2848          getAllMatches = true;
2849      }
2850      
2851      // Check if xmlNode array passed as argument is valid
2852      if (xmlNodes == null || xmlNodes.length <= 0) {
2853          return applyingNodes;
2854      }
2855  
2856      // Fetch current host attributes.
2857      var globalHostInformation = getHostInformation();
2858  
2859      // Add "environment" key since we want to support environment matching too.
2860      var hostInformation = new ActiveXObject("Scripting.Dictionary");
2861      var keys = globalHostInformation.keys().toArray();
2862      for (var i=0; i<keys.length; i++) {
2863          hostInformation.Add(keys[i], globalHostInformation.Item(keys[i]));
2864      }
2865      hostInformation.Add("environment", "");
2866  
2867      // Check all nodes whether they match the current host.
2868      for (var i=0; i < xmlNodes.length; i++) {
2869          var xmlNode = xmlNodes[i];
2870          if (xmlNode == null) {
2871              // Skip to next node
2872              continue;
2873          }
2874          // Set to true if all host attributes from XML specification match
2875          // this host.
2876          var hostMatchFound = true;
2877  
2878          // Fetch all XML attributes which correspond to a defined host property.
2879          var xmlNodeAttrs = new  ActiveXObject("Scripting.Dictionary");
2880          for (var iAttribute=0; iAttribute < xmlNode.attributes.length; iAttribute++) {
2881              if( hostInformation.Item(xmlNode.attributes[iAttribute].name) != null ) {
2882                  xmlNodeAttrs.Add(xmlNode.attributes[iAttribute].name, xmlNode.attributes[iAttribute].value);
2883              }
2884          }
2885          
2886          // Check whether all of the attributes match the current host.
2887          var attrsKeys = xmlNodeAttrs.keys().toArray();
2888          for (var iAttr=0; iAttr<attrsKeys.length; iAttr++) {
2889              var xmlNodeAttrName = attrsKeys[iAttr];
2890              var xmlNodeAttrValue = xmlNodeAttrs.Item(xmlNodeAttrName);
2891  
2892              // Check whether the attribute matches the current host.
2893              var attributeMatchFound = checkHostAttribute(xmlNodeAttrName, xmlNodeAttrValue);
2894              
2895              // Verify if the attribute does match to current host.
2896              if (attributeMatchFound != true) {
2897                  // No match found. Advance to next host.
2898                  dinfo("No value of '" + xmlNodeAttrName + "' matched '" + xmlNodeAttrValue + "'. Skipping to next definition.");
2899                  hostMatchFound = false;
2900                  break;
2901              }
2902              /*
2903               * else { // This attribute matched, continue with next attribute hostMatchFound = true; continue; }
2904               */
2905          }
2906  
2907          // If not all attributes match the current host definition then the node is not included.
2908          // All nodes which do not specify advanced host match attributes are included too.
2909          if (hostMatchFound) {
2910              // All attributes matched
2911  
2912              // Print some debug information about which extended host attributes matched.
2913              if (xmlNodeAttrs.count > 0) {
2914                  var attrsKeys = xmlNodeAttrs.keys().toArray();
2915                  var attrDesc = new Array();
2916                  for (var iAttrKeys=0; iAttrKeys<attrsKeys.length; iAttrKeys++) {
2917                      attrDesc.push(attrsKeys[iAttrKeys] + "=" + xmlNodeAttrs.Item(attrsKeys[iAttrKeys]));
2918                  }
2919                  dinfo("XML node with special host attribute match found: " + attrDesc.join(", "));
2920              }
2921  
2922              // Verify if the XML node has a <condition /> sub-node
2923              var conditionMatched = true;
2924              var conditionNode = getConditions(xmlNode);
2925              if (conditionNode != null) {
2926                  for (var iCond=0; iCond < conditionNode.length; iCond++) {
2927                      var condition = conditionNode[iCond];
2928                      // Run all checks
2929                      conditionMatched = checkAll(getChecks(condition));
2930                      if (conditionMatched) {
2931                          dinfo("Additional conditions matched successfully.");
2932                      } else {
2933                          conditionMatched = false;
2934                          dinfo("Additional conditions did not match.");
2935                          break;
2936                      }
2937                  }
2938              }
2939  
2940              // Insert node to list of matched nodes.
2941              if (conditionMatched) {
2942                  applyingNodes.push(xmlNode);
2943                  if (!getAllMatches) {
2944                      dinfo("Single-match mode. Host match finished.");
2945                      break;
2946                  }
2947              }
2948          } else {
2949              dinfo("Could not match all attributes of XML node to current host. Skipping to next definition.");
2950          }
2951      }
2952  
2953      return applyingNodes;
2954  }
2955  
2956  /**
2957   * Retrieves host nodes from given "hosts" XML documents. Searches for nodes
2958   * having matching attributes and returns their array.
2959   * 
2960   * First matching host node is returned by default. If switch /applymultiple is
2961   * used all matching host nodes are returned.
2962   * 
2963   * @return returns the first matching host XML node or the list of all matching
2964   *         host XML nodes if applymultiple is true. Returns null if no host node
2965   *         matches.
2966   */
2967  function getHostsApplying() {
2968      if (applyingHostNodes == null) {
2969          // Create new array to store matching hosts.
2970          hostNodesApplying = new Array();
2971  
2972          // Get available host definitions.
2973          var hostNodes = getHostNodes();
2974  
2975          // Check each node independently.
2976          for (var iHost=0; iHost < hostNodes.length; iHost++) {
2977              var hostNode = hostNodes[iHost];
2978  
2979              // Check conditions to determine whether the host definition is
2980              // applied.
2981              var previousEnv = getEnv();
2982              var variables = getVariables(hostNode, null);
2983  
2984              // Apply variables to environment.
2985              for (var iVariable=0; iVariable < variables.length; iVariable++) {
2986                  var varDefinition = variables[iVariable];
2987                  var variableKeys = varDefinition.keys().toArray();
2988                  for (var iVarKey = 0; iVarKey < variableKeys.length; iVarKey++) {
2989                      var key = variableKeys[iVarKey];
2990                      var value = varDefinition.Item(key);
2991                      setEnv(key, value);
2992                  }
2993              }
2994              
2995              // Checkthis host node for special conditions.
2996              var hostList = new Array();
2997              hostList.push(hostNode);
2998              hostList = filterConditionalNodes(hostList, true);
2999              if (hostList.length < 1) {
3000                  // Restore environment.
3001                  loadEnv(previousEnv);
3002                  // Skipt to next host node.
3003                  continue;
3004              }
3005              
3006              // Get host name attribute.
3007              var hostNameAttribute = getHostNameAttribute(hostNode);
3008  
3009              if (hostNameAttribute != null && hostNameAttribute != "") {
3010                  // Try direct match first (non-regular-expression matching).
3011                  if (hostNameAttribute.toUpperCase() == getHostname().toUpperCase()) {
3012                      // Append host to applying hosts.
3013                      hostNodesApplying.push(hostNode);
3014  
3015                  } else {
3016                      
3017                      // Flag to check if IP-address match succeeded.
3018                      var ipMatchSuccess = false;
3019                      try {
3020                          // Try IPv4-address matching.
3021                          // Get IPv4 addresses (might be multiple).
3022                          var ipAddresses = getIPAddresses();
3023      
3024                          // check for each address if a host node matches
3025                          // try non-regular-expression matching
3026                          for (var iIPAdresses=0; iIPAdresses < ipAddresses.length; iIPAdresses++) {
3027                              var ipAddress = ipAddresses[iIPAdresses];
3028      
3029                              // splitvalues
3030                              // dinfo("Trying to match IP '" + ipAddress + "' to " +
3031                              // "'" + matchPattern + "'");
3032                              var splitIP = ipAddress.split(".");
3033                              var splitPattern = hostNameAttribute.split(".");
3034                              // check if format was correct
3035                              if (splitIP.length == 4 &&
3036                                  splitPattern.length == 4) {
3037                                  var firstValue = 0;
3038                                  var secondValue = 0;
3039                                  var match = true;
3040                                  for (var k=0; k<splitIP.length; k++) {
3041                                      // get first range value
3042                                      var ipOctet = parseInt(splitIP[k]);
3043                                      var matchOctet = splitPattern[k];
3044  
3045                                      // check if ip octet defines a range
3046                                      var splitMatchOctet = matchOctet.split("-");
3047                                      firstValue = parseInt(splitMatchOctet[0]);
3048                                      if (splitMatchOctet.length > 1) {
3049                                          secondValue = parseInt(splitMatchOctet[1]);
3050                                      } else {
3051                                          secondValue = firstValue;
3052                                      }
3053                                      if (firstValue > secondValue) {
3054                                          // swap values
3055                                          var temp = firstValue;
3056                                          firstValue = secondValue;
3057                                          secondValue = temp;
3058                                      }
3059                                      // let's finally see if the ip octet is outside the range
3060                                      if ((ipOctet < firstValue || ipOctet > secondValue)) {
3061                                          // if octet did not match the requirements
3062                                          // dinfo("no match!");
3063                                          match = false;
3064                                          // no need to continue
3065                                          break;
3066                                      }
3067                                  }
3068                                  // If all matched, take this profile.
3069                                  if (match) {
3070                                      dinfo("Found host '" + hostNameAttribute +
3071                                              "' matching IP '" + ipAddress + "'");
3072                                      // Append host to applying hosts.
3073                                      hostNodesApplying.push(hostNode);
3074                                      ipMatchSuccess = true;
3075                                      break;
3076                                  }
3077                              }
3078                          }
3079                      } catch(e) {
3080                          ipMatchSuccess = false;
3081                          dinfo("IP-Address match failed: " + e.description);
3082                      }
3083  
3084                      // If we still got no match with, then try regular expression matching.
3085                      if (!ipMatchSuccess) {
3086                          try {
3087                              var hostNameAttributeMatcher = new RegExp("^" + hostNameAttribute + "$", "i");
3088      
3089                              if (hostNameAttributeMatcher.test(getHostname()) == true) {
3090                                  hostNodesApplying.push(hostNode);
3091                              }
3092                          } catch (e) {
3093                              warning("Invalid regular expression for host name matching: '" +
3094                                      hostNameAttribute + "'.");
3095                          }
3096                      }
3097                  }
3098  
3099              } else {
3100  
3101                  // Host "name" attribute is missing or empty. Include host as potential match.
3102                  // This allows to filter this host later using extended host matching
3103                  hostNodesApplying.push(hostNode);
3104              }
3105              
3106              // Restore environment.
3107              loadEnv(previousEnv);
3108          }
3109          
3110          // Filter host nodes by matching them to the local host.
3111          // hostNodesApplying = filterConditionalNodes(hostNodesApplying, isApplyMultiple());
3112  
3113          // Matches might have returned multiple matching results. In case of
3114          // single-matching mode (default) only the first result shall be
3115          // returned
3116          if (!isApplyMultiple() && hostNodesApplying.length > 1) {
3117              var applyingHostNode = hostNodesApplying[0];
3118              hostNodesApplying = new Array();
3119              hostNodesApplying.push(applyingHostNode);
3120          }
3121  
3122          if (hostNodesApplying.length <= 0) {
3123              hostNodesApplying = null;
3124              throw new Error("Unable to find any matching host definition!");
3125          }
3126          applyingHostNodes = hostNodesApplying;
3127      }
3128  
3129      return applyingHostNodes;
3130  }
3131  
3132  /**
3133   * Returns an array of host nodes which specify the host regular expression and
3134   * the corresponding profile
3135   */
3136  function getHostNodes() {
3137      return getHosts().selectNodes("host");
3138  }
3139  
3140  /**
3141   * Returns the profile-id associated with the given host node.
3142   * The node structure is defined as follows:
3143   * 
3144   * The profile-id or the enclosed <profile... /> nodes might be omitted but not
3145   * both!
3146   * 
3147   * @param hostNode XML node of the host definition
3148   * @return array of strings with referenced profiles
3149   *         (array might be of length 0 if no profiles are defined)
3150   */
3151  function getHostProfiles(hostNode) {
3152      // create array to store profile IDs
3153      var profileList = new Array();
3154  
3155      // try to receive profile ID from host node
3156      var profileID = hostNode.getAttribute("profile-id");
3157  
3158      if (profileID != null) {
3159          // convert to lower case if case-sensitivity is off
3160          if (!isCaseSensitive()) {
3161              profileList.push(profileID.toLowerCase());
3162          } else {
3163              profileList.push(profileID);
3164          }
3165      }
3166  
3167      // Load host definition environment (environment might be used in condition
3168      // checks.
3169      var previousEnv = getEnv();
3170      var variables = getVariables(hostNode, null);
3171  
3172      // Apply variables to environment.
3173      for (var iVariable=0; iVariable < variables.length; iVariable++) {
3174          var varDefinition = variables[iVariable];
3175          var variableKeys = varDefinition.keys().toArray();
3176          for (var iVarKey = 0; iVarKey < variableKeys.length; iVarKey++) {
3177              var key = variableKeys[iVarKey];
3178              var value = varDefinition.Item(key);
3179              setEnv(key, value);
3180          }
3181      }
3182      
3183      var profileNodes = hostNode.selectNodes("profile");
3184      if (profileNodes != null) {
3185          // Get only dependencies which match the current host.
3186          var matchingProfileNodes = filterConditionalNodes(profileNodes, true);
3187          for (var iProfile=0; iProfile<matchingProfileNodes.length; iProfile++) {
3188              var profileNode = matchingProfileNodes[iProfile];
3189              // get id attribute
3190              var profileId = profileNode.getAttribute("id");
3191  
3192              // convert to lower case if case-sensitivity is off
3193              if (!isCaseSensitive()) {
3194                  profileList.push(profileId.toLowerCase());
3195              } else {
3196                  profileList.push(profileId);
3197              }
3198          }
3199      }
3200  
3201      // Restore environment.
3202      loadEnv(previousEnv);
3203      
3204      if (profileList.length > 0) {
3205          var message = "Profiles applying to the current host:\n";
3206          for (var iProfileIndex=0; iProfileIndex<profileList.length; iProfileIndex++) {
3207              message += profileList[iProfileIndex] + "\n";
3208          }
3209          dinfo(message);
3210      } else {
3211          error("No profiles assigned to the current host!");
3212      }
3213  
3214      return profileList;
3215  }
3216  
3217  /**
3218   * Returns XML node which contains all host configurations
3219   */
3220  function getHosts() {
3221      if(hosts == null) {
3222          var newHosts = createHosts();
3223          setHosts(newHosts);
3224      }
3225      return hosts;
3226  }
3227  
3228  /**
3229   * Returns a list of variables from the applying hosts definition.
3230   * 
3231   * @param array
3232   *            Object of type Array to which the the variables appended.
3233   *            In case null is supplied it returns a new Array object.
3234   * @return Object of type Scripting.Dictionary which contains all key/value
3235   *         pairs from the applying hosts.
3236   */
3237  function getHostsVariables(array) {
3238      dinfo("Reading variables from hosts[s]");
3239  
3240      // Fetch host definitions which apply to current host.
3241      if (hostsVariables == null) {
3242          hostsVariables = new Array();
3243          var hostNodes = getHostsApplying() ;
3244          for (var iHostNode=0; iHostNode < hostNodes.length; iHostNode++) {
3245              var hostNode = hostNodes[iHostNode];
3246              dinfo("Reading variables from host: " + getHostNodeDescription(hostNode));
3247  
3248              // Add variables from host XML node.
3249              hostsVariables = getVariables(hostNode, hostsVariables);
3250          }
3251      }
3252  
3253      // Concatenate variable list if list was passed as parameter.
3254      var concatenatedVariables = hostsVariables;
3255      if (array != null) {
3256          // concatenatedVariables = concatenateDictionary(dictionary, hostsVariables);
3257          concatenatedVariables = hostsVariables.concat(array);
3258      }
3259  
3260      return concatenatedVariables;
3261  }
3262  
3263  /**
3264   * Returns the corresponding string defined within the configuration.
3265   * 
3266   * @param stringID
3267   *            the identification of the corresponding string as listed within
3268   *            the configuration
3269   * 
3270   * @return returns the string as it appears within the configuration. Returns
3271   *         null if the string id is not defined.
3272   */
3273  function getLocalizedString(stringID) {
3274      if (languageNode == null && getConfig() != null) {
3275          // read node which contains all the strings
3276          var languagesNodes = getConfig().selectNodes("languages");
3277  
3278          if (languagesNodes != null) {
3279              // there might be multiple languages nodes
3280              for (var i=0; i < languagesNodes.length; i++) {
3281                  // get language nodes
3282                  var languageNodes = languagesNodes[i].selectNodes("language");
3283  
3284                  for (var j=0; j < languageNodes.length && languageNode == null; j++) {
3285                      var currentLangNode = languageNodes[j];
3286  
3287                      // get associated language LCIDs
3288                      var lcidString = currentLangNode.getAttribute("lcid");
3289                      var lcids = lcidString.split(",");
3290                      for (var k=0; k < lcids.length; k++) {
3291                          // check if it corresponds to the system LCID
3292                          var currentLcid = trimLeadingZeroes(trim(lcids[k]));
3293                          if (currentLcid == getLocale()) {
3294                              dinfo("Found language definition node for language ID " + currentLcid);
3295                              languageNode = currentLangNode;
3296                              break;
3297                          }
3298                      }
3299                  }
3300              }
3301          }
3302  
3303      }
3304  
3305      // check if language has not been found
3306      if (languageNode == null) {
3307          // create empty node
3308          languageNode = createXml("language");
3309      }
3310  
3311      // try to find node matching the requested sting id
3312      var stringNode = languageNode.selectSingleNode("string[@id='" + stringID + "']");
3313      if (stringNode != null) {
3314          return stringNode.text;
3315      } else {
3316          dinfo("No locale language definition found for message ID '" + stringID +
3317              "' (language LCID '" + getLocale() + "').");
3318          return null;
3319      }
3320  }
3321  
3322  /**
3323   * Returns array of package IDs which includes package IDs of chained packages.
3324   * Returns empty array in case the package does not have any chained packages.
3325   * 
3326   * @param packageNode
3327   *            the package node to read the list of chained packages from
3328   * @param packageList
3329   *            optional reference to an array which is used to insert the chained
3330   *            packages to. Specify null to create a new Array
3331   * @return Array specified in packageList parameter extended by package IDs
3332   *         (string values) which represent the chained packages
3333   */
3334  function getPackageChained(packageNode, packageList) {
3335      // output array
3336      if (packageList == null) {
3337          packageList = new Array();
3338      }
3339  
3340      if(packageNode != null) {
3341          var includeNodes = packageNode.selectNodes("chain");
3342          if (includeNodes != null) {
3343              matchingChainNodes = filterConditionalNodes(includeNodes, true);
3344              for (var i=0; i < matchingChainNodes.length; i++) {
3345                  var dependId = matchingChainNodes[i].getAttribute("package-id");
3346  
3347                  // convert to lower case if case-insensitive mode is on
3348                  if (dependId != null) {
3349                      if (!isCaseSensitive()) {
3350                          dependId = dependId.toLowerCase();
3351                      }
3352                      packageList.push(dependId);
3353                  }
3354              }
3355          }
3356      }
3357  
3358      return packageList;
3359  }
3360  
3361  /**
3362   * Defines how package checks are used during package installation.
3363   * 
3364   * Currently supported values:
3365   *
3366   * "always" (default):
3367   * When a package is new to the host then first the checks are run in order to
3368   * verify whether the package is already installed. If the checks succeed then
3369   * it is assumed that no further installation is needed. The package is silently
3370   * added to the host without executing any commands.
3371   * 
3372   * "never":
3373   * When a package is new to the host then the install commands are run in any
3374   * case (without doing checks first). Note: Checks will still be done after
3375   * package installation to verify whether installation was successful.
3376   *
3377   * @param packageNode Package XML node to read attribute from.
3378   * @returns "always" or "never" according to precheck-install attribute of
3379   *           package.
3380   */
3381  function getPackagePrecheckPolicyInstall(packageNode) {
3382      var checkPolicy = "always";
3383      var installCheckPolicy = packageNode.getAttribute("precheck-install");
3384      if (installCheckPolicy != null) {
3385          checkPolicy = installCheckPolicy;
3386      }
3387      return checkPolicy;
3388  }
3389  
3390  /**
3391   * Defines how package checks are used during package removal.
3392   * 
3393   * Currently supported values:
3394   * 
3395   * "always":
3396   * When a package is removed from a host then the checks will be executed
3397   * before removal is processes. If the checks fail this potentially means that
3398   * the package has been removed already. In such case the package remove
3399   * commands will be skipped.
3400   * 
3401   * "never" (default):
3402   * When a package is about to be removed from the host then WPKG will execute
3403   * the remove commands in any case without executing the checks first.
3404   * Note: Checks will still be done after package removal to verify whether the
3405   * removal was successful.
3406   * 
3407   * @param packageNode Package XML node to read attribute from.
3408   * @returns "always" or "never" according to precheck-remove attribute of
3409   *           package.
3410   */
3411  function getPackagePrecheckPolicyRemove(packageNode) {
3412      var checkPolicy = "never";
3413      var removeCheckPolicy = packageNode.getAttribute("precheck-remove");
3414      if (removeCheckPolicy != null) {
3415          checkPolicy = removeCheckPolicy;
3416      }
3417      return checkPolicy;
3418  }
3419  
3420  /**
3421   * Defines how package checks are used during package upgrade.
3422   * 
3423   * Currently supported values:
3424   *
3425   * "always":
3426   * When a package is upgraded the checks specified will be be executed before
3427   * the upgrade takes place. If checks succeed, then the upgrade will not be
3428   * performed (WPKG just assumes that the new version is already applied
3429   * correctly.
3430   * Please note that your checks shall verify a specific software version and
3431   * not just a generic check which is true for all versions. If your checks
3432   * are true for the old version too then WPKG would never perform the upgrade
3433   * in this mode.
3434   * 
3435   * "never" (default):
3436   * When a package is about to be upgraded then WPKG will execute the upgrade
3437   * commands in any case without executing the checks first. This is the
3438   * recommended behavior.
3439   * Note: Checks will still be done after package upgrade to verify whether the
3440   * upgrade was successful.
3441   * 
3442   * @param packageNode Package XML node to read attribute from.
3443   * @returns "always" or "never" according to precheck-upgrade attribute of
3444   *           package.
3445   */
3446  function getPackagePrecheckPolicyUpgrade(packageNode) {
3447      var checkPolicy = "never";
3448      var upgradeCheckPolicy = packageNode.getAttribute("precheck-upgrade");
3449      if (upgradeCheckPolicy != null) {
3450          checkPolicy = upgradeCheckPolicy;
3451      }
3452      return checkPolicy;
3453  }
3454  
3455  /**
3456   * Defines how package checks are used during package downgrade.
3457   * 
3458   * Currently supported values:
3459   *
3460   * "always":
3461   * When a package is downgraded the checks specified will be be executed before
3462   * the downgrade takes place. If checks succeed, then the downgrade will not be
3463   * performed (WPKG just assumes that the old version is already applied
3464   * correctly.
3465   * Please note that your checks shall verify a specific software version and
3466   * not just a generic check which is true for all versions. If your checks
3467   * are true for the new/current version too then WPKG would never perform the
3468   * downgrade in this mode.
3469   * 
3470   * "never" (default):
3471   * When a package is about to be downgraded then WPKG will execute the
3472   * downgrade commands in any case without executing the checks first. This is
3473   * the recommended behavior.
3474   * Note: Checks will still be done after package downgrade to verify whether
3475   * the downgrade was successful.
3476   * 
3477   * @param packageNode Package XML node to read attribute from.
3478   * @returns "always" or "never" according to precheck-downgrade attribute of
3479   *           package.
3480   */
3481  function getPackagePrecheckPolicyDowngrade(packageNode) {
3482      var checkPolicy = "never";
3483      var downgradeCheckPolicy = packageNode.getAttribute("precheck-downgrade");
3484      if (downgradeCheckPolicy != null) {
3485          checkPolicy = downgradeCheckPolicy;
3486      }
3487      return checkPolicy;
3488  }
3489  
3490  /**
3491   * Returns an array of <check /> XML sub-nodes on a given XML node.
3492   * In case extended host matching attributes are used only the checks which match the
3493   * current host are returned.
3494   * 
3495   * @param xmlNode The XML node from which all 'check' sub-nodes are read
3496   * @return Array of XML nodes containing all 'check'-nodes which match to the current host.
3497   *         Returns empty array if no checks are defined.
3498   *         If extended host matching attributes like "hostname", "os" or similar are used
3499   *         then checks which do not match the current host are not returned.
3500   */
3501  function getChecks(xmlNode) {
3502      var checkNodes = xmlNode.selectNodes("check");
3503      /*
3504      var checkNodes = xmlNode.selectNodes("wpkg:check");
3505      if (checkNodes.length <= 0) {
3506          // Maybe amespace was wrongly specified.
3507          // Try default namespace.
3508          checkNodes = xmlNode.selectNodes("check");
3509      }
3510      */
3511      return filterConditionalNodes(checkNodes);
3512  }
3513  
3514  /**
3515   * This is a convenience-method to get all downgrade commands.
3516   * 
3517   * @param packageNode
3518   *            package XML node which contains 'downgrade' nodes
3519   * @return Array of 'downgrade' XML nodes, returns empty array if no nodes are
3520   *         defined
3521   */
3522  function getPackageCmdDowngrade(packageNode, includeChain) {
3523      // Fetch commands from package node.
3524      var commandNodes = getPackageCmd(packageNode, "downgrade", null);
3525  
3526      // Return list of applying install commands.
3527      return commandNodes;
3528  }
3529  
3530  /**
3531   * This is a convenience-method to get all install commands.
3532   * 
3533   * @param packageNode
3534   *            package XML node which contains 'install' nodes
3535   * @return Array of 'install' XML nodes, returns empty array if no nodes are
3536   *         defined
3537   */
3538  function getPackageCmdInstall(packageNode, includeChain) {
3539      // Fetch commands from package node.
3540      var commandNodes = getPackageCmd(packageNode, "install", null);
3541  
3542      // Return list of applying install commands.
3543      return commandNodes;
3544  }
3545  
3546  
3547  /**
3548   * This is a convenience-method to get all remove commands.
3549   * 
3550   * @param packageNode
3551   *            package XML node which contains 'remove' nodes
3552   * @return Array of 'remove' XML nodes, returns empty array if no nodes are
3553   *         defined
3554   */
3555  function getPackageCmdRemove(packageNode, includeChain) {
3556      // Fetch commands from package node.
3557      var commandNodes = getPackageCmd(packageNode, "remove", null);
3558  
3559      // Return list of applying install commands.
3560      return commandNodes;
3561  }
3562  
3563  /**
3564   * This is a convenience-method to get all upgrade commands.
3565   * 
3566   * @param packageNode
3567   *            package XML node which contains 'remove' nodes
3568   * @return Array of 'upgrade' XML nodes, returns empty array if no nodes are
3569   *         defined
3570   */
3571  function getPackageCmdUpgrade(packageNode, includeChain) {
3572      // Fetch commands from package node.
3573      var commandNodes = getPackageCmd(packageNode, "upgrade", null);
3574  
3575      // Return list of applying install commands.
3576      return commandNodes;
3577  }
3578  
3579  
3580  /**
3581   * Returns a list of commands which apply to the given command type.
3582   * Common types are 'install', 'upgrade', 'downgrade' or 'remove' but WPKG
3583   * allows any custom type definition within the commands/command XML structure.
3584   * For example it is possible to specify <command type="test-type" /> and then
3585   * receive all "test-type" commands using this method.
3586   * 
3587   * @param packageNode
3588   *            package XML node which contains command nodes.
3589   * @param type
3590   *            Type description. Defines which command group to receive.
3591   * @param includeChain
3592   *             Array of command types (install/upgrade/downgrade/remove) already
3593   *          included.
3594   *          This is used to detect inclusion loops (recursive inclusion).
3595   * @return Array of command XML nodes, returns empty array if no nodes are
3596   *         defined
3597   */
3598  function getPackageCmd(packageNode, type, includeChain) {
3599      // Verify input parameters.
3600      if (packageNode == null) {
3601          return null;
3602      }
3603  
3604      // Type must be specified in order to get command group.
3605      if (type == null || type == "") {
3606          return null;
3607      }
3608  
3609      var alreadyIncluded;
3610      if (includeChain == null) {
3611          alreadyIncluded = new Array();
3612      } else {
3613          alreadyIncluded = includeChain;
3614      }
3615      alreadyIncluded.push(type);
3616      
3617      // This variable holds the result set returned.
3618      var commandNodeList = new Array();
3619      
3620      // Fetch commands directly attached to package node
3621      var directCommandNodes = null;
3622      switch (type) {
3623      case "install":
3624          directCommandNodes = packageNode.selectNodes("install");
3625          break;
3626      case "upgrade":
3627          directCommandNodes = packageNode.selectNodes("upgrade");
3628          break;
3629      case "downgrade":
3630          directCommandNodes = packageNode.selectNodes("downgrade");
3631          break;
3632      case "remove":
3633          directCommandNodes = packageNode.selectNodes("remove");
3634          break;
3635      default:
3636          // Command type is none of the "default" types This command type is
3637          // supported in command nodes only.
3638          break;
3639      }
3640  
3641      // Fetch command-nodes from <commands><command type="type" /></commands> structure.
3642      var commandNodes = packageNode.selectNodes("commands/command[@type=\"" + type + "\"]");
3643  
3644      // Merge command lists.
3645      if (directCommandNodes != null) {
3646          for (var iCmd=0; iCmd < directCommandNodes.length; iCmd++) {
3647              commandNodeList.push(directCommandNodes[iCmd]);
3648          }
3649      }
3650      if (commandNodes != null) {
3651          for (var iCmd=0; iCmd < commandNodes.length; iCmd++) {
3652              commandNodeList.push(commandNodes[iCmd]);
3653          }
3654      }
3655      
3656      // Filter out all packages which do not apply to current host.
3657      commandNodeList = filterConditionalNodes(commandNodeList, true);
3658  
3659      // Expand command includes.
3660      // Create array which is returned as a complete command list.
3661      var fullCommandList = new Array();
3662  
3663      // Check all commands for inclusion.
3664      for (var iTypeCommands=0; iTypeCommands<commandNodeList.length; iTypeCommands++) {
3665          var command = commandNodeList[iTypeCommands];
3666          var include = getCommandInclude(command);
3667          
3668          // Inclusion found.
3669          if (include != null) {
3670              dinfo("Found inclusion for command type " + include + ".");
3671              
3672              // Clone array of already included command types which helps to
3673              // detect duplicated includes.
3674              // The same loop will check whether the type to be included has
3675              // already been included (recursive inclusion detection).
3676              var prevIncluded = new Array();
3677              for (var j=0; j<alreadyIncluded.length; j++) {
3678                  var includeElement = alreadyIncluded[j];
3679                  if (includeElement == include) {
3680                      throw new Error("Recursive inclusion detected!");
3681                  } else {
3682                      prevIncluded.push(alreadyIncluded[j]);
3683                  }
3684              }
3685  
3686              // Fetch commands of specified type (if any)
3687              var includedCommands = getPackageCmd(packageNode, include, prevIncluded);
3688  
3689              // Insert fetched commands to command list.
3690              if (includedCommands != null) {
3691                  for (var iIncCmds=0; iIncCmds<includedCommands.length; iIncCmds++) {
3692                      fullCommandList.push(includedCommands[iIncCmds]);
3693                  }
3694              }
3695          } else {
3696              // Include command in command-list.
3697              fullCommandList.push(command);
3698          }
3699      }
3700  
3701      // Return list of applying commands.
3702      return fullCommandList;
3703  }
3704  
3705  
3706  /**
3707   * Returns array of package IDs which represent the package dependencies.
3708   * Returns empty array in case the package does not have any dependency.
3709   * 
3710   * @param packageNode
3711   *            the package node to read the list of dependencies from
3712   * @param packageList
3713   *            optional reference to an array which is used to insert the
3714   *            dependencies to. Specify null to create a new Array
3715   * @return Array specified in packageList parameter extended by package IDs
3716   *         (string values) which represent the dependencies
3717   */
3718  function getPackageDependencies(packageNode, packageList) {
3719      // output array
3720      if (packageList == null) {
3721          packageList = new Array();
3722      }
3723  
3724      if(packageNode != null) {
3725          var dependNodes = packageNode.selectNodes("depends");
3726          if (dependNodes != null) {
3727              // Get only dependencies which match the current host.
3728              var matchingDependNodes = filterConditionalNodes(dependNodes, true);
3729              for (var i=0; i < matchingDependNodes.length; i++) {
3730                  var dependId = matchingDependNodes[i].getAttribute("package-id");
3731  
3732                  // convert to lower case if case-insensitive mode is on
3733                  if (dependId != null) {
3734                      if (!isCaseSensitive()) {
3735                          dependId = dependId.toLowerCase();
3736                      }
3737                      packageList.push(dependId);
3738                  }
3739              }
3740          }
3741      }
3742  
3743      return packageList;
3744  }
3745  
3746  /**
3747   * Returns the package execute attribute value (String)
3748   * 
3749   * @param packageNode
3750   *            the package node to get the attribute from
3751   * @return package execute attribute value, empty string if undefined
3752   */
3753  function getPackageExecute(packageNode) {
3754      var execAttr = packageNode.getAttribute("execute");
3755      if (execAttr == null) {
3756          execAttr = "";
3757      }
3758      return execAttr;
3759  }
3760  
3761  /**
3762   * Returns the package ID string from the given package XML node.
3763   * 
3764   * @return package ID
3765   */
3766  function getPackageID(packageNode) {
3767      return packageNode.getAttribute("id");
3768  }
3769  
3770  /**
3771   * Returns array of package IDs which represent the package includes. Returns
3772   * empty array in case the package does not have any dependency.
3773   * 
3774   * @param packageNode
3775   *            the package node to read the list of includes from
3776   * @param packageList
3777   *            optional reference to an array which is used to insert the
3778   *            includes to. Specify null to create a new Array
3779   * @return Array specified in packageList parameter extended by package IDs
3780   *         (string values) which represent the includes
3781   */
3782  function getPackageIncludes(packageNode, packageList) {
3783      // output array
3784      if (packageList == null) {
3785          packageList = new Array();
3786      }
3787  
3788      if(packageNode != null) {
3789          var includeNodes = packageNode.selectNodes("include");
3790          if (includeNodes != null) {
3791              var matchingIncludeNodes = filterConditionalNodes(includeNodes, true);
3792              for (var i=0; i < matchingIncludeNodes.length; i++) {
3793                  var dependId = matchingIncludeNodes[i].getAttribute("package-id");
3794  
3795                  // convert to lower case if case-insensitive mode is on
3796                  if (dependId != null) {
3797                      if (!isCaseSensitive()) {
3798                          dependId = dependId.toLowerCase();
3799                      }
3800                      packageList.push(dependId);
3801                  }
3802              }
3803          }
3804      }
3805  
3806      return packageList;
3807  }
3808  
3809  /**
3810   * Reads the "manualInstall" attribute from a package node.
3811   * This attribute is true only if the package as installed manually via
3812   * command line. It is false for packages which are initially installed by
3813   * package synchronization.
3814   * 
3815   * @param packageNode the package from which the attribute is read.
3816   * @returns {Boolean} True if package was installed manually, false if it is
3817   *        applied by profile.
3818   */
3819  function getPackageManualInstallation(packageNode) {
3820      // Initialize return variable.
3821      var isManualInstall = false;
3822  
3823      // Read yctual value.
3824      var manualInstall = packageNode.getAttribute("manualInstall");
3825  
3826      // Evaluate result.
3827      if (manualInstall != null && manualInstall == "true") {
3828          isManualInstall = true;
3829      }
3830      return isManualInstall;
3831  }
3832  
3833  /**
3834   * Returns the package name from the given package XML node
3835   * 
3836   * @return returns the package name attribute - empty string if no name is
3837   *         defined
3838   */
3839  function getPackageName(packageNode) {
3840      var packageName = "";
3841      if(packageNode != null) {
3842          packageName = packageNode.getAttribute("name");
3843          if (packageName == null) {
3844              packageName = "";
3845          }
3846      }
3847      return packageName;
3848  }
3849  
3850  /**
3851   * Returns the corresponding package XML node from the package database
3852   * (packages.xml). Returns null in case no such package exists.
3853   */
3854  function getPackageNode(packageID) {
3855      // get first node which matched the specified ID
3856      return getPackages().selectSingleNode("package[@id='" + packageID +"']");
3857  }
3858  
3859  /**
3860   * Returns the corresponding package XML node to the requested package ID by
3861   * searching the packages database first. If the package cannot be located
3862   * within the package database it prints an error and looks for the node within
3863   * the local settings database.
3864   * If even the local database does not contain such a package entry then it
3865   * prints an error about missing package definition. In case '/quitonerror' is
3866   * set it exits.
3867   *
3868   * If the package could be located within the local package database it prints
3869   * a warning and returns the local package node.
3870   *
3871   * Algorithmic description:
3872   *
3873   * <pre>
3874   * search package node within local package database
3875   * if found
3876   *         return it
3877   * else
3878   *         print warning
3879   *         look for package within local settings
3880   *         if found
3881   *             print warning
3882   *             return it
3883   *         else
3884   *             print error (or exit by throwing error in case of /quitonerror)
3885   *             return null
3886   *         fi
3887   * fi
3888   * </pre>
3889   */
3890  function getPackageNodeFromAnywhere(packageID) {
3891      var packageNode = null;
3892  
3893      // try to get package node from package database
3894      var packageDBNode = getPackageNode(packageID);
3895  
3896      // check if node exists; if not then try to get the node from the settings
3897      if(packageDBNode != null) {
3898          // package found in package database, mark for installation/upgrade
3899          dinfo("Found package node '" + getPackageName(packageDBNode) + "' (" +
3900                  getPackageID(packageDBNode) + ") in package database.");
3901          packageNode = packageDBNode;
3902      } else {
3903          // error package not in package database
3904          // looking for package node within the local settings file
3905          /*
3906           * var packageNotFoundMessage = "Profile inconsistency: Package '" + packageID + "' does not exist within the
3907           * package database. " + "Please contact your system administrator!";
3908           * 
3909           * warning(packageNotFoundMessage);
3910           */
3911  
3912          // try to get package node from local settings
3913          var packageSettingsNode = getSettingNode(packageID);
3914  
3915          // if no package definition has been found jet the package is not
3916          // installed
3917          if(packageSettingsNode != null) {
3918              // Check if the package has been manually installed.
3919              var messageLocalOnly = "";
3920              var isManualInstall = getPackageManualInstallation(packageSettingsNode);
3921              if (isManualInstall == true) {
3922                  messageLocalOnly = "Manually installed package not found in server database.";
3923              } else {
3924                  messageLocalOnly = "Database inconsistency: Package with package ID '" +
3925                                  packageID + "' missing in package database. Package information " +
3926                                  "found on local installation:\n";
3927              }
3928              messageLocalOnly += "Package ID: " + messageLocalOnly + "\n" +
3929                              "Package Name: " + getPackageName(packageSettingsNode) + "\n" +
3930                              "Package Revision: " + getPackageRevision(packageSettingsNode) + "\n";
3931              warning(messageLocalOnly);
3932              packageNode = packageSettingsNode;
3933          } else {
3934              var messageNotFound = "Database inconsistency: Package with ID '" + packageID +
3935                      "' does not exist within the package database or the local settings file. " +
3936                      "Please contact your system administrator!";
3937              if (isQuitOnError()) {
3938                  throw new Error(messageNotFound);
3939              } else {
3940                  error(messageNotFound);
3941              }
3942          }
3943      }
3944  
3945      // return result
3946      return packageNode;
3947  }
3948  
3949  /**
3950   * Returns an array of all package nodes that can be installed. This list
3951   * includes all packages found in the package database. It does not include
3952   * local packages from the settings file (currently installed ones).
3953   * 
3954   * @return Array containing XML nodes (package nodes). Array might be of size 0
3955   */
3956  function getPackageNodes() {
3957      // Retrieve packages.
3958      var packageNodes = getPackages().selectNodes("package");
3959  
3960      // make sure a package ID exists only once
3961      packageNodes = uniqueAttributeNodes(packageNodes, "id");
3962  
3963      // return this array
3964      return packageNodes;
3965  }
3966  
3967  /**
3968   * Returns the package notify attribute value
3969   * 
3970   * @param packageNode
3971   *            the package node to get the notify attribute from
3972   * @return Notify attribute value (true in case of String "true" false
3973   *         otherwise.
3974   */
3975  function getPackageNotify(packageNode) {
3976      var returnvalue = true;
3977      var notify = packageNode.getAttribute("notify");
3978      if (notify == "false") {
3979          returnvalue = false;
3980      }
3981      return returnvalue;
3982  }
3983  
3984  /**
3985   * Returns the package priority from the given package XML node
3986   * 
3987   * @return package priority - returns 0 if no priority is defined
3988   */
3989  function getPackagePriority(packageNode) {
3990      var priority = packageNode.getAttribute("priority");
3991      if (priority == null) {
3992          priority = 0;
3993      }
3994      return parseInt(priority);
3995  }
3996  
3997  
3998  /**
3999   * Returns the package reboot attribute value. This attribute can add
4000   * additional reboots but not limit or invalidate reboot flags set on the
4001   * command-level.
4002   *
4003   * This value can have three states:
4004   * 
4005   * <pre>
4006   * "true"      Immediate reboot after package installation.
4007   *             This will take precedence of any command-level reboot="postponed"
4008   *             attribute if present and reboot immediately after package
4009   *             installation.
4010   *             A reboot="true" attribute on command-level will still result in
4011   *             an immediate reboot.
4012   *             Resulting status depending on command-level reboot flag:
4013   *             "true"      immediate reboot after command execution
4014   *             "delayed"   reboot after package installation
4015   *             "postponed" reboot after package installation
4016   *             "false"     reboot after package installation
4017   * "postponed" Schedule reboot after installing all packages within this
4018   *             session, for example after synchronizing.
4019   *             Resulting status depending on command-level reboot flag:
4020   *             "true"      immediate reboot after command execution
4021   *             "delayed"   reboot after package installation
4022   *             "postponed" reboot after all actions are completed
4023   *             "false"     reboot after all actions are completed
4024   * "false"     No reboot unless one is defined at command-level.
4025   * or not set  Resulting status depending on command-level reboot flag:
4026   *             "true"      immediate reboot after command execution
4027   *             "delayed"   reboot after package installation
4028   *             "postponed" reboot after all actions are completed
4029   *             "false"     no reboot
4030   * </pre>
4031   *
4032   * As a result there are four possibilities to schedule a reboot in order of
4033   * precedence:
4034   * 
4035   * <pre>
4036   * immediate   Command node specified reboot=true, immediate reboot takes place.
4037   * package     Reboot is issued right after installing:
4038   *             - package specifies reboot="true"
4039   *               OR
4040   *             - any command node specified reboot="delayed"
4041   * postponed   Reboot will take place after all packages have been applied.
4042   *             - package specifies reboot="postponed"
4043   *               OR
4044   *             - any command node specified reboot="postponed"
4045   * none        No reboot is issued by this package:
4046   *             - package does not specify reboot or specifies reboot="false"
4047   *               AND
4048   *             - no command node specified any form of reboot reboot
4049   * </pre>
4050   *
4051   * This means that an immediate reboot always has the highest priority. You
4052   * can just set "reboot markers" on a "timeline" on package and command level
4053   * where the closest reboot marker will be executed:
4054   * immediate => package => postponed => none
4055   *
4056   * @return one of the states (string values):
4057   *             "true", always reboot after package installation
4058   *             "postponed", reboot before script exits
4059   *             "false", reboot only if command specified reboot=delayed/postponed
4060   *
4061   */
4062  function getPackageReboot(packageNode) {
4063      var rebootAction = "false";
4064      var packageReboot = packageNode.getAttribute("reboot");
4065      if (packageReboot != null) {
4066          if (packageReboot == "true") {
4067              rebootAction = packageReboot;
4068          } else if (packageReboot == "postponed") {
4069              rebootAction = packageReboot;
4070          }
4071      }
4072      return rebootAction;
4073  }
4074  
4075  /**
4076   * Adds all packages referenced by the specified package node to the given
4077   * array. In other words all dependencies, chained packages and includes of the
4078   * given node will be appended to the array. If you specify null or an empty
4079   * array the array returned will contain all packages from the dependency tree
4080   * of the given package node.
4081   * 
4082   * @param packageNode
4083   *            full dependency tree of the specified package will be added to the
4084   *            given array.
4085   * @param packageArray
4086   *            Array to which all referenced packages are added to. Specify null
4087   *            to create a new array finally containing only the dependency tree
4088   *            of the specified package.
4089   * @return array containing all referenced packages (full package nodes). NOTE:
4090   *         The returned array is not sorted.
4091   */
4092  function getPackageReferences(packageNode, packageArray) {
4093      if (packageArray == null) {
4094          packageArray = new Array();
4095      }
4096  
4097      // get dependencies, includes and chains
4098      var linkedPackageIDs = getPackageDependencies(packageNode, null);
4099      getPackageIncludes(packageNode, linkedPackageIDs);
4100      getPackageChained(packageNode, linkedPackageIDs);
4101  
4102      // add nodes if they are not yet part of the array
4103      for (var i=0; i < linkedPackageIDs.length; i++) {
4104          var currentNode = getPackageNodeFromAnywhere(linkedPackageIDs[i]);
4105          if (currentNode != null) {
4106              if(!searchArray(packageArray, currentNode)) {
4107                  dinfo("Adding referenced package '" + getPackageName(currentNode) + "' (" +
4108                          getPackageID(currentNode) + ") for package '" +
4109                          getPackageName(packageNode) + "' (" + getPackageID(packageNode) +
4110                          ")");
4111                  // add the package first (so it's not added again, this prevents
4112                  // loops)
4113                  packageArray.push(currentNode);
4114  
4115                  // add dependencies of these package as well
4116                  getPackageReferences(currentNode, packageArray);
4117              } else {
4118                  dinfo("Referenced package '"  + getPackageName(currentNode) + "' (" +
4119                          getPackageID(currentNode) + ") for package '" +
4120                          getPackageName(packageNode) + "' (" + getPackageID(packageNode) +
4121                          ") already added.");
4122              }
4123          }
4124      }
4125  }
4126  
4127  /**
4128   * Returns the package version string from the given package XML node. Returns 0
4129   * if package has no revision specified.
4130   * 
4131   * @return String representing the package revision (might be a dot-separated
4132   *         version) <#>[.<#>]*
4133   */
4134  function getPackageRevision(packageNode) {
4135      var packageRevision = packageNode.getAttribute("revision");
4136      if (packageRevision == null) {
4137          // set to string "0" if no revision is defined
4138          packageRevision = 0 + "";
4139      } else {
4140          // check if the revision contains the "%" character (environment
4141          // variable)
4142          if( packageRevision.match(new RegExp("%.+%"), "ig") ) {
4143              // Generate the correct environment.
4144              var previousEnv = getEnv();
4145  
4146              // set package specific environment
4147              loadPackageEnv(packageNode);
4148  
4149              // expand environment strings
4150              var wshObject = new ActiveXObject("WScript.Shell");
4151              packageRevision = wshObject.ExpandEnvironmentStrings(packageRevision);
4152  
4153              // reset environment
4154              loadEnv(previousEnv);
4155          }
4156      }
4157      return packageRevision;
4158  }
4159  
4160  /**
4161   * Returns XML node which contains all packages (package database).
4162   */
4163  function getPackages() {
4164      if(packages == null) {
4165          var newPackages = createPackages();
4166          setPackages(newPackages);
4167      }
4168      return packages;
4169  }
4170  
4171  /**
4172   * Returns the action to be performed on a given package if the package is
4173   * applied to the current host.
4174   * Valid actions are:
4175   * "none"      No action; package installed already
4176   * "install"   Installation, package is new on the host
4177   * "upgrade"   Upgrade package which already exists on the system
4178   *             New version higher than installed version
4179   * "downgrade" Downgrade package which already exists on the system
4180   *             New version lower than installed version
4181   * 
4182   * @param packageNode
4183   *           The package to be checked.
4184   * @returns Action to be performed. Can be 0=nothing, 1=install, 2=upgrade, 3=downgrade.
4185   */
4186  function getPackageInstallAction(packageNode) {
4187      // Action to be performed when
4188      var actionNone = "none";
4189      var actionInstall = "install";
4190      var actionUpgrade = "upgrade";
4191      var actionDowngrade = "downgrade";
4192      var action = actionNone;
4193  
4194      var packageName = getPackageName(packageNode);
4195      var packageID   = getPackageID(packageNode);
4196      var packageRev  = getPackageRevision(packageNode);
4197      var executeAttr = getPackageExecute(packageNode);
4198      // var notifyAttr  = getPackageNotify(packageNode);
4199  
4200       // Search for the package in the local settings.
4201      var installedPackage = getSettingNode(packageID);
4202  
4203      // String to print in events which identifies the package.
4204      var packageMessage = "Package '" + packageName + "' (" + packageID + "): ";
4205  
4206      // Evaluate type of installation (install/upgrade/downgrade/none).
4207      // INSTALL:
4208      if (installedPackage == null) {
4209          // ONE-TIME INSTALL PACKAGE, NOT INSTALLED YET (according to settings)
4210          // Install the package after checking that it is not installed already.
4211          dinfo(packageMessage + "Not in local package database; Marking for installation.");
4212          action = actionInstall;
4213  
4214      // UPGRADE/DOWNGRADE:
4215      } else {
4216          // Get revision of installed package.
4217          var packageRevInstalled = getPackageRevision(installedPackage);
4218          // Compare Versions.
4219          var comparisonResult = versionCompare(packageRev, packageRevInstalled);
4220  
4221          if (comparisonResult > 0) {
4222              // ONE-TIME INSTALL PACKAGE, UPGRADE:
4223              info(packageMessage +
4224                  "Already installed but version mismatch.\n" +
4225                  "Installed revision: '" + packageRevInstalled + "'\n" +
4226                  "Available revision: '" + packageRev + "'.\n" +
4227                  "Preparing upgrade."
4228                  );
4229              action = actionUpgrade;
4230  
4231          } else if (comparisonResult < 0) {
4232              // ONE-TIME INSTALL PACKAGE, DOWNGRADE:
4233              info(packageMessage +
4234                  "Already installed but version mismatch.\n" +
4235                  "Installed revision '" + packageRevInstalled + "'\n" +
4236                  "Available revision: '" + packageRev + "'.\n" +
4237                  "Preparing downgrade."
4238                  );
4239              action = actionDowngrade;
4240  
4241          } else {
4242              // ONE-TIME INSTALL PACKAGE, ALREADY INSTALLED:
4243  
4244              if (executeAttr == "always") {
4245                  // ALWAYS EXECUTION PACKAGE
4246                  // Packages with exec attribute "always" will be installed on each run; regardless of their version.
4247                  dinfo(packageMessage + "Is requested to be executed 'always'. Preparing installation.");
4248                  action = actionInstall;
4249  
4250              } else if (isForceInstall()) {
4251                  // if installation is forced, install anyway
4252                  info(packageMessage + "Already installed. Re-installation enforced.");
4253                  action = actionInstall;
4254  
4255              } else {
4256                  // If execute is 'once' then package checks are not executed.
4257                  // We just trust that the package is installed.
4258                  if (executeAttr == "once") {
4259                      dinfo(packageMessage + "Installed already.");
4260                      action = actionNone;
4261                  } else {
4262                      // In case no execution attribute is defined
4263                      // check real package state.
4264                      if (getQueryMode() == "remote") {
4265                          // Assume package is properly installed.
4266                          action = actionNone;
4267                      } else {
4268                          // Verify that package is still installed.
4269                          if (isInstalled(installedPackage)) {
4270                              action = actionNone;
4271                          } else {
4272                              // Package found in local database but checks failed.
4273                              // Maybe the user uninstalled the package manually.
4274                              dinfo(packageMessage + "Installed but checks failed. Re-Installing.");
4275                              action = actionInstall;
4276                          }
4277                      }
4278                  }
4279              }
4280          }
4281      }
4282      return action;
4283  }
4284  
4285  /**
4286   * Returns list of packages which have been manually installed.
4287   * 
4288   * @returns List of packages manually installed in local settings database.
4289   *          Returns empty array if no package is found.
4290   */
4291  function getPackagesManuallyInstalled() {
4292      if (manuallyInstalled == null) {
4293          // Get list of currently installed packages.
4294          var settings = getSettings();
4295          
4296          // Filter manually installed packages.
4297          // Fetch command-nodes from <commands><command type="type" /></commands> structure.
4298          manuallyInstalled = settings.selectNodes("package[@manualInstall=\"true\"]");
4299  
4300          // Return empty array if no package is found.
4301          if (manuallyInstalled == null) {
4302              manuallyInstalled = new Array();
4303          }
4304      }
4305      return manuallyInstalled;
4306  }
4307  
4308  /**
4309   * Returns an array of packages which are not assigned to the current host any more.
4310   * 
4311   * Packages which are manually installed are not included in the list of packages
4312   * to be removed. Except if the package does not exist on server side any more.
4313   * Therefore in case a package is removed from the server it is removed from
4314   * clients as well even if the package was installed manually because it is to be
4315   * assumed tha the administrator no longer wants to support this type of software.
4316   *  
4317   * @return Array of packages which will be removed during synchronization.
4318   */
4319  function getPackagesRemoved() {
4320      dinfo("Evaluating packages to be removed.");
4321      /**
4322       * Get package nodes referenced within the profile (and profile
4323       * dependencies). This includes package dependencies as well.
4324       */
4325      var profilePackageNodes = getProfilePackageNodes();
4326  
4327      // Get list of currently installed packages.
4328      var installedPackages = getSettingNodes();
4329  
4330      // Array to store packages to be removed.
4331      var removablesArray = new Array();
4332  
4333      // Loop over each installed package and check whether it still applies.
4334      for (var iInstalledPkg = 0; iInstalledPkg < installedPackages.length; iInstalledPkg++) {
4335          var installedPackageNode = installedPackages[iInstalledPkg];
4336          dinfo("Found installed package '" + getPackageName(installedPackageNode) + "' (" +
4337                  getPackageID(installedPackageNode) + ").");
4338  
4339          // Search for the installed package in available packages.
4340          var found = false;
4341  
4342          for (var j=0; j < profilePackageNodes.length; j++) {
4343              var profilePackageNode = profilePackageNodes[j];
4344              if (getPackageID(installedPackageNode) == getPackageID(profilePackageNode)) {
4345                  dinfo("Package '" + getPackageName(installedPackageNode) + "' (" +
4346                          getPackageID(installedPackageNode) + ") found in profile packages.");
4347                  found = true;
4348                  break;
4349              }
4350          }
4351  
4352          // If package is no longer present, mark for remove if not installed manually.
4353          if (!found) {
4354              // Check if package was installed manually.
4355              // Manually installed packages remain on the system.
4356              var packageMessage = "Package '" + getPackageName(installedPackageNode) + "' (" +
4357              getPackageID(installedPackageNode) + "): ";
4358              var isManuallyInstalled = getPackageManualInstallation(installedPackageNode);
4359              if (isManuallyInstalled == true) {
4360                  if (isZombie(installedPackageNode)) {
4361                      // Package is not in server package database any more.
4362                      dinfo("Package was manually installed but is " +
4363                              "not in package database any more.  Marking package for removal.");
4364                      removablesArray.push(installedPackageNode);
4365                  } else {
4366                      dinfo("Package was manually installed and is " +
4367                              "still available in package database. Keeping package.");
4368                  }
4369              } else {
4370                  dinfo(packageMessage + "Marked for removal.");
4371                  removablesArray.push(installedPackageNode);
4372              }
4373          }
4374      }
4375  
4376      return removablesArray;
4377  }
4378  
4379  
4380  /**
4381   * Returns a list of variables for the given package.
4382   * 
4383   * @param packageNode
4384   *            The package node to get the variables from.
4385   * @param array
4386   *            Object of type Array to which the the variables appended.
4387   *            In case null is supplied it returns a new Array object.
4388   * @return Object of type Scripting.Dictionary which contains all key/value
4389   *         pairs from the given package including its dependencies
4390   */
4391  function getPackageVariables(packageNode, array) {
4392      dinfo("Reading variables from package '" + getPackageName(packageNode) + "'.");
4393      array = getVariables(packageNode, array);
4394      return array;
4395  }
4396  
4397  /**
4398   * Returns array of profile nodes which represent the profile dependencies.
4399   * Returns empty array in case the profile does not have any dependency.
4400   * 
4401   * @return Array of strings representing the references to dependent profiles
4402   */
4403  function getProfileDependencies(profileNode) {
4404      // output array
4405      var dependencyNodes = new Array();
4406  
4407      var dependNodes = profileNode.selectNodes("depends");
4408      if (dependNodes != null) {
4409          // Get only dependencies which match the current host.
4410          var matchingDependNodes = filterConditionalNodes(dependNodes, true);
4411          for (var i=0; i < matchingDependNodes.length; i++) {
4412              var dependencyId = matchingDependNodes[i].getAttribute("profile-id");
4413  
4414              // convert dependency to lower case if case-sensitive mode is off
4415              if (dependencyId != null && !isCaseSensitive()) {
4416                  dependencyId = dependencyId.toLowerCase();
4417              }
4418  
4419              // get the profile node
4420              var dependencyNode = getProfileNode(dependencyId);
4421              if (dependencyNode != null) {
4422                  dependencyNodes.push(dependencyNode);
4423              } else {
4424                  error("Profile '" + dependencyId + "' referenced but not " +
4425                          "found. Ignoring profile.");
4426              }
4427          }
4428      }
4429  
4430      return dependencyNodes;
4431  }
4432  
4433  /**
4434   * Returns the corresponding profile ID stored within the given profile XML
4435   * node.
4436   * 
4437   * @return String representing the ID of the supplied profile node.
4438   */
4439  function getProfileID(profileNode) {
4440      return profileNode.getAttribute("id");
4441  }
4442  
4443  /**
4444   * Returns an array of strings which represents the profiles directly referenced
4445   * by the applying host node. The profiles are evaluated as follows:
4446   * <pre>
4447   * - /profile:<profile> parameter
4448   * - /host:<hostname> parameter matching within hosts.xml
4449   * - profiles defined within host.xml which are assigned to the matching hosts entry
4450   * </pre>
4451   *
4452   * @return array of strings representing the referenced profiles
4453   */
4454  function getProfileList() {
4455      if (applyingProfilesDirect == null) {
4456          var profilesMatching = new Array();
4457  
4458          // get arguments
4459          var argn = getArgv().Named;
4460  
4461          // Set the profile from either the command line or the hosts file.
4462          if (argn("profile") != null) {
4463              profilesMatching.push(argn("profile"));
4464          } else {
4465              var hostNodes = getHostsApplying();
4466              for (var ihostNode=0; ihostNode < hostNodes.length; ihostNode++) {
4467                  profilesMatching = profilesMatching.concat(getHostProfiles(hostNodes[ihostNode]));
4468              }
4469              if (profilesMatching.length <= 0) {
4470                  throw new Error("Could not find any profile for host " + getHostname() + ".");
4471              }
4472          }
4473          applyingProfilesDirect = profilesMatching;
4474      }
4475      return applyingProfilesDirect;
4476  }
4477  
4478  /**
4479   * Returns the corresponding profile XML node from the profile database
4480   * (profile.xml). Returns null in case no such profile exists.
4481   * 
4482   * @param profileID
4483   *            String representation of profile to get the node from.
4484   */
4485  function getProfileNode(profileID) {
4486      // get first node which matched the specified ID
4487      return getProfiles().selectSingleNode("profile[@id='" + profileID +"']");
4488  }
4489  
4490  /**
4491   * Returns an array of all profile nodes available.
4492   * 
4493   * @return array of profile XML nodes.
4494   */
4495  function getProfileNodes() {
4496      // Retrieve packages.
4497      var profileNodes = getProfiles().selectNodes("profile");
4498  
4499      // make sure a package ID exists only once
4500      profileNodes = uniqueAttributeNodes(profileNodes, "id");
4501  
4502      // return this array
4503      return profileNodes;
4504  }
4505  
4506  /**
4507   * Returns an array of strings which contains a list of package IDs referenced
4508   * by the currently applied profile(s).
4509   * 
4510   * The list will contain all referenced IDs within profile.xml which apply to
4511   * the current profile(s) (including profile dependencies). Packages which are
4512   * referenced but do not exist within the package database (packages.xml) are
4513   * included as well. So be aware that in case of inconsistency between
4514   * profiles.xml and packages.xml it might be possible that the returned list
4515   * refers to packages not available within packages.xml.
4516   * 
4517   * NOTE: The list does NOT contain IDs of package dependencies. Just the list of
4518   * packages as referred in profiles.xml. Dependency information is only available
4519   * within the concrete package nodes within packages.xml. Refer to
4520   * getProfilePackageNodes() to get packages including dependencies.
4521   * 
4522   * If you like to get a list of full package nodes have a look at
4523   * getProfilePackageNodes() but note that it cannot return full nodes for
4524   * packages referenced within profiles.xml but missing in the package database.
4525   * 
4526   * @return array of package IDs applying to this profile (empty array if no
4527   *         packages are assigned).
4528   */
4529  function getProfilePackageIDs() {
4530      // Get array of all profiles that apply to the base profile.
4531      // This includes depending profiles
4532      var profileArray = getProfilesApplying();
4533  
4534      // Create array to store all referenced package IDs
4535      var packageIDs = new Array();
4536  
4537      // New date object, used for install/uninstall date comparison.
4538      var now = new Date();
4539  
4540      // Add each profile's package IDs to the array.
4541      for (var i=0; i < profileArray.length; i++) {
4542          profileNode = profileArray[i];
4543  
4544          // Load profile environment.
4545          var previousEnv = getEnv();
4546  
4547          // Array to store all variables found.
4548          var variables =  new Array();
4549  
4550          // Host variables first...
4551          variables = getHostsVariables(variables);
4552  
4553          // Get variables of this profile.
4554          variables = getVariables(profileNode, variables);
4555  
4556          // Apply variables to environment.
4557          for (var iVariable=0; iVariable < variables.length; iVariable++) {
4558              var varDefinition = variables[iVariable];
4559              var variableKeys = varDefinition.keys().toArray();
4560              for (var iVarKey = 0; iVarKey < variableKeys.length; iVarKey++) {
4561                  var key = variableKeys[iVarKey];
4562                  var value = varDefinition.Item(key);
4563                  setEnv(key, value);
4564              }
4565          }
4566  
4567          // Fetch packages from profile.
4568          var profilePackageNodes = profileNode.selectNodes("package");
4569          // Filter out packages which shall not apply to this host
4570          var packageNodes = filterConditionalNodes(profilePackageNodes, true);
4571  
4572          // Add all package IDs to the array and avoid duplicates
4573          for (var j = 0; j < packageNodes.length; j++) {
4574              // get package ID
4575              var packageNode = packageNodes[j];
4576              var packageId = packageNode.getAttribute("package-id");
4577              // Skip package if package ID is not defined.
4578              if (packageId == null || packageId == "") {
4579                  continue;
4580              }
4581              
4582              // Use package methods for profile package node because the
4583              // attribute is the same.
4584              var installDate = getProfilePackageInstallDate(packageNode);
4585              var uninstallDate = getProfilePackageUninstallDate(packageNode);
4586              var includePackage = true;
4587  
4588              // Check if package
4589              
4590              // Check if the package should be included regarding installation
4591              // period.
4592              if (installDate != null || uninstallDate != null) {
4593                  // either install or uninstall date was defined
4594                  if (now >= installDate &&
4595                      now <= uninstallDate) {
4596                      includePackage = true;
4597                      dinfo("Package'" + packageId + "' specified an install date range: " +
4598                          installDate + " to " + uninstallDate +
4599                          "; current time (" + now + ") is within the time frame. Including package.");
4600                  } else {
4601                      includePackage = false;
4602                      dinfo("Package '" + packageId + "' specified an install date range: " +
4603                          installDate + " to " + uninstallDate +
4604                          "; out of range, skipping package (local time: " + now + ").");
4605                  }
4606              }
4607  
4608              // Search array for pre-existing ID, we don't want duplicates.
4609              if (includePackage) {
4610                  // Check if package shall be included case-sensitive. If not;
4611                  // convert to lower-case.
4612                  if (!isCaseSensitive()) {
4613                      packageId = packageId.toLowerCase();
4614                  }
4615                  var alreadyAdded = false;
4616                  for (var k=0; k < packageIDs.length; k++) {
4617                      if (packageIDs[k] == packageId) {
4618                          alreadyAdded = true;
4619                          break;
4620                      }
4621                  }
4622                  if (!alreadyAdded) {
4623                      packageIDs.push(packageId);
4624                  }
4625              }
4626          }
4627          // Restore environment.
4628          loadEnv(previousEnv);
4629      }
4630  
4631      return packageIDs;
4632  }
4633  
4634  /**
4635   * Returns date object reflecting installation date defined in given node
4636   * 
4637   * @param packageNode
4638   *            the package definition node as specified within the profile
4639   *            definition
4640   * @return date object representing installation date. Null if date is undefined.
4641   */
4642  function getProfilePackageInstallDate(packageNode) {
4643      var installDate = null;
4644      var packageInstallDate = packageNode.getAttribute("installdate");
4645      if (packageInstallDate != null) {
4646          installDate = parseISODate(packageInstallDate, false);
4647      }
4648      return installDate;
4649  }
4650  
4651  /**
4652   * Returns an array of package nodes that should be applied to the current
4653   * profile. This function returns full package nodes.
4654   *
4655   * NOTE: Since the profile
4656   * just contains the package IDs referenced within profiles.xml but not
4657   * existing within the packages database (packages.xml) will not be part of the
4658   * list.
4659   * 
4660   * In case you like to get a list of package IDs referenced by the profile
4661   * (regardless if the package definition exists) have a look at
4662   * getProfilePackageIDs().
4663   * 
4664   * @return array of package nodes applying to the assigned profile(s)
4665   */
4666  function getProfilePackageNodes() {
4667      if (profilePackageNodes == null) {
4668          // Create a new empty package array.
4669          packageNodes = new Array();
4670  
4671          /*
4672           * get package IDs which apply to the profile (without dependencies, includes and chained packages) regardless
4673           * if the package definition is available or not.
4674           */
4675          var packageIDs = getProfilePackageIDs();
4676  
4677          // get package definitions and all dependencies
4678          for ( var i = 0; i < packageIDs.length; i++) {
4679              var packageID = packageIDs[i];
4680              dinfo("Adding package with ID '" + packageID + "' to profile packages.");
4681              var packageNode = getPackageNodeFromAnywhere(packageID);
4682  
4683              // add dependencies first
4684              if (packageNode != null) {
4685                  getPackageReferences(packageNode, packageNodes);
4686                  if (!searchArray(packageNodes, packageNode)) {
4687                      // Add the new node to the array _after_ adding dependencies.
4688                      packageNodes.push(packageNode);
4689                  }
4690              }
4691          }
4692          profilePackageNodes = packageNodes;
4693      }
4694      return profilePackageNodes;
4695  }
4696  
4697  /**
4698   * Returns Date representation of 'uninstalldate' attribute from the given
4699   * package definition as specified within the profile.
4700   * 
4701   * @param packageNode
4702   *            the package node to read the 'uninstalldate' attribute from
4703   * @return Date object representing uninstall date of the given package. Returns
4704   *         null in case the 'uninstalldate' attribute is not set.
4705   */
4706  function getProfilePackageUninstallDate(packageNode) {
4707      var uninstallDate = null;
4708      var packageUninstallDate = packageNode.getAttribute("uninstalldate");
4709      if (packageUninstallDate != null) {
4710          uninstallDate = parseISODate(packageUninstallDate, true);
4711      }
4712      return uninstallDate;
4713  }
4714  
4715  /**
4716   * Returns XML node which contains all profiles (profile database).
4717   */
4718  function getProfiles() {
4719      if(profiles == null) {
4720          var newProfiles = createProfiles();
4721          setProfiles(newProfiles);
4722      }
4723      return profiles;
4724  }
4725  
4726  /**
4727   * Returns an array of profile nodes that should be applied to the current
4728   * profile. This includes also all profile dependencies.
4729   * 
4730   * @return array of profiles (directly associated profiles and dependencies)
4731   */
4732  function getProfilesApplying() {
4733      dinfo("Getting profiles which apply to this node.");
4734      if (applyingProfilesAll == null) {
4735          // create cache entry
4736          var profilesApplying = new Array();
4737  
4738          // get list of applying profiles
4739          var profileList = getProfileList();
4740  
4741          for (var i=0; i<profileList.length; i++) {
4742              // receive profile node
4743              var profileNode = getProfileNode(profileList[i]);
4744  
4745              if (profileNode != null) {
4746                  dinfo("Applying profile: " + getProfileID(profileNode));
4747  
4748                  // Add the current profile's node as the first element in the
4749                  // array.
4750                  profilesApplying.push(profileNode);
4751  
4752                  appendProfileDependencies(profilesApplying, profileNode);
4753              } else {
4754                  error("Profile '" + profileList[i] + "' applies to this host but was not found!");
4755              }
4756          }
4757          applyingProfilesAll = profilesApplying;
4758      }
4759      return applyingProfilesAll;
4760  }
4761  
4762  /**
4763   * Returns the log level associated with a given profile.
4764   * 
4765   * @return merged log levels from all applying profiles. For example if one
4766   *         profile specifies info logging and a second profile specifies error.
4767   *         The resulting log level will be info+error. Returns null if no custom
4768   *         log level is specified for this profile.
4769   */
4770  function getProfilesLogLevel() {
4771      // set initial bitmask to 0x00;
4772      var logLevel = 0x00;
4773  
4774      // merge log levels
4775      try {
4776          var profileList = getProfileList();
4777          for (var i=0; i<profileList.length; i++) {
4778              var profileId = profileList[i];
4779              var profileNode = getProfileNode(profileId);
4780              if (profileNode != null) {
4781                  // add bitmask
4782                  logLevel = logLevel | profileNode.getAttribute("logLevel");
4783              }
4784          }
4785      } catch (e) {
4786          // Unable to read profile-specific log leve.
4787          // Maybe there is no profile found for this host.
4788          dinfo("No profile-specific log level found.");
4789      }
4790      if (logLevel > 0x00) {
4791          return logLevel;
4792      } else {
4793          return null;
4794      }
4795  }
4796  
4797  /**
4798   * Returns a list of variables from the Profile.
4799   * 
4800   * @param array
4801   *            Object of type Array to which the the variables are appended.
4802   *            In case null is supplied it returns a new Array object.
4803   * @return Object of type Scripting.Dictionary which contains all key/value
4804   *         pairs from the given profile including its dependencies
4805   */
4806  function getProfileVariables(array) {
4807      dinfo("Reading variables from profile[s]");
4808      if (profilesVariables == null) {
4809          profilesVariables = new Array();
4810          var profileArray = getProfilesApplying();
4811          // dinfo(profileArray.length + " profiles apply to this host.");
4812  
4813          /*
4814           * add each profile's variables to the array in reverse order reversing the order is done in order to allow
4815           * overwriting of variables from dependent profiles
4816           */
4817  
4818          for (var iProfiles=profileArray.length-1; iProfiles >= 0; iProfiles--) {
4819              var profileNode = profileArray[iProfiles];
4820              dinfo("Reading variables from profile " + getProfileID(profileNode));
4821  
4822              // Add variables from profile XML node.
4823              profilesVariables = getVariables(profileNode, profilesVariables);
4824          }
4825      }
4826  
4827      var concatenatedVariables = profilesVariables;
4828      if (array != null) {
4829          concatenatedVariables = profilesVariables.concat(array);
4830      }
4831  
4832      return concatenatedVariables;
4833  }
4834  
4835  /**
4836   * Returns current state of query mode.
4837   * @returns {String} Current query mode.
4838   */
4839  function getQueryMode() {
4840      return queryMode;
4841  }
4842  
4843  /**
4844   * Returns the corresponding package XML node from the settings database
4845   * (wpkg.xml). Returns null in case no such package is installed.
4846   * 
4847   * @param packageID
4848   *            ID of the package to be returned
4849   * @return returns package XML node as stored within the settings. Returns null
4850   *         if no such package exists.
4851   */
4852  function getSettingNode(packageID) {
4853      // get first node which matched the specified ID
4854      return getSettings().selectSingleNode("package[@id='" + packageID +"']");
4855  }
4856  
4857  
4858  /**
4859   * Tries to read host attributes from the settings database.
4860   * All host attributes found in the settings database will be used to override
4861   * attributes of the local host.
4862   */
4863  function getSettingHostAttributes() {
4864      // Fetch settings.
4865      var settings = getSettings();
4866      var attributes = settings.attributes;
4867  
4868      // Check whether attributes are defined.
4869      if (attributes.length > 0) {
4870  
4871          // Reset cache for host information.
4872          resetHostInformationCache();
4873          
4874          for (var iAttribute=0; iAttribute < attributes.length; iAttribute++) {
4875              var node = attributes.item(iAttribute);
4876              var attribute = node.nodeName;
4877  
4878              var value = node.nodeValue;
4879              switch (attribute) {
4880              case "hostname":
4881                  setHostname(value);
4882                  break;
4883  
4884              case "architecture":
4885                  setArchitecture(value);
4886                  break;
4887  
4888              case "os":
4889                  setHostOS(value);
4890                  break;
4891  
4892              case "ipaddresses":
4893                  var ipList = value.split(",");
4894                  setIPAddresses(ipList);
4895                  break;
4896  
4897              case "domainname":
4898                  setDomainName(value);
4899                  break;
4900  
4901              case "groups":
4902                  var hostGroupList = value.split(",");
4903                  setHostGroups(hostGroupList);
4904                  break;
4905  
4906              case "lcid":
4907                  setLocale(value);
4908                  break;
4909  
4910              case "lcidOS":
4911                  setLocaleOS(value);
4912                  break;
4913  
4914              default:
4915                  break;
4916              }
4917          }
4918      }
4919  }
4920  
4921  /**
4922   * Returns an array of all installed packages from the local wpkg.xml
4923   * 
4924   * @return Array of installed packages (XML nodes)
4925   */
4926  function getSettingNodes() {
4927      // retrieve packages
4928      var packageNodes = getSettings().selectNodes("package");
4929  
4930      // make sure a package ID exists only once
4931      // commented since the local database should not contain duplicated entries
4932      packageNodes = uniqueAttributeNodes(packageNodes, "id");
4933  
4934      // return this array
4935      return packageNodes;
4936  }
4937  
4938  /**
4939   * Returns current path to settings file.
4940   * 
4941   * @returns Settings file FS object.
4942   */
4943  function getSettingsPath() {
4944      if (settings_file == null || settings_file == "") {
4945          // Will be used for file operations.
4946          var fso = new ActiveXObject("Scripting.FileSystemObject");
4947  
4948          // Evaluate path.
4949          // Our default settings file is located in %SystemRoot%\system32.
4950          // If settings path was not specified via command line, then evaluate it
4951          // from the configuration file or fall back to default.
4952          if (settings_file_path == null) {
4953              var SystemFolder = 1;
4954              settings_file_path = fso.GetSpecialFolder(SystemFolder);
4955          }
4956          settings_file = settings_file_path + "\\" + settings_file_name;
4957          settings_file_processed = false;
4958      }
4959  
4960      if (!settings_file_processed) {
4961          // Check whether [PROFILE] epxression was used and repace it.
4962          var profileExp = new RegExp("\\[PROFILE\\]", "g");
4963          if (profileExp.test(settings_file) == true) {
4964              // This will throw an error if profile is not available yet.
4965              var profileList = getProfileList();
4966      
4967              // concatenate profile names or throw error if no names
4968              // available
4969              if (profileList.length > 0) {
4970                  var allProfiles = "";
4971                  for (var i=0; i<profileList.length; i++) {
4972                      if (allProfiles == "") {
4973                          allProfiles = profileList[i];
4974                      } else {
4975                          allProfiles += "-" + profileList[i];
4976                      }
4977                  }
4978                  settings_file = settings_file.replace(profileExp, allProfiles);
4979              } else {
4980                  throw new Error("Profile information not available.");
4981              }
4982          }
4983      
4984          // Check whether [HOSTNAME] expression was used and replace it.
4985          var hostnameExp = new RegExp("\\[HOSTNAME\\]", "g");
4986          if (hostnameExp.test(settings_file) == true) {
4987              settings_file = settings_file.replace(hostnameExp, getHostname());
4988          }
4989      }
4990  
4991      return settings_file;
4992  }
4993  
4994  /**
4995   * Returns XML node which contains all settings (local package database).
4996   */
4997  function getSettings() {
4998      if(settings == null) {
4999          var newSettings = createSettings();
5000          setSettings(newSettings, true);
5001      }
5002      return settings;
5003  }
5004  
5005  /**
5006   * Returns the checkResults node of the settings database.
5007   * 
5008   * @returns checkResults node of currently loaded settings database.
5009   */
5010  function getSettingsCheckResults() {
5011      var currentSettings = getSettings();
5012      var checkResults = currentSettings.selectSingleNode("checkResults");
5013      if (checkResults == null) {
5014          var xmlDoc = new ActiveXObject("Msxml2.DOMDocument.3.0");
5015          checkResults = xmlDoc.createElement("checkResults");
5016          currentSettings.appendChild(checkResults);
5017      }
5018      return checkResults;
5019  }
5020  
5021  /**
5022   * Adds the given check node to the checkResults list in the settings database.
5023   * 
5024   * @param checkNode Check XML node to be inserted.
5025   * @param result Result of the check on current node.
5026   */
5027  function addSettingsCheckResult(checkNode, result) {
5028      try {
5029          // Clone XML node to be added to settings.
5030          var settingsCheckNode = checkNode.cloneNode(false);
5031  
5032          // Check if there is already a check with the same attributes.
5033          var previousChecks = getSettingsCheck(settingsCheckNode);
5034  
5035          // Fetching checkResults node from settings.
5036          var checkResults = getSettingsCheckResults();
5037      
5038          // If a check was found then remove it from the results in order to avoid
5039          // duplicate entries. Checks might also be executed multiple times (with
5040          // potentially different results) and only the last result should be kept.
5041          if (previousChecks != null) {
5042              for (var i=0; i < previousChecks.length; i++) {
5043                  dinfo("Replacing check results of previous evaluation");
5044                  var previousCheck = previousChecks[i];
5045                  checkResults.removeChild(previousCheck);
5046              }
5047          }
5048      
5049          // Add result attribute.
5050          var resultValue = "false";
5051          if(result != null && result == true) {
5052              resultValue = "true";
5053          }
5054          settingsCheckNode.setAttribute("result", resultValue);
5055          
5056          // Add check results node.
5057          checkResults.appendChild(settingsCheckNode);
5058      
5059          // Save modified settings.
5060          saveSettings(false);
5061      } catch (e) {
5062          error("Unable to add result of check to settings: " + e.message);
5063      }
5064  }
5065  
5066  /**
5067   * Returns result of pre-evaluated check from settings node.
5068   * 
5069   * @param checkNode the check node for which to look in the settings
5070   * "checkResults" nodes to verify if the check has been executed already.
5071   * 
5072   * @returns result of already evaluated check. Returns null if the check has
5073   *          not been evaluated and saved to settings node before.
5074   */
5075  function getSettingsCheckResult(checkNode) {
5076      var result = null;
5077      var previousChecks = getSettingsCheck(checkNode);
5078      if (previousChecks != null) {
5079          // Get latest check result.
5080          var previousCheck = previousChecks[previousChecks.length-1];
5081          var checkResult = previousCheck.getAttribute("result");
5082          if (checkResult != null && checkResult == "true") {
5083              result = true;
5084          } else {
5085              result = false;
5086          }
5087          dinfo("Found previously executed check with result '" + result + "'.");
5088      }
5089      return result;
5090  }
5091  
5092  /**
5093   * Takes a check as a parameter and looks for the same check in the local
5094   * settings database. If an identical check with results is found, then this
5095   * check is returned in an array. Returns null if no identical check could be
5096   * found in the local settings database.
5097   * 
5098   * @param checkNode check to seek for in local settings databse.
5099   *  
5100   * @returns Array of matching checks; returns null if no check match.
5101   */
5102  function getSettingsCheck(checkNode) {
5103      if (checkNode == null) {
5104          return null;
5105      }
5106      var result = null;
5107      var currentSettings = getSettings();
5108  
5109      var checkResults = currentSettings.selectSingleNode("checkResults");
5110  
5111      if (checkResults != null) {
5112          var attributes = checkNode.attributes;
5113  
5114          // Check whether attributes are defined.
5115          if (attributes.length > 0) {
5116              var attributesClause = "";
5117              var checkMessage = "";
5118              for (var iAttribute=0; iAttribute < attributes.length; iAttribute++) {
5119                  if (attributesClause != "") {
5120                      attributesClause += " and ";
5121                      checkMessage += ", ";
5122                  }
5123                  var node = attributes.item(iAttribute);
5124                  var attribute = node.nodeName;
5125                  var value = node.nodeValue.replace(new RegExp("\\\\", "g"), "\\\\");
5126                  attributesClause += "@" + attribute + "=\"" + value + "\"";
5127                  checkMessage += attribute + "='" + value +"'";
5128              }
5129              // Get all nodes which match the attributes.
5130              dinfo("Searching for previously executed checks with attributes " + checkMessage);
5131              var xPathQuery = "check[" + attributesClause + "]";
5132              // dinfo("Query to find previously executed check: " + xPathQuery);
5133              var checkNodes = checkResults.selectNodes(xPathQuery);
5134  
5135              if (checkNodes != null) {
5136                  // Make sure the check nodes found do not contain any attributes
5137                  // not present in comparison node.
5138                  for (var iCheck=0; iCheck < checkNodes.length; iCheck++) {
5139                      var checkNode = checkNodes[iCheck];
5140                      // dinfo("Found previously executed check: " + checkNode.xml);
5141                      var checkAttributes = checkNode.attributes;
5142                      var allAttrFound = true;
5143                      
5144                      // Iterate over all attributes of the check node found and
5145                      // verify that the attribute is found in comparison node.
5146                      // (Except the result attribute)
5147                      for (var iAttr=0; iAttr < checkAttributes.length; iAttr++) {
5148                          var attrFound = false;
5149                          var checkAttrSettings = checkAttributes.item(iAttr).nodeName;
5150  
5151                          if (checkAttrSettings == "result") {
5152                              attrFound = true;
5153                          } else {
5154                              for (var iRefAttr=0; iRefAttr < attributes.length; iRefAttr++) {
5155                                  var checkAttrRef = attributes.item(iRefAttr).nodeName;
5156                                  if (checkAttrRef == checkAttrSettings) {
5157                                      attrFound = true;
5158                                      break;
5159                                  }
5160                              }
5161                          }
5162  
5163                          // If attribute has not been found in comparison node
5164                          // Then the node contains different checks.
5165                          if (attrFound == false) {
5166                              allAttrFound = false;
5167                              break;
5168                          }
5169                      }
5170                      
5171                      // If all attributes were found in original query node then
5172                      // the check is identical to the one in the settings DB.
5173                      if (allAttrFound) {
5174                          if (result == null) {
5175                              result = new Array();
5176                          }
5177                          result.push(checkNode);
5178                      }
5179                  }
5180              }
5181              if (result != null) {
5182                  dinfo("Found " + result.length + " previously executed checks.");
5183              } else {
5184                  dinfo("Unable to find any previously executed checks with these attributes.");
5185              }
5186          }
5187      }
5188      return result;
5189  }
5190  
5191  
5192  /**
5193   * Returns a list of package nodes (Array object) which have been scheduled for
5194   * removal but are not removed due to the /noremove flag.
5195   * 
5196   * @return Array of package nodes which would have been removed during this
5197   *         session
5198   */
5199  function getSkippedRemoveNodes() {
5200      if (skippedRemoveNodes == null) {
5201          skippedRemoveNodes = new Array();
5202      }
5203      return skippedRemoveNodes;
5204  }
5205  
5206  /**
5207   * Returns a list of key/value pairs representing all variable definitions from
5208   * the given XML node.
5209   * 
5210   * @param XMLNode
5211   *            The XML node to get the variables from
5212   * @param array
5213   *            Object of type Array to which the the variables are appended.
5214   *            In case null is supplied it returns a new Array object.
5215   *            Each array element is a dictionary object containing a key and
5216   *            a value. The key is the variable name and the value is the
5217   *            variable value to be assigned.
5218   * @return Object of type Scripting.Dictionary which contains all key/value
5219   *         pairs from the given XML node.
5220   */
5221  function getVariables(XMLNode, array) {
5222      // a new empty array of variables
5223      var variables = null;
5224  
5225      // make sure variables is either created or assigned
5226      if(array == null) {
5227          // variables = new ActiveXObject("Scripting.Dictionary");
5228          variables = new Array();
5229      } else {
5230          variables = array;
5231      }
5232  
5233      var variableNodes = XMLNode.selectNodes("variable");
5234  
5235      // Perform host matching on variables.
5236      variableNodes = filterConditionalNodes(variableNodes, true);
5237  
5238      for (var i=0; i < variableNodes.length; i++) {
5239          var variableName = variableNodes[i].getAttribute("name");
5240          var variableValue = variableNodes[i].getAttribute("value");
5241  
5242          if (variableName == null || variableValue == null) {
5243              error("Incomplete variable specification found. " +
5244                      "Variable name is '" + variableName + "' and variable value is '" +
5245                      variableValue + "'. Ignoring variable.");
5246              continue;
5247          }
5248          
5249          // Expand environment variables in value.
5250          // variableValue = shell.ExpandEnvironmentStrings(variableValue);
5251          dinfo("Got variable '" + variableName + "' of value '" + variableValue + "'");
5252  
5253          var variable = new ActiveXObject("Scripting.Dictionary");
5254          variable.Add(variableName, variableValue);
5255          
5256          // Add to variables list.
5257          variables.push(variable);
5258      }
5259  
5260      return variables;
5261  }
5262  
5263  /**
5264   * Installs the specified package node to the system. If an old package node is
5265   * supplied performs an upgrade. In case the old package node is null an
5266   * installation is performed.
5267   * 
5268   */
5269  function installPackage(packageNode) {
5270      // Initialize return value.
5271      var success = false;
5272      
5273      var packageName = getPackageName(packageNode);
5274      var packageID   = getPackageID(packageNode);
5275      var packageRev  = getPackageRevision(packageNode);
5276      var executeAttr = getPackageExecute(packageNode);
5277      var notifyAttr  = getPackageNotify(packageNode);
5278      var rebootAttr  = getPackageReboot(packageNode);
5279  
5280      // Get check policies.
5281      var installCheckPolicy = getPackagePrecheckPolicyInstall(packageNode);
5282      var upgradeCheckPolicy = getPackagePrecheckPolicyUpgrade(packageNode);
5283      var downgradeCheckPolicy = getPackagePrecheckPolicyDowngrade(packageNode);
5284  
5285      dinfo("Going to install package '" + packageName + "' (" + packageID +
5286          "), Revision " + packageRev + ", (execute flag is '" + executeAttr +
5287          "', notify flag is '" + notifyAttr + "').");
5288  
5289       // search for the package in the local settings
5290      var installedPackage = getSettingNode(packageID);
5291  
5292      // Check if package is manually installed.
5293      if (installedPackage != null) {
5294          var isManual = getPackageManualInstallation(installedPackage);
5295          if (isManual == true) {
5296              // Transfer manual install flag to new package.
5297              setPackageManualInstallation(packageNode, true);
5298          }
5299      }
5300      
5301      
5302      // if set then the package installation will be bypassed
5303      var bypass = false;
5304      // type of installation "install" or "upgrade"
5305      var typeInstall = "install";
5306      var typeUpgrade = "upgrade";
5307      var typeDowngrade = "downgrade";
5308      var installType = typeInstall;
5309  
5310      // string to print in events which identifies the package
5311      var packageMessage = "Package '" + packageName + "' (" + packageID + ")" +
5312                          ": ";
5313  
5314      // check if the package has been executed already
5315      if(searchArray(packagesInstalled, packageNode)) {
5316          // has been installed already during this session
5317          dinfo(packageMessage +
5318                  "Already installed once during this session.\n" +
5319                  "Checking if package is properly installed.");
5320          bypass=true;
5321  
5322          // check if installation of package node was successful
5323          if ((installedPackage != null) &&
5324              (versionCompare(getPackageRevision(installedPackage), packageRev) >= 0)) {
5325              // package successfully installed
5326              dinfo(packageMessage + "Verified; " +
5327                  "package successfully installed during this session.");
5328  
5329              success = true;
5330  
5331          } else {
5332              dinfo(packageMessage +
5333                  "Installation failed during this session.");
5334              // package installation must have been failed
5335  
5336              success = false;
5337  
5338          }
5339      } else {
5340          // mark package as processed
5341          packagesInstalled.push(packageNode);
5342  
5343          dinfo(packageMessage + "Not yet processed during this session.");
5344          bypass = false;
5345          // evaluate what do do with the package
5346  
5347          // Get action of package to be installed.
5348          var packageAction = getPackageInstallAction(packageNode);
5349          
5350          // Evaluate installation actions.
5351          switch (packageAction) {
5352          case "none":
5353              // No package actions shall be performed.
5354              dinfo(packageMessage + "Already installed.");
5355              installType = typeUpgrade;
5356              bypass = true;
5357              success = true;
5358              break;
5359  
5360          case "install":
5361              // Package needs to be installed.
5362              dinfo(packageMessage + "Prepared for installation.");
5363              installType = typeInstall;
5364              bypass = false;
5365              success = false;
5366  
5367              // If execute attribute is set to "always" just continue with installation.
5368              if (executeAttr == "always") {
5369                  break;
5370              }
5371              if (installCheckPolicy == "never") {
5372                  // Checks shall be bypassed and package is installed in any case.
5373                  dinfo(packageMessage + "Skipping checks whether package is already installed.");
5374              } else {
5375                  // Default is to execute checks first in order to evaluate if
5376                  // package is already installed.
5377                  if (isInstalled(packageNode)) {
5378                      info(packageMessage +
5379                          "Already installed (checks succeeded). Checking dependencies and chained packages.");
5380  
5381                      // append new node to local xml
5382                      addSettingsNode(packageNode, true);
5383  
5384                      // install all dependencies
5385                      var depSuccess = installPackageReferences(packageNode, "dependencies");
5386                      if (depSuccess) {
5387                          info(packageMessage +
5388                               "Package and all dependencies are already installed. Skipping.");
5389  
5390                      } else {
5391                          info(packageMessage +
5392                              "Installed but at least one dependency is missing.");
5393                      }
5394  
5395                      // install all chained packages
5396                      var chainedSuccess = installPackageReferences(packageNode, "chained");
5397                      if (chainedSuccess) {
5398                          info(packageMessage +
5399                               "Package and all chained packages are already installed. Skipping.");
5400  
5401                      } else {
5402                          info(packageMessage +
5403                          "Installed but at least one chained package is missing.");
5404                      }
5405  
5406                      // Bypass installation as installations seems to be done already.
5407                      bypass = true;
5408                      installType = typeInstall;
5409  
5410                      // Still set success to true since the package seems to be
5411                      // installed properly (check succeed).
5412                      success = true;
5413  
5414                  } else {
5415                      // Package not installed yet. Perform normal installation.
5416                      info(packageMessage +
5417                          "Not installed (checks failed). Preparing installation.");
5418                  }
5419              }
5420              break;
5421  
5422          case "upgrade":
5423              // Package needs to be upgraded.
5424              dinfo(packageMessage + "Prepared for upgrade.");
5425              installType = typeUpgrade;
5426              bypass = false;
5427              success = false;
5428  
5429              // If check policy is set to "always" then verify if the upgrade
5430              // might have been performed already.
5431              if (upgradeCheckPolicy == "always" && isInstalled(packageNode)) {
5432                  // Package marked for upgrade but upgrade is not necessary.
5433                  dinfo(packageMessage +
5434                          "Forced checks on upgrades succeeded. Package already up to date.");
5435  
5436                  // Update local package database.
5437                  addSettingsNode(packageNode, true);
5438  
5439                  // Package does not need to be upgraded.
5440                  bypass = true;
5441                  success = true;
5442              }
5443              break;
5444  
5445          case "downgrade":
5446              // Package needs to be downgraded.
5447              dinfo(packageMessage + "Prepared for downgrade.");
5448              installType = typeDowngrade;
5449              bypass = false;
5450              success = false;
5451  
5452              // If check policy is set to "always" then verify if the downgrade
5453              // might have been performed already.
5454              if (downgradeCheckPolicy == "always" && isInstalled(packageNode)) {
5455                  dinfo(packageMessage +
5456                      "Forced checks on downgrade succeeded. Package already downgraded.");
5457  
5458                  // Update local package database.
5459                  addSettingsNode(packageNode, true);
5460  
5461                  // Package does not need to be downgraded.
5462                  bypass = true;
5463                  success = true;
5464              }
5465              break;
5466  
5467          default:
5468              bypass = true;
5469              error("Unknown package action: " + packageAction);
5470              break;
5471          }
5472      }
5473  
5474      if (!bypass) {
5475          // Store current environment.
5476          var previousEnv = getEnv();
5477  
5478          try {
5479              // install dependencies
5480              var depInstallSuccess = installPackageReferences(packageNode, "dependencies");
5481  
5482              // abort installation in case dependencies could not be installed
5483              if (!depInstallSuccess) {
5484                  throw new Error("Installing dependencies failed");
5485              }
5486  
5487              // print event log entry
5488              info("Installing '" + packageName + "' (" + packageID + ")...");
5489              logStatus("Performing operation (" + installType + ") on '" + packageName + "' (" + packageID + ")");
5490  
5491              // stores if the package needs a reboot after installation
5492              var rebootRequired = false;
5493  
5494              // stores if the package needs a reboot after installing all
5495              // packages
5496              var rebootPostponed = false;
5497  
5498              // Generate the correct environment.
5499  
5500              // Set package specific environment.
5501              loadPackageEnv(packageNode);
5502  
5503              // Select command lines to install.
5504              var cmds;
5505              dinfo("Install type: " + installType);
5506              if (installType == typeUpgrade) {
5507                  // installation is an upgrade
5508                  cmds = getPackageCmdUpgrade(packageNode, null);
5509                  dinfo("Fetched " + cmds.length + " upgrade command(s).");
5510              } else if (installType == typeDowngrade) {
5511                  // prepare downgrade
5512                  cmds = getPackageCmdDowngrade(packageNode, null);
5513                  dinfo("Fetched " + cmds.length + " downgrade command(s).");
5514              }else {
5515                  // installation is default
5516                  cmds = getPackageCmdInstall(packageNode, null);
5517                  dinfo("Fetched " + cmds.length + " install command(s).");
5518              }
5519  
5520              // Get downloads from package node (if any).
5521              var downloadNodes = getDownloads(packageNode, null);
5522              // Append downloads from command node.
5523              for (var iCommands = 0; iCommands < cmds.length; iCommands++) {
5524                  var commandNode = cmds[iCommands ];
5525                  getDownloads(commandNode, downloadNodes);
5526              }
5527  
5528              // Download all specified downloads.
5529              var downloadResult = downloadAll(downloadNodes);
5530              if (downloadResult != true) {
5531                  var failureMessage = "Failed to download all files.";
5532                  if (isQuitOnError()) {
5533                      throw new Error(failureMessage);
5534                  } else {
5535                      error(failureMessage);
5536                  }
5537              }
5538  
5539              // execute each command line
5540              for (var iCmd = 0; iCmd < cmds.length; iCmd++) {
5541                  // execute commands
5542                  var cmdNode = cmds[iCmd];
5543                  var cmd = getCommandCmd(cmdNode);
5544                  if(cmd == null) {
5545                      error("Error: Command missing. Please fix the package. Ignoring command.");
5546                      continue;
5547                  }
5548                  var timeout = getCommandTimeout(cmdNode);
5549                  var workdir = getCommandWorkdir(cmdNode);
5550  
5551                  // mark system as changed (command execution in progress)
5552                  setSystemChanged();
5553                  if (notifyAttr) {
5554                      // notify user about start of installation
5555                      notifyUserStart();
5556                  }
5557  
5558                  var result = 0;
5559                  result = exec(cmd, timeout, workdir);
5560  
5561                  // search for exit code
5562                  var exitAction = getCommandExitCodeAction(cmdNode, result);
5563  
5564                  // check for special exit codes
5565                  if (exitAction != null) {
5566                      if (exitAction == "reboot") {
5567                          // This exit code forces a reboot.
5568                          info("Command in installation of " + packageName +
5569                              " returned exit code [" + result + "]. This " +
5570                              "exit code requires an immediate reboot.");
5571                          reboot();
5572                      } else if (exitAction == "delayedReboot") {
5573                          // This exit code schedules a reboot
5574                          info("Command in installation of " + packageName +
5575                              " returned exit code [" + result + "]. This " +
5576                              "exit code schedules a reboot.");
5577                          // schedule reboot
5578                          rebootRequired = true;
5579                          // proceed with next command
5580                          continue;
5581                      } else if (exitAction == "postponedReboot") {
5582                          info("Command in installation of " + packageName +
5583                              " returned exit code [" + result + "]. This " +
5584                              "exit code schedules a postponed reboot.");
5585                          rebootPostponed = true;
5586                          setPostponedReboot(rebootPostponed);
5587                          // execute next command
5588                          continue;
5589                      } else {
5590                          // this exit code is successful
5591                          info("Command in installation of " + packageName +
5592                              " returned exit code [" + result + "]. This " +
5593                              "exit code indicates success.");
5594                          // execute next command
5595                          continue;
5596                      }
5597                  } else if(result == 0) {
5598                      // if exit code is 0, return success
5599                      // execute next command
5600                      dinfo("Command in installation of " + packageName +
5601                          " returned exit code [" + result + "]. Success.");
5602                      continue;
5603                  } else {
5604                      // command did not succeed, throw an error
5605                      throw new Error("Exit code returned non-successful value (" +
5606                              result + ") on command '" + cmd + "'");
5607                  }
5608              }
5609  
5610              // packages with checks have to pass the isInstalled() test
5611              if (getChecks(packageNode).length > 0 && !isInstalled(packageNode)) {
5612                  // package failed for now
5613                  success = false;
5614  
5615                  // check if a delayed reboot has been scheduled
5616                  // if reboot is scheduled it might be OK if the package check
5617                  // fails
5618                  if (rebootRequired || rebootAttr == "true") {
5619                      warning("Package processing (" + installType + ") failed for package " +
5620                          packageName + ".\nHowever the package requires a reboot to complete. Rebooting.");
5621                      // reboot system without adding to local settings yet
5622                      reboot();
5623                  } else if (rebootPostponed || rebootAttr == "postponed") {
5624                      warning("Package processing (" + installType + ") failed for package " +
5625                          packageName + ".\nHowever the package schedules a postponed reboot.");
5626                  } else {
5627                      // package installation failed
5628                      var failMessage = "Could not process (" + installType + ") " + packageName + ".\n" +
5629                                  "Failed checking after installation.";
5630                      if (isQuitOnError()) {
5631                          throw new Error(failMessage);
5632                      } else {
5633                          error(failMessage);
5634                      }
5635                  }
5636              } else {
5637                  success = true;
5638                  // append new node to local xml
5639                  addSettingsNode(packageNode, true);
5640  
5641                  // install chained packages
5642                  var chainedStatus = installPackageReferences(packageNode, "chained");
5643                  if (chainedStatus) {
5644                      info(packageMessage +
5645                           "Package and all chained packages installed successfully.");
5646  
5647                  } else {
5648                      info(packageMessage +
5649                      "Package installed but at least one chained package failed to install.");
5650                  }
5651  
5652                  // Reboot the system if needed.
5653                  if (rebootRequired || rebootAttr == "true") {
5654                      info("Installation of " + packageName + " successful, system " +
5655                          "rebooting.");
5656                      reboot();
5657                  } else if (rebootPostponed || rebootAttr == "postponed") {
5658                      info("Installation of " + packageName + " successful, postponed reboot scheduled.");
5659                      setPostponedReboot(true);
5660                  } else {
5661                      info("Processing (" + installType + ") of " + packageName + " successful.");
5662                  }
5663              }
5664          } catch (err) {
5665              success = false;
5666              var errorMessage = "Could not process (" + installType + ") package '" +
5667                               packageName + "' (" + packageID + "):\n" + err.description + ".";
5668              if (isQuitOnError()) {
5669                  throw new Error(errorMessage);
5670              } else {
5671                  error(errorMessage);
5672              }
5673          } finally {
5674              // cleaning up temporary downloaded files
5675              dinfo("Cleaning up temporary downloaded files");
5676              // clean downloads
5677              downloadsClean(downloadNodes);
5678  
5679              //  PATCH SE3
5680              var packageID = getPackageID(packageNode);
5681              //var packageID = packageNode.getAttribute("id");
5682              var lnkDir = packageNode.getAttribute("lnk");
5683              var category = packageNode.getAttribute("category");
5684              if (lnkDir != null) {
5685                  if (category != null) {
5686                      info("Deplacement automatique des raccourcis de id=" + packageID + " depuis %AllUsersProfile%\\Menu Démarrer\\Programmes\\" + lnkDir + " dans la Category " + category + ".");
5687                      exec("%ComSpec% /C call %Z%\\wpkg\\AnalyseCategory.bat \"" + packageID + "\" \"" + lnkDir + "\"", timeout, workdir);
5688                  } else {
5689                      info("Pas de deplacement automatique des raccourcis car 'category' est absent dans " + packageID + ".");
5690                  }
5691              } else {
5692                  info("Pas de deplacement automatique des raccourcis car 'lnk' est absent dans " + packageID + ".");
5693              }
5694              log(4,"==============================================================="); // saut ligne entre les paquets à installer
5695              // FIN PATCH SE3
5696  
5697              // restore old environment
5698              dinfo("Restoring previous environment.");
5699              // restore previous environment
5700              loadEnv(previousEnv);
5701          }
5702      }
5703      return success;
5704  }
5705  
5706   /**
5707       * Installs all packages references of the selected type. Returns true in
5708       * case all references could be installed. Returns false if at least one
5709       * reference failed.
5710       * 
5711       * @param packageNode
5712       *            package to install the references of (XML node) NOTE: The
5713       *            package itself is not installed.
5714       * @param referenceType
5715       *            select "dependencies" or "chained". Defaults to
5716       *            "dependencies".
5717       * @return true=all dependencies installed successful; false=at least one
5718       *         dependency failed
5719       */
5720   function installPackageReferences(packageNode, referenceType) {
5721       var problemDesc = "";
5722       var refSuccess = true;
5723  
5724       // get references
5725       var type;
5726       var references = new Array();
5727       switch (referenceType) {
5728      case "chained":
5729          type = "chained";
5730           references = getPackageChained(packageNode, null);
5731          break;
5732  
5733      default:
5734          type = "dependencies";
5735          references = getPackageDependencies(packageNode, null);
5736          break;
5737      }
5738       if (references.length > 0) {
5739           info("Installing references (" + type + ") of '" +
5740                   getPackageName(packageNode) +
5741                   "' (" + getPackageID(packageNode) + ").");
5742       }
5743       for (var i=0; i < references.length; i++) {
5744           var refPackage = getPackageNodeFromAnywhere(references[i]);
5745           if (refPackage == null) {
5746               problemDesc += "Package references '" + references[i] +
5747                           "' but no such package exists";
5748               refSuccess = false;
5749               break;
5750           } else {
5751               // install this package
5752               var success = installPackage(refPackage);
5753               if (!success) {
5754                   problemDesc += "Installation of reference (" + type + ") package '"
5755                       + getPackageName(refPackage) + "' ("
5756                       + getPackageID(refPackage) + ") failed";
5757                   refSuccess = false;
5758                   // skip remaining references
5759                   break;
5760               }
5761           }
5762       }
5763       if (refSuccess) {
5764           var successMessage = "Installation of references (" + type + ") for '" +
5765                            getPackageName(packageNode) + "' (" +
5766                            getPackageID(packageNode) + ") successfully finished.";
5767           dinfo(successMessage);
5768       } else {
5769           var failMessage = "Installation of references (" + type + ") for '" +
5770                            getPackageName(packageNode) + "' (" +
5771                            getPackageID(packageNode) + ") failed. " + problemDesc;
5772           if (isQuitOnError()) {
5773               throw new Error(failMessage);
5774           } else {
5775               error(failMessage);
5776           }
5777       }
5778  
5779       return refSuccess;
5780   }
5781  
5782  
5783  /**
5784   * Installs a package by name.
5785   * 
5786   * @param name Package ID of package to be installed.
5787   * @param manualInstall Boolean value specifying whether the package is
5788   *        manually added. These packages are handled differently and not
5789   *        removed during synchronization. 
5790   */
5791  function installPackageName(name, manualInstall) {
5792      // Check package name.
5793      if (name == null || name == "") {
5794          info("Package ID missing!");
5795          return;
5796      }
5797  
5798      // Query manual installation flag.
5799      var isManual = false;
5800      if (manualInstall != null && manualInstall == true) {
5801          isManual = true;
5802      }
5803  
5804      // Query the package node.
5805      var node = getPackageNode(name);
5806  
5807      if (node == null) {
5808          info("Package " + name + " not found!");
5809          return;
5810      }
5811      
5812      // Set manual installation flag.
5813      if (isManual) {
5814          setPackageManualInstallation(node, true);
5815      }
5816  
5817       installPackage(node);
5818  }
5819  
5820  /**
5821   * Returns true if running on a 64-bit system. False if running on a 32-bit
5822   * system.
5823   * 
5824   * Please note that WPKG needs to be run from the local 64-bit cscript
5825   * instance in order to be able to access 64-bit directories and registry keys.
5826   * The 64-bit instance of cscript is located at %SystemRoot%\system32\. If
5827   * cscript from %SystemRoot%\SysWOW64\ is used (32-bit binary) then all reads to
5828   * %ProgramFiles% will be redirected to %ProgramFiles(x86). Hence it is not
5829   * possible for WPKG to access the "real" %ProgramFiles% folder with the 64-bit
5830   * binaries. The same applies for the registry. If 32-bit cscript is used all
5831   * reads to HKLM\Software\* are redirected to HKLM\Software\Wow6432Node\*.
5832   * 
5833   * WARNING: If cscript is invoked from a 32-bit application it is not possible
5834   * to run the 64-bit version of cscript since the real %SystemRoot%\System32
5835   * directory is not visible to 32-bit applications. So Windows will invoke the
5836   * 32-bit version even if the full path is specified!
5837   * 
5838   * A work-around is to copy the 64-bit cmd.exe from %SystemRoot%\System32
5839   * manually to a temporary folder and invoke it by using
5840   * c:\path\to\64-bit\cmd.exe /c \\path\to\wpkg.js
5841   * 
5842   * @return true in case the system is running on a 64-bit Windows version.
5843   *         otherwise false is returned.
5844   */
5845  function is64bit() {
5846      if (x64 == null) {
5847          x64 = false;
5848          var architecture = getArchitecture();
5849          if (architecture != "x86") {
5850              x64 = true;
5851          }
5852      }
5853      return x64;
5854  }
5855  
5856  /**
5857   * Returns the current setting of apply multiple configuration.
5858   * 
5859   * @returns Current state of apply multiple setting.
5860   */
5861  function isApplyMultiple() {
5862      return applyMultiple;
5863  }
5864  
5865  
5866  /**
5867   * returns current state of case sensitivity flag
5868   * 
5869   * @return true if case case sensitivity is enabled, false if it is disabled
5870   *         (boolean)
5871   */
5872  function isCaseSensitive() {
5873      return caseSensitivity;
5874  }
5875  
5876  /**
5877   * Returns current debug status.
5878   * 
5879   * @return true if debug state is on, false if debug is off
5880   */
5881  function isDebug() {
5882      return debug;
5883  }
5884  
5885  /**
5886   * Returns current dry run status.
5887   * 
5888   * @return true if dry run state is on, false if dry run is off
5889   */
5890  function isDryRun() {
5891      return dryrun;
5892  }
5893  
5894  /**
5895   * Returns current value of the force flag.
5896   * 
5897   * @return true if force is enabled, false if it is disabled (boolean).
5898   */
5899  function isForce() {
5900      return force;
5901  }
5902  
5903  /**
5904   * Returns current value of the forceinstall flag.
5905   * 
5906   * @return true if forced installation is enabled, false if it is disabled
5907   *         (boolean).
5908   */
5909  function isForceInstall() {
5910      return forceInstall;
5911  }
5912  
5913  /**
5914   * Returns if log should be appended or overwritten
5915   * 
5916   * @return true in case log should be appended. false if it should be
5917   *         overwritten (boolean).
5918   */
5919  function isLogAppend() {
5920      return logAppend;
5921  }
5922  
5923  /**
5924   * Check if package is installed.
5925   * 
5926   * @return returns true in case the package is installed, false otherwise
5927   * @throws Error
5928   *             in case checks could not be evaluated
5929   */
5930  function isInstalled(packageNode) {
5931      var packageName = getPackageName(packageNode);
5932      var result = true;
5933  
5934      dinfo ("Checking existence of package: " + packageName);
5935  
5936      // Get a list of checks to perform before installation.
5937      var checkNodes = getChecks(packageNode);
5938  
5939      // When there are no check conditions, say "not installed".
5940      if (checkNodes.length == 0) {
5941          return false;
5942      }
5943  
5944      // Save current environment.
5945      var previousEnv = getEnv();
5946  
5947      // load package specific environment
5948      loadPackageEnv(packageNode);
5949  
5950      // Verify checks
5951      result = checkAll(checkNodes);
5952  
5953      // restore environment
5954      loadEnv(previousEnv);
5955  
5956      return result;
5957  }
5958  
5959  /**
5960   * Returns current status of /noDownload parameter
5961   * 
5962   * @return true in case downloads shall be disabled, false if downloads are enabled
5963   */
5964  function isNoDownload() {
5965      return noDownload;
5966  }
5967  
5968  /**
5969   * Returns current status of /noforcedremove parameter
5970   * 
5971   * @return true in case forced remove is enabled, false if it is disabled
5972   */
5973  function isNoForcedRemove() {
5974      return noForcedRemove;
5975  }
5976  
5977  /**
5978   * Returns if the nonotify flag is set or not.
5979   * 
5980   * @return true if nonotify flag is set, false if nonotify is not set (boolean)
5981   */
5982  function isNoNotify() {
5983      return nonotify;
5984  }
5985  
5986  /**
5987   * Returns if the noreboot flag is set or not.
5988   * 
5989   * @return true if noreboot flag is set, false if noreboot is not set (boolean)
5990   */
5991  function isNoReboot() {
5992      return noreboot;
5993  }
5994  
5995  /**
5996   * Returns the current state (boolean) of the noremove flag.
5997   * 
5998   * @return true if noremove flag is set, false if noremove is not set (boolean)
5999   */
6000  function isNoRemove() {
6001      return noRemove;
6002  }
6003  
6004  /**
6005   * Returns if the noRunningState flag is set or not.
6006   * 
6007   * @return true if noRunningState flag is set, false if noRunningState is not
6008   *         set (boolean)
6009   */
6010  function isNoRunningState() {
6011      return noRunningState;
6012  }
6013  
6014  /**
6015   * Returns the current state of postponed reboots. If it returns true a reboot
6016   * is scheduled when the script exits (after completing all actions).
6017   * 
6018   * @return current status of postponed reboot (boolean)
6019   */
6020  function isPostponedReboot() {
6021      return postponedReboot;
6022  }
6023  
6024  /**
6025   * Returns current value of the sendStatus flag
6026   * 
6027   * @return true in case status should be sent, otherwise returns false
6028   */
6029  function isSendStatus() {
6030      return sendStatus;
6031  }
6032  
6033  /**
6034   * Returns true in case a package has been processed yet. Returns false if no
6035   * package has been processed yet at all.
6036   * 
6037   * @return true in case a package has been processed, false otherwise.
6038   */
6039  function isSystemChanged() {
6040      return systemChanged;
6041  }
6042  
6043  /**
6044   * Returns the current value of the upgrade-before-remove feature flag.
6045   * 
6046   * @return true in case upgrade-before-remove should be enabled, otherwise
6047   *         returns false.
6048   */
6049  function isUpgradeBeforeRemove() {
6050      return !noUpgradeBeforeRemove;
6051  }
6052  
6053  /**
6054   * Returns current value of skip event log setting.
6055   * 
6056   * @return true in case event log logging is enabled, false if it is disabled
6057   *         (boolean).
6058   */
6059  function isSkipEventLog() {
6060      return skipEventLog;
6061  }
6062  
6063  /**
6064   * Returns current state of event log fallback mode (logging to STDOUT instead
6065   * of event log.
6066   * 
6067   * @returns {Boolean} Current status of event log fallback mode.
6068   */
6069  function isEventLogFallback() {
6070      return eventLogFallback;
6071  }
6072  
6073  /**
6074   * Returns true if quiet mode is on. False otherwise.
6075   * 
6076   * @return true if quiet flag is set, false if it is unset (boolean)
6077   */
6078  function isQuiet() {
6079      return quietMode;
6080  }
6081  
6082  /**
6083   * Returns current value of quit on error setting (see '/quitonerror' parameter)
6084   * 
6085   * @return true in case quit on error is enabled, false if it is disabled
6086   *         (boolean).
6087   */
6088  function isQuitOnError() {
6089      return quitonerror;
6090  }
6091  
6092  /**
6093   * Checks if a package is a zombie package which means that it exists within the
6094   * locale package database (wpkg.xml) but not on server database (packages.xml).
6095   * 
6096   * @return true in case the package is a zombie, false otherwise
6097   */
6098  function isZombie(packageNode) {
6099      var packageName = getPackageID(packageNode);
6100      var allPackagesArray = getPackageNodes();
6101      var zombie = true;
6102      dinfo("Checking " + packageName + " zombie state.");
6103      for (var i=0; i < allPackagesArray.length; i++) {
6104          if (getPackageID(allPackagesArray[i]) == packageName) {
6105              zombie = false;
6106              break;
6107          }
6108      }
6109  
6110      // print message for zombie packages
6111      if (zombie) {
6112          var errorMessage = "Error while synchronizing package " + packageName +
6113          "\nZombie found: package installed but not in packages database.";
6114          if (isQuitOnError()) {
6115              errorMessage += " Aborting synchronization.";
6116              error(errorMessage);
6117              throw new Error(errorMessage);
6118          } else {
6119              errorMessage += " Removing package.";
6120              error(errorMessage);
6121          }
6122      }
6123  
6124      return zombie;
6125  }
6126  
6127  
6128  /**
6129   * Query and print local host information (read from the host where wpkg.js is
6130   * executed).
6131   */
6132  function queryHostInformation() {
6133      // Reset cache for host information.
6134      resetHostInformationCache();
6135  
6136      var hostInfoAttributes = getHostInformation();
6137      var hostInfo = hostInfoAttributes.keys().toArray();
6138      // Initialize output message.
6139      var message = "Host information attributes from local host:\n";
6140      // Fetch all host information attributes.
6141      for (var i=0; i < hostInfo.length; i++) {
6142          var hostInfoKey = hostInfo[i];
6143          message += "    " + hostInfoKey + ":";
6144  
6145          // Pad label to 20 characters (minus one for the colon ":").
6146          var padding = 19 - hostInfoKey.length;
6147          for (var iPadding=0; iPadding < padding; iPadding++) {
6148              message += " ";
6149          }
6150          message += hostInfoAttributes.Item(hostInfoKey) + "\n";
6151      }
6152      message += "\n\n";
6153  
6154      // If remote query mode is active reset host information.
6155      if (getQueryMode() == "remote") {
6156          dinfo("Query mode: remote");
6157          getSettingHostAttributes();
6158      }
6159      
6160      // Print message.
6161      alert(message);
6162  }
6163  
6164  
6165  /**
6166   * Query and print host information fread from settings file. This requires
6167   * that host information is available in settings file. You must have
6168   * settingsHostInfo enabled in your configuration.
6169   */
6170  function queryHostInformationFromSettings() {
6171      // Fetch settings.
6172      var settings = getSettings();
6173      var attributes = settings.attributes;
6174  
6175      // Initialize output message.
6176      var message = "Host information attributes from settings database:\n";
6177  
6178      // Check whether attributes are defined.
6179      if (attributes.length > 0) {
6180      
6181          for (var iAttribute=0; iAttribute < attributes.length; iAttribute++) {
6182              var node = attributes.item(iAttribute);
6183              var attribute = node.nodeName;
6184              var value = node.nodeValue;
6185  
6186              message += "    " + attribute + ":";
6187      
6188              // Pad label to 20 characters (minus one for the colon ":").
6189              var padding = 19 - attribute.length;
6190              for (var iPadding=0; iPadding < padding; iPadding++) {
6191                  message += " ";
6192              }
6193              message += value + "\n";
6194          }
6195      } else {
6196          message += "    No host attributes found in settings database.\n" +
6197              "Make sure \"settingsHostInfo\" is enabled in your configuration.\n";
6198      }
6199      message += "\n\n";
6200  
6201      // Print message.
6202      alert(message);
6203  }
6204  
6205  /**
6206   * Queries all available packages (from package database and local settings) and
6207   * prints a quick summary.
6208   */
6209  function queryAllPackages() {
6210      // Retrieve packages.
6211      var settingsNodes = getSettingNodes();
6212      var packagesNodes = getPackageNodes();
6213  
6214      // Concatenate both lists.
6215      var packageNodes = concatenateList(settingsNodes, packagesNodes);
6216      packageNodes = uniqueAttributeNodes(packageNodes, "id");
6217  
6218      // Create a string to append package descriptions to.
6219      var message = "All available packages (" + packageNodes.length + "):\n";
6220  
6221      // query all packages
6222      for (var i = 0; i < packageNodes.length; i++) {
6223          message += queryPackage(packageNodes[i], null) + "\n\n";
6224      }
6225  
6226      alert(message);
6227  }
6228  
6229  /**
6230   * Show the user a list of packages that are currently installed.
6231   */
6232  function queryInstalledPackages() {
6233      // Retrieve currently installed nodes.
6234      var packageNodes = getSettingNodes();
6235  
6236      // Create a string to append package descriptions to.
6237      var message = "Packages currently installed:\n";
6238  
6239      for (var i = 0; i < packageNodes.length; i++) {
6240          message += queryPackage(packageNodes[i], null) + "\n\n";
6241      }
6242  
6243      alert(message);
6244  }
6245  
6246  /**
6247   * Show the user information about a specific package.
6248   * 
6249   * @param packageNode
6250   *            The package node to print information of
6251   * @param packageAction
6252   *            Optional argument to include the action applied to the package
6253   *            in the package information. If set to null the package action
6254   *            information is omitted from the output.
6255   * @return string representing the package information
6256   */
6257  function queryPackage(packageNode, packageAction) {
6258      var message = "";
6259      if (packageNode != null) {
6260          var settingNode = getSettingNode(getPackageID(packageNode));
6261          var executeAttribute = getPackageExecute(packageNode);
6262          if (executeAttribute == null || executeAttribute == "") {
6263              executeAttribute = "-";
6264          }
6265  
6266          message = getPackageName(packageNode) + "\n";
6267          message += "    ID:                " + getPackageID(packageNode) + "\n";
6268          
6269          if (settingNode != null && packageAction != null) {
6270              var newPackageRevision = getPackageRevision(packageNode);
6271              var oldPackageRevision = getPackageRevision(settingNode);
6272              if (newPackageRevision != oldPackageRevision) {
6273                  message += "    Revision (new):    " + getPackageRevision(packageNode) + "\n";
6274                  message += "    Revision (old):    " + getPackageRevision(settingNode) + "\n";
6275              } else {
6276                  message += "    Revision:          " + getPackageRevision(packageNode) + "\n";
6277              }
6278          } else {
6279              message += "    Revision:          " + getPackageRevision(packageNode) + "\n";
6280          }
6281          
6282          // PATCH SE3
6283          //if (packageAction != null) {
6284          //    message += "    Action:            " + packageAction + "\n";
6285          //}
6286          // FIN PATCH SE3
6287  
6288          message += "    Reboot:            " + getPackageReboot(packageNode) + "\n";
6289          // PATCH SE3
6290          //message += "    Execute:           " + executeAttribute + "\n";
6291          //message += "    Priority:          " + getPackagePriority(packageNode) + "\n";
6292          // FIN PATCH SE3
6293          
6294          if (settingNode != null) {
6295              message += "    Status:            Installed\n";
6296          } else {
6297              message += "    Status:            Not Installed\n";
6298          }
6299          
6300      } else {
6301          message += "No such package\n";
6302      }
6303  
6304      return message;
6305  }
6306  
6307  /**
6308   * Shows the user a list of packages that are currently not installed.
6309   */
6310  function queryUninstalledPackages() {
6311      // Create a string to append package descriptions to.
6312      var message = "Packages not installed:\n";
6313  
6314      // Get list of all available packages from package database.
6315      var packageNodes = getPackageNodes();
6316  
6317      // Check for each package if it is installed.
6318      for (var i = 0; i < packageNodes.length; i++) {
6319          if (getSettingNode(getPackageID(packageNodes[i])) == null) {
6320              message += queryPackage(packageNodes[i], null) + "\n\n";
6321          }
6322      }
6323  
6324      alert(message);
6325  }
6326  
6327  /**
6328   * Query packages listed in the current profile.
6329   * @param listInstall List packages which are pending to be installed.
6330   * @param listUpgrade List packages which are pending to be upgraded.
6331   * @param listDowngrade List packages which are pending to be downgraded.
6332   * @param listRemove List packages which are pending to be removed.
6333   * @param listUnmodified List packages which are not modified during synchronization.
6334   */
6335  function queryProfilePackages(listInstall, listUpgrade, listDowngrade, listRemove, listUnmodified) {
6336      // Message to be shown as a result of query.
6337      var message = "Current profile packages:\n";
6338  
6339      // Message which is appended when system is modified (includes execute="change" packages).
6340      var messageOnChangeOnly = "";
6341  
6342      // Flag whether the system would be modified when WPKG is run.
6343      var systemModified = false;
6344  
6345      var profilePackageNodes = getProfilePackageNodes();
6346      // Read all packages applying to current profile.
6347      for (var i=0; i<profilePackageNodes.length; i++) {
6348          var packageNode = profilePackageNodes[i];
6349  
6350          // Check package action which would be applied during synchronization.
6351          var packageAction = getPackageInstallAction(packageNode);
6352  
6353          // "none" No action; package installed already
6354          // "install" Installation, package is new on the host
6355          // "upgrade" Upgrade package which already exists on the system
6356          // New version higher than installed version
6357          // "downgrade" Downgrade package which already exists on the system
6358          // New version lower than installed version
6359          var packageMessage = "";
6360          var changesSystem = false;
6361          switch (packageAction) {
6362          case "none":
6363              if (listUnmodified) {
6364                  packageMessage += queryPackage(packageNode, "None") + "\n\n";
6365              }
6366              break;
6367  
6368          case "install":
6369              if (listInstall) {
6370                  packageMessage += queryPackage(packageNode, "Installation pending") + "\n\n";
6371                  changesSystem = true;
6372              }
6373              break;
6374  
6375          case "upgrade":
6376              if (listUpgrade) {
6377                  packageMessage += queryPackage(packageNode, "Upgrade pending") + "\n\n";
6378                  changesSystem = true;
6379              }
6380              break;
6381  
6382          case "downgrade":
6383              if (listDowngrade) {
6384                  packageMessage += queryPackage(packageNode, "Downgrade pending") + "\n\n";
6385                  changesSystem = true;
6386              }
6387              break;
6388  
6389          default:
6390              break;
6391          }
6392          var executeAttribute = getPackageExecute(packageNode);
6393          if (executeAttribute == "changed") {
6394              messageOnChangeOnly += packageMessage;
6395          } else {
6396              message += packageMessage;
6397              // If the package modifies the system also packages which are
6398              // executed on change only shall be executed.
6399              if (changesSystem) {
6400                  systemModified = changesSystem;
6401              }
6402          }
6403      }
6404      if (systemModified) {
6405          message += messageOnChangeOnly;
6406      }
6407      
6408      // Print packages which are pending for removal.
6409      if (listRemove) {
6410          var removeList = getPackagesRemoved();
6411          for (var i=0; i<removeList.length; i++) {
6412              var packageNode = removeList[i];
6413              message += queryPackage(packageNode, "Remove pending") + "\n\n";
6414          }
6415      }
6416      alert(message);
6417  }
6418  
6419  /**
6420   * Removes the specified package node from the system. This function will remove
6421   * all packages which depend on the one to be removed prior to the package
6422   * itself. In case the /force parameter is set the function will even remove the
6423   * requested package if not all packages depending on it could be removed. Note
6424   * that these packages might probably not work any more in such case.
6425   * 
6426   * @param packageNode
6427   *            Package to be removed
6428   * @return True in case of successful remove of package and all packages
6429   *         depending on it. False in case of failed package uninstall of failed
6430   *         uninstall of package depending on it.
6431   */
6432  function removePackage(packageNode) {
6433      var packageName = getPackageName(packageNode);
6434      var packageID = getPackageID(packageNode);
6435      var notifyAttr = getPackageNotify(packageNode);
6436  
6437      // stores if the package needs a reboot after removing
6438      var rebootRequired = false;
6439      // stores if a postponed reboot should be scheduled
6440      var rebootPostponed = false;
6441  
6442      // Get package removal check policy.
6443      var checkPolicy = getPackagePrecheckPolicyRemove(packageNode);
6444  
6445      var success = true;
6446      var bypass = false;
6447  
6448      // string to print in events which identifies the package
6449      var packageMessage = "Package '" + packageName + "' (" + packageID + ")" +
6450                          ": ";
6451  
6452      // check if package has been processed already
6453      if(searchArray(packagesRemoved, packageNode)) {
6454          // package has been removed during this session already
6455          dinfo(packageMessage +
6456                  "Already removed once during this session.\n" +
6457                  "Checking if package has been removed properly.");
6458          bypass=true;
6459  
6460          // check if installation of package node was successful
6461          var installedPackage = getSettingNode(packageID);
6462          if (installedPackage == null) {
6463              // package successfully removed
6464              dinfo(packageMessage + "Verified; " +
6465                  "package successfully removed during this session.");
6466  
6467              success = true;
6468  
6469          } else {
6470              dinfo(packageMessage +
6471                  "Package removal failed during this session.");
6472              // package removal must have failed
6473  
6474              success = false;
6475          }
6476      } else {
6477          dinfo(packageMessage + "Not yet processed during this session.");
6478      }
6479  
6480      // Verify whether checks shall be used to verify if the package
6481      // has been removed already.
6482      if (checkPolicy == "always" && !isInstalled(packageNode)) {
6483          dinfo(packageMesseage + "Package already removed from system. Skipping removal.");
6484          // Package already removed. Skip removal.
6485          success = true;
6486  
6487          // Remove package node from local xml.
6488          removeSettingsNode(packageNode, true);
6489  
6490          // set package as processed in order to prevent processing multiple
6491          // times
6492          packagesRemoved.push(packageNode);
6493  
6494          // Cancel further removal processing.
6495          bypass = true;
6496      }
6497  
6498  
6499      if (!bypass) {
6500          // set package as processed in order to prevent processing multiple
6501          // times
6502          packagesRemoved.push(packageNode);
6503  
6504          if (isNoRemove()) {
6505              var message = "Package removal disabled: ";
6506              // check if the package is still installed
6507              if (isInstalled(packageNode)) {
6508                  // the package is installed - keep it and add to skipped nodes
6509                  dinfo(message + "Package " + packageName +  " (" + packageID +
6510                      ") will not be removed.");
6511                  addSkippedRemoveNodes(packageNode);
6512  
6513                  // package is not effectively removed
6514                  success = false;
6515              } else {
6516                  // Get a list of checks to perform before installation.
6517                  var checkNodes = getChecks(packageNode);
6518  
6519                  if (checkNodes.length != 0) {
6520                      // package not installed - remove from local settings file
6521                      dinfo(message + "Package " + packageName +  " (" + packageID +
6522                          ") will be removed from local settings because it is not installed.");
6523                      removeSettingsNode(packageNode, true);
6524                      success = true;
6525                  } else {
6526                      // unable to detect if the package is installed properly
6527                      // assume it's still installed
6528                      dinfo(message + "Package " + packageName +  " (" + packageID +
6529                              ") remains within local settings (no checks defined so WPKG " +
6530                              "cannot verify if the package is still installed properly).");
6531                      success = false;
6532                  }
6533              }
6534          } else {
6535              // remove dependent packages first
6536              var allSuccess = removePackagesDependent(packageNode);
6537              if (!allSuccess && !isForce()) {
6538                  // removing of at least one dependent package failed
6539                  var failedRemove = "Failed to remove package which depends on '"
6540                          + packageName + " (" + packageID + "), skipping removal.\n"
6541                          + "You might use the /force flag to force removal but "
6542                          + "remember that the package depending on this one might "
6543                          + "stop working.";
6544                  success = false;
6545  
6546                  if (isQuitOnError()) {
6547                      throw new Error(0, failedRemove);
6548                  } else {
6549                      error(failedRemove);
6550                  }
6551              } else {
6552                  // Save environment.
6553                  var previousEnv = getEnv();
6554                  
6555                  try {
6556                      info("Removing " + packageName + " (" + packageID + ")...");
6557  
6558                      // select command lines to remove
6559                      var cmds = getPackageCmdRemove(packageNode, null);
6560  
6561                      // set package specific environment
6562                      loadPackageEnv(packageNode);
6563  
6564                      // Get downloads from package node (if any).
6565                      var downloadNodes = getDownloads(packageNode, null);
6566                      // Append downloads from command node.
6567                      for (var iCommand = 0; iCommand < cmds.length; iCommand++) {
6568                          var commandNode = cmds[iCommand ];
6569                          getDownloads(commandNode, downloadNodes);
6570                      }
6571  
6572                      // Download all specified downloads.
6573                      var downloadResult = downloadAll(downloadNodes);
6574                      if (downloadResult != true) {
6575                          var failureMessage = "Failed to download all files.";
6576                          if (isQuitOnError()) {
6577                              throw new Error(failureMessage);
6578                          } else {
6579                              error(failureMessage);
6580                          }
6581                      }
6582                      
6583                      // execute all remove commands
6584                      for (var iCommand = 0; iCommand  < cmds.length; iCommand++) {
6585                          // execute commands
6586                          var cmdNode = cmds[iCommand ];
6587                          var cmd = getCommandCmd(cmdNode);
6588                          if(cmd == null) {
6589                              error("Error: Command missing. Please fix the package. Ignoring command.");
6590                              continue;
6591                          }
6592                          var timeout = getCommandTimeout(cmdNode);
6593                          var workdir = getCommandWorkdir(cmdNode);
6594  
6595                          // mark system as changed (command execution in
6596                          // progress)
6597                          setSystemChanged();
6598                          if(notifyAttr) {
6599                              notifyUserStart();
6600                          }
6601  
6602                          var result = exec(cmd, timeout, workdir);
6603  
6604                          dinfo("Command returned result: " + result);
6605  
6606                          // check if there is an exit code defined
6607                          var exitAction = getCommandExitCodeAction(cmdNode, result);
6608  
6609                          // Check for special exit codes.
6610                          if (exitAction != null) {
6611                              if (exitAction == "reboot") {
6612                                  // This exit code forces a reboot.
6613                                  info("Command in removal of " + packageName + " returned " +
6614                                      "exit code [" + result + "]. This exit code " +
6615                                      "requires an immediate reboot.");
6616  
6617                                  // Verify if the package is a zombie (not in package
6618                                  // database any more). If it is a zombie, and not referenced
6619                                  // in the profile then prevent endless reboots by removing
6620                                  // the package from local database.
6621                                  if(isZombie(packageNode)) {
6622                                      // check if still referenced within the profile
6623                                      var profilePackageArray = getProfilePackageNodes();
6624                                      var referenceFound = false;
6625                                      for (var iPackage = 0; iPackage < profilePackageArray.length; iPackage++) {
6626                                          if (packageID == getPackageID(profilePackageArray[iPackage])) {
6627                                              referenceFound = true;
6628                                              break;
6629                                          }
6630                                      }
6631                                      // if package is a zombie and not referenced
6632                                      // within the profile remove the settings entry
6633                                      if(!referenceFound && !isNoForcedRemove()) {
6634                                          removeSettingsNode(packageNode, true);
6635                                          info("Removed '" + packageName + "' ("
6636                                              + packageID + ") from local settings.\n" +
6637                                                  "Package initiated immediate reboot and is a zombie.");
6638                                      }
6639                                  }
6640  
6641                                  reboot();
6642                              } else if(exitAction == "delayedReboot") {
6643                                  // This exit code schedules a reboot
6644                                          info("Command in removal of " + packageName +
6645                                              " returned exit code [" + result + "]. This " +
6646                                              "exit code schedules a reboot.");
6647                                  // schedule reboot
6648                                  rebootRequired = true;
6649                                  // execute next command
6650                                  continue;
6651                              } else if(exitAction == "postponedReboot") {
6652                                  info("Command in removal of " + packageName +
6653                                      " returned exit code [" + result + "]. This " +
6654                                      "exit code schedules a postponed reboot.");
6655                                  rebootPostponed = true;
6656                                  setPostponedReboot(rebootPostponed);
6657                                  // execute next command
6658                                  continue;
6659                              } else {
6660                                  // This exit code is successful.
6661                                  info("Command in removal of " + packageName + " returned " +
6662                                      " exit code [" + result + "]. This exit code " +
6663                                      "indicates success.");
6664                                  continue;
6665                              }
6666                          } else if(result == 0) {
6667                              // if exit code is 0, return success
6668                              // execute next command
6669                              dinfo("Command in removal of " + packageName +
6670                                  " returned exit code [" + result + "]. Success.");
6671                              continue;
6672                          } else {
6673                              // command did not succeed, log error
6674                              var failedCmd = "Exit code returned non-successful value: " +
6675                                  result + "\nPackage: " + packageName + ".\nCommand:\n" + cmd;
6676                              // error occurred during remove
6677                              success = false;
6678  
6679                              if (isQuitOnError()) {
6680                                  throw new Error(0, failedCmd);
6681                              } else {
6682                                  error(failedCmd);
6683                              }
6684                          }
6685                      }
6686                  } catch (err) {
6687                      success = false;
6688                      var errorMessage = "Could not process (remove) package '" +
6689                                           packageName + "' (" + packageID + "):\n" + err.description + ".";
6690                      if (isQuitOnError()) {
6691                          throw new Error(errorMessage);
6692                      } else {
6693                          error(errorMessage);
6694                      }
6695                  } finally {
6696  
6697                      // PATCH SE3
6698                      var packageID = getPackageID(packageNode);
6699                      //var packageID = packageNode.getAttribute("id");
6700                      var lnkDir = packageNode.getAttribute("lnk");
6701                      var category = packageNode.getAttribute("category");
6702                      if (lnkDir != null) {
6703                          if (category != null) {
6704                              info("Suppression des raccourcis de id=" + packageID + " depuis %AllUsersProfile%\\Menu Démarrer\\Programmes\\" + category + "\\" + lnkDir);
6705                              exec("%ComSpec% /C call %Z%\\wpkg\\AnalyseCategory.bat \"" + packageID + "\" \"" + lnkDir + "\" remove", timeout, workdir);
6706                          } else {
6707                              info("Pas de suppression automatique des raccourcis car 'category' est absent dans " + packageID + ".");
6708                          }
6709                      } else {
6710                          info("Pas de suppression automatique des raccourcis car 'lnk' est absent dans " + packageID + ".");
6711                      }
6712                      // FIN PATCH SE3
6713  
6714                      // restore old environment
6715                      dinfo("Restoring previous environment.");
6716  
6717                      // restore previous environment
6718                      loadEnv(previousEnv);
6719                  }
6720              }
6721  
6722              // read reboot attribute
6723              var rebootAttr = getPackageReboot(packageNode);
6724  
6725              // Use package checks to prove if package has been removed.
6726              // Zombies are removed in any case (even if uninstall failed) except
6727              // if the
6728              // "/noforcedremove" parameter was set
6729              if (!isInstalled(packageNode)) {
6730                  // Remove package node from local xml.
6731                  removeSettingsNode(packageNode, true);
6732  
6733                  if (rebootRequired || rebootAttr == "true") {
6734                      info("Removal of " + packageName + " successful, system " +
6735                          "rebooting.");
6736                      reboot();
6737                  } else if (rebootPostponed || rebootAttr == "postponed") {
6738                      info("Removal of " + packageName + " successful, postponed reboot scheduled.");
6739                  } else {
6740                      info("Removal of " + packageName + " successful.");
6741                  }
6742              } else {
6743                  // Check if package is a zombie.
6744                  if(isZombie(packageNode)) {
6745                      // Check if still referenced within the profile.
6746                      var packageArray = getProfilePackageNodes();
6747                      var referenced = false;
6748                      for (var i=0; i < packageArray.length; i++) {
6749                          if (packageID == getPackageID(packageArray[i])) {
6750                              referenced = true;
6751                              break;
6752                          }
6753                      }
6754                      // If package is a zombie and not referenced within the profile
6755                      // remove the settings entry.
6756                      if(!referenced && !isNoForcedRemove()) {
6757                          removeSettingsNode(packageNode, true);
6758                          warning("Errors occurred while removing '" + packageName + "' ("
6759                              + packageID + ").\nPackage has been removed anyway because it was a zombie " +
6760                              "and not referenced within the profile.");
6761                      }
6762                  } else if (rebootRequired || rebootAttr == "true") {
6763                      warning("Package processing (remove) failed for package " +
6764                          packageName + ".\nHowever the package requires a reboot to complete. Rebooting.");
6765                      // reboot system without adding to local settings yet
6766                      reboot();
6767                  } else if (rebootPostponed || rebootAttr == "postponed") {
6768                      warning("Package processing (remove) failed for package " +
6769                          packageName + ".\nHowever the package schedules a postponed reboot.");
6770                  } else {
6771                      // package installation failed
6772                      success = false;
6773                      message = "Could not process (remove) " + packageName + ".\n" +
6774                                  "Package still installed.";
6775                      if (isQuitOnError()) {
6776                          throw new Error(message);
6777                      } else {
6778                          error(message);
6779                      }
6780                  }
6781              }
6782              log(4,"==============================================================="); // saut ligne entre les paquets à installer
6783          }
6784      }
6785  
6786      // return status
6787      return success;
6788  }
6789  
6790  /**
6791   * Removes a package by name.
6792   * 
6793   * @param name
6794   *            name of the package to be removed (package ID).
6795   * @return True in case of successful remove of package and all packages
6796   *         depending on it. False in case of failed package uninstall of failed
6797   *         uninstall of package depending on it.
6798   */
6799  function removePackageName(name) {
6800      // Query the package node.
6801      var node = getSettingNode(name);
6802  
6803      // return code
6804      var success = false;
6805  
6806      dinfo("Removing package '" + name + "'.");
6807  
6808      if (node == null) {
6809  
6810          // check if the package has been removed during this session
6811          var alreadyRemoved = false;
6812          for (var iRemovedPkg = 0; iRemovedPkg < packagesRemoved.length; iRemovedPkg++) {
6813              var removedPackage = packagesRemoved[iRemovedPkg];
6814              if (name == getPackageID(removedPackage)) {
6815                  alreadyRemoved = true;
6816                  break;
6817              }
6818          }
6819          if (alreadyRemoved) {
6820              dinfo("Package '" + name + "' already removed during this session.");
6821              success = true;
6822          } else {
6823              info("Package '" + name + "' currently not installed.");
6824              success = false;
6825          }
6826      } else {
6827          success = removePackage(node);
6828      }
6829      return success;
6830  }
6831  
6832  /**
6833   * Removes all packages which depends on the given package. Returns true in case
6834   * all packages could be removed. Returns false if at least one dependent
6835   * package failed to remove.
6836   * 
6837   * @param packageNode
6838   *            package to install the dependencies of (XML node) NOTE: The
6839   *            package itself is not installed.
6840   * @return true=all dependencies installed successful; false=at least one
6841   *         dependency failed
6842   */
6843  function removePackagesDependent(packageNode) {
6844      var packageID = getPackageID(packageNode);
6845      var packageName = getPackageName(packageNode);
6846  
6847      var problemDesc = "";
6848      // search for all packages which depend on the one to be removed
6849      var dependencies = new Array();
6850      var installedPackages = getSettingNodes();
6851      for (var iInstPkg = 0; iInstPkg<installedPackages.length; iInstPkg++) {
6852          // get dependencies of this package
6853          var pkgDeps = getPackageDependencies(installedPackages[iInstPkg]);
6854          for (var j=0; j<pkgDeps.length; j++) {
6855              if (pkgDeps[j] == packageID) {
6856                  dependencies.push(installedPackages[iInstPkg]);
6857                  break;
6858              }
6859          }
6860      }
6861      if (dependencies.length > 0) {
6862          info("Removing packages depending on '" + packageName +
6863              "' (" + packageID + ").");
6864      }
6865      var depSuccess = true;
6866      for (var iDependencies = 0; iDependencies < dependencies.length; iDependencies++) {
6867          var dependingPackage = dependencies[iDependencies];
6868          // install this package
6869          var success = removePackage(dependingPackage);
6870          if (!success) {
6871              problemDesc += "Removal of depending package '"
6872                  + getPackageName(dependingPackage) + "' ("
6873                  + getPackageID(dependingPackage) + ") failed";
6874              depSuccess = false;
6875              // skip remaining dependencies
6876              break;
6877          }
6878      }
6879  
6880      if (depSuccess) {
6881          dinfo("Removal of depending packages for '" +
6882                   packageName + "' (" +
6883                   packageID + ") successfully finished.");
6884      } else {
6885          var failMessage = "Removal of depending packages for '" +
6886                           packageName + "' (" +
6887                          packageID + ") failed. " + problemDesc;
6888          if (isQuitOnError()) {
6889              throw new Error(failMessage);
6890          } else {
6891              error(failMessage);
6892          }
6893      }
6894  
6895      return depSuccess;
6896  }
6897  
6898  /**
6899   * Removes a package node from the settings XML node
6900   * 
6901   * @param packageNode
6902   *            The package node to be removed from settings.
6903   * @param saveImmediately
6904   *            Set to true in order to save settings immediately after removing.
6905   *            Settings will not be saved immediately if value is false.
6906   * @return Returns true in case of success, returns false if no node could be
6907   *         removed
6908   */
6909  function removeSettingsNode(packageNode, saveImmediately) {
6910      // make sure the settings node is selected
6911      var packageID = getPackageID(packageNode);
6912      dinfo("Removing package id '" + packageID + "' from settings.");
6913      var settingsNode = getSettingNode(packageID);
6914      var success = false;
6915      if(settingsNode != null) {
6916          success = removeNode(getSettings(), settingsNode);
6917      }
6918      // save settings if remove was successful
6919      if (success && saveImmediately) {
6920          saveSettings(true);
6921      }
6922      return success;
6923  }
6924  
6925  /**
6926   * Erases host information cache to enforce re-reading of host information when
6927   * getter methods like getHostInformation(), getHostOS(), getLocale() etc are
6928   * executed. 
6929   */
6930  function resetHostInformationCache() {
6931      // Empty caches.
6932      hostName = null;
6933      hostOs = null;
6934      domainName = null;
6935      ipAddresses = null;
6936      hostGroups = null;
6937      hostArchitecture = null;
6938      hostAttributes = null;
6939  }
6940  
6941  
6942  /**
6943   * Sets state of multiple profile assignment.
6944   * 
6945   * @param newState
6946   *            new debug state
6947   */
6948  function setApplyMultiple(newState) {
6949      applyMultiple = newState;
6950  }
6951  
6952  /**
6953   * Set new architecture for this host.
6954   * @param newArchitecture Architecture to used for this host.
6955   */
6956  function setArchitecture(newArchitecture) {
6957      hostArchitecture = newArchitecture;
6958  }
6959  
6960  /**
6961   * Sets new status of the case-sensitive flag
6962   * 
6963   * @param newSensitivity
6964   *            true to enable case sensitivity, false to disable it (boolean)
6965   */
6966  function setCaseSensitivity(newSensitivity) {
6967      caseSensitivity = newSensitivity;
6968  }
6969  
6970  /**
6971   * Sets debug value to the given state.
6972   * 
6973   * @param newState
6974   *            new debug state
6975   */
6976  function setDebug(newState) {
6977      debug = newState;
6978  }
6979  
6980  /**
6981   * Sets domain name used by the script.
6982   * 
6983   * @param newDomainName
6984   *            new domain name
6985   */
6986  function setDomainName(newDomainName) {
6987      domainName = newDomainName;
6988  }
6989  
6990  /**
6991   * Sets dry run value to the given state.
6992   * 
6993   * @param newState
6994   *            new dry run state
6995   */
6996  function setDryRun(newState) {
6997      dryrun = newState;
6998  }
6999  
7000  /**
7001   * Sets a new value for the forceinstall flag.
7002   * 
7003   * @param newState
7004   *            new value for the forceinstall flag (boolean)
7005   */
7006  function setForce(newState) {
7007      force = newState;
7008  }
7009  
7010  /**
7011   * Sets a new value for the forceinstall flag.
7012   * 
7013   * @param newState
7014   *            new value for the forceinstall flag (boolean)
7015   */
7016  function setForceInstall(newState) {
7017      forceInstall = newState;
7018  }
7019  
7020  /**
7021   * Set new group names the host belongs to.
7022   * 
7023   * @param newGroupNames
7024   *            Array of group names the host belongs to.
7025   */
7026  function setHostGroups(newGroupNames) {
7027      hostGroups = newGroupNames;
7028  }
7029  
7030  /**
7031   * Set a new host name which will be used by the script. This is useful for
7032   * debugging purposes.
7033   * 
7034   * @param newHostname
7035   *            host name to be used
7036   */
7037  function setHostname(newHostname) {
7038      hostName = newHostname;
7039  }
7040  
7041  /**
7042   * Set new host OS variable overwriting automatically-detected value.
7043   * 
7044   * @param newHostOS
7045   *            host OS name
7046   */
7047  function setHostOS(newHostOS) {
7048      hostOs = newHostOS;
7049  }
7050  
7051  
7052  /**
7053   * Sets a new profile-id attribute to the given host XML node
7054   * 
7055   * @param hostNode
7056   *            the host XML node to modify
7057   * @param profileID
7058   *            the new profile ID to be written to this node
7059   */
7060  function setHostProfile(hostNode, profileID) {
7061      hostNode.setAttribute("profile-id", profileID);
7062  }
7063  
7064  /**
7065   * Set a new hosts node
7066   * 
7067   * @param newHosts
7068   *            the new hosts XML node to be used fro now on
7069   */
7070  function setHosts(newHosts) {
7071      hosts = newHosts;
7072  }
7073  
7074  /**
7075   * Set a new IP address list array.
7076   * 
7077   * @param newIPAdresses
7078   *            Array of IP addresses to be used by script.
7079   */
7080  function setIPAddresses(newIPAdresses) {
7081      ipAddresses = newIPAdresses;
7082  }
7083  
7084  /**
7085   * Set new value for log file pattern
7086   * 
7087   * @param pattern
7088   *            new pattern to be used
7089   * @return returns the pattern with expanded environment variables
7090   */
7091  function setLogfilePattern(pattern) {
7092      var wshShell = new ActiveXObject("WScript.Shell");
7093      logfilePattern = wshShell.ExpandEnvironmentStrings(pattern);
7094      return logfilePattern;
7095  }
7096  
7097  /**
7098   * Sets new value for the no-download flag.
7099   * 
7100   * @param newState
7101   *            new value for the no-download flag (boolean).
7102   *            If set to true then all downloads are disabled (just skipped).
7103   */
7104  function setNoDownload(newState) {
7105      noDownload = newState;
7106  }
7107  
7108  /**
7109   * Sets new value for the noforcedremove flag.
7110   * 
7111   * @param newState
7112   *            new value for the noforcedremove flag (boolean).
7113   */
7114  function setNoForcedRemove(newState) {
7115      noForcedRemove = newState;
7116  }
7117  
7118  /**
7119   * Sets new state for the noreboot flag.
7120   * 
7121   * @param newState
7122   *            new state of the noreboot flag (boolean)
7123   */
7124  function setNoReboot(newState) {
7125      noreboot = newState;
7126  }
7127  
7128  /**
7129   * Sets new state for the noremove flag.
7130   * 
7131   * @param newState
7132   *            new state of the noremove flag (boolean)
7133   */
7134  function setNoRemove(newState) {
7135      noRemove = newState;
7136  }
7137  
7138  /**
7139   * Sets new state for the noRunningState flag.
7140   * 
7141   * @param newState
7142   *            new state of the noreboot flag (boolean)
7143   */
7144  function setNoRunningState(newState) {
7145      noRunningState = newState;
7146  }
7147  
7148  /**
7149   * Sets a new package id-attribute to the given host XML node
7150   * 
7151   * @param packageNode
7152   *            the package XML node to modify
7153   * @param packageID
7154   *            the new package ID to be written to this node
7155   */
7156  function setPackageID(packageNode, packageID) {
7157      packageNode.setAttribute("id", packageID);
7158  }
7159  
7160  /**
7161   * Set a new value for the manual installation flag of the given package.
7162   * Manual installations are flagged only for packages which are installed via
7163   * command line directly and not via synchronization.
7164   * 
7165   * @param packageNode package to be modified.
7166   * @param manualInstall {Boolean} new value of package installation flag.
7167   */
7168  function setPackageManualInstallation(packageNode, manualInstall) {
7169      if (packageNode == null) {
7170          error("No package node specified. Cannot set manual installation flag.");
7171          return;
7172      }
7173      if (manualInstall == null) {
7174          error("No manual installation flag value specified.");
7175          return;
7176      }
7177      if (manualInstall == true) {
7178          packageNode.setAttribute("manualInstall", "true");
7179      }
7180  }
7181  
7182  /**
7183   * Set a new packages node.
7184   * 
7185   * @param newPackages
7186   *            the new packages XML node to be used fro now on
7187   */
7188  function setPackages(newPackages) {
7189      packages = newPackages;
7190      // iterate through all packages and set the package id to lower case
7191      // this allows XPath search for lowercase value later on (case-insensitive)
7192      if (packages != null && !isCaseSensitive()) {
7193          var packageNodes = getPackageNodes();
7194          for (var i=0; i<packageNodes.length; i++) {
7195              var packageNode = packageNodes[i];
7196              setPackageID(packageNode, getPackageID(packageNode).toLowerCase());
7197          }
7198      }
7199  }
7200  
7201  /**
7202   * Sets the status of postponed reboot. A postponed reboot schedules a system
7203   * reboot after finishing all actions (right before the script exits).
7204   * 
7205   * @param newState
7206   *            new state of postponed reboot
7207   */
7208  function setPostponedReboot(newState) {
7209      postponedReboot = newState;
7210  }
7211  
7212  /**
7213   * Sets a new profile id-attribute to the given profile XML node
7214   * 
7215   * @param profileNode
7216   *            the profile XML node to modify
7217   * @param profileID
7218   *            the new profile ID to be written to this node
7219   */
7220  function setProfileID(profileNode, profileID) {
7221      profileNode.setAttribute("id", profileID);
7222  }
7223  
7224  /**
7225   * Set a new profiles node
7226   * 
7227   * @param newProfiles
7228   *            the new profiles XML node to be used fro now on
7229   */
7230  function setProfiles(newProfiles) {
7231      profiles = newProfiles;
7232      // iterate through all profiles and set the profile id to lower case
7233      // this allows XPath search for lowercase value later on (case-insensitive)
7234      if (profiles != null && !isCaseSensitive()) {
7235          var profileNodes = getProfileNodes();
7236          for (var i=0; i<profileNodes.length; i++) {
7237              var profileNode = profileNodes[i];
7238              setProfileID(profileNode, getProfileID(profileNode).toLowerCase());
7239          }
7240      }
7241  }
7242  
7243  /**
7244   * Sets query mode to new state. Allowed states are "remote" and "local".
7245   * 
7246   * @param newState query mode value to be set.
7247   */
7248  function setQueryMode(newState) {
7249      if (newState != null && (newState == "remote" || newState == "local")) {
7250          queryMode = newState;
7251      }
7252  }
7253  
7254  /**
7255   * Sets new state of the quiet flag
7256   * 
7257   * @param newState
7258   *            new status of quiet flag (boolean)
7259   */
7260  function setQuiet(newState) {
7261      quietMode = newState;
7262  }
7263  
7264  /**
7265   * Sets a new value for the quit on error flag.
7266   * 
7267   * @param newState
7268   *            new value for the quit on error flag (boolean).
7269   */
7270  function setQuitOnError(newState) {
7271      quitonerror = newState;
7272  }
7273  
7274  /**
7275   * Sets new value for the reboot command (rebootCmd).
7276   * 
7277   * @param newCommand
7278   */
7279  function setRebootCmd(newCommand) {
7280      var wshShell = new ActiveXObject("WScript.Shell");
7281      rebootCmd = wshShell.ExpandEnvironmentStrings(newCommand);
7282  }
7283  
7284  /**
7285   * Set state of application so other applications can see that it is running by
7286   * reading from the registry.
7287   * 
7288   * @param statename
7289   *            String which is written to the registry as a value of the
7290   *            "running" key
7291   */
7292  function setRunningState(statename) {
7293      var WshShell = new ActiveXObject("WScript.Shell");
7294      var val;
7295  
7296      try {
7297          val = WshShell.RegWrite(sRegWPKG_Running, statename);
7298      } catch (e) {
7299          val = null;
7300      }
7301  
7302      return val;
7303  }
7304  
7305  /**
7306   * Sets new value for the sendStatus flag which defines if status messages are
7307   * sent to the calling program using STDOUT
7308   * 
7309   * @param newStatus
7310   *            new value for the sendStatus flag (boolean)
7311   */
7312  function setSendStatus(newStatus) {
7313      sendStatus = newStatus;
7314  }
7315  
7316  /**
7317   * Set a new settings node
7318   * 
7319   * @param newSettings
7320   *            the new settings XML node to be used fro now on
7321   */
7322  function setSettings(newSettings, saveImmediately) {
7323      settings = newSettings;
7324      // iterate through all packages and set the package id to lower case
7325      // this allows XPath search for lowercase value later on (case-insensitive)
7326      if (settings != null && !isCaseSensitive()) {
7327          var packageNodes = getSettingNodes();
7328          for (var i=0; i<packageNodes.length; i++) {
7329              var packageNode = packageNodes[i];
7330              setPackageID(packageNode, getPackageID(packageNode).toLowerCase());
7331          }
7332      }
7333      // save new settings
7334      if(saveImmediately) {
7335          saveSettings(true);
7336      }
7337  }
7338  
7339  /**
7340   * Set path to local settings file (locak package database).
7341   * The path might contain environment variables as well as the following
7342   * expressions:
7343   *     [HOSTNAME]  Replaced by the executing hostname.
7344   *     [PROFILE]   Replaced by the concatenated list of profiles applied.
7345   * @param path path to settings XML file.
7346   */
7347  function setSettingsPath(path) {
7348      if (path == null || path == "") {
7349          error("Path to settings is required");
7350          return;
7351      }
7352  
7353      var wshObject = new ActiveXObject("WScript.Shell");
7354      var expandedSettingsPath = wshObject.ExpandEnvironmentStrings(path);
7355  
7356      // Set global variable holding settings file path.
7357      settings_file = expandedSettingsPath;
7358  }
7359  
7360  
7361  /**
7362   * Sets the system changed attribute to true. Call this method to make WPKG
7363   * aware that a system change has been done.
7364   * 
7365   * @return returns current system change status (always true after this method
7366   *         has been called
7367   */
7368  function setSystemChanged() {
7369      systemChanged = true;
7370      return systemChanged;
7371  }
7372  
7373  /**
7374   * Set new value for the boolean flag to disable/enable event log logging.
7375   * 
7376   * @param newValue
7377   *            value to be used for the skip event log flag from now on.
7378   */
7379  function setSkipEventLog(newValue) {
7380      skipEventLog = newValue;
7381  }
7382  
7383  /**
7384   * Set event log fallback to new value (enabled/disabled).
7385   * 
7386   * @param newValue
7387   *           value to be used for the event log fallback flag.
7388   */
7389  function setEventLogFallback(newValue) {
7390      eventLogFallback = newValue;
7391  }
7392  
7393  /**
7394   * Sorts package nodes by priority flag.
7395   * 
7396   * @param packageNodes
7397   *            JScript Array containing package node entries
7398   * @param sortBy
7399   *            select the field to sort on. Supported Values are "PRIORITY" and
7400   *            "NAME"
7401   * @param sortOrder
7402   *            order in which the elements are sorted (integer) valid values:<br>1
7403   *            sort ascending (default)<br>2 sort descending
7404   * 
7405   * @return new Array containing the same package nodes in sorted order (sorted
7406   *         by priority)
7407   */
7408  function sortPackageNodes(packageNodes, sortBy, sortOrder) {
7409      // create array to do the sorting on
7410      var sortedPackages = new Array();
7411      for (var iPkgNodes = 0; iPkgNodes < packageNodes.length; iPkgNodes++) {
7412          sortedPackages.push(packageNodes[iPkgNodes]);
7413      }
7414      // Classic bubble-sort algorithm on selected attribute
7415      for (var iSortedPkg = 0; iSortedPkg < sortedPackages.length - 1; iSortedPkg++) {
7416          for (var j=0; j < sortedPackages.length - 1 - iSortedPkg; j++) {
7417              var prio1;
7418              var prio2;
7419              var priVal1 = null;
7420              var priVal2 = null;
7421  
7422              switch(sortBy) {
7423                  case "NAME":
7424                      priVal1 = getPackageName(sortedPackages[j]);
7425                      priVal2 = getPackageName(sortedPackages[j + 1]);
7426                      break;
7427                  default:
7428                      priVal1 = parseInt(getPackagePriority(sortedPackages[j]));
7429                      priVal2 = parseInt(getPackagePriority(sortedPackages[j + 1]));
7430                      break;
7431              }
7432              // If a priority is not set, we assume 0.
7433  
7434              if (priVal1 == null) {
7435                  prio1 = 0;
7436              } else {
7437                  prio1 = priVal1;
7438              }
7439  
7440              if (priVal2 == null) {
7441                  prio2 = 0;
7442              } else {
7443                  prio2 = priVal2;
7444              }
7445  
7446              var swapElements = false;
7447              switch (sortOrder) {
7448                  case 2:
7449                      if (prio1 < prio2) {
7450                          swapElements = true;
7451                      }
7452                      break;
7453                  default:
7454                      if (prio1 > prio2) {
7455                          swapElements = true;
7456                      }
7457                      break;
7458              }
7459              // If the priority of the first one in the list exceeds the second,
7460              // swap the packages.
7461              if (swapElements) {
7462                  var tmp = sortedPackages[j];
7463                  sortedPackages[j] = sortedPackages[j + 1];
7464                  sortedPackages[j + 1] = tmp;
7465              }
7466          }
7467      }
7468      return sortedPackages;
7469  }
7470  
7471  /**
7472   * Sorts the settings file by package name. Returns sorted package XML node.
7473   */
7474  function sortSettings() {
7475      // sort current setting nodes
7476      var sortedPackages = sortPackageNodes(getSettingNodes(), "NAME", 1);
7477  
7478      // Get setting checks.
7479      var settingsChecks = getSettingsCheckResults();
7480      
7481      // create new (empty) settings node
7482      var sortedSettings = createSettings();
7483      sortedSettings.appendChild(settingsChecks);
7484      
7485      // use this settings node
7486      setSettings(sortedSettings, false);
7487  
7488      // fill new settings node with sorted packages (same order)
7489      for (var i=0; i<sortedPackages.length; i++) {
7490          addSettingsNode(sortedPackages[i], false);
7491      }
7492  }
7493  
7494  /**
7495   * Synchronizes the current package state to that of the specified profile,
7496   * adding, removing or upgrading packages.
7497   */
7498  function synchronizeProfile() {
7499      // send message to client
7500      logStatus("Starting software synchronization");
7501  
7502      /**
7503       * Get package nodes referenced within the profile (and profile
7504       * dependencies). This includes package dependencies as well.
7505       */
7506      var profilePackageNodes = getProfilePackageNodes();
7507      dinfo("Synchronizing. Number of packages referenced by profile: " + profilePackageNodes.length + ".");
7508  
7509      var localPackages = getPackagesManuallyInstalled();
7510      if (localPackages.length > 0) {
7511          dinfo("Synchronizing. Locally installed packages: " + localPackages.length + ".");
7512          for(var i=0; i<localPackages.length; i++) {
7513              // Fetch latest package node to schedule installation/upgrade.
7514              var localPackage = localPackages[i];
7515              var latestVersion = getPackageNode(getPackageID(localPackage));
7516              if (latestVersion != null) {
7517                  profilePackageNodes.push(latestVersion);
7518              }
7519          }
7520      }
7521  
7522      // Get list of packages scheduled for removal.
7523      // This excludes manually installed packages except if they do not exist.
7524      var removablesArray = getPackagesRemoved();
7525  
7526      dinfo("Number of packages to remove: " + removablesArray.length);
7527      logStatus("Number of packages to be removed: " + removablesArray.length);
7528      /*
7529       * upgrade packages to be removed to latest version first. This allows system administrators to provide a fixed
7530       * version of the package which allows clean uninstall.
7531       * 
7532       * This was done to allow fixing a broken uninstall-procedure on server side. Without upgrading to the latest
7533       * version here it might happen that the package cannot be removed without the possibility to fix it. If you remove
7534       * the package completely from the package database it will be forced to be removed from the local settings file
7535       * even if uninstall fails.
7536       * 
7537       * NOTE: This is not done within the same loop as the removal (see below) in order to prevent re-installing already
7538       * removed dependencies.
7539       */
7540      // sort packages to upgrade the ones with highest priority first
7541      if (isUpgradeBeforeRemove()) {
7542          var sortedUpgradeList = sortPackageNodes(removablesArray, "PRIORITY", 2);
7543          for (var iSortedPkg = 0; iSortedPkg < sortedUpgradeList.length; iSortedPkg++) {
7544              var upgradePkgNode = sortedUpgradeList[iSortedPkg];
7545              // upgrade package if package is available on server database
7546              var serverPackage = getPackageNode(getPackageID(upgradePkgNode));
7547              if (serverPackage != null) {
7548                  logStatus("Remove: Checking status of '" + getPackageName(serverPackage) +
7549                          "' (" + (iSortedPkg+1) + "/" + sortedUpgradeList.length + ")");
7550                  // start upgrade first
7551                  installPackage(serverPackage);
7552              }
7553          }
7554      }
7555  
7556      // Remove packages which do not exist in package database or do not apply
7557      // to the profile
7558      // reverse-sort packages to remove the one with lowest priority first
7559      var sortedRemovablesArray = sortPackageNodes(removablesArray, "PRIORITY", 1);
7560      for (var iRemovables = 0; iRemovables < sortedRemovablesArray.length; iRemovables++) {
7561          var removePkgNode = sortedRemovablesArray[iRemovables];
7562          // remove package from system
7563          // the settings node might have been changed during update before
7564          // reload it.
7565          logStatus("Remove: Removing package '" + getPackageName(removePkgNode) +
7566                  "' (" + (iRemovables+1) + "/" + sortedRemovablesArray.length + ")");
7567          // removePackage(getSettingNode(getPackageID(removePkgNode)));
7568          removePackageName(getPackageID(removePkgNode));
7569      }
7570  
7571      // create array to do the sorting on
7572      var sortedPackages = sortPackageNodes(profilePackageNodes, "PRIORITY", 2);
7573  
7574      /*
7575       * Move packages with execute=changed attribute to independent array in order to allow them to be executed after the
7576       * other packages.
7577       */
7578      var packagesToInstall = new Array();
7579      var packagesAwaitingChange = new Array();
7580      // NOTE: This should not change the sort order of the packages.
7581      for (var iPkg = 0; iPkg < sortedPackages.length; iPkg++) {
7582          var packageNode = sortedPackages[iPkg];
7583          var executeAttribute = getPackageExecute(packageNode);
7584          if (executeAttribute == "changed") {
7585              packagesAwaitingChange.push(packageNode);
7586          } else {
7587              packagesToInstall.push(packageNode);
7588          }
7589      }
7590  
7591      /*
7592       * Loop over each available package and install it. No check required if package is already installed or not. The
7593       * install method will check by itself if the package needs to be installed/upgraded or no action is needed.
7594       */
7595      for (var iInstallPkg=0; iInstallPkg < packagesToInstall.length; iInstallPkg++) {
7596          // install/upgrade package
7597          logStatus("Install: Verifying package '" + getPackageName(packagesToInstall[iInstallPkg]) +
7598                  "' (" + (iInstallPkg + 1) + "/" + packagesToInstall.length + ")");
7599          installPackage(packagesToInstall[iInstallPkg]);
7600      }
7601  
7602      /*
7603       * Install packages which might have been postponed because no other change has been done to the system.
7604       */
7605      for(var iChangeAwait = 0; iChangeAwait < packagesAwaitingChange.length; iChangeAwait++) {
7606          // try applying this packages again now.
7607          if (isSystemChanged()) {
7608              logStatus("Install: Verifying package (system changed) '" + getPackageName(packagesAwaitingChange[iChangeAwait]) +
7609                      "' (" + (packagesToInstall.length + iChangeAwait + 1) + "/" + sortedPackages.length + ")");
7610  
7611              installPackage(packagesAwaitingChange[iChangeAwait]);
7612          } else {
7613              logStatus("Install: No system change, skipping '" + getPackageName(packagesAwaitingChange[iChangeAwait]) +
7614                      "' (" + (packagesToInstall.length + iChangeAwait + 1) + "/" + sortedPackages.length + ")");
7615          }
7616      }
7617  
7618      logStatus("Finished software synchronization");
7619  
7620      // If we had previously warned the