Fixed Windows version check for CBS registry tree.

Partially implemented device-dependent driver installation.
This commit is contained in:
László Valkó 2017-12-31 11:55:21 +01:00
parent 61190595d5
commit ff05673dcf
3 changed files with 376 additions and 9 deletions

View file

@ -21,6 +21,7 @@ require Exporter;
set_log_level set_log_level
set_log_base_dir set_log_base_dir
set_current_pkg_name set_current_pkg_name
get_win_major
get_win_version get_win_version
get_default_vars get_default_vars
set_datetime_vars set_datetime_vars
@ -192,6 +193,13 @@ sub compare_versions ($$)
} }
} }
sub get_win_major ()
{
my ($osver, $osmajor, $osminor, $osbuild) = Win32::GetOSVersion();
return $osmajor;
}
sub get_win_version () sub get_win_version ()
{ {
my ($osver, $osmajor, $osminor, $osbuild) = Win32::GetOSVersion(); my ($osver, $osmajor, $osminor, $osbuild) = Win32::GetOSVersion();
@ -199,6 +207,13 @@ sub get_win_version ()
return $osmajor.'.'.$osminor; return $osmajor.'.'.$osminor;
} }
sub get_win_build ()
{
my ($osver, $osmajor, $osminor, $osbuild) = Win32::GetOSVersion();
return $osbuild;
}
sub get_default_vars (;$) sub get_default_vars (;$)
{ {
my ($config) = @_; my ($config) = @_;
@ -219,6 +234,7 @@ sub get_default_vars (;$)
$$vars{arch} = $arch; $$vars{arch} = $arch;
$$vars{xarch} = $xarch; $$vars{xarch} = $xarch;
$$vars{osversion} = get_win_version(); $$vars{osversion} = get_win_version();
$$vars{osbuild} = get_win_build();
$$vars{programfiles32} = $programfiles32; $$vars{programfiles32} = $programfiles32;
$$vars{pkgtooldir} = $pkgtool_dir; $$vars{pkgtooldir} = $pkgtool_dir;
$$vars{logdir} = $log_base_dir if defined $log_base_dir; $$vars{logdir} = $log_base_dir if defined $log_base_dir;

View file

@ -139,12 +139,16 @@ else {
} }
my $error = scan_package_dirs($config, $base_directory); my $error = scan_package_dirs($config, $base_directory);
exit(1) if defined $error; exit(1) if defined $error;
#$error = scan_driver_dirs($config, $base_directory);
#exit(1) if defined $error;
$$config{'package-def'} = {} unless defined $$config{'package-def'}; $$config{'package-def'} = {} unless defined $$config{'package-def'};
$$config{'global-variables'} = $globals; $$config{'global-variables'} = $globals;
my $db = {}; my $db = {};
read_installed_packages($db); read_installed_packages($db);
read_installed_patches($db); read_installed_patches($db);
read_present_devices($db);
read_installed_infs($db);
my $counters = { my $counters = {
RebootFlag => 0, RebootFlag => 0,

View file

@ -10,7 +10,10 @@ require Exporter;
get_default_dnsdomain get_default_dnsdomain
read_installed_patches read_installed_patches
read_installed_packages read_installed_packages
read_present_devices
read_installed_infs
scan_package_dirs scan_package_dirs
scan_driver_dir
handle_pkg handle_pkg
get_install_sets get_install_sets
); );
@ -33,6 +36,44 @@ use Win32::File::VersionInfo;
use Win32API::File qw(:Func :Misc :FILE_SHARE_ :GENERIC_); use Win32API::File qw(:Func :Misc :FILE_SHARE_ :GENERIC_);
use LWP::UserAgent; use LWP::UserAgent;
my $driver_syntax = {
Type => 'map',
Elements => {
Type => 'struct',
Check => \&check_cfg_drvdef,
Keywords => {
'description' => {
Type => 'string',
Mandatory => 1
},
'inf-file' => {
Type => 'string',
Mandatory => 1
},
'cert-file' => {
Type => 'string'
},
'device-filter' => {
Type => 'list',
Mandatory => 1,
Elements => {
Type => 'struct',
Keywords => {
'bus' => {
Type => 'string',
Mandatory => 1
},
'device' => {
Type => 'string',
Mandatory => 1
}
}
}
}
}
}
};
my $pkgdef_syntax = { my $pkgdef_syntax = {
Type => 'map', Type => 'map',
Elements => { Elements => {
@ -266,6 +307,13 @@ my $patchdef_syntax = {
} }
}; };
my $driver_cfg_syntax = {
Type => 'struct',
Keywords => {
'driver' => $driver_syntax,
}
};
my $pkgdef_cfg_syntax = { my $pkgdef_cfg_syntax = {
Type => 'struct', Type => 'struct',
Keywords => { Keywords => {
@ -378,6 +426,15 @@ my $global_cfg_syntax = {
} }
} }
}, },
'drivers' => {
Type => 'struct',
Keywords => {
'filename' => {
Type => 'string',
Mandatory => 1
}
}
},
'mbr-drive' => { 'mbr-drive' => {
Type => 'string' Type => 'string'
}, },
@ -406,6 +463,9 @@ my $global_cfg_syntax = {
'mbr-source-file' => { 'mbr-source-file' => {
Type => 'string' Type => 'string'
}, },
'driver-directory' => {
Type => 'string'
},
'remove-version' => { 'remove-version' => {
Type => 'string' Type => 'string'
}, },
@ -438,6 +498,12 @@ my $global_cfg_syntax = {
}, },
'enabled' => { 'enabled' => {
Type => 'integer' Type => 'integer'
},
'filename' => {
Type => 'string'
},
'max-depth' => {
Type => 'integer'
} }
} }
} }
@ -683,8 +749,8 @@ sub read_installed_patches ($)
delete $$db{PatchesChanged}; delete $$db{PatchesChanged};
my $patches = {}; my $patches = {};
my $winver = get_win_version(); my $winmajor = get_win_major();
if ($winver ge '6.0') { 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' });
if (! defined $cbspatches) { if (! defined $cbspatches) {
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');
@ -769,6 +835,79 @@ sub refresh_installed_packages ($)
read_installed_packages($db); read_installed_packages($db);
} }
sub read_devices ($$)
{
my ($devices, $registry) = @_;
foreach my $busname ($registry->SubKeyNames) {
my $bus = $registry->{$busname};
next unless defined $bus;
foreach my $devname ($bus->SubKeyNames) {
my $inst = {
Bus => $busname,
Device => $devname
};
my $device = $busname.'\\'.$devname;
$$devices{$device} = $inst;
}
}
}
sub read_present_devices ($)
{
my ($db) = @_;
my $devices = {};
my $uninst = $Registry->Open('HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Enum\\', { Access => 'KEY_READ' });
if (! defined $uninst) {
print_log('global', ERROR, 'Cannot find registry entry: %s', 'HKLM\\SYSTEM\\CurrentControlSet\\Enum');
return undef;
}
read_devices($devices, $uninst);
$$db{Devices} = $devices;
return $db;
}
sub read_installed_infs ($)
{
my ($db) = @_;
my $directory = $ENV{'SYSTEMROOT'}.'\\Inf';
my $infs = {};
print_log('global', DEBUG3, 'Scanning directory %s for .INF files', $directory);
if (! opendir(DIR, $directory)) {
print_log('global', ERROR, 'Cannot read directory %s: %s', $directory, $!);
return undef;
}
while (1) {
my $name = readdir(DIR);
last unless defined $name;
next if $name eq '.' || $name eq '..';
next unless $name =~ /\.inf$/io;
my $path = $directory.'\\'.$name;
my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size) = stat($path);
if (! defined $size) {
print_log('global', INFO, 'Cannot read .INF file size for %s: %s', $path, $!);
next;
}
my $inf = {
Path => $path,
Filename => $name,
Size => $size
};
$$infs{$path} = $inf;
print_log('global', DEBUG3, 'Found .INF file %s (%d bytes)', $path, $size);
}
closedir(DIR);
$$db{INFs} = $infs;
return $db;
}
sub check_cfg_drvdef ($$)
{
return 1;
}
sub check_cfg_pkgdef ($$) sub check_cfg_pkgdef ($$)
{ {
my ($install, $label) = @_; my ($install, $label) = @_;
@ -1775,16 +1914,16 @@ sub pkg_check_condition ($$$)
return ($condition); return ($condition);
} }
sub scan_dir ($$$$); sub scan_package_dir ($$$$);
sub scan_dir ($$$$) sub scan_package_dir ($$$$)
{ {
my ($config, $dir, $maxdepth, $filename) = @_; my ($config, $dir, $maxdepth, $filename) = @_;
print_log('global', DEBUG1, 'Scanning package directory %s for %d levels', $dir, $maxdepth); print_log('global', DEBUG1, 'Scanning package directory %s for %d levels', $dir, $maxdepth);
if (! opendir(DIR, $dir)) { if (! opendir(DIR, $dir)) {
print_log('global', 'ERROR', 'Cannot scan directory %s', $dir); print_log('global', ERROR, 'Cannot scan directory %s', $dir);
return 1; return 1;
} }
my $subdirs = []; my $subdirs = [];
@ -1801,7 +1940,7 @@ sub scan_dir ($$$$)
$maxdepth--; $maxdepth--;
foreach my $path (@$subdirs) { foreach my $path (@$subdirs) {
my $error = scan_dir($config, $path, $maxdepth, $filename); my $error = scan_package_dir($config, $path, $maxdepth, $filename);
return $error if defined $error; return $error if defined $error;
} }
@ -1862,12 +2001,82 @@ sub scan_package_dirs ($$)
my $vars = get_default_vars($config); my $vars = get_default_vars($config);
foreach my $dir (@$dirs) { foreach my $dir (@$dirs) {
my $scandir = substitute_variables($vars, $dir, 1, $basedir, 'global'); my $scandir = substitute_variables($vars, $dir, 1, $basedir, 'global');
my $error = scan_dir($config, $scandir, $maxdepth, $filename); my $error = scan_package_dir($config, $scandir, $maxdepth, $filename);
return $error if defined $error; return $error if defined $error;
} }
return undef; return undef;
} }
sub scan_driver_subdir ($$$$$);
sub scan_driver_subdir ($$$$$)
{
my ($config, $drvconfig, $dir, $maxdepth, $filename) = @_;
print_log('global', DEBUG1, 'Scanning driver directory %s for %d levels', $dir, $maxdepth);
if (! opendir(DIR, $dir)) {
print_log('global', INFO, 'Cannot scan directory %s', $dir);
return undef;
}
my $subdirs = [];
my $files = [];
while (1) {
my $name = readdir(DIR);
last unless defined $name;
next if $name eq '.' || $name eq '..';
my $path = $dir.'\\'.$name;
push @$subdirs, $path if $maxdepth > 0 && -d $path;
push @$files, $path if $name eq $filename;
}
closedir(DIR);
$maxdepth--;
foreach my $path (@$subdirs) {
my $error = scan_driver_subdir($config, $drvconfig, $path, $maxdepth, $filename);
return $error if defined $error;
}
my $drvdefs = $$drvconfig{'driver'};
foreach my $path (@$files) {
print_log('global', DEBUG1, 'Found driver definition file %s', $path);
my $addconfig = parse_cfg_file($path, $driver_cfg_syntax);
return 1 unless defined $addconfig;
my $adddrvdefs = $$addconfig{'driver'};
if (defined $adddrvdefs) {
foreach my $key (keys %$adddrvdefs) {
my $def = $$adddrvdefs{$key};
$drvdefs = $$drvconfig{'driver'} = {} unless defined $drvdefs;
if (defined $$drvdefs{$key}) {
print_log('global', WARNING, 'Found driver re-definition for %s in file %s', $key, $path);
next;
}
$$def{'definition-directory'} = $dir;
$$drvdefs{$key} = $def;
print_log('global', DEBUG3, 'Found driver definition for %s (%s) in file %s', $key, $$def{description}, $path);
}
}
}
return undef;
}
sub scan_driver_dir ($$$)
{
my ($config, $basedir, $drvconfig) = @_;
my $filename = $$drvconfig{'filename'};
my $maxdepth = $$drvconfig{'max-depth'};
my $dir = $$drvconfig{'driver-directory'};
return undef unless defined $filename && defined $dir;
print_log('global', INFO, 'Scanning driver directory %s', $dir);
my $vars = get_default_vars($config);
my $scandir = substitute_variables($vars, $dir, 1, $basedir, 'global');
return scan_driver_subdir($config, $drvconfig, $scandir, $maxdepth, $filename);
}
sub match_package_version_for_processing ($$$) sub match_package_version_for_processing ($$$)
{ {
my ($instver, $op, $ver) = @_; my ($instver, $op, $ver) = @_;
@ -2376,6 +2585,141 @@ sub do_modify_user ($$$$$)
return 1; return 1;
} }
sub install_cert ($)
{
my ($certpath) = @_;
my $sourcefile = $ENV{systemroot}.'\\System32\\certmgr.exe';
my $paramlist = ['-add', $certpath, '-c', '-s', '-r', 'localMachine', 'TrustedPublisher'];
my ($error, $exitcode) = run_exe('global', undef, undef, undef, $sourcefile, $paramlist, 0);
if (defined $error) {
print_log('global', ERROR, 'Error installing certificate %s: %s',
$certpath, $error);
return 0;
}
print_log('global', DEBUG1, 'Installed certifiate %s', $certpath);
return 1;
}
sub install_pnp_driver ($)
{
my ($infpath) = @_;
my $sourcefile = $ENV{systemroot}.'\\System32\\pnputil.exe';
my $paramlist = ['-i', '-a', $infpath];
my ($error, $exitcode) = run_exe('global', undef, undef, undef, $sourcefile, $paramlist, 0);
if (defined $error) {
print_log('global', ERROR, 'Error installing extra driver %s: %s',
$infpath, $error);
return 0;
}
print_log('global', DEBUG1, 'Installed extra driver %s', $infpath);
return 1;
}
sub check_if_driver_matches ($$)
{
my ($db, $drvdef) = @_;
my $filter = $$drvdef{'device-filter'};
return 0 unless defined $filter;
my $devices = $$db{Devices};
foreach my $devpath (sort keys %$devices) {
my $device = $$devices{$devpath};
next unless defined $device;
my $busname = $$device{Bus};
my $devname = $$device{Device};
my $matches = 0;
foreach my $filtrow (@$filter) {
my $busfilter = $$filtrow{bus};
my $devfilter = $$filtrow{device};
next unless defined $busfilter && defined $devfilter;
next unless $busname =~ /$busfilter/ && $devname =~ /$devfilter/;
$matches = 1;
last;
}
return 1;
}
return 0;
}
sub handle_driver ($$$$$$)
{
my ($config, $base_directory, $db, $pkg, $counters, $update) = @_;
my $name = $$pkg{name};
my $error = scan_driver_dir($config, $base_directory, $pkg);
if (defined $error) {
print_log('global', INFO, 'Skipping driver set %s because no driver directory', $name);
push @{$$counters{SkipList}}, $name;
$$counters{SkipCount}++;
return 0;
}
my $drvdefs = $$pkg{'driver'};
if (! defined $drvdefs) {
print_log('global', INFO, 'Skipping driver set %s because no driver definition found', $name);
push @{$$counters{SkipList}}, $name;
$$counters{SkipCount}++;
return 0;
}
foreach my $drvname (sort keys %$drvdefs) {
my $drvdef = $$drvdefs{$drvname};
my $drvinstname = $name.'/'.$drvname;
my $infpath = $$drvdef{'definition-directory'}.'/'.$$drvdef{'inf-file'};
my $certpath = defined $$drvdef{'cert-file'} ? $$drvdef{'definition-directory'}.'/'.$$drvdef{'cert-file'} : undef;
if (! -r $infpath) {
print_log('global', INFO, 'Skipping driver %s because .INF file %s not found', $drvinstname, $infpath);
push @{$$counters{FailList}}, $drvinstname;
$$counters{FailCount}++;
next;
}
if (defined $certpath && ! -r $certpath) {
print_log('global', INFO, 'Skipping driver %s because cert file %s not found', $drvinstname, $certpath);
push @{$$counters{FailList}}, $drvinstname;
$$counters{FailCount}++;
next;
}
print_log('global', DEBUG2, 'Checking if driver %s is needed', $drvinstname);
my $needed = check_if_driver_matches($db, $drvdef);
if (! $needed) {
print_log('global', INFO, 'Skipping driver %s because relevant device is not present', $drvinstname);
push @{$$counters{SkipList}}, $drvinstname;
$$counters{SkipCount}++;
next;
}
# check if present
print_log('global', WARNING, 'Driver %s to install: not installed - %s',
$drvinstname, $update? 'installing '.$infpath : 'INSTALL');
if ($update) {
my $rc = 1;
if (defined $certpath) {
$rc = install_cert($certpath);
}
if ($rc) {
$rc = install_pnp_driver($infpath);
}
if (! $rc) {
push @{$$counters{FailList}}, $drvinstname;
$$counters{FailCount}++;
}
else {
push @{$$counters{InstalledList}}, $drvinstname;
$$counters{InstalledCount}++;
}
}
else {
push @{$$counters{ToInstallList}}, $drvinstname;
$$counters{ToInstallCount}++;
}
}
return 1;
}
sub read_mbr_file ($$) sub read_mbr_file ($$)
{ {
my ($config, $pkg) = @_; my ($config, $pkg) = @_;
@ -2692,6 +3036,9 @@ sub handle_pkg ($$$$$$)
if (defined $$pkg{'mbr-source-file'}) { if (defined $$pkg{'mbr-source-file'}) {
return handle_mbr($config, $pkg, $counters, $update); return handle_mbr($config, $pkg, $counters, $update);
} }
if (defined $$pkg{'driver-directory'}) {
return handle_driver($config, $base_directory, $db, $pkg, $counters, $update);
}
my $pkgdefs = $$config{'package-def'}; my $pkgdefs = $$config{'package-def'};
my $patchdefs = $$config{'patch-def'}; my $patchdefs = $$config{'patch-def'};
@ -2772,14 +3119,14 @@ sub handle_pkg ($$$$$$)
print_log('global', INFO, 'Ignoring patch set %s patches %s: %s', $name, join(',', @{$$patchdef{kb}}), $error); print_log('global', INFO, 'Ignoring patch set %s patches %s: %s', $name, join(',', @{$$patchdef{kb}}), $error);
push @{$$counters{FailList}}, @{$$patchdef{kb}}; push @{$$counters{FailList}}, @{$$patchdef{kb}};
$$counters{FailCount} += scalar @{$$patchdef{kb}}; $$counters{FailCount} += scalar @{$$patchdef{kb}};
return; next;
} }
if (defined $condcheck) { if (defined $condcheck) {
if (! $condcheck) { if (! $condcheck) {
print_log('global', INFO, 'Skipping patch set %s patches %s on false condition', $name, join(',', @{$$patchdef{kb}})); print_log('global', INFO, 'Skipping patch set %s patches %s on false condition', $name, join(',', @{$$patchdef{kb}}));
push @{$$counters{SkipList}}, @{$$patchdef{kb}}; push @{$$counters{SkipList}}, @{$$patchdef{kb}};
$$counters{SkipCount} += @{$$patchdef{kb}}; $$counters{SkipCount} += @{$$patchdef{kb}};
return; next;
} }
print_log('global', DEBUG1, 'Considering patch set %s patches %s with true condition', $name, join(',', @{$$patchdef{kb}})); print_log('global', DEBUG1, 'Considering patch set %s patches %s with true condition', $name, join(',', @{$$patchdef{kb}}));
} }