Create a new silent installer

From WPKG | Open Source Software Deployment and Distribution
Jump to: navigation, search

Packages come in many types.

MSI

MSI files are fairly common and usually easy to install and remove silently; a simple installer might look like this:

<package id="nicurl" name="NI Curl" revision="%version%.1" reboot="false">
        <variable name="version" value="15.0.358" />

        <check type="uninstall" condition="versiongreaterorequal" path="NI Curl 15.0.0 (64-bit)" value="15.0.358" />

        <install cmd="msiexec /qn /l* c:\netinst\logs\nicurl15.0.0(64-bit) /i &quot;%SOFTWARE%\ni.com\15.0.0\Products\NI_Curl\nicurl64.msi&quot;">
                <exit code="3010" reboot="false" />
        </install>

        <upgrade include="install" />

        <!-- Remove - rip out the MSI -->
        <remove cmd="msiexec /qn /x {7F805F1F-7E86-4D39-9BB5-1F7C6B06F316}">
                <!-- 1605 - already removed -->
                <exit code="1605" />
        </remove>
</package>

Such simple installers can be automatically generated using [msitools] and the perl script below, which is offered without warranty.

#!/usr/bin/perl -w
# To try to automatically build some WPKG XML from an MSI (or two, if they're different bit-ness)
# Code released under the GPL; feel free to improve and share.
# Use: wpkg-msi path.to.msi
# Use: wpkg-msi -msi32 path.to.32bit.msi -msi64 path.to.64bit.msi

use strict;
use Data::Dumper;
use Getopt::Long;
use Sys::Hostname;
use XML::Writer;

# Figure out which MSI files we're being called on
my @msis=@ARGV;

my $settings={};

# Read settings from MSI
my $msi64='';
my $msi32='';
GetOptions ("msi32=s" => \$msi32,  # 32bit MSI
            "msi64=s" => \$msi64); # 64bit MSI
if ($msi64 eq '' and $msi32 eq '' and defined($ARGV[0])) {
 $msi64=$ARGV[0];
 $msi32=$ARGV[0];
}
$settings->{'64'}=&readmsi($msi64);
$settings->{'32'}=&readmsi($msi32);

# Build the WPKG XML
my $name=$settings->{'64'}->{'ProductName'}.'|'.$settings->{'32'}->{'ProductName'};
my $id=lc($name);
$id=~ s/\s+//g;
my $version='';
if ($settings->{'64'}->{'ProductVersion'} eq $settings->{'32'}->{'ProductVersion'}) {
 $version=$settings->{'64'}->{'ProductVersion'};
} else {
 $version=$settings->{'64'}->{'ProductVersion'}.'|'.$settings->{'32'}->{'ProductVersion'};
}

my $writer=XML::Writer->new(ENCODING=>'utf-8');
$writer->comment('vim: set softtabstop=3:');
$writer->comment('Autogenerated by '.$0." running on ".hostname." as user ".getpwuid( $< ));
$writer->startTag('package',id=>$id,name=>$name,revision=>'%version%.1',reboot=>'false');
print "\n\t";
$writer->emptyTag('variable',name=>'version',value=>$version);
print "\n\t";

