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