diff options
Diffstat (limited to '')
-rw-r--r-- | setup_native/scripts/admin.pl | 1244 |
1 files changed, 1244 insertions, 0 deletions
diff --git a/setup_native/scripts/admin.pl b/setup_native/scripts/admin.pl new file mode 100644 index 000000000..714da400c --- /dev/null +++ b/setup_native/scripts/admin.pl @@ -0,0 +1,1244 @@ +# +# 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 +################################################################################# + +BEGIN +{ + $prog = "msi installer"; + $targetdir = ""; + $databasepath = ""; + $starttime = ""; + $globaltempdirname = "ooopackagingXXXXXX"; + $savetemppath = ""; + $msiinfo_available = 0; + $path_displayed = 0; + $localmsidbpath = ""; + + $plat = $^O; + + if ( $plat =~ /cygwin/i ) + { + $separator = "/"; + $pathseparator = "\:"; + } + else + { + $separator = "\\"; + $pathseparator = "\;"; + } +} + +################################################################################# +# 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); +} + +################################################################################# +# Collecting parameter +################################################################################# + +sub getparameter +{ + if (( $#ARGV < 3 ) || ( $#ARGV > 3 )) { usage(); } + + while ( $#ARGV >= 0 ) + { + my $param = shift(@ARGV); + + if ($param eq "-t") { $targetdir = shift(@ARGV); } + elsif ($param eq "-d") { $databasepath = shift(@ARGV); } + else + { + print "\n**********************************************\n"; + print "Error: Unknown parameter: $param"; + print "\n**********************************************\n"; + usage(); + exit(-1); + } + } +} + +################################################################################# +# Checking content of parameter +################################################################################# + +sub controlparameter +{ + if ( $targetdir eq "" ) + { + print "\n******************************************************\n"; + print "Error: Target directory not defined (parameter -t)!"; + print "\n******************************************************\n"; + usage(); + exit(-1); + } + + if ( $databasepath eq "" ) + { + print "\n******************************************************\n"; + print "Error: Path to msi database not defined (parameter -d)!"; + print "\n******************************************************\n"; + usage(); + exit(-1); + } + + if ( -d $databasepath ) + { + $databasepath =~ s/\\\s*$//; + $databasepath =~ s/\/\s*$//; + + 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"); } + + $databasepath = $databasepath . $separator . ${$msifiles}[0]; + } + + 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; + + get_path_from_fullqualifiedname(\$path); + + $path =~ s/\\\s*$//; + $path =~ s/\/\s*$//; + + my $msidbpath = ""; + if ( $path =~ /^\s*$/ ) { $msidbpath = $msidbname; } + else { $msidbpath = $path . $separator . $msidbname; } + + 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"); + + print("\nChecking required files:\n"); + + foreach $onefile ( @needed_files_in_path ) + { + print("... searching $onefile ..."); + + my $fileref = get_sourcepath_from_filename_and_includepath(\$onefile, $patharrayref); + + if ( $$fileref eq "" ) + { + $error = 1; + print( "$onefile not found\n" ); + } + else + { + print( "\tFound: $$fileref\n" ); + } + } + + if ( $error ) { exit_program("ERROR: Could not find all needed files in path (using setsolar should help)!"); } + + print("\nChecking optional files:\n"); + + foreach $onefile ( @optional_files_in_path ) + { + print("... searching $onefile ..."); + + my $fileref = get_sourcepath_from_filename_and_includepath(\$onefile, $patharrayref); + + if ( $$fileref eq "" ) + { + print( "$onefile not found\n" ); + if ( $onefile eq "msiinfo.exe" ) { $msiinfo_available = 0; } + } + else + { + print( "\tFound: $$fileref\n" ); + if ( $onefile eq "msiinfo.exe" ) { $msiinfo_available = 1; } + } + } + +} + +########################################################################## +# 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*$//; + + $onefile = $includepath . $separator . $$searchfilenameref; + + if ( -f $onefile ) + { + $foundsourcefile = 1; + last; + } + } + + if (!($foundsourcefile)) { $onefile = ""; } + + return \$onefile; +} + +######################################################## +# Finding all files with a specified file extension +# in a specified directory. +######################################################## + +sub find_file_with_file_extension +{ + my ($extension, $dir) = @_; + + my @allfiles = (); + my @sourcefiles = (); + + $dir =~ s/\Q$separator\E\s*$//; + + opendir(DIR, $dir); + @sourcefiles = readdir(DIR); + closedir(DIR); + + my $onefile; + + foreach $onefile (@sourcefiles) + { + if ((!($onefile eq ".")) && (!($onefile eq ".."))) + { + if ( $onefile =~ /^\s*(\S.*?)\.$extension\s*$/ ) + { + push(@allfiles, $onefile) + } + } + } + + return \@allfiles; +} + +############################################################## +# 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 +############################################################## + +sub create_directory +{ + my ($directory) = @_; + + if ( ! -d $directory ) { mkdir($directory, 0775); } +} + +############################################################## +# 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); + + if ($returnvalue) + { + $created_directory = 1; + + my $localcall = "chmod 775 $directory \>\/dev\/null 2\>\&1"; + system($localcall); + } + else + { + $created_directory = 0; + } + } + else + { + $created_directory = 1; + } + + return $created_directory; +} + +########################################### +# 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" +############################################ + +sub exit_program +{ + my ($message) = @_; + + print "\n***************************************************************\n"; + print "$message\n"; + print "***************************************************************\n"; + remove_complete_directory($savetemppath, 1); + print "\n" . get_time_string(); + exit(-1); +} + +################################################################################# +# 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; + + my $localcabfilename = qx{cygpath -w "$cabfilename"}; + $localcabfilename =~ s/\\/\\\\/g; + $localcabfilename =~ s/\s*$//g; + + $systemcall = $expandfile . " " . $localcabfilename . " -F:\* " . $localunpackdir . " \>\/dev\/null 2\>\&1"; + } + else + { + $systemcall = $expandfile . " " . $cabfilename . " -F:\* " . $unpackdir . " \> " . $expandlogfile; + } + + my $returnvalue = system($systemcall); + + 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; + } + + # Export of all tables by using "*" + + $systemcall = $msidb . " -d " . $fullmsidatabasepath . " -f " . $workdir . " -e $tablelist"; + print "\nAnalyzing msi database\n"; + $returnvalue = system($systemcall); + + 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; + } + } + + return ( $contains_internal_cabfiles, \%allcabfileshash ); +} + +################################################################# +# 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; } + + my @all_excluded_cabfiles = (); + + if( $^O =~ /cygwin/i ) + { + $msidatabase = qx{cygpath -w "$msidatabase"}; + $msidatabase =~ s/\\/\\\\/g; + $msidatabase =~ s/\s*$//g; + } + else + { + # msidb.exe really wants backslashes. (And double escaping because system() expands the string.) + $msidatabase =~ s/\//\\\\/g; + } + + foreach my $onefile ( keys %{$allcabfiles} ) + { + my $systemcall = $msidb . " -d " . $msidatabase . " -x " . $onefile; + system($systemcall); + push(@all_excluded_cabfiles, $onefile); + } + + \@all_excluded_cabfiles; +} + +################################################################################ +# 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) = @_; + + my %table = (); + + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; } + + if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\s*$/ ) + { + my $dir = $1; + my $parent = $2; + my $name = $3; + + if ( $name =~ /^\s*(.*?)\s*\:\s*(.*?)\s*$/ ) { $name = $2; } + if ( $name =~ /^\s*(.*?)\s*\|\s*(.*?)\s*$/ ) { $name = $2; } + + my %helphash = (); + $helphash{'Directory_Parent'} = $parent; + $helphash{'DefaultDir'} = $name; + $table{$dir} = \%helphash; + } + } + + return \%table; +} + +################################################################################# +# Analyzing the content of Component.idt +################################################################################# + +sub analyze_component_file +{ + my ($filecontent) = @_; + + my %table = (); + + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; } + + 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; + + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; } + + if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ ) + { + my $file = $1; + my $comp = $2; + my $filename = $3; + my $sequence = $8; + + if ( $filename =~ /^\s*(.*?)\s*\|\s*(.*?)\s*$/ ) { $filename = $2; } + + my %helphash = (); + $helphash{'Component'} = $comp; + $helphash{'FileName'} = $filename; + $helphash{'Sequence'} = $sequence; + + $table{$file} = \%helphash; + + $fileorder{$sequence} = $file; + + if ( $sequence > $maxsequence ) { $maxsequence = $sequence; } + } + } + + return (\%table, \%fileorder, $maxsequence); +} + +#################################################################################### +# 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"); + + foreach $dir (@startparents) { create_directory_tree($dir, \%fullpathhash, $targetdir, $dirhash); } + + # 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) = @_; + + my $localcall = "chmod $privileges " . "\"" . $destfile . "\""; + system($localcall); +} + +#################################################################################### +# Cygwin: Setting privileges for files recursively +#################################################################################### + +sub change_privileges_full +{ + my ($target) = @_; + + print "Changing privileges\n"; + + my $localcall = "chmod -R 755 " . "\"" . $target . "\""; + system($localcall); +} + +###################################################### +# 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 + + $directory = $directory . "_" . $pid . $time; + + if ( ! -d $directory ) { create_directory($directory); } + else { exit_program("ERROR: Directory $directory already exists!"); } + + return $directory; +} + +#################################################################################### +# 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'}; + + $destfile = $destdir . $separator . $destfile; + my $sourcefile = $unpackdir . $separator . $file; + + 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; + + $localinstalldir =~ s/\\\s*$//; + $localinstalldir =~ s/\/\s*$//; + + $sourcefile = $localinstalldir . $separator . $subdir . $realfilename; + + if ( ! -f $sourcefile ) { exit_program("ERROR: File not found: \"$oldsourcefile\" (or \"$sourcefile\")."); } + } + + my $copyreturn = copy($sourcefile, $destfile); + + if ( ! $copyreturn) { exit_program("ERROR: Could not copy $source to $dest\n"); } + + # if (( $^O =~ /cygwin/i ) && ( $destfile =~ /\.exe\s*$/ )) { change_privileges($destfile, "775"); } + } + # else # allowing missing sequence numbers ? + # { + # exit_program("ERROR: No file assigned to sequence $i"); + # } + } +} + +###################################################### +# 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"; } + + opendir(DIR, $directory); + @content = readdir(DIR); + closedir(DIR); + + my $oneitem; + + foreach $oneitem (@content) + { + if ((!($oneitem eq ".")) && (!($oneitem eq ".."))) + { + my $item = $directory . $separator . $oneitem; + + if ( -f $item || -l $item ) # deleting files or links + { + unlink($item); + } + + if ( -d $item ) # recursive + { + remove_complete_directory($item, 0); + } + } + } + + # try to remove empty directory + my $returnvalue = rmdir $directory; + if ( ! $returnvalue ) { print "Warning: Problem with removing empty dir $directory\n"; } + } +} + +#################################################################################### +# Defining a temporary path +#################################################################################### + +sub get_temppath +{ + my $temppath = ""; + + if (( $ENV{'TMP'} ) || ( $ENV{'TEMP'} )) + { + if ( $ENV{'TMP'} ) { $temppath = $ENV{'TMP'}; } + elsif ( $ENV{'TEMP'} ) { $temppath = $ENV{'TEMP'}; } + + $temppath =~ s/\Q$separator\E\s*$//; # removing ending slashes and backslashes + $temppath = $temppath . $separator . $globaltempdirname; + $temppath = mkdtemp($temppath); + + my $dirsave = $temppath; + + $temppath = $temppath . $separator . "a"; + $temppath = create_pid_directory($temppath); + + if ( ! -d $temppath ) { exit_program("ERROR: Failed to create directory $temppath ! Possible reason: Wrong privileges in directory $dirsave."); } + + if ( $^O =~ /cygwin/i ) + { + $temppath =~ s/\\/\\\\/g; + chomp( $temppath = qx{cygpath -w "$temppath"} ); + } + + $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++; + + if ( $second < 10 ) { $second = "0" . $second; } + if ( $minute < 10 ) { $minute = "0" . $minute; } + if ( $hour < 10 ) { $hour = "0" . $hour; } + if ( $day < 10 ) { $day = "0" . $day; } + if ( $month < 10 ) { $month = "0" . $month; } + + my $timestring = $year . "/" . $month . "/" . $day . " " . $hour . ":" . $minute . ":" . $second; + + return $timestring; +} + +############################################################### +# 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. +############################################################### + +sub write_sis_info +{ + my ($msidatabase) = @_; + + print "Setting SIS in msi database\n"; + + if ( ! -f $msidatabase ) { exit_program("ERROR: Cannot find file $msidatabase"); } + + my $msiinfo = "msiinfo.exe"; # Has to be in the path + my $infoline = ""; + my $systemcall = ""; + my $returnvalue = ""; + + # Required setting for administrative installations: + # -w 4 (source files are unpacked), wordcount + # -s <date of admin installation>, LastPrinted, Syntax: <yyyy/mm/dd hh:mm:ss> + # -l <person_making_admin_installation>, LastSavedBy + + my $wordcount = 4; # Unpacked files + my $lastprinted = get_sis_time_string(); + my $lastsavedby = "Installer"; + + my $localmsidatabase = $msidatabase; + + if( $^O =~ /cygwin/i ) + { + $localmsidatabase = qx{cygpath -w "$localmsidatabase"}; + $localmsidatabase =~ s/\\/\\\\/g; + $localmsidatabase =~ s/\s*$//g; + } + + $systemcall = $msiinfo . " " . "\"" . $localmsidatabase . "\"" . " -w " . $wordcount . " -s " . "\"" . $lastprinted . "\"" . " -l $lastsavedby"; + + $returnvalue = system($systemcall); + + 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 +############################################################### + +sub get_time_string +{ + my $currenttime = time(); + $currenttime = $currenttime - $starttime; + $currenttime = convert_timestring($currenttime); + $currenttime = localtime() . " \(" . $currenttime . "\)\n"; + return $currenttime; +} + +#################################################################################### +# Simulating an administrative installation +#################################################################################### + +$starttime = time(); + +getparameter(); +controlparameter(); +check_local_msidb(); +check_system_path(); +my $temppath = get_temppath(); + +print("\nmsi database: $databasepath\n"); +print("Destination directory: $targetdir\n" ); + +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); +} + +# Reading tables +$filename = $helperdir . $separator . "Directory.idt"; +$filecontent = read_file($filename); +my $dirhash = analyze_directory_file($filecontent); + +$filename = $helperdir . $separator . "Component.idt"; +$filecontent = read_file($filename); +my $componenthash = analyze_component_file($filecontent); + +$filename = $helperdir . $separator . "File.idt"; +$filecontent = read_file($filename); +my ( $filehash, $fileorder, $maxsequence ) = analyze_file_file($filecontent); + +# Creating the directory structure +my $fullpathhash = create_directory_structure($dirhash, $targetdir); + +# Copying files +copy_files_into_directory_structure($fileorder, $filehash, $componenthash, $fullpathhash, $maxsequence, $unpackdir, $installdir, $dirhash); +if ( $^O =~ /cygwin/i ) { change_privileges_full($targetdir); } + +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); + +print "\nSuccessful installation: " . get_time_string(); |