#!/usr/bin/perl # # $Id: install_software.pl,v 1.1 2005/10/23 22:36:46 jmates Exp $ # # Copyright (c) 2005, Alex Dioso. # All rights reserved. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # ##################################################################### # # Removed warnings as Time::Local 1.10 and Win32 don't mix. 1.11 is # supposed to fix it but haven't figured out how to install a cpan # module into ActiveState. ActiveState has their own repository using # ppm but they only have up to 1.10. # # Install a software package using a url, compare with currently installed # version, if any. Must be run from Activestate perl. use strict; use LWP::Simple; use Digest::MD5; use Win32::TieRegistry ( Delimiter => "/" ); use Win32::OLE qw/ in /; use Win32::OLE::Variant; use File::Temp qw/ tempfile tempdir /; use Archive::Zip; use Cwd qw/ chdir getcwd /; my $olddir = getcwd(); use constant FALSE => 0; use constant TRUE => 1; use constant NOMSIEXEC => 101; use constant CURRENTEXISTS => 102; use constant NEWEREXISTS => 103; use constant PROCESSRUNNING => 104; use constant OLEGETOBJFAIL => 105; usage() unless ( scalar @ARGV >= 5 ); my $windir = $ENV{WINDIR}; my $system32 = "$windir\\system32"; my $msiexec = "$system32\\msiexec.exe"; unless ( -e $msiexec ) { remark( 'error', 'Cannot find file', { name => $msiexec } ); exit NOMSIEXEC; } my $searchname = shift; my $url = shift; my $version = shift; my $md5 = shift; my $procname = shift; my $getprocresult = process_running($procname); if ( $getprocresult == -1 ) { remark( 'error', 'Failed to get OLE object' ); exit OLEGETOBJFAIL; } if ( $getprocresult == 1 ) { remark( 'info', 'Process currently running', { name => $procname } ); exit PROCESSRUNNING; } # See if we have the latest version my $Uninstall = $Registry->{ "LMachine/SOFTWARE/Microsoft/Windows/CurrentVersion/Uninstall/"}; my $current_ver = -2; for my $key ( keys %{$Uninstall} ) { if ( exists $Uninstall->{"$key/DisplayName"} ) { if ( $Uninstall->{"$key/DisplayName"} =~ m/$searchname/ ) { $current_ver = 0; if ( exists $Uninstall->{"$key/DisplayVersion"} ) { my ($temp_ver) = $Uninstall->{"$key/DisplayVersion"} =~ m/([0-9.]+)/; $current_ver = get_higher_version( $current_ver, $temp_ver ); } } } } if ( $version eq $current_ver ) { remark( 'info', 'Specified version already installed', { version => $version } ); exit CURRENTEXISTS; } if ( $current_ver eq get_higher_version( $current_ver, $version ) ) { remark( 'info', 'Newer version already installed', { version => $current_ver } ); exit NEWEREXISTS; } # If we get here then we don't have the specified version installed and we # don't have a newer version installed. # Download the file to a temp file my ($extension) = $url =~ m/\.(msi|exe|zip)$/i or die "Valid extensions are exe, msi, and zip!\n"; my $dir = tempdir( CLEANUP => 1 ); my ( $fh, $file ) = tempfile( DIR => $dir, SUFFIX => ".$extension", UNLINK => 1 ); getstore( $url, "$file" ); die "Couldn't download $file!\n" unless -e "$file"; # Verfiy the file binmode($fh); my $ctx = Digest::MD5->new; $ctx->addfile($fh); die "Installer didn't match the given md5!\n" unless ( $md5 eq $ctx->hexdigest ); # If we were given a zip file, uncompress it and set $file to what we really # should install if ( $file =~ m/.+\.zip$/ ) { unzip( $file, $dir ) or die "Failed to unzip $file!\n"; chdir($dir) or die "Failed to chdir: dir=$dir, errno=$!\n"; $file = "$dir\\" . shift; } # If the file is a msi, use msiexec if ( $file =~ m/.+\.msi$/ ) { unshift( @ARGV, $msiexec ); push( @ARGV, "/i" ); push( @ARGV, "$file" ); } # Else execute the file as an installer else { unshift( @ARGV, "$file" ); } close($fh); # Execute our built up install command system @ARGV; chdir $olddir; # Can't use the builtin zip functionality of WinXP because the # FileSystemObjects we create with OLE can't access c:\windows\temp # Need to use Archive::Zip sub unzip { my $file = shift; my $dir = shift; my $zip = Archive::Zip->new(); if ( $zip->read($file) != 0 ) { remark( 'info', 'failed to open', { file => $file } ); return FALSE; } chdir $dir; $zip->extractMember($_) for $zip->members; return TRUE; } # Do a case insensitive search for the process sub process_running { my $procname = shift; my $Machine = "\\\\."; my $Class = "winmgmts:{impersonationLevel=impersonate}$Machine\\Root\\cimv2"; my $WMI = Win32::OLE->GetObject($Class) or return -1; foreach my $proc ( in( $WMI->InstancesOf("Win32_Process") ) ) { return 1 if $proc->{Name} =~ m/$procname/i; } return 0; } sub get_higher_version { my @first = split /\./, shift; my @second = split /\./, shift; # We want the smaller array so we don't run beyond # if the arrays are even up to the last element of the smaller array # then the larger array is the newer version my $size = scalar @first < scalar @second ? scalar @first : scalar @second; for ( 0 .. $size - 1 ) { if ( $first[$_] > $second[$_] ) { return join ".", @first; } elsif ( $first[$_] < $second[$_] ) { return join ".", @second; } } # If we get here then the arrays are equal up to the smaller array so the # larger array must be the higher version return join ".", ( scalar @first > scalar @second ? @first : @second ); } sub remark { my $priority = shift; my $message = shift; my $attributes = shift; unless ( exists $ENV{DEBUG} ) { return 1 if $priority eq 'info'; } chomp $message; my $attr_str; if ($attributes) { $attr_str = join ', ', map { $attributes->{$_} ||= ''; "$_=$attributes->{$_}" } sort keys %$attributes; } print STDERR "$priority: $message" . ( $attr_str ? ": $attr_str" : '' ) . "\n"; return 1; } sub usage { print <