Ninthwave has asked for the wisdom of the Perl Monks concerning the following question:

Does anyone know where I can find a utility to read these wonderful little things from Microsoft. I can read them in WinZip and thought well I could use Archive::Zip to open them and get the information out of the file. But this errors when trying to open the file.

The Error

C:\Patch_Adams\scripts>hotfix_update.pl IO error: opening c:/Patch_Adams/Patches/Windows2000-KB824146-x86-ENU. +exe for re ad : No such file or directory Archive::Zip::Archive::read('Archive::Zip::Archive=HASH(0x2f48 +95c)','c:/ Patch_Adams/Patches/Windows2000-KB824146-x86-ENU.exe') called at C:\Pa +tch_Adams\ scripts\hotfix_update.pl line 83 main::Add_Hotfix('Windows2000-KB824146-x86-ENU.exe','win2k') c +alled at C :\Patch_Adams\scripts\hotfix_update.pl line 177 read error at C:\Patch_Adams\scripts\hotfix_update.pl line 83.

The Code

use strict; use Win32::ODBC; use Archive::Zip qw( :ERROR_CODES :CONSTANTS ); our $PATCH_PATH ="c:/Patch_Adams/Patches/"; our $DSN = "scanner"; our $TABLE = "hotfixes"; our @valid_os = ("nt4wk","nt4sv","win2k","winxp","w2003"); sub Check_Array(\@\@){ my ($installed_ref , $hotfix_ref) = @_; my %installed = (); my @ready2install = (); foreach my $hotfix (@{$hotfix_ref}){$installed{$hotfix}=1} foreach my $hotfix (@{$installed_ref}){ unless ($installed{$hotfix}){ push (@ready2install , $hotfix); } } return @ready2install; } sub Add_Hotfix($$){ my ($hotfix , $os) = @_; my $zip = Archive::Zip->new(); die 'read error' unless $zip->read("${PATCH_PATH}${hotfix}") == AZ +_OK; } my (@installed_nt4wk , @installed_nt4sv , @installed_win2k , @installe +d_w2003 , @installed_winxp); my (@hotfix_nt4wk , @hotfix_nt4sv , @hotfix_win2k , @hotfix_w2003 , @h +otfix_winxp); my (@ready_nt4wk , @ready_nt4sv , @ready_win2k, @ready_w2003 , @ready_ +winxp); foreach my $os (@valid_os){ my $path_os = "${PATCH_PATH}${os}"; opendir (DIR, $path_os) or die "$!: Can not open $path_os!"; while(defined(my $installed_hotfix = readdir(DIR))){ next if $installed_hotfix =~/^\.\.?$/; chomp ($installed_hotfix); push (@installed_nt4wk , $installed_hotfix) if ($os eq 'nt4wk' +); push (@installed_nt4sv , $installed_hotfix) if ($os eq 'nt4sv' +); push (@installed_win2k , $installed_hotfix) if ($os eq 'win2k' +); push (@installed_w2003 , $installed_hotfix) if ($os eq 'w2003' +); push (@installed_winxp , $installed_hotfix) if ($os eq 'winxp' +); } closedir (DIR); } #** If all these directories are empty we need not run any more of th +is #** program at all. + my $length_nt4wk = scalar (@installed_nt4wk); my $length_nt4sv = scalar (@installed_nt4sv); my $length_win2k = scalar (@installed_win2k); my $length_w2003 = scalar (@installed_w2003); my $length_winxp = scalar (@installed_winxp); my $length_of_all = $length_nt4wk + $length_nt4sv + $length_win2k + $l +ength_w2003 + $length_winxp; die "No hotfix files are installed in directory $PATCH_PATH\n" if ($le +ngth_of_all eq 0); #** Open the database and read into arrays the hotfixes for each os. + my $db = new Win32::ODBC($DSN); die "$! : DSN $DSN not available." if (! $db); die "$! : there is a problem with table $TABLE" if( $db->Sql("SELECT * + FROM $TABLE" )); my %hotfix_record; while ($db->FetchRow){ %hotfix_record = $db->DataHash("operating_system","software_name") +; push (@hotfix_nt4wk, $hotfix_record{software_name}) if ($hotfix_re +cord{operating_system} eq 'nt4wk'); push (@hotfix_nt4sv, $hotfix_record{software_name}) if ($hotfix_re +cord{operating_system} eq 'nt4sv'); push (@hotfix_win2k, $hotfix_record{software_name}) if ($hotfix_re +cord{operating_system} eq 'win2k'); push (@hotfix_w2003, $hotfix_record{software_name}) if ($hotfix_re +cord{operating_system} eq 'w2003'); push (@hotfix_winxp, $hotfix_record{software_name}) } $db->Close(); #** Compare the installed arrays to the database arrays and make list +s of #** hotfixes to be installed. + @ready_nt4wk = Check_Array (@installed_nt4wk , @hotfix_nt4wk); @ready_nt4sv = Check_Array (@installed_nt4sv , @hotfix_nt4sv); @ready_win2k = Check_Array (@installed_win2k , @hotfix_win2k); @ready_w2003 = Check_Array (@installed_w2003 , @hotfix_w2003); @ready_winxp = Check_Array (@installed_winxp , @hotfix_winxp); #** Loop through arrays and install hotfix files per os. + foreach my $hotfix (@ready_nt4wk){Add_Hotfix ($hotfix,$valid_os[0])} foreach my $hotfix (@ready_nt4sv){Add_Hotfix ($hotfix,$valid_os[1])} foreach my $hotfix (@ready_win2k){Add_Hotfix ($hotfix,$valid_os[2])} foreach my $hotfix (@ready_winxp){Add_Hotfix ($hotfix,$valid_os[3])} foreach my $hotfix (@ready_w2003){Add_Hotfix ($hotfix,$valid_os[4])}

