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