diff --git a/pkgtool.pm b/pkgtool.pm index 7bc2862..0c18306 100644 --- a/pkgtool.pm +++ b/pkgtool.pm @@ -1658,18 +1658,10 @@ sub run_exe ($$$$$$$;$) return (undef, undef); } -sub remove_package_fallback ($$$$$$$$$$$) +sub remove_package_uninstall ($$$$$$$$$) { - my ($channel, $vars, $paramlist, $inst, $condition, $dispname, $ver, $printver, $db, $counters, $removecontext) = @_; + my ($uninst, $channel, $vars, $paramlist, $dispname, $printver, $db, $counters, $removecontext) = @_; - print_log($channel, DEBUG1, 'Removing package %s%s%s', $dispname, $printver, - defined $condition ? ' with true condition' : ''); - - my $uninst = $$inst{Uninstall}; - my $quninst = $$inst{QUninstall}; - return sprintf('Cannot remove package %s%s, no remove steps defined and no UninstallString registry entry found', - $dispname, $ver) unless defined $uninst || defined $quninst; - $uninst = $quninst if defined $quninst; $uninst =~ s/(msiexec[^ ]* )\/i/$1\/X/io; $uninst =~ s/(msiexec[^ ]* )/$1\/qb \/norestart \/passive /io; print_log($channel, DEBUG2, 'Uninstall command: %s', $uninst); @@ -1705,9 +1697,168 @@ sub remove_package_fallback ($$$$$$$$$$$) return undef; } -sub remove_packages ($$$$$$$$$$) +sub remove_package_fallback ($$$$$$$$$$$) { - my ($channel, $vars, $def, $paramlist, $list, $condition, $db, $basedir, $config, $counters) = @_; + my ($channel, $vars, $paramlist, $inst, $condition, $dispname, $ver, $printver, $db, $counters, $removecontext) = @_; + + print_log($channel, DEBUG1, 'Removing package %s%s%s', $dispname, $printver, + defined $condition ? ' with true condition' : ''); + + my $uninst = $$vars{uninstallstring}; + return sprintf('Cannot remove package %s%s, no remove steps defined and no UninstallString registry entry found', + $dispname, $ver) unless defined $uninst; + return remove_package_uninstall($uninst, $channel, $vars, $paramlist, $dispname, $printver, $db, $counters, $removecontext); +} + +sub remove_package_steps ($$$$$$$$$$$$$$$$) +{ + my ($channel, $removesteps, $paramlist, $inst, $condition, $ver, $printver, + $db, $def, $name, $dispname, $basedir, $genericmsiparams, + $config, $counters, $removecontext) = @_; + + my $version = $ver; + $version = '' unless defined $version; + print_log($channel, INFO, 'Removing package %s version %s', $dispname, $version); + + my $uninst = $$inst{Uninstall}; + my $quninst = $$inst{QUninstall}; + $quninst = $uninst unless defined $quninst; + + my $vars = get_default_vars($config); + set_datetime_vars($vars); + $$vars{pkgname} = $name; + $$vars{pkgdispname} = $dispname; + $$vars{pkgversion} = $version; + $$vars{uninstallstring} = $quninst if defined $quninst; + my $appdir = substitute_variables($vars, $$def{'source-directory'}, 1, $basedir, $channel); + $$vars{appdir} = $appdir; + + my $realremove = 0; + my $i = 0; + foreach my $step (@$removesteps) { + my $type = $$step{type}; + $i++; + print_log($channel, DEBUG2, 'Removal step #%d type %s', $i, $type); + + my $error; + my $exitcode; + my $ignore = $$step{'ignore-failure'}; + $ignore = 0 unless defined $ignore; + + my $condition = $$step{condition}; + if (defined $condition && $type ne 'remove-pkg') { + print_log($channel, DEBUG2, 'Evaluating condition expression %s in step #%d', + $condition, $i); + ($condition, $error) = check_condition($channel, $vars, $condition); + if (! defined $condition) { + print_log($channel, INFO, 'Ignoring step #%d: %s', $i, $error); + next; + } + if (! $condition) { + print_log($channel, INFO, 'Ignoring step #%d with false condition', $i); + next; + } + print_log($channel, INFO, 'Executing step #%d with true condition', $i); + } + + if ($type eq 'remove-pkg') { + my $paramlist = $$step{parameters}; + $paramlist = [] unless defined $paramlist; + $error = remove_package_fallback($channel, $vars, $paramlist, $inst, $condition, $dispname, + $ver, $printver, $db, $counters, $removecontext); + delete $$vars{'installed-version'}; + $realremove = 1 unless defined $error; + } + elsif ($type eq 'run') { + my $bg = defined $$step{background} && $$step{background}; + my $chdir = defined $$step{chdir} ? + substitute_variables($vars, $$step{chdir}, 1, $appdir, $channel) : undef; + my $sourcefile = substitute_variables($vars, $$step{'source-file'}, 1, $appdir, $channel); + my $paramlist = $$step{parameters}; + $paramlist = [] unless defined $paramlist; + ($error, $exitcode) = run_exe($channel, $db, $vars, $chdir, $sourcefile, $paramlist, $bg); + } + elsif ($type eq 'msi') { + my $sourcefile = substitute_variables($vars, $$step{'source-file'}, 1, $appdir, $channel); + my $exename = substitute_variables($vars, '%systemroot%/System32/msiexec.exe', 1, undef, $channel); + my $paramlist = $$step{parameters}; + $paramlist = [] unless defined $paramlist; + unshift @$paramlist, '/x', $sourcefile; + if (-f $sourcefile) { + push @$paramlist, @$genericmsiparams if defined $genericmsiparams; + ($error, $exitcode) = run_exe($channel, $db, $vars, undef, $exename, $paramlist, 0); + } + else { + $error = sprintf('Cannot find MSI file %s: %s', $sourcefile, $!); + $exitcode = -1; + } + } + elsif ($type eq 'reg') { + my $sourcefile = substitute_variables($vars, $$step{'source-file'}, 1, $appdir, $channel); + my $exename = substitute_variables($vars, '%systemroot%/System32/reg.exe', 1, undef, $channel); + my $paramlist = ['import', $sourcefile]; + if (-f $sourcefile) { + ($error, $exitcode) = run_exe($channel, $db, $vars, undef, $exename, $paramlist, 0); + } + else { + $error = sprintf('Cannot find REG file %s: %s', $sourcefile, $!); + $exitcode = -1; + } + } + elsif ($type eq 'file') { + my $sourcefile = substitute_variables($vars, $$step{'source-file'}, 1, $appdir, $channel); + my $targetfile = substitute_variables($vars, $$step{'target-file'}, 1); + $error = copy_file($channel, $sourcefile, $targetfile); + } + elsif ($type eq 'delete-file') { + my $recurse = $$step{recurse}; + $recurse = defined $recurse && $recurse; + my $targetfile = substitute_variables($vars, $$step{'target-file'}, 1); + $error = delete_dir_file(0, $channel, $recurse, $targetfile); + } + elsif ($type eq 'delete-dir') { + my $recurse = $$step{recurse}; + $recurse = defined $recurse && $recurse; + my $targetfile = substitute_variables($vars, $$step{'target-file'}, 1); + $error = delete_dir_file(1, $channel, $recurse, $targetfile); + } + elsif ($type eq 'setvar') { + my $varname = $$step{variable}; + my $expression = $$step{expression}; + my $filename = $$step{filename}; + if (defined $expression) { + print_log($channel, DEBUG2, 'Evaluating variable %s expression %s in step #%d', + $varname, $expression, $i); + $error = set_install_var($channel, $vars, $varname, $expression); + } + else { + print_log($channel, DEBUG2, 'Evaluating variable %s filename %s in step #%d', + $varname, $filename, $i); + my $file = substitute_variables($vars, $filename, 1, $appdir, $channel); + $$vars{$varname} = $file; + } + } + if (defined $error) { + $ignore = 1 if defined $exitcode && ($exitcode == 194 || $exitcode == 63); + if (! $ignore) { + return sprintf('Removal failed in step #%d: %s', $i, $error); + } + print_log($channel, INFO, 'Ignoring failed step #%d: %s', $i, $error); + } + } + + print_log($channel, INFO, 'Finished removing package %s version %s', $dispname, $version); + if (! $realremove) { + push @{$$counters{RemovedList}}, $dispname; + $$counters{RemovedCount}++; + } + return undef; +} + +sub remove_packages ($$$$$$$$$$$$) +{ + my ($channel, $vars, $def, $name, $paramlist, $list, $condition, $db, + $basedir, $genericmsiparams, $config, $counters) = @_; my $removecontext = {}; $list = sort_packages_to_remove($list); @@ -1732,8 +1883,16 @@ sub remove_packages ($$$$$$$$$$) next; } } - my $error = remove_package_fallback($channel, $vars, $paramlist, $inst, $condition, $dispname, - $ver, $printver, $db, $counters, $removecontext); + my $error; + my $removesteps = $$def{remove}; + if (defined $removesteps) { + $error = remove_package_steps($channel, $removesteps, $paramlist, $inst, $condition, $ver, $printver, + $db, $def, $name, $dispname, $basedir, $genericmsiparams, $config, $counters, $removecontext); + } + else { + $error = remove_package_fallback($channel, $vars, $paramlist, $inst, $condition, $dispname, + $ver, $printver, $db, $counters, $removecontext); + } return $error if defined $error; } return undef; @@ -1759,9 +1918,9 @@ sub set_install_var ($$$$) return undef; } -sub remove_package ($$$$$$$) +sub remove_package ($$$$$$$$) { - my ($db, $def, $name, $list, $basedir, $config, $counters) = @_; + my ($db, $def, $name, $list, $basedir, $genericmsiparams, $config, $counters) = @_; set_current_pkg_name($name); @@ -1773,8 +1932,8 @@ sub remove_package ($$$$$$$) my $paramlist = $$def{'remove-parameters'}; $paramlist = [] unless defined $paramlist; - my $error = remove_packages('pkg', $vars, $def, $paramlist, - [map { $$_[1] } @$list], undef, $db, $basedir, $config, $counters); + my $error = remove_packages('pkg', $vars, $def, $name, $paramlist, + [map { $$_[1] } @$list], undef, $db, $basedir, $genericmsiparams, $config, $counters); print_log('pkg', ERROR, 'Package removal failed: %s', $error) if defined $error; set_current_pkg_name(undef); @@ -1835,7 +1994,8 @@ sub install_package ($$$$$$$$$) $paramlist = [] unless defined $paramlist; my $list = find_installed_packages('pkg', $def, $db, $name, $basedir, $config, 0); if (defined $list) { - $error = remove_packages('pkg', $vars, $def, $paramlist, $list, $condition, $db, $basedir, $config, $counters); + $error = remove_packages('pkg', $vars, $def, $name, $paramlist, $list, $condition, + $db, $basedir, $genericmsiparams, $config, $counters); delete $$vars{'installed-version'}; } else { @@ -3318,7 +3478,8 @@ sub handle_pkg ($$$$$$) $$counters{ToInstallCount}++; } if ($update) { - remove_package($db, $def, $name, $toremove, $base_directory, $config, $counters) + remove_package($db, $def, $name, $toremove, $base_directory, + $$config{'generic-msi-parameters'}, $config, $counters) if scalar @$toremove > 0; install_package($db, $def, $found, $name, $desired, $base_directory, $$config{'generic-msi-parameters'}, $config, $counters)