From ff05673dcffa89f5f63ea70c907e9a6876e06591 Mon Sep 17 00:00:00 2001 From: Valko Laszlo Date: Sun, 31 Dec 2017 11:55:21 +0100 Subject: [PATCH] Fixed Windows version check for CBS registry tree. Partially implemented device-dependent driver installation. --- logging.pm | 16 +++ pkgtool.pl | 4 + pkgtool.pm | 365 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 376 insertions(+), 9 deletions(-) diff --git a/logging.pm b/logging.pm index 6b4fa80..3a08db9 100644 --- a/logging.pm +++ b/logging.pm @@ -21,6 +21,7 @@ require Exporter; set_log_level set_log_base_dir set_current_pkg_name + get_win_major get_win_version get_default_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 () { my ($osver, $osmajor, $osminor, $osbuild) = Win32::GetOSVersion(); @@ -199,6 +207,13 @@ sub get_win_version () return $osmajor.'.'.$osminor; } +sub get_win_build () +{ + my ($osver, $osmajor, $osminor, $osbuild) = Win32::GetOSVersion(); + + return $osbuild; +} + sub get_default_vars (;$) { my ($config) = @_; @@ -219,6 +234,7 @@ sub get_default_vars (;$) $$vars{arch} = $arch; $$vars{xarch} = $xarch; $$vars{osversion} = get_win_version(); + $$vars{osbuild} = get_win_build(); $$vars{programfiles32} = $programfiles32; $$vars{pkgtooldir} = $pkgtool_dir; $$vars{logdir} = $log_base_dir if defined $log_base_dir; diff --git a/pkgtool.pl b/pkgtool.pl index 3d52048..95c96f8 100644 --- a/pkgtool.pl +++ b/pkgtool.pl @@ -139,12 +139,16 @@ else { } my $error = scan_package_dirs($config, $base_directory); 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{'global-variables'} = $globals; my $db = {}; read_installed_packages($db); read_installed_patches($db); +read_present_devices($db); +read_installed_infs($db); my $counters = { RebootFlag => 0, diff --git a/pkgtool.pm b/pkgtool.pm index ba1f962..8ae1842 100644 --- a/pkgtool.pm +++ b/pkgtool.pm @@ -10,7 +10,10 @@ require Exporter; get_default_dnsdomain read_installed_patches read_installed_packages + read_present_devices + read_installed_infs scan_package_dirs + scan_driver_dir handle_pkg get_install_sets ); @@ -33,6 +36,44 @@ use Win32::File::VersionInfo; use Win32API::File qw(:Func :Misc :FILE_SHARE_ :GENERIC_); 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 = { Type => 'map', Elements => { @@ -266,6 +307,13 @@ my $patchdef_syntax = { } }; +my $driver_cfg_syntax = { + Type => 'struct', + Keywords => { + 'driver' => $driver_syntax, + } +}; + my $pkgdef_cfg_syntax = { Type => 'struct', Keywords => { @@ -378,6 +426,15 @@ my $global_cfg_syntax = { } } }, + 'drivers' => { + Type => 'struct', + Keywords => { + 'filename' => { + Type => 'string', + Mandatory => 1 + } + } + }, 'mbr-drive' => { Type => 'string' }, @@ -406,6 +463,9 @@ my $global_cfg_syntax = { 'mbr-source-file' => { Type => 'string' }, + 'driver-directory' => { + Type => 'string' + }, 'remove-version' => { Type => 'string' }, @@ -438,6 +498,12 @@ my $global_cfg_syntax = { }, 'enabled' => { Type => 'integer' + }, + 'filename' => { + Type => 'string' + }, + 'max-depth' => { + Type => 'integer' } } } @@ -683,8 +749,8 @@ sub read_installed_patches ($) delete $$db{PatchesChanged}; my $patches = {}; - my $winver = get_win_version(); - if ($winver ge '6.0') { + 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' }); if (! defined $cbspatches) { 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); } +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 ($$) { my ($install, $label) = @_; @@ -1775,16 +1914,16 @@ sub pkg_check_condition ($$$) return ($condition); } -sub scan_dir ($$$$); +sub scan_package_dir ($$$$); -sub scan_dir ($$$$) +sub scan_package_dir ($$$$) { my ($config, $dir, $maxdepth, $filename) = @_; print_log('global', DEBUG1, 'Scanning package directory %s for %d levels', $dir, $maxdepth); if (! opendir(DIR, $dir)) { - print_log('global', 'ERROR', 'Cannot scan directory %s', $dir); + print_log('global', ERROR, 'Cannot scan directory %s', $dir); return 1; } my $subdirs = []; @@ -1801,7 +1940,7 @@ sub scan_dir ($$$$) $maxdepth--; 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; } @@ -1862,12 +2001,82 @@ sub scan_package_dirs ($$) my $vars = get_default_vars($config); foreach my $dir (@$dirs) { 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 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 ($$$) { my ($instver, $op, $ver) = @_; @@ -2376,6 +2585,141 @@ sub do_modify_user ($$$$$) 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 ($$) { my ($config, $pkg) = @_; @@ -2692,6 +3036,9 @@ sub handle_pkg ($$$$$$) if (defined $$pkg{'mbr-source-file'}) { 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 $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); push @{$$counters{FailList}}, @{$$patchdef{kb}}; $$counters{FailCount} += scalar @{$$patchdef{kb}}; - return; + next; } if (defined $condcheck) { if (! $condcheck) { print_log('global', INFO, 'Skipping patch set %s patches %s on false condition', $name, join(',', @{$$patchdef{kb}})); push @{$$counters{SkipList}}, @{$$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}})); }