Implemented utilizing info extracted from the patch files and stored in a database to recognize when a patch is already installed.

This commit is contained in:
László Valkó 2020-06-12 01:43:03 +02:00
parent f1db30b2d6
commit 8b9844a6e8

View file

@ -1,4 +1,4 @@
package pkgtool;
package pkgtool;
use strict;
@ -409,6 +409,10 @@ my $global_cfg_syntax = {
Type => 'string',
Mandatory => 1
},
'msupdate-path' => {
Type => 'string',
Mandatory => 1
},
'log-directory' => {
Type => 'string',
Mandatory => 1
@ -684,9 +688,9 @@ sub get_registry_value ($)
return $value;
}
sub read_os_patches ($$)
sub read_os_patches ($$$)
{
my ($patches, $registry) = @_;
my ($patches, $pkgs, $registry) = @_;
foreach my $name ($registry->SubKeyNames) {
my $sub = $registry->{$name};
@ -695,10 +699,10 @@ sub read_os_patches ($$)
my $installclient = get_registry_value($sub->{'InstallClient'});
my $state = get_registry_value($sub->{'CurrentState'});
next unless defined $installname && defined $installclient;
next unless $installname =~ /^[^~]*KB(\d[0-9a-zA-Z]+)~/o;
my $original = $installclient eq 'DISM Package Manager Provider';
my $update = $installclient eq 'WindowsUpdateAgent';
next unless $original || $update;
if ($installname =~ /^[^~]*KB(\d[0-9a-zA-Z]+)~/o) {
my $kb = $1;
my $number = $kb =~ /^(\d+)/o ? $1 : $kb;
if ($installname =~ /^[^~]*KB\d[0-9a-zA-Z]+~[^~]*~[^~]*~[^~]*~(\d+(\.\d+)*)/o) {
@ -731,11 +735,38 @@ sub read_os_patches ($$)
$$p{Current} = 1 if $state & 0x20;
}
}
if ($name =~ /^([^~]*)~([^~]*)~([^~]*)~([^~]*)~([^~]*)$/o) {
my $pkg = $$pkgs{$name};
if (! defined $pkg) {
my $pkgname = $1;
my $key = $2;
my $arch = $3;
my $pkgver = $5;
$pkg = $$pkgs{$name} = {
Type => 'OS',
Packages => { OS => 1 },
InstallName => $name,
PackageName => $pkgname,
PackageVersion => $pkgver,
Arch => $arch,
InstallClient => $installclient,
Original => $original,
Update => $update,
Current => 0,
Flags => 0
};
}
if (defined $state) {
$$pkg{Flags} |= $state;
$$pkg{Current} = 1 if $state & 0x20;
}
}
}
}
sub xread_pkg_patches ($$)
sub xread_pkg_patches ($$$)
{
my ($patches, $registry) = @_;
my ($patches, $pkgs, $registry) = @_;
foreach my $pkgname ($registry->SubKeyNames) {
my $pkg = $registry->{$pkgname};
@ -771,9 +802,9 @@ sub xread_pkg_patches ($$)
}
}
sub read_pkg_patches ($$)
sub read_pkg_patches ($$$)
{
my ($patches, $registry) = @_;
my ($patches, $pkgs, $registry) = @_;
foreach my $id ($registry->SubKeyNames) {
my $pkg = $registry->{$id};
@ -891,6 +922,7 @@ sub read_installed_patches ($)
delete $$db{PatchesChanged};
my $patches = {};
my $pkgs = {};
my $winmajor = get_win_major();
if ($winmajor >= 6) {
my $cbspatches = $Registry->Open('HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\Packages\\', { Access => 'KEY_READ' });
@ -898,7 +930,7 @@ sub read_installed_patches ($)
print_log('global', ERROR, 'Cannot find registry entry: %s', 'HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\Packages');
return undef;
}
read_os_patches($patches, $cbspatches);
read_os_patches($patches, $pkgs, $cbspatches);
}
# my $updates = $Registry->Open('HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Updates\\', { Access => 'KEY_READ' });
# if (! defined $updates) {
@ -910,8 +942,9 @@ sub read_installed_patches ($)
print_log('global', ERROR, 'Cannot find registry entry: %s', 'HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData\\S-1-5-18\\Products');
return undef;
}
read_pkg_patches($patches, $updates);
read_pkg_patches($patches, $pkgs, $updates);
$$db{Patches} = $patches;
$$db{Pkgs} = $pkgs;
return $db;
}
@ -2719,6 +2752,67 @@ sub get_pkg_instances ($$$$$)
return $instance;
}
sub get_patch_vars ($$$$$)
{
my ($config, $base_directory, $pdef, $patchdef, $kb) = @_;
my $vars = get_default_vars($config);
set_datetime_vars($vars);
my $basedir = substitute_variables($vars, $$pdef{'base-directory'}, 1, $base_directory, 'pkg');
$$vars{basedir} = $basedir;
my $patchdir = substitute_variables($vars, $$patchdef{'source-directory'}, 1, $basedir, 'pkg');
$$vars{patchdir} = $patchdir;
$$vars{patch} = $kb;
my $number = $kb =~ /^(\d+)/o ? $1 : $kb;
my $extra = $kb =~ /^\d+([^0-9].*)$/o ? '-'.$1 : '';
$$vars{patchnum} = $number;
$$vars{patchextra} = $extra;
$$vars{patchprefix} = defined $$patchdef{prefix} ? $$patchdef{prefix} : 'Windows'.get_win_version().'-';
$$vars{patchkbname} = defined $$patchdef{kbname} ? $$patchdef{kbname} : 'KB';
$$vars{patchedition} = defined $$patchdef{edition} ? $$patchdef{edition} : '';
$$vars{patcharch} = defined $$patchdef{arch} ? $$patchdef{arch} : '-'.$$vars{xarch};
$$vars{patchsuffix} = defined $$patchdef{suffix} ? $$patchdef{suffix} : '';
my $style = $$patchdef{style};
$$vars{patchext} = $style eq 'exe' ? '.exe' : $style eq 'msu' ? '.msu' : $style eq 'msp' ? '.msp' : $style eq 'cab' ? '.cab' : '';
my $sourcespec = $$patchdef{'source-file'};
$sourcespec = '%patchprefix%%patchkbname%%patchnum%%patchextra%%patchedition%%patcharch%%patchsuffix%%patchext%' unless defined $sourcespec;
my $sourcefile = substitute_variables($vars, $sourcespec, 1, $patchdir, 'pkg');
$$vars{sourcefile} = $sourcefile;
my $pkgid = substitute_variables($vars, $sourcespec, 0, undef, 'pkg');
$$vars{packageid} = $1 if $pkgid =~ /^(.*)\.msu$/oi;
return $vars;
}
sub get_msupdate_info ($$$)
{
my ($config, $pkgid, $vars) = @_;
return undef unless defined $pkgid;
my $urlpkgid = $pkgid;
my $url = 'http://'.$$config{'install-host'}.$$config{'msupdate-path'}.'?id='.$urlpkgid;
print_log('global', DEBUG1, 'Getting patch information from \'%s\'', $url);
my $ua = LWP::UserAgent->new;
my $response = $ua->get($url);
if (! $response->is_success) {
print_log('global', ERROR, 'Error getting patch information from \'%s\': %s', $url, $response->status_line);
return 1;
}
foreach my $line (split /\n/, $response->decoded_content) {
chomp $line;
print_log('global', DEBUG3, 'Received response line: %s', $line);
next unless $line =~ /^([^=]+)=(.*)$/o;
my $key = $1;
my $value = $2;
$$vars{'mspatch_'.$key} = $value;
print_log('global', DEBUG1, 'Found patch information %s=%s', $key, $value);
}
if (defined $$vars{mspatch_pkg_name} && defined $$vars{mspatch_pkg_key} &&
defined $$vars{mspatch_pkg_arch} && defined $$vars{mspatch_pkg_version}) {
$$vars{pkgname} = $$vars{mspatch_pkg_name}.'~'.$$vars{mspatch_pkg_key}.'~'.$$vars{mspatch_pkg_arch}.'~~'.$$vars{mspatch_pkg_version};
}
return undef;
}
sub assess_patch ($$$$$$$$$)
{
my ($config, $base_directory, $db, $name, $pdef, $patchdef, $kb, $update, $counters) = @_;
@ -2778,24 +2872,51 @@ sub assess_patch ($$$$$$$$$)
return 1;
}
my $vars = get_default_vars($config);
set_datetime_vars($vars);
my $basedir = substitute_variables($vars, $$pdef{'base-directory'}, 1, $base_directory, 'pkg');
$$vars{basedir} = $basedir;
my $patchdir = substitute_variables($vars, $$patchdef{'source-directory'}, 1, $basedir, 'pkg');
$$vars{patchdir} = $patchdir;
$$vars{patch} = $kb;
my $number = $kb =~ /^(\d+)/o ? $1 : $kb;
my $extra = $kb =~ /^\d+([^0-9].*)$/o ? '-'.$1 : '';
$$vars{patchnum} = $number;
$$vars{patchextra} = $extra;
my $vars = get_patch_vars($config, $base_directory, $pdef, $patchdef, $kb);
refresh_installed_patches($db);
return 1 if defined get_msupdate_info($config, $$vars{packageid}, $vars);
my $pkgname = $$vars{pkgname};
my $patches = $$db{Patches};
my $pkgs = $$db{Pkgs};
my $foundpatch = $$patches{$kb};
if (defined $foundpatch) {
my $foundpkg = defined $pkgname ? $$pkgs{$pkgname} : undef;
if (defined $foundpkg) {
my $foundforpkgs = $$foundpkg{Packages};
my $missing = [];
my $found = [];
foreach my $dispname (sort keys %$foundforpkgs) {
print_log('global', DEBUG4, 'Patch %s found installed for package (%s)', $kb, $dispname);
}
foreach my $pkgname (sort keys %$foundpkgs) {
my $founddispnames = $$foundpkgs{$pkgname};
my $any = 0;
foreach my $dispname (sort keys %$founddispnames) {
if (defined $$foundforpkgs{$dispname}) {
print_log('global', DEBUG4, 'Patch %s found installed for referenced package %s (%s)',
$kb, $pkgname, $dispname);
$any = 1;
last;
}
}
if ($any) {
push @$found, $pkgname;
print_log('global', DEBUG3, 'Patch %s installed for referenced package %s', $kb, $pkgname);
}
else {
push @$missing, $pkgname;
print_log('global', DEBUG3, 'Patch %s not installed for any referenced package %s', $kb, $pkgname);
}
}
if (scalar @$missing == 0) {
print_log('global', WARNING, 'Patch %s: installed for all required packages - OK', $kb);
return 1;
}
print_log('global', WARNING, 'Patch %s: %s%s%smissing for packages %s - %s',
$kb, (scalar @$found > 0 ? 'installed for ' : ''),
join(',', @$found), (scalar @$found > 0 ? ', ' : ''),
join(',', @$missing), $update ? 'installing' : 'NEEDED');
}
elsif (defined $foundpatch) {
my $foundforpkgs = $$foundpatch{Packages};
my $missing = [];
my $found = [];
@ -2849,33 +2970,14 @@ sub install_patch ($$$$$$$)
print_log('pkg', INFO, 'Installing patch %s', $kb);
my $vars = get_default_vars($config);
set_datetime_vars($vars);
my $vars = get_patch_vars($config, $base_directory, $pdef, $patchdef, $kb);
$$vars{pkgname} = $name;
$$vars{patch} = $kb;
my $number = $kb =~ /^(\d+)/o ? $1 : $kb;
my $extra = $kb =~ /^\d+([^0-9].*)$/o ? '-'.$1 : '';
$$vars{patchnum} = $number;
$$vars{patchextra} = $extra;
my $basedir = substitute_variables($vars, $$pdef{'base-directory'}, 1, $base_directory, 'pkg');
$$vars{basedir} = $basedir;
my $patchdir = substitute_variables($vars, $$patchdef{'source-directory'}, 1, $basedir, 'pkg');
$$vars{patchdir} = $patchdir;
$$vars{patchprefix} = defined $$patchdef{prefix} ? $$patchdef{prefix} : 'Windows'.get_win_version().'-';
$$vars{patchkbname} = defined $$patchdef{kbname} ? $$patchdef{kbname} : 'KB';
$$vars{patchedition} = defined $$patchdef{edition} ? $$patchdef{edition} : '';
$$vars{patcharch} = defined $$patchdef{arch} ? $$patchdef{arch} : '-'.$$vars{xarch};
$$vars{patchsuffix} = defined $$patchdef{suffix} ? $$patchdef{suffix} : '';
my $style = $$patchdef{style};
$$vars{patchext} = $style eq 'exe' ? '.exe' : $style eq 'msu' ? '.msu' : $style eq 'msp' ? '.msp' : $style eq 'cab' ? '.cab' : '';
my $sourcespec = $$patchdef{'source-file'};
$sourcespec = '%patchprefix%%patchkbname%%patchnum%%patchextra%%patchedition%%patcharch%%patchsuffix%%patchext%' unless defined $sourcespec;
my $sourcefile = substitute_variables($vars, $sourcespec, 1, $patchdir, 'pkg');
my $error;
my $exitcode;
my $style = $$patchdef{style};
my $patchdir = $$vars{patchdir};
my $sourcefile = $$vars{sourcefile};
if ($style eq 'exe') {
my $chdir = defined $$patchdef{chdir} ?
substitute_variables($vars, $$patchdef{chdir}, 1, $patchdir, 'pkg') : undef;