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:
parent
f1db30b2d6
commit
8b9844a6e8
252
pkgtool.pm
252
pkgtool.pm
|
@ -1,4 +1,4 @@
|
||||||
package pkgtool;
|
package pkgtool;
|
||||||
|
|
||||||
use strict;
|
use strict;
|
||||||
|
|
||||||
|
@ -409,6 +409,10 @@ my $global_cfg_syntax = {
|
||||||
Type => 'string',
|
Type => 'string',
|
||||||
Mandatory => 1
|
Mandatory => 1
|
||||||
},
|
},
|
||||||
|
'msupdate-path' => {
|
||||||
|
Type => 'string',
|
||||||
|
Mandatory => 1
|
||||||
|
},
|
||||||
'log-directory' => {
|
'log-directory' => {
|
||||||
Type => 'string',
|
Type => 'string',
|
||||||
Mandatory => 1
|
Mandatory => 1
|
||||||
|
@ -684,9 +688,9 @@ sub get_registry_value ($)
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub read_os_patches ($$)
|
sub read_os_patches ($$$)
|
||||||
{
|
{
|
||||||
my ($patches, $registry) = @_;
|
my ($patches, $pkgs, $registry) = @_;
|
||||||
|
|
||||||
foreach my $name ($registry->SubKeyNames) {
|
foreach my $name ($registry->SubKeyNames) {
|
||||||
my $sub = $registry->{$name};
|
my $sub = $registry->{$name};
|
||||||
|
@ -695,47 +699,74 @@ sub read_os_patches ($$)
|
||||||
my $installclient = get_registry_value($sub->{'InstallClient'});
|
my $installclient = get_registry_value($sub->{'InstallClient'});
|
||||||
my $state = get_registry_value($sub->{'CurrentState'});
|
my $state = get_registry_value($sub->{'CurrentState'});
|
||||||
next unless defined $installname && defined $installclient;
|
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 $original = $installclient eq 'DISM Package Manager Provider';
|
||||||
my $update = $installclient eq 'WindowsUpdateAgent';
|
my $update = $installclient eq 'WindowsUpdateAgent';
|
||||||
next unless $original || $update;
|
next unless $original || $update;
|
||||||
my $kb = $1;
|
if ($installname =~ /^[^~]*KB(\d[0-9a-zA-Z]+)~/o) {
|
||||||
my $number = $kb =~ /^(\d+)/o ? $1 : $kb;
|
my $kb = $1;
|
||||||
if ($installname =~ /^[^~]*KB\d[0-9a-zA-Z]+~[^~]*~[^~]*~[^~]*~(\d+(\.\d+)*)/o) {
|
my $number = $kb =~ /^(\d+)/o ? $1 : $kb;
|
||||||
my $version = $1;
|
if ($installname =~ /^[^~]*KB\d[0-9a-zA-Z]+~[^~]*~[^~]*~[^~]*~(\d+(\.\d+)*)/o) {
|
||||||
if (defined $version && $version ne '') {
|
my $version = $1;
|
||||||
my @versionlist = split /\./, $version;
|
if (defined $version && $version ne '') {
|
||||||
my $revnum = $versionlist[2];
|
my @versionlist = split /\./, $version;
|
||||||
if (defined $revnum && $revnum =~ /^\d+$/o && $revnum > 1) {
|
my $revnum = $versionlist[2];
|
||||||
$kb .= 'v'.$revnum;
|
if (defined $revnum && $revnum =~ /^\d+$/o && $revnum > 1) {
|
||||||
|
$kb .= 'v'.$revnum;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
my $p = $$patches{$kb};
|
||||||
|
if (! defined $p) {
|
||||||
|
$p = $$patches{$kb} = {
|
||||||
|
Type => 'OS',
|
||||||
|
Packages => { OS => 1 },
|
||||||
|
InstallName => $name,
|
||||||
|
InstallClient => $installclient,
|
||||||
|
Original => $original,
|
||||||
|
Update => $update,
|
||||||
|
KB => $kb,
|
||||||
|
Number => $number,
|
||||||
|
Current => 0,
|
||||||
|
Flags => 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (defined $state) {
|
||||||
|
$$p{Flags} |= $state;
|
||||||
|
$$p{Current} = 1 if $state & 0x20;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
my $p = $$patches{$kb};
|
if ($name =~ /^([^~]*)~([^~]*)~([^~]*)~([^~]*)~([^~]*)$/o) {
|
||||||
if (! defined $p) {
|
my $pkg = $$pkgs{$name};
|
||||||
$p = $$patches{$kb} = {
|
if (! defined $pkg) {
|
||||||
Type => 'OS',
|
my $pkgname = $1;
|
||||||
Packages => { OS => 1 },
|
my $key = $2;
|
||||||
InstallName => $name,
|
my $arch = $3;
|
||||||
InstallClient => $installclient,
|
my $pkgver = $5;
|
||||||
Original => $original,
|
$pkg = $$pkgs{$name} = {
|
||||||
Update => $update,
|
Type => 'OS',
|
||||||
KB => $kb,
|
Packages => { OS => 1 },
|
||||||
Number => $number,
|
InstallName => $name,
|
||||||
Current => 0,
|
PackageName => $pkgname,
|
||||||
Flags => 0
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (defined $state) {
|
|
||||||
$$p{Flags} |= $state;
|
|
||||||
$$p{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) {
|
foreach my $pkgname ($registry->SubKeyNames) {
|
||||||
my $pkg = $registry->{$pkgname};
|
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) {
|
foreach my $id ($registry->SubKeyNames) {
|
||||||
my $pkg = $registry->{$id};
|
my $pkg = $registry->{$id};
|
||||||
|
@ -891,6 +922,7 @@ sub read_installed_patches ($)
|
||||||
|
|
||||||
delete $$db{PatchesChanged};
|
delete $$db{PatchesChanged};
|
||||||
my $patches = {};
|
my $patches = {};
|
||||||
|
my $pkgs = {};
|
||||||
my $winmajor = get_win_major();
|
my $winmajor = get_win_major();
|
||||||
if ($winmajor >= 6) {
|
if ($winmajor >= 6) {
|
||||||
my $cbspatches = $Registry->Open('HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\Packages\\', { Access => 'KEY_READ' });
|
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');
|
print_log('global', ERROR, 'Cannot find registry entry: %s', 'HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\Packages');
|
||||||
return undef;
|
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' });
|
# my $updates = $Registry->Open('HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Updates\\', { Access => 'KEY_READ' });
|
||||||
# if (! defined $updates) {
|
# 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');
|
print_log('global', ERROR, 'Cannot find registry entry: %s', 'HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData\\S-1-5-18\\Products');
|
||||||
return undef;
|
return undef;
|
||||||
}
|
}
|
||||||
read_pkg_patches($patches, $updates);
|
read_pkg_patches($patches, $pkgs, $updates);
|
||||||
$$db{Patches} = $patches;
|
$$db{Patches} = $patches;
|
||||||
|
$$db{Pkgs} = $pkgs;
|
||||||
return $db;
|
return $db;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2719,6 +2752,67 @@ sub get_pkg_instances ($$$$$)
|
||||||
return $instance;
|
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 ($$$$$$$$$)
|
sub assess_patch ($$$$$$$$$)
|
||||||
{
|
{
|
||||||
my ($config, $base_directory, $db, $name, $pdef, $patchdef, $kb, $update, $counters) = @_;
|
my ($config, $base_directory, $db, $name, $pdef, $patchdef, $kb, $update, $counters) = @_;
|
||||||
|
@ -2778,24 +2872,51 @@ sub assess_patch ($$$$$$$$$)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $vars = get_default_vars($config);
|
my $vars = get_patch_vars($config, $base_directory, $pdef, $patchdef, $kb);
|
||||||
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;
|
|
||||||
|
|
||||||
refresh_installed_patches($db);
|
refresh_installed_patches($db);
|
||||||
|
return 1 if defined get_msupdate_info($config, $$vars{packageid}, $vars);
|
||||||
|
my $pkgname = $$vars{pkgname};
|
||||||
my $patches = $$db{Patches};
|
my $patches = $$db{Patches};
|
||||||
|
my $pkgs = $$db{Pkgs};
|
||||||
my $foundpatch = $$patches{$kb};
|
my $foundpatch = $$patches{$kb};
|
||||||
|
my $foundpkg = defined $pkgname ? $$pkgs{$pkgname} : undef;
|
||||||
if (defined $foundpatch) {
|
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 $foundforpkgs = $$foundpatch{Packages};
|
||||||
my $missing = [];
|
my $missing = [];
|
||||||
my $found = [];
|
my $found = [];
|
||||||
|
@ -2849,33 +2970,14 @@ sub install_patch ($$$$$$$)
|
||||||
|
|
||||||
print_log('pkg', INFO, 'Installing patch %s', $kb);
|
print_log('pkg', INFO, 'Installing patch %s', $kb);
|
||||||
|
|
||||||
my $vars = get_default_vars($config);
|
my $vars = get_patch_vars($config, $base_directory, $pdef, $patchdef, $kb);
|
||||||
set_datetime_vars($vars);
|
|
||||||
$$vars{pkgname} = $name;
|
$$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 $error;
|
||||||
my $exitcode;
|
my $exitcode;
|
||||||
|
my $style = $$patchdef{style};
|
||||||
|
my $patchdir = $$vars{patchdir};
|
||||||
|
my $sourcefile = $$vars{sourcefile};
|
||||||
if ($style eq 'exe') {
|
if ($style eq 'exe') {
|
||||||
my $chdir = defined $$patchdef{chdir} ?
|
my $chdir = defined $$patchdef{chdir} ?
|
||||||
substitute_variables($vars, $$patchdef{chdir}, 1, $patchdir, 'pkg') : undef;
|
substitute_variables($vars, $$patchdef{chdir}, 1, $patchdir, 'pkg') : undef;
|
||||||
|
|
Loading…
Reference in a new issue