[ 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