# # This file is part of the LibreOffice project. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # # This file incorporates work covered by the following license notice: # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed # with this work for additional information regarding copyright # ownership. The ASF licenses this file to you under the Apache # License, Version 2.0 (the "License"); you may not use this file # except in compliance with the License. You may obtain a copy of # the License at http://www.apache.org/licenses/LICENSE-2.0 . #
use Cwd;
use File::Copy;
use File::Temp qw/ :mktemp /;
################################################################################# # Global settings #################################################################################
################################################################################# # Program information #################################################################################
sub usage
{
print <<End;
----------------------------------------------------------------------
This program installs a Windows Installer installation set
without using msiexec.exe. The installation is comparable
with an administrative installation using the Windows Installer
service.
Required parameter:
-d Path to installation set or msi database
-t Target directory
--------------------------------------------------------------------- End
exit(-1);
}
my $msifiles = find_file_with_file_extension("msi", $databasepath);
if ( $#{$msifiles} < 0 ) { exit_program("ERROR: Did not find msi database in directory $installationdir"); } if ( $#{$msifiles} > 0 ) { exit_program("ERROR: Did find more than one msi database in directory $installationdir"); }
if ( ! -f $databasepath ) { exit_program("ERROR: Did not find msi database in directory $databasepath."); }
if ( ! -d $targetdir ) { create_directories($targetdir); }
}
############################################################################# # The program msidb.exe can be located next to the Perl program. Then it is # not necessary to find it in the PATH variable. #############################################################################
sub check_local_msidb
{
my $msidbname = "msidb.exe";
my $perlprogramm = $0;
my $path = $perlprogramm;
if ( -f $msidbpath )
{
$localmsidbpath = $msidbpath;
print "Using $msidbpath (next to \"admin.pl\")\n";
}
}
############################################################################# # Converting a string list with separator $listseparator # into an array #############################################################################
sub convert_stringlist_into_array
{
my ( $includestringref, $listseparator ) = @_;
my @newarray = ();
my $first;
my $last = ${$includestringref};
while ( $last =~ /^\s*(.+?)\Q$listseparator\E(.+)\s*$/) # "$" for minimal matching
{
$first = $1;
$last = $2; # Problem with two directly following listseparators. For example a path with two ";;" directly behind each other
$first =~ s/^$listseparator//;
push(@newarray, "$first\n");
}
push(@newarray, "$last\n");
return \@newarray;
}
######################################################### # Checking the local system # Checking existence of needed files in include path #########################################################
sub check_system_path
{
my $onefile;
my $error = 0;
my $pathvariable = $ENV{'PATH'};
my $local_pathseparator = $pathseparator;
if( $^O =~ /cygwin/i )
{ # When using cygwin's perl the PATH variable is POSIX style and ...
$pathvariable = qx{cygpath -mp "$pathvariable"} ; # has to be converted to DOS style for further use.
$local_pathseparator = ';';
}
my $patharrayref = convert_stringlist_into_array(\$pathvariable, $local_pathseparator);
my @needed_files_in_path = ("expand.exe"); if ( $localmsidbpath eq "" ) { push(@needed_files_in_path, "msidb.exe"); } # not found locally -> search in path
my @optional_files_in_path = ("msiinfo.exe");
########################################################################## # Searching a file in a list of paths ##########################################################################
sub get_sourcepath_from_filename_and_includepath
{
my ($searchfilenameref, $includepatharrayref) = @_;
my $onefile = "";
my $foundsourcefile = 0;
for ( my $j = 0; $j <= $#{$includepatharrayref}; $j++ )
{
my $includepath = ${$includepatharrayref}[$j];
$includepath =~ s/^\s*//;
$includepath =~ s/\s*$//;
######################################################## # Finding all files with a specified file extension # in a specified directory. ########################################################
sub find_file_with_file_extension
{
my ($extension, $dir) = @_;
############################################################## # Creating a directory with all parent directories ##############################################################
sub create_directories
{
my ($directory) = @_;
if ( ! try_to_create_directory($directory) )
{
my $parentdir = $directory;
get_path_from_fullqualifiedname(\$parentdir);
create_directories($parentdir); # recursive
}
create_directory($directory); # now it has to succeed
}
############################################################## # Creating one directory ##############################################################
############################################################## # Trying to create a directory, no error if this fails ##############################################################
sub try_to_create_directory
{
my ($directory) = @_;
my $returnvalue = 1;
my $created_directory = 0;
if (!(-d $directory))
{
$returnvalue = mkdir($directory, 0775);
########################################### # Getting path from full file name ###########################################
sub get_path_from_fullqualifiedname
{
my ($longfilenameref) = @_;
if ( $$longfilenameref =~ /\Q$separator\E/ ) # Is there a separator in the path? Otherwise the path is empty.
{ if ( $$longfilenameref =~ /^\s*(\S.*\Q$separator\E)(\S.+\S?)/ )
{
$$longfilenameref = $1;
}
} else
{
$$longfilenameref = ""; # there is no path
}
}
############################################################## # Getting file name from full file name ##############################################################
sub make_absolute_filename_to_relative_filename
{
my ($longfilenameref) = @_;
# Either '/' or '\'. if ( $$longfilenameref =~ /^.*[\/\\](\S.+\S?)/ )
{
$$longfilenameref = $1;
}
}
############################################ # Exiting the program with an error # This function is used instead of "die" ############################################
################################################################################# # Unpacking cabinet files with expand #################################################################################
sub unpack_cabinet_file
{
my ($cabfilename, $unpackdir) = @_;
my $expandfile = "expand.exe"; # has to be in the PATH
# expand.exe has to be located in the system directory. # Cygwin has another tool expand.exe, that converts tabs to spaces. This cannot be used of course. # But this wrong expand.exe is typically in the PATH before this expand.exe, to unpack # cabinet files.
if ( $^O =~ /cygwin/i )
{
$expandfile = $ENV{'SYSTEMROOT'} . "/system32/expand.exe"; # Has to be located in the systemdirectory
$expandfile =~ s/\\/\//; if ( ! -f $expandfile ) { exit_program("ERROR: Did not find file $expandfile in the Windows system folder!"); }
}
my $expandlogfile = $unpackdir . $separator . "expand.log";
# exclude cabinet file # my $systemcall = $cabarc . " -o X " . $mergemodulehash->{'cabinetfile'};
my $systemcall = ""; if ( $^O =~ /cygwin/i ) {
my $localunpackdir = qx{cygpath -w "$unpackdir"};
$localunpackdir =~ s/\\/\\\\/g;
if ($returnvalue) { exit_program("ERROR: Could not execute $systemcall !"); }
}
################################################################################# # Extracting tables from msi database #################################################################################
sub extract_tables_from_database
{
my ($fullmsidatabasepath, $workdir, $tablelist) = @_;
my $msidb = "msidb.exe"; # Has to be in the path if ( $localmsidbpath ) { $msidb = $localmsidbpath; }
my $infoline = "";
my $systemcall = "";
my $returnvalue = "";
if ( $^O =~ /cygwin/i ) {
chomp( $fullmsidatabasepath = qx{cygpath -w "$fullmsidatabasepath"} ); # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
$fullmsidatabasepath =~ s/\\/\\\\/g;
$workdir =~ s/\\/\\\\/g; # and if there are still slashes, they also need to be double backslash
$fullmsidatabasepath =~ s/\//\\\\/g;
$workdir =~ s/\//\\\\/g;
}
if ($returnvalue)
{
$infoline = "ERROR: Could not execute $systemcall !\n";
exit_program($infoline);
}
}
######################################################## # Check, if this installation set contains # internal cabinet files included into the msi # database. ########################################################
sub check_for_internal_cabfiles
{
my ($cabfilehash) = @_;
my $contains_internal_cabfiles = 0;
my %allcabfileshash = ();
foreach my $filename ( keys %{$cabfilehash} )
{ if ( $filename =~ /^\s*\#/ ) # starting with a hash
{
$contains_internal_cabfiles = 1; # setting real filename without hash as key and name with hash as value
my $realfilename = $filename;
$realfilename =~ s/^\s*\#//;
$allcabfileshash{$realfilename} = $filename;
}
}
################################################################# # Exclude all cab files from the msi database. #################################################################
sub extract_cabs_from_database
{
my ($msidatabase, $allcabfiles) = @_;
my $infoline = "";
my $fullsuccess = 1;
my $msidb = "msidb.exe"; # Has to be in the path if ( $localmsidbpath ) { $msidb = $localmsidbpath; }
################################################################################ # Collect all DiskIds to the corresponding cabinet files from Media.idt. ################################################################################
sub analyze_media_file
{
my ($filecontent) = @_;
my %diskidhash = ();
for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
{ if ( $i < 3 ) { next; }
if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
{
my $diskid = $1;
my $cabfile = $4;
$diskidhash{$cabfile} = $diskid;
}
}
return \%diskidhash;
}
################################################################################ # Analyzing the content of Directory.idt #################################################################################
sub analyze_directory_file
{
my ($filecontent) = @_;
################################################################################# # Analyzing the content of Component.idt #################################################################################
sub analyze_component_file
{
my ($filecontent) = @_;
if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
{
my $component = $1;
my $dir = $3;
$table{$component} = $dir;
}
}
return \%table;
}
################################################################################# # Analyzing the content of File.idt #################################################################################
sub analyze_file_file
{
my ($filecontent) = @_;
my %table = ();
my %fileorder = ();
my $maxsequence = 0;
if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
{
my $file = $1;
my $comp = $2;
my $filename = $3;
my $sequence = $8;
#################################################################################### # Recursively creating the directory tree ####################################################################################
sub create_directory_tree
{
my ($parent, $pathcollector, $fulldir, $dirhash) = @_;
foreach my $dir ( keys %{$dirhash} )
{ if (( $dirhash->{$dir}->{'Directory_Parent'} eq $parent ) && ( $dirhash->{$dir}->{'DefaultDir'} ne "." ))
{
my $dirname = $dirhash->{$dir}->{'DefaultDir'}; # Create the directory
my $newdir = $fulldir . $separator . $dirname; if ( ! -f $newdir ) { mkdir $newdir; } # Saving in collector
$pathcollector->{$dir} = $newdir; # Iteration
create_directory_tree($dir, $pathcollector, $newdir, $dirhash);
}
}
}
#################################################################################### # Creating the directory tree ####################################################################################
sub create_directory_structure
{
my ($dirhash, $targetdir) = @_;
print "Creating directories\n";
my %fullpathhash = ();
my @startparents = ("TARGETDIR", "INSTALLLOCATION");
# Also adding the paths of the startparents foreach $dir (@startparents)
{ if ( ! exists($fullpathhash{$dir}) ) { $fullpathhash{$dir} = $targetdir; }
}
return \%fullpathhash;
}
#################################################################################### # Cygwin: Setting privileges for files ####################################################################################
sub change_privileges
{
my ($destfile, $privileges) = @_;
###################################################### # Creating a new directory with defined privileges ######################################################
sub create_directory_with_privileges
{
my ($directory, $privileges) = @_;
my $returnvalue = 1;
my $infoline = "";
if (!(-d $directory))
{
my $localprivileges = oct("0".$privileges); # changes "777" to 0777
$returnvalue = mkdir($directory, $localprivileges);
if ($returnvalue)
{
my $localcall = "chmod $privileges $directory \>\/dev\/null 2\>\&1";
system($localcall);
}
} else
{
my $localcall = "chmod $privileges $directory \>\/dev\/null 2\>\&1";
system($localcall);
}
}
###################################################### # Creating a unique directory with pid extension ######################################################
sub create_pid_directory
{
my ($directory) = @_;
$directory =~ s/\Q$separator\E\s*$//;
my $pid = $$; # process id
my $time = time(); # time
#################################################################################### # Copying files into installation set ####################################################################################
sub copy_files_into_directory_structure
{
my ($fileorder, $filehash, $componenthash, $fullpathhash, $maxsequence, $unpackdir, $installdir, $dirhash) = @_;
print "Copying files\n";
for ( my $i = 1; $i <= $maxsequence; $i++ )
{ if ( exists($fileorder->{$i}) )
{
my $file = $fileorder->{$i}; if ( ! exists($filehash->{$file}->{'Component'}) ) { exit_program("ERROR: Did not find component for file: \"$file\"."); }
my $component = $filehash->{$file}->{'Component'}; if ( ! exists($componenthash->{$component}) ) { exit_program("ERROR: Did not find directory for component: \"$component\"."); }
my $dirname = $componenthash->{$component}; if ( ! exists($fullpathhash->{$dirname}) ) { exit_program("ERROR: Did not find full directory path for dir: \"$dirname\"."); }
my $destdir = $fullpathhash->{$dirname}; if ( ! exists($filehash->{$file}->{'FileName'}) ) { exit_program("ERROR: Did not find \"FileName\" for file: \"$file\"."); }
my $destfile = $filehash->{$file}->{'FileName'};
if ( ! -f $sourcefile )
{ # It is possible, that this was an unpacked file # Looking in the dirhash, to find the subdirectory in the installation set (the id is $dirname) # subdir is not recursively analyzed, only one directory.
my $oldsourcefile = $sourcefile;
my $subdir = ""; if ( exists($dirhash->{$dirname}->{'DefaultDir'}) ) { $subdir = $dirhash->{$dirname}->{'DefaultDir'} . $separator; }
my $realfilename = $filehash->{$file}->{'FileName'};
my $localinstalldir = $installdir;
###################################################### # Removing a complete directory with subdirectories ######################################################
sub remove_complete_directory
{
my ($directory, $start) = @_;
my @content = ();
my $infoline = "";
$directory =~ s/\Q$separator\E\s*$//;
if ( -d $directory )
{ if ( $start ) { print "Removing directory $directory\n"; }
# try to remove empty directory
my $returnvalue = rmdir $directory; if ( ! $returnvalue ) { print "Warning: Problem with removing empty dir $directory\n"; }
}
}
#################################################################################### # Defining a temporary path ####################################################################################
$savetemppath = $temppath;
} else
{
exit_program("ERROR: Could not set temporary directory (TMP and TEMP not set!).");
}
return $temppath;
}
#################################################################################### # Reading one file ####################################################################################
sub read_file
{
my ($localfile) = @_;
my @localfile = ();
open( IN, "<$localfile" ) || exit_program("ERROR: Cannot open file $localfile for reading");
# Don't use "my @localfile = <IN>" here, because # perl has a problem with the internal "large_and_huge_malloc" function # when calling perl using MacOS 10.5 with a perl built with MacOS 10.4 while ( $line = <IN> ) {
push @localfile, $line;
}
close( IN );
return \@localfile;
}
############################################################### # Setting the time string for the # Summary Information stream in the # msi database of the admin installations. ###############################################################
sub get_sis_time_string
{ # Syntax: <yyyy/mm/dd hh:mm:ss>
my $second = (localtime())[0];
my $minute = (localtime())[1];
my $hour = (localtime())[2];
my $day = (localtime())[3];
my $month = (localtime())[4];
my $year = 1900 + (localtime())[5];
$month++;
############################################################### # Writing content of administrative installations into # Summary Information Stream of msi database. # This is required for example for following # patch processes using Windows Installer service. ###############################################################
if ($returnvalue)
{
$infoline = "ERROR: Could not execute $systemcall !\n";
exit_program($infoline);
}
}
############################################################### # Convert time string ###############################################################
sub convert_timestring
{
my ($secondstring) = @_;
my $timestring = "";
if ( $secondstring < 60 ) # less than a minute
{ if ( $secondstring < 10 ) { $secondstring = "0" . $secondstring; }
$timestring = "00\:$secondstring min\.";
}
elsif ( $secondstring < 3600 )
{
my $minutes = $secondstring / 60;
my $seconds = $secondstring % 60; if ( $minutes =~ /(\d*)\.\d*/ ) { $minutes = $1; } if ( $minutes < 10 ) { $minutes = "0" . $minutes; } if ( $seconds < 10 ) { $seconds = "0" . $seconds; }
$timestring = "$minutes\:$seconds min\.";
} else# more than one hour
{
my $hours = $secondstring / 3600;
my $secondstring = $secondstring % 3600;
my $minutes = $secondstring / 60;
my $seconds = $secondstring % 60; if ( $hours =~ /(\d*)\.\d*/ ) { $hours = $1; } if ( $minutes =~ /(\d*)\.\d*/ ) { $minutes = $1; } if ( $hours < 10 ) { $hours = "0" . $hours; } if ( $minutes < 10 ) { $minutes = "0" . $minutes; } if ( $seconds < 10 ) { $seconds = "0" . $seconds; }
$timestring = "$hours\:$minutes\:$seconds hours";
}
return $timestring;
}
############################################################### # Returning time string for logging ###############################################################
my $helperdir = $temppath . $separator . "installhelper";
create_directory($helperdir);
# Get File.idt, Component.idt and Directory.idt from database
my $tablelist = "File Directory Component Media CustomAction";
extract_tables_from_database($databasepath, $helperdir, $tablelist);
# Set unpackdir
my $unpackdir = $helperdir . $separator . "unpack";
create_directory($unpackdir);
# Reading media table to check for internal cabinet files
my $filename = $helperdir . $separator . "Media.idt"; if ( ! -f $filename ) { exit_program("ERROR: Could not find required file: $filename !"); }
my $filecontent = read_file($filename);
my $cabfilehash = analyze_media_file($filecontent);
# Check, if there are internal cab files
my ( $contains_internal_cabfiles, $all_internal_cab_files) = check_for_internal_cabfiles($cabfilehash);
if ( $contains_internal_cabfiles )
{ # Set unpackdir
my $cabdir = $helperdir . $separator . "internal_cabs";
create_directory($cabdir);
my $from = cwd();
chdir($cabdir); # Exclude all cabinet files from database
my $all_excluded_cabs = extract_cabs_from_database($databasepath, $all_internal_cab_files);
print "Unpacking files from internal cabinet file(s)\n"; foreach my $cabfile ( @{$all_excluded_cabs} ) { unpack_cabinet_file($cabfile, $unpackdir); }
chdir($from);
}
# Unpack all cab files into $helperdir, cab files must be located next to msi database
my $installdir = $databasepath;
get_path_from_fullqualifiedname(\$installdir);
my $databasefilename = $databasepath;
make_absolute_filename_to_relative_filename(\$databasefilename);
my $cabfiles = find_file_with_file_extension("cab", $installdir);
if (( $#{$cabfiles} < 0 ) && ( ! $contains_internal_cabfiles )) { exit_program("ERROR: Did not find any cab file in directory $installdir"); }
print "Unpacking files from cabinet file(s)\n"; for ( my $i = 0; $i <= $#{$cabfiles}; $i++ )
{
my $cabfile = $installdir . $separator . ${$cabfiles}[$i];
unpack_cabinet_file($cabfile, $unpackdir);
}
my $msidatabase = $targetdir . $separator . $databasefilename;
my $copyreturn = copy($databasepath, $msidatabase); if ( ! $copyreturn) { exit_program("ERROR: Could not copy $source to $dest\n"); }
# Saving info in Summary Information Stream of msi database (required for following patches) if ( $msiinfo_available ) { write_sis_info($msidatabase); }
# Removing the helper directory
remove_complete_directory($temppath, 1);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.