Does anyone have any experience playing with these type of files or know of a module or utility that can handle this type of data. I don't want to reinvent the wheel but I am having a hard time finding information on the format of the files.

Yes I know I need to clean up most of the code but it processes the data correctly, which is my concern for now, it just will not open the .exe. For a note this is the Microsoft Hotfix files. I want to place them in a directory as approved hot fixes and read the update.ver exe which lists the files updated. I would parse this into an xml document with the file version numbers. That way I can automatically create records for my scanner to compare instead of manually entering the information.

"No matter where you go, there you are." BB

Replies are listed 'Best First'.
Re: Self Extracting Cabinet Files in Perl
by Limbic~Region (Chancellor) on Nov 12, 2003 at 18:30 UTC
    Ninthwave,
    According to WinZip, they do not require any external programs to deal with Microsoft's .cab files (not to be confused with Norton's). They have most likely incorporated Microsoft's toolkit.

    The toolkit provides binaries and documents covering the standards. I am not sure if it is sufficient to do your own Perl port.

    I didn't find anything searching CPAN, but I did find CabExtract if you need it to run in a non-windows environment.

    Cheers - L~R

      Thank you, I am still researching the links. I am debating having the files stored on a linux box and the processing that is win32 dependent on a 2000 box. I don't know yet I would like it to work on both in the end. But that envolves the evolution of Tie::Registry to be POSIX capable, and a change to DBI for me. But this code is the working model I would than tweak it once it processes correctly. Out of curiousity did you search for these links, have you found them before or a combination of both, because I can usually find enough information to get me started but I couldn't on this subject.

      "No matter where you go, there you are." BB
Re: Self Extracting Cabinet Files in Perl
by jmanning2k (Pilgrim) on Nov 12, 2003 at 21:29 UTC
    On win32 systems, you might try just a system call to their expand.exe program.
    system("expand source.cab destdir/");

      I was trying not to actually expand the files but just to access the files in the cabinet. With Archive::Zip you can mount the zip file as a directory and traverse it. These files are .exe and self extracting so I wonder if expand might choke on them or try to run it. But I will give it a try though I am looking at cabextract and what the code tells me.

      "No matter where you go, there you are." BB

      It appears that extract will error when faced with a self extracting cabinet file. If if the file properties show it as a cabinet file. I have also tried to use cabarc from the links submitted by Limbic~Region which also errors on self extracting cabinet files. I have now taken a plunge and am trying to use the Win32::API to access cabinet.dll. If WinZip can doit perl should be able to. I will post any frustrations, queries or successes in this node. But thnank you for the pointers it has again openned my eyes to the fact that Win32 Modules needed ported to POSIX systems and that Microsoft Standards are well always the same no matter how much they open them :( But we know this, why god oh why must we still have to face them on a daily basis just so the idiot two cubicles over can make a spreadsheet do simple math and call themselves a programmer.

      "No matter where you go, there you are." BB
Re: Self Extracting Cabinet Files in Perl
by graff (Chancellor) on Nov 17, 2003 at 04:17 UTC
    Sorry to be coming into this a bit late -- it's not clear to me whether your basic problem has been solved, but I just wanted to make one suggestion about your script.

    Use hashes that are keyed by "os_type". As posted, you have about 4 times more lines of code than you really need, because you are using separate array/variable names for each OS. Not only does it take longer to create these unnecessary lines of code, it will also take much longer to maintain, fix, adapt, etc. Try something like this:

    ... my %installed; # these will be HoA my %hotfix; # where the hash keys are my %ready; # taken from @valid_os my $ninstalled = 0; foreach my $os (@valid_os) { my $path_os = $PATCHPATH . $os; opendir( DIR, $path_os ) or die "dir $path_os not found: $!"; while (my $_ = readdir(DIR)) { next if ( /^\.\.?$/ ); push( @{$installed{$os}}, $_ ); ### SEE NOTE BELOW $ninstalled++; } closedir DIR; } die "No hotfix files are installed in $PATCH_PATH\n" unless ($ninstall +ed); ... while ($db->FetchRow) { my %hotfix_record = $db->DataHash("os","swname"); push( @{$hotfix{$os}}, $hotfix_record{swname} ); } ... for my $os (@valid_os) { @{$ready{$os}} = Check_Array( @{$installed{$os}}, @{$hotfix{$os}} +); for my $hotfix ( @{$ready{$os}} ) { Add_Hotfix( $hotfix, $os ); } }
    This way, when you need to add yet another OS to the list, you only need to change the one line that assigns values to the "@valid_os" array, and everything else is taken care of, without further ado.

    NOTE: The code in your OP was doing a "chomp" on the string that was returned by readdir(). NO, DO NOT DO THAT. Directories are not like text files; the file name string you get from a directory via readdir() does not have a line-feed at the end. This would probably explain the error message that you reported at the beginning ("file not found").