if ( ($settings->{'64'}->{'ProductName'} eq $settings->{'32'}->{'ProductName'}) 
  && ($settings->{'64'}->{'ProductVersion'} eq $settings->{'32'}->{'ProductVersion'}) ) {
 # Same product version
 print "\n\t";
 $writer->emptyTag('check',type=>'uninstall',
                           condition=>'versiongreaterorequal',
                           path=>$settings->{'32'}->{'ProductName'},
                           value=>$version);
} else {
 # 64bit check
 print "\n\t";
 $writer->emptyTag('check',type=>'uninstall',
                           condition=>'versiongreaterorequal',
                           path=>$name,
			   architecture=>'x64',
                           value=>$settings->{'64'}->{'ProductVersion'});
 # 32bit check
 print "\n\t";
 $writer->emptyTag('check',type=>'uninstall',
                           condition=>'versiongreaterorequal',
                           path=>$name,
			   architecture=>'x86',
                           value=>$settings->{'32'}->{'ProductVersion'});
}
if ($msi32 eq $msi64) {
 my $msi=$msi32;
 $msi =~ s/.*deploy\$/%SOFTWARE%/;
 $msi =~ s/\//\\/g;
 print "\n\n\t";
 $writer->startTag('install',cmd=>'msiexec /qn /l* c:\\netinst\\logs\\'.$id.'.log /i "'.$msi.'"');
 print "\n\t\t";
 $writer->emptyTag('exit',code=>'3010',reboot=>'false');
 print "\n\t";
 $writer->endTag('install');

} else {
 print "\n\n\t";
 my $msi=$msi32;
 $msi =~ s/.*deploy\$/%SOFTWARE%/;
 $msi =~ s/\//\\/g;
 $writer->startTag('install',architecture=>'x86',cmd=>'msiexec /qn /l* c:\\netinst\\logs\\'.$id.'.log /i "'.$msi32.'"');
 print "\n\t\t";
 $writer->comment('Handle installer exit codes here');
 print "\n\t\t";
 $writer->emptyTag('exit',code=>'3010',reboot=>'false');
 print "\n\t";
 $writer->endTag('install');
 print "\n\n\t";
 $msi=$msi64;
 $msi =~ s/.*deploy\$/%SOFTWARE%/;
 $msi =~ s/\//\\/g;
 $writer->startTag('install',architecture=>'x64',cmd=>'msiexec /qn /l* c:\\netinst\\logs\\'.$id.'.log /i "'.$msi64.'"');
 print "\n\t\t";
 $writer->comment('Handle installer exit codes here');
 print "\n\t\t";
 $writer->emptyTag('exit',code=>'3010',reboot=>'false');
 print "\n\t";
 $writer->endTag('install');
}

print "\n\n\t";
$writer->emptyTag('upgrade',include=>'install');

if ( ($settings->{'64'}->{'ProductCode'} eq $settings->{'32'}->{'ProductCode'}) ) {
 print "\n\n\t";
 $writer->comment('Remove - rip out the MSI');
 print "\n\t";
 $writer->startTag('remove',cmd=>'msiexec /qn /x '.$settings->{'32'}->{'ProductCode'});
 print "\n\t\t";
 $writer->comment('1605 - already removed');
 print "\n\t\t";
 $writer->emptyTag('exit',code=>'1605');
 print "\n\t";
 $writer->endTag('remove');
} else {
 print "\n\n\t";
 $writer->comment('Remove - rip out the MSI');
 print "\n\t";
 $writer->startTag('remove',architecture=>'x64',cmd=>'msiexec /qn /x '.$settings->{'64'}->{'ProductCode'});
 print "\n\t\t";
 $writer->comment('1605 - already removed');
 print "\n\t\t";
 $writer->emptyTag('exit',code=>'1605');
 print "\n\t";
 $writer->endTag('remove');

 print "\n\t";
 $writer->startTag('remove',architecture=>'x86',cmd=>'msiexec /qn /x '.$settings->{'32'}->{'ProductCode'});
 print "\n\t\t";
 $writer->comment('1605 - already removed');
 print "\n\t\t";
 $writer->emptyTag('exit',code=>'1605');
 print "\n\t";
 $writer->endTag('remove');
}

print "\n";
$writer->endTag('package');
$writer->end();

print "\n";
exit;

sub readmsi($) {
 my $msi=shift;
 my $fh;
 open ($fh, "msiinfo export \"$msi\" Property |");
 my $settings={};
 while (my $line=<$fh>){
  $line =~ s/\r//;
  chomp($line);
  $line =~ /^([^\s]+)\s+(.*)$/;
  chomp($2);
  $settings->{$1}=$2;
 }
 close ($fh);
 return $settings;
}