Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / solenv / bin / modules / installer / windows / mergemodule.pm
blobf737c2b905c66b73045fa23b7271ad906ba302aa
2 # This file is part of the LibreOffice project.
4 # This Source Code Form is subject to the terms of the Mozilla Public
5 # License, v. 2.0. If a copy of the MPL was not distributed with this
6 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 # This file incorporates work covered by the following license notice:
10 # Licensed to the Apache Software Foundation (ASF) under one or more
11 # contributor license agreements. See the NOTICE file distributed
12 # with this work for additional information regarding copyright
13 # ownership. The ASF licenses this file to you under the Apache
14 # License, Version 2.0 (the "License"); you may not use this file
15 # except in compliance with the License. You may obtain a copy of
16 # the License at http://www.apache.org/licenses/LICENSE-2.0 .
19 package installer::windows::mergemodule;
21 use strict;
22 use warnings;
24 use Cwd;
25 use Digest::MD5;
26 use installer::converter;
27 use installer::exiter;
28 use installer::files;
29 use installer::globals;
30 use installer::logger;
31 use installer::pathanalyzer;
32 use installer::remover;
33 use installer::scriptitems;
34 use installer::systemactions;
35 use installer::worker;
36 use installer::windows::idtglobal;
37 use installer::windows::language;
39 #################################################################
40 # Merging the Windows MergeModules into the msi database.
41 #################################################################
43 sub merge_mergemodules_into_msi_database
45 my ($mergemodules, $filesref, $msifilename, $languagestringref, $allvariables, $includepatharrayref, $allupdatesequences, $allupdatelastsequences, $allupdatediskids) = @_;
47 my $domerge = 0;
48 if (( $#{$mergemodules} > -1 ) && ( ! $installer::globals::languagepack ) && ( ! $installer::globals::helppack )) { $domerge = 1; }
50 if ( $domerge )
52 installer::logger::include_header_into_logfile("Merging merge modules into msi database");
53 installer::logger::print_message( "... merging msm files into msi database ... \n" );
54 installer::logger::include_timestamp_into_logfile("\nPerformance Info: MergeModule into msi database, start");
56 my $msidb = "msidb.exe"; # Has to be in the path
57 my $cabinetfile = "MergeModule.CABinet"; # the name of each cabinet file in a merge file
58 my $infoline = "";
59 my $systemcall = "";
60 my $returnvalue = "";
62 # 1. Analyzing the MergeModule (has only to be done once)
63 # a. -> Extracting cabinet file: msidb.exe -d <msmfile> -x MergeModule.CABinet
64 # b. -> Number of files in cabinet file: msidb.exe -d <msmfile> -f <directory> -e File
65 # c. -> List of components: msidb.exe -d <msmfile> -f <directory> -e Component
67 if ( ! $installer::globals::mergemodules_analyzed )
69 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Analyzing MergeModules, start");
70 $infoline = "Analyzing all Merge Modules\n\n";
71 push( @installer::globals::logfileinfo, $infoline);
73 %installer::globals::mergemodules = ();
75 my $mergemoduledir = installer::systemactions::create_directories("mergefiles", $languagestringref);
77 my $mergemodule;
78 foreach $mergemodule ( @{$mergemodules} )
80 my $filename = $mergemodule->{'Name'};
81 my $mergefile = $ENV{'MSM_PATH'} . $filename;
83 if ( ! -f $mergefile ) { installer::exiter::exit_program("ERROR: msm file not found: $filename ($mergefile)!", "merge_mergemodules_into_msi_database"); }
84 my $completesource = $mergefile;
86 my $mergegid = $mergemodule->{'gid'};
87 my $workdir = $mergemoduledir . $installer::globals::separator . $mergegid;
88 if ( ! -d $workdir ) { installer::systemactions::create_directory($workdir); }
90 $infoline = "Analyzing Merge Module: $filename\n";
91 push( @installer::globals::logfileinfo, $infoline);
93 # copy msm file into working directory
94 my $completedest = $workdir . $installer::globals::separator . $filename;
95 installer::systemactions::copy_one_file($completesource, $completedest);
96 if ( ! -f $completedest ) { installer::exiter::exit_program("ERROR: msm file not found: $completedest !", "merge_mergemodules_into_msi_database"); }
98 # changing directory
99 my $from = cwd();
100 my $to = $workdir;
101 chdir($to);
103 # remove an existing cabinet file
104 if ( -f $cabinetfile ) { unlink($cabinetfile); }
106 # exclude cabinet file
107 $systemcall = $msidb . " -d " . $filename . " -x " . $cabinetfile;
108 $returnvalue = system($systemcall);
110 $infoline = "Systemcall: $systemcall\n";
111 push( @installer::globals::logfileinfo, $infoline);
113 if ($returnvalue)
115 $infoline = "ERROR: Could not execute $systemcall !\n";
116 push( @installer::globals::logfileinfo, $infoline);
117 installer::exiter::exit_program("ERROR: Could not extract cabinet file from merge file: $completedest !", "merge_mergemodules_into_msi_database");
119 else
121 $infoline = "Success: Executed $systemcall successfully!\n";
122 push( @installer::globals::logfileinfo, $infoline);
125 # exclude tables from mergefile
126 # Attention: All listed tables have to exist in the database. If they not exist, an error window pops up
127 # and the return value of msidb.exe is not zero. The error window makes it impossible to check the existence
128 # of a table with the help of the return value.
129 # Solution: Export of all tables by using "*" . Some tables must exist (File Component Directory), other
130 # tables do not need to exist (MsiAssembly).
132 if ( $^O =~ /cygwin/i ) {
133 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
134 my $localworkdir = $workdir;
135 $localworkdir =~ s/\//\\\\/g;
136 $systemcall = $msidb . " -d " . $filename . " -f " . $localworkdir . " -e \\\*";
138 else
140 $systemcall = $msidb . " -d " . $filename . " -f " . $workdir . " -e \*";
143 $returnvalue = system($systemcall);
145 $infoline = "Systemcall: $systemcall\n";
146 push( @installer::globals::logfileinfo, $infoline);
148 if ($returnvalue)
150 $infoline = "ERROR: Could not execute $systemcall !\n";
151 push( @installer::globals::logfileinfo, $infoline);
152 installer::exiter::exit_program("ERROR: Could not exclude tables from merge file: $completedest !", "merge_mergemodules_into_msi_database");
154 else
156 $infoline = "Success: Executed $systemcall successfully!\n";
157 push( @installer::globals::logfileinfo, $infoline);
160 # Determining files
161 my $idtfilename = "File.idt"; # must exist
162 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: File \"$idtfilename\" not found in directory \"$workdir\" !", "merge_mergemodules_into_msi_database"); }
163 my $filecontent = installer::files::read_file($idtfilename);
164 my @file_idt_content = ();
165 my $filecounter = 0;
166 my %mergefilesequence = ();
167 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
169 if ( $i <= 2 ) { next; } # ignoring first three lines
170 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
171 $filecounter++;
172 push(@file_idt_content, ${$filecontent}[$i]);
173 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.+?)\t(.*?)\t(.*?)\t(.*?)\t(\d+?)\s*$/ )
175 my $filename = $1;
176 my $filesequence = $8;
177 $mergefilesequence{$filename} = $filesequence;
179 else
181 my $linecount = $i + 1;
182 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "merge_mergemodules_into_msi_database");
186 # Determining components
187 $idtfilename = "Component.idt"; # must exist
188 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: File \"$idtfilename\" not found in directory \"$workdir\" !", "merge_mergemodules_into_msi_database"); }
189 $filecontent = installer::files::read_file($idtfilename);
190 my %componentnames = ();
191 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
193 if ( $i <= 2 ) { next; } # ignoring first three lines
194 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
195 if ( ${$filecontent}[$i] =~ /^\s*(\S+)\s+/ ) { $componentnames{$1} = 1; }
198 # Determining directories
199 $idtfilename = "Directory.idt"; # must exist
200 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: File \"$idtfilename\" not found in directory \"$workdir\" !", "merge_mergemodules_into_msi_database"); }
201 $filecontent = installer::files::read_file($idtfilename);
202 my %mergedirectories = ();
203 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
205 if ( $i <= 2 ) { next; } # ignoring first three lines
206 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
207 if ( ${$filecontent}[$i] =~ /^\s*(\S+)\s+/ ) { $mergedirectories{$1} = 1; }
210 # Determining assemblies
211 $idtfilename = "MsiAssembly.idt"; # does not need to exist
212 my $hasmsiassemblies = 0;
213 my %mergeassemblies = ();
214 if ( -f $idtfilename )
216 $filecontent = installer::files::read_file($idtfilename);
217 $hasmsiassemblies = 1;
218 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
220 if ( $i <= 2 ) { next; } # ignoring first three lines
221 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
222 if ( ${$filecontent}[$i] =~ /^\s*(\S+)\s+/ ) { $mergeassemblies{$1} = 1; }
226 # It is possible, that other tables have to be checked here. This happens, if tables in the
227 # merge module have to know the "Feature" or the "Directory", under which the content of the
228 # msm file is integrated into the msi database.
230 # Determining name of cabinet file in installation set
231 my $cabfilename = $mergemodule->{'Cabfilename'};
232 if ( $cabfilename ) { installer::packagelist::resolve_packagevariables(\$cabfilename, $allvariables, 0); }
234 # Analyzing styles
235 # Flag REMOVE_FILE_TABLE is required for msvc9 Merge-Module, because otherwise msidb.exe
236 # fails during integration of msm file into msi database.
238 my $styles = "";
239 my $removefiletable = 0;
240 if ( $mergemodule->{'Styles'} ) { $styles = $mergemodule->{'Styles'}; }
241 if ( $styles =~ /\bREMOVE_FILE_TABLE\b/ ) { $removefiletable = 1; }
243 if ( $removefiletable )
245 my $removeworkdir = $workdir . $installer::globals::separator . "remove_file_idt";
246 if ( ! -d $removeworkdir ) { installer::systemactions::create_directory($removeworkdir); }
247 my $completeremovedest = $removeworkdir . $installer::globals::separator . $filename;
248 installer::systemactions::copy_one_file($completedest, $completeremovedest);
249 if ( ! -f $completeremovedest ) { installer::exiter::exit_program("ERROR: msm file not found: $completeremovedest !", "merge_mergemodules_into_msi_database"); }
251 # Unpacking msm file
252 if ( $^O =~ /cygwin/i ) {
253 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
254 my $localcompleteremovedest = $completeremovedest;
255 my $localremoveworkdir = $removeworkdir;
256 $localcompleteremovedest =~ s/\//\\\\/g;
257 $localremoveworkdir =~ s/\//\\\\/g;
258 $systemcall = $msidb . " -d " . $localcompleteremovedest . " -f " . $localremoveworkdir . " -e \\\*";
260 else
262 $systemcall = $msidb . " -d " . $completeremovedest . " -f " . $removeworkdir . " -e \*";
265 $returnvalue = system($systemcall);
267 my $idtfilename = $removeworkdir . $installer::globals::separator . "File.idt";
268 if ( -f $idtfilename ) { unlink $idtfilename; }
269 unlink $completeremovedest;
271 # Packing msm file without "File.idt"
272 if ( $^O =~ /cygwin/i ) {
273 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
274 my $localcompleteremovedest = $completeremovedest;
275 my $localremoveworkdir = $removeworkdir;
276 $localcompleteremovedest =~ s/\//\\\\/g;
277 $localremoveworkdir =~ s/\//\\\\/g;
278 $systemcall = $msidb . " -c -d " . $localcompleteremovedest . " -f " . $localremoveworkdir . " -i \\\*";
280 else
282 $systemcall = $msidb . " -c -d " . $completeremovedest . " -f " . $removeworkdir . " -i \*";
284 $returnvalue = system($systemcall);
286 # Using this msm file for merging
287 if ( -f $completeremovedest ) { $completedest = $completeremovedest; }
288 else { installer::exiter::exit_program("ERROR: Could not find msm file without File.idt: $completeremovedest !", "merge_mergemodules_into_msi_database"); }
291 # Saving MergeModule info
293 my %onemergemodulehash = ();
294 $onemergemodulehash{'mergefilepath'} = $completedest;
295 $onemergemodulehash{'workdir'} = $workdir;
296 $onemergemodulehash{'cabinetfile'} = $workdir . $installer::globals::separator . $cabinetfile;
297 $onemergemodulehash{'filenumber'} = $filecounter;
298 $onemergemodulehash{'componentnames'} = \%componentnames;
299 $onemergemodulehash{'componentcondition'} = $mergemodule->{'ComponentCondition'};
300 $onemergemodulehash{'attributes_add'} = $mergemodule->{'Attributes_Add'};
301 $onemergemodulehash{'cabfilename'} = $cabfilename;
302 $onemergemodulehash{'feature'} = $mergemodule->{'Feature'};
303 $onemergemodulehash{'rootdir'} = $mergemodule->{'RootDir'};
304 $onemergemodulehash{'name'} = $mergemodule->{'Name'};
305 $onemergemodulehash{'mergefilesequence'} = \%mergefilesequence;
306 $onemergemodulehash{'mergeassemblies'} = \%mergeassemblies;
307 $onemergemodulehash{'mergedirectories'} = \%mergedirectories;
308 $onemergemodulehash{'hasmsiassemblies'} = $hasmsiassemblies;
309 $onemergemodulehash{'removefiletable'} = $removefiletable;
310 $onemergemodulehash{'fileidtcontent'} = \@file_idt_content;
312 $installer::globals::mergemodules{$mergegid} = \%onemergemodulehash;
314 # Collecting all cab files, to copy them into installation set
315 if ( $cabfilename ) { $installer::globals::copy_msm_files{$cabfilename} = $onemergemodulehash{'cabinetfile'}; }
317 chdir($from);
320 $infoline = "All Merge Modules successfully analyzed\n";
321 push( @installer::globals::logfileinfo, $infoline);
323 $installer::globals::mergemodules_analyzed = 1;
324 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Analyzing MergeModules, stop");
326 $infoline = "\n";
327 push( @installer::globals::logfileinfo, $infoline);
330 # 2. Change msi database (has to be done for every msi database -> for every language)
331 # a. Merge msm file into msi database: msidb.exe -d <msifile> -m <mergefile>
332 # b. Extracting tables from msi database: msidb.exe -d <msifile> -f <directory> -e File Media, ...
333 # c. Changing content of msi database in tables: File, Media, Directory, FeatureComponent
334 # d. Including tables into msi database: msidb.exe -d <msifile> -f <directory> -i File Media, ...
335 # e. Copying cabinet file into installation set (later)
337 my $counter = 0;
338 my $mergemodulegid;
339 foreach $mergemodulegid (keys %installer::globals::mergemodules)
341 my $mergemodulehash = $installer::globals::mergemodules{$mergemodulegid};
342 $counter++;
344 installer::logger::include_header_into_logfile("Merging Module: $mergemodulehash->{'name'}");
345 installer::logger::print_message( "\t... $mergemodulehash->{'name'} ... \n" );
347 $msifilename = installer::converter::make_path_conform($msifilename);
348 my $workdir = $msifilename;
349 installer::pathanalyzer::get_path_from_fullqualifiedname(\$workdir);
351 # changing directory
352 my $from = cwd();
353 my $to = $workdir;
354 chdir($to);
356 # Saving original msi database
357 installer::systemactions::copy_one_file($msifilename, "$msifilename\.$counter");
359 # Merging msm file, this is the "real" merge command
361 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Before merging database");
363 if ( $^O =~ /cygwin/i ) {
364 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
365 my $localmergemodulepath = $mergemodulehash->{'mergefilepath'};
366 my $localmsifilename = $msifilename;
367 $localmergemodulepath =~ s/\//\\\\/g;
368 $localmsifilename =~ s/\//\\\\/g;
369 $systemcall = $msidb . " -d " . $localmsifilename . " -m " . $localmergemodulepath;
371 else
373 $systemcall = $msidb . " -d " . $msifilename . " -m " . $mergemodulehash->{'mergefilepath'};
375 $returnvalue = system($systemcall);
377 $infoline = "Systemcall: $systemcall\n";
378 push( @installer::globals::logfileinfo, $infoline);
380 if ($returnvalue)
382 $infoline = "ERROR: Could not execute $systemcall . Returnvalue: $returnvalue!\n";
383 push( @installer::globals::logfileinfo, $infoline);
384 installer::exiter::exit_program("Could not merge msm file into database: $mergemodulehash->{'mergefilepath'}\n$infoline", "merge_mergemodules_into_msi_database");
386 else
388 $infoline = "Success: Executed $systemcall successfully!\n";
389 push( @installer::globals::logfileinfo, $infoline);
392 installer::logger::include_timestamp_into_logfile("\nPerformance Info: After merging database");
394 # Saving original idt files
395 if ( -f "File.idt" ) { installer::systemactions::rename_one_file("File.idt", "old.File.idt.$counter"); }
396 if ( -f "Media.idt" ) { installer::systemactions::rename_one_file("Media.idt", "old.Media.idt.$counter"); }
397 if ( -f "Directory.idt" ) { installer::systemactions::rename_one_file("Directory.idt", "old.Directory.idt.$counter"); }
398 if ( -f "Director.idt" ) { installer::systemactions::rename_one_file("Director.idt", "old.Director.idt.$counter"); }
399 if ( -f "FeatureComponents.idt" ) { installer::systemactions::rename_one_file("FeatureComponents.idt", "old.FeatureComponents.idt.$counter"); }
400 if ( -f "FeatureC.idt" ) { installer::systemactions::rename_one_file("FeatureC.idt", "old.FeatureC.idt.$counter"); }
401 if ( -f "MsiAssembly.idt" ) { installer::systemactions::rename_one_file("MsiAssembly.idt", "old.MsiAssembly.idt.$counter"); }
402 if ( -f "MsiAssem.idt" ) { installer::systemactions::rename_one_file("MsiAssem.idt", "old.MsiAssem.idt.$counter"); }
403 if ( -f "Componen.idt" ) { installer::systemactions::rename_one_file("Componen.idt", "old.Componen.idt.$counter"); }
405 # Extracting tables
407 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Before extracting tables");
409 my $workingtables = "File Media Directory FeatureComponents"; # required tables
410 # Optional tables can be added now
411 if ( $mergemodulehash->{'hasmsiassemblies'} ) { $workingtables = $workingtables . " MsiAssembly"; }
412 if ( ( $mergemodulehash->{'componentcondition'} ) || ( $mergemodulehash->{'attributes_add'} ) ) { $workingtables = $workingtables . " Component"; }
414 # Table "Feature" has to be exported, but it is not necessary to import it.
415 if ( $^O =~ /cygwin/i ) {
416 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
417 my $localmsifilename = $msifilename;
418 my $localworkdir = $workdir;
419 $localmsifilename =~ s/\//\\\\/g;
420 $localworkdir =~ s/\//\\\\/g;
421 $systemcall = $msidb . " -d " . $localmsifilename . " -f " . $localworkdir . " -e " . "Feature " . $workingtables;
423 else
425 $systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -e " . "Feature " . $workingtables;
427 $returnvalue = system($systemcall);
429 $infoline = "Systemcall: $systemcall\n";
430 push( @installer::globals::logfileinfo, $infoline);
432 if ($returnvalue)
434 $infoline = "ERROR: Could not execute $systemcall !\n";
435 push( @installer::globals::logfileinfo, $infoline);
436 installer::exiter::exit_program("ERROR: Could not exclude tables from msi database: $msifilename !", "merge_mergemodules_into_msi_database");
438 else
440 $infoline = "Success: Executed $systemcall successfully!\n";
441 push( @installer::globals::logfileinfo, $infoline);
444 installer::logger::include_timestamp_into_logfile("\nPerformance Info: After extracting tables");
446 # Using 8+3 table names, that are used, when tables are integrated into database. The export of tables
447 # creates idt-files, that have long names.
449 if ( -f "Directory.idt" ) { installer::systemactions::rename_one_file("Directory.idt", "Director.idt"); }
450 if ( -f "FeatureComponents.idt" ) { installer::systemactions::rename_one_file("FeatureComponents.idt", "FeatureC.idt"); }
451 if ( -f "MsiAssembly.idt" ) { installer::systemactions::rename_one_file("MsiAssembly.idt", "MsiAssem.idt"); }
452 if ( -f "Component.idt" ) { installer::systemactions::rename_one_file("Component.idt", "Componen.idt"); }
454 # Changing content of tables: File, Media, Directory, FeatureComponent, MsiAssembly, Component
455 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing Media table");
456 change_media_table($mergemodulehash, $workdir, $mergemodulegid, $allupdatelastsequences, $allupdatediskids);
457 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing File table");
458 $filesref = change_file_table($mergemodulehash, $workdir, $allupdatesequences, $includepatharrayref, $filesref, $mergemodulegid);
459 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing FeatureComponent table");
460 change_featurecomponent_table($mergemodulehash, $workdir);
461 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing Directory table");
462 change_directory_table($mergemodulehash, $workdir);
463 if ( $mergemodulehash->{'hasmsiassemblies'} )
465 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing MsiAssembly table");
466 change_msiassembly_table($mergemodulehash, $workdir);
469 if ( ( $mergemodulehash->{'componentcondition'} ) || ( $mergemodulehash->{'attributes_add'} ) )
471 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing Component table");
472 change_component_table($mergemodulehash, $workdir);
475 # msidb.exe does not merge InstallExecuteSequence, AdminExecuteSequence and AdvtExecuteSequence. Instead it creates
476 # new tables ModuleInstallExecuteSequence, ModuleAdminExecuteSequence and ModuleAdvtExecuteSequence that need to be
477 # merged into the three ExecuteSequences with the following process (also into InstallUISequence.idt).
479 # Saving original idt files
480 if ( -f "InstallE.idt" ) { installer::systemactions::rename_one_file("InstallE.idt", "old.InstallE.idt.$counter"); }
481 if ( -f "InstallU.idt" ) { installer::systemactions::rename_one_file("InstallU.idt", "old.InstallU.idt.$counter"); }
482 if ( -f "AdminExe.idt" ) { installer::systemactions::rename_one_file("AdminExe.idt", "old.AdminExe.idt.$counter"); }
483 if ( -f "AdvtExec.idt" ) { installer::systemactions::rename_one_file("AdvtExec.idt", "old.AdvtExec.idt.$counter"); }
484 if ( -f "ModuleInstallExecuteSequence.idt" ) { installer::systemactions::rename_one_file("ModuleInstallExecuteSequence.idt", "old.ModuleInstallExecuteSequence.idt.$counter"); }
485 if ( -f "ModuleAdminExecuteSequence.idt" ) { installer::systemactions::rename_one_file("ModuleAdminExecuteSequence.idt", "old.ModuleAdminExecuteSequence.idt.$counter"); }
486 if ( -f "ModuleAdvtExecuteSequence.idt" ) { installer::systemactions::rename_one_file("ModuleAdvtExecuteSequence.idt", "old.ModuleAdvtExecuteSequence.idt.$counter"); }
488 # Extracting tables
489 my $moduleexecutetables = "ModuleInstallExecuteSequence ModuleAdminExecuteSequence ModuleAdvtExecuteSequence"; # new tables
490 my $executetables = "InstallExecuteSequence InstallUISequence AdminExecuteSequence AdvtExecuteSequence"; # tables to be merged
493 if ( $^O =~ /cygwin/i ) {
494 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
495 my $localmsifilename = $msifilename;
496 my $localworkdir = $workdir;
497 $localmsifilename =~ s/\//\\\\/g;
498 $localworkdir =~ s/\//\\\\/g;
499 $systemcall = $msidb . " -d " . $localmsifilename . " -f " . $localworkdir . " -e " . "Feature " . $moduleexecutetables;
501 else
503 $systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -e " . "Feature " . $moduleexecutetables;
505 $returnvalue = system($systemcall);
507 if ( $^O =~ /cygwin/i ) {
508 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
509 my $localmsifilename = $msifilename;
510 my $localworkdir = $workdir;
511 $localmsifilename =~ s/\//\\\\/g;
512 $localworkdir =~ s/\//\\\\/g;
513 $systemcall = $msidb . " -d " . $localmsifilename . " -f " . $localworkdir . " -e " . "Feature " . $executetables;
515 else
517 $systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -e " . "Feature " . $executetables;
519 $returnvalue = system($systemcall);
521 # Using 8+3 table names, that are used, when tables are integrated into database. The export of tables
522 # creates idt-files, that have long names.
524 if ( -f "InstallExecuteSequence.idt" ) { installer::systemactions::rename_one_file("InstallExecuteSequence.idt", "InstallE.idt"); }
525 if ( -f "InstallUISequence.idt" ) { installer::systemactions::rename_one_file("InstallUISequence.idt", "InstallU.idt"); }
526 if ( -f "AdminExecuteSequence.idt" ) { installer::systemactions::rename_one_file("AdminExecuteSequence.idt", "AdminExe.idt"); }
527 if ( -f "AdvtExecuteSequence.idt" ) { installer::systemactions::rename_one_file("AdvtExecuteSequence.idt", "AdvtExec.idt"); }
529 # Merging content of tables ModuleInstallExecuteSequence, ModuleAdminExecuteSequence and ModuleAdvtExecuteSequence
530 # into tables InstallExecuteSequence, AdminExecuteSequence and AdvtExecuteSequence
531 if ( -f "ModuleInstallExecuteSequence.idt" )
533 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing InstallExecuteSequence table");
534 change_executesequence_table($mergemodulehash, $workdir, "InstallE.idt", "ModuleInstallExecuteSequence.idt");
535 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing InstallUISequence table");
536 change_executesequence_table($mergemodulehash, $workdir, "InstallU.idt", "ModuleInstallExecuteSequence.idt");
539 if ( -f "ModuleAdminExecuteSequence.idt" )
541 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing AdminExecuteSequence table");
542 change_executesequence_table($mergemodulehash, $workdir, "AdminExe.idt", "ModuleAdminExecuteSequence.idt");
545 if ( -f "ModuleAdvtExecuteSequence.idt" )
547 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing AdvtExecuteSequence table");
548 change_executesequence_table($mergemodulehash, $workdir, "AdvtExec.idt", "ModuleAdvtExecuteSequence.idt");
551 installer::logger::include_timestamp_into_logfile("\nPerformance Info: All tables edited");
553 # Including tables into msi database
555 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Before including tables");
557 if ( $^O =~ /cygwin/i ) {
558 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
559 my $localmsifilename = $msifilename;
560 my $localworkdir = $workdir;
561 $localmsifilename =~ s/\//\\\\/g;
562 $localworkdir =~ s/\//\\\\/g;
563 foreach my $table (split / /, $workingtables . ' ' . $executetables) {
564 $systemcall = $msidb . " -d " . $localmsifilename . " -f " . $localworkdir . " -i " . $table;
565 my $retval = system($systemcall);
566 $infoline = "Systemcall returned $retval: $systemcall\n";
567 push( @installer::globals::logfileinfo, $infoline);
568 $returnvalue |= $retval;
571 else
573 $systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -i " . $workingtables. " " . $executetables;
574 $returnvalue = system($systemcall);
575 $infoline = "Systemcall: $systemcall\n";
576 push( @installer::globals::logfileinfo, $infoline);
580 if ($returnvalue)
582 $infoline = "ERROR: Could not execute $systemcall !\n";
583 push( @installer::globals::logfileinfo, $infoline);
584 installer::exiter::exit_program("ERROR: Could not include tables into msi database: $msifilename !", "merge_mergemodules_into_msi_database");
586 else
588 $infoline = "Success: Executed $systemcall successfully!\n";
589 push( @installer::globals::logfileinfo, $infoline);
592 installer::logger::include_timestamp_into_logfile("\nPerformance Info: After including tables");
594 chdir($from);
597 if ( ! $installer::globals::mergefiles_added_into_collector ) { $installer::globals::mergefiles_added_into_collector = 1; } # Now all mergemodules are merged for one language.
599 installer::logger::include_timestamp_into_logfile("\nPerformance Info: MergeModule into msi database, stop");
602 return $filesref;
605 #########################################################################
606 # Analyzing the content of the media table.
607 #########################################################################
609 sub analyze_media_file
611 my ($filecontent, $workdir) = @_;
613 my %filehash = ();
614 my $linecount = 0;
615 my $counter = 0;
616 my $filename = "Media.idt";
618 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
620 if ( $i <= 2 ) { next; } # ignoring first three lines
621 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
622 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.+?)\t(.+?)\t(.*?)\s*$/ )
624 my %line = ();
625 # Format: DiskId LastSequence DiskPrompt Cabinet VolumeLabel Source
626 $line{'DiskId'} = $1;
627 $line{'LastSequence'} = $2;
628 $line{'DiskPrompt'} = $3;
629 $line{'Cabinet'} = $4;
630 $line{'VolumeLabel'} = $5;
631 $line{'Source'} = $6;
633 $counter++;
634 $filehash{$counter} = \%line;
636 else
638 $linecount = $i + 1;
639 installer::exiter::exit_program("ERROR: Unknown line format in table \"$filename\" in \"$workdir\" (line $linecount) !", "analyze_media_file");
643 return \%filehash;
646 #########################################################################
647 # Setting the DiskID for the new cabinet file
648 #########################################################################
650 sub get_diskid
652 my ($mediafile, $allupdatediskids, $cabfilename) = @_;
654 my $diskid = 0;
655 my $line;
657 if (( $installer::globals::updatedatabase ) && ( exists($allupdatediskids->{$cabfilename}) ))
659 $diskid = $allupdatediskids->{$cabfilename};
661 else
663 foreach $line ( keys %{$mediafile} )
665 if ( $mediafile->{$line}->{'DiskId'} > $diskid ) { $diskid = $mediafile->{$line}->{'DiskId'}; }
668 $diskid++;
671 return $diskid;
674 #########################################################################
675 # Setting the global LastSequence variable
676 #########################################################################
678 sub set_current_last_sequence
680 my ($mediafile) = @_;
682 my $lastsequence = 0;
683 my $line;
684 foreach $line ( keys %{$mediafile} )
686 if ( $mediafile->{$line}->{'LastSequence'} > $lastsequence ) { $lastsequence = $mediafile->{$line}->{'LastSequence'}; }
689 $installer::globals::lastsequence_before_merge = $lastsequence;
692 #########################################################################
693 # Setting the LastSequence for the new cabinet file
694 #########################################################################
696 sub get_lastsequence
698 my ($mergemodulehash, $allupdatelastsequences) = @_;
700 my $lastsequence = 0;
702 if (( $installer::globals::updatedatabase ) && ( exists($allupdatelastsequences->{$mergemodulehash->{'cabfilename'}}) ))
704 $lastsequence = $allupdatelastsequences->{$mergemodulehash->{'cabfilename'}};
706 else
708 $lastsequence = $installer::globals::lastsequence_before_merge + $mergemodulehash->{'filenumber'};
711 return $lastsequence;
714 #########################################################################
715 # Setting the DiskPrompt for the new cabinet file
716 #########################################################################
718 sub get_diskprompt
720 my ($mediafile) = @_;
722 my $diskprompt = "";
723 my $line;
724 foreach $line ( keys %{$mediafile} )
726 if ( exists($mediafile->{$line}->{'DiskPrompt'}) )
728 $diskprompt = $mediafile->{$line}->{'DiskPrompt'};
729 last;
733 return $diskprompt;
736 #########################################################################
737 # Setting the VolumeLabel for the new cabinet file
738 #########################################################################
740 sub get_volumelabel
742 my ($mediafile) = @_;
744 my $volumelabel = "";
745 my $line;
746 foreach $line ( keys %{$mediafile} )
748 if ( exists($mediafile->{$line}->{'VolumeLabel'}) )
750 $volumelabel = $mediafile->{$line}->{'VolumeLabel'};
751 last;
755 return $volumelabel;
758 #########################################################################
759 # Setting the Source for the new cabinet file
760 #########################################################################
762 sub get_source
764 my ($mediafile) = @_;
766 my $source = "";
767 my $line;
768 foreach $line ( keys %{$mediafile} )
770 if ( exists($mediafile->{$line}->{'Source'}) )
772 $source = $mediafile->{$line}->{'Source'};
773 last;
777 return $source;
780 #########################################################################
781 # For each Merge Module one new line has to be included into the
782 # media table.
783 #########################################################################
785 sub create_new_media_line
787 my ($mergemodulehash, $mediafile, $allupdatelastsequences, $allupdatediskids) = @_;
789 my $diskid = get_diskid($mediafile, $allupdatediskids, $mergemodulehash->{'cabfilename'});
790 my $lastsequence = get_lastsequence($mergemodulehash, $allupdatelastsequences);
791 my $diskprompt = get_diskprompt($mediafile);
792 my $cabinet = $mergemodulehash->{'cabfilename'};
793 my $volumelabel = get_volumelabel($mediafile);
794 my $source = get_source($mediafile);
796 if ( $installer::globals::include_cab_in_msi ) { $cabinet = "\#" . $cabinet; }
798 my $newline = "$diskid\t$lastsequence\t$diskprompt\t$cabinet\t$volumelabel\t$source\n";
800 return $newline;
803 #########################################################################
804 # Setting the last diskid in media table.
805 #########################################################################
807 sub get_last_diskid
809 my ($mediafile) = @_;
811 my $lastdiskid = 0;
812 my $line;
813 foreach $line ( keys %{$mediafile} )
815 if ( $mediafile->{$line}->{'DiskId'} > $lastdiskid ) { $lastdiskid = $mediafile->{$line}->{'DiskId'}; }
818 return $lastdiskid;
821 #########################################################################
822 # Setting global variable for last cab file name.
823 #########################################################################
825 sub set_last_cabfile_name
827 my ($mediafile, $lastdiskid) = @_;
829 my $line;
830 foreach $line ( keys %{$mediafile} )
832 if ( $mediafile->{$line}->{'DiskId'} == $lastdiskid ) { $installer::globals::lastcabfilename = $mediafile->{$line}->{'Cabinet'}; }
834 my $infoline = "Setting last cabinet file: $installer::globals::lastcabfilename\n";
835 push( @installer::globals::logfileinfo, $infoline);
838 #########################################################################
839 # In the media table the new cabinet file has to be added or the
840 # number of the last cabinet file has to be increased.
841 #########################################################################
843 sub change_media_table
845 my ( $mergemodulehash, $workdir, $mergemodulegid, $allupdatelastsequences, $allupdatediskids ) = @_;
847 my $infoline = "Changing content of table \"Media\"\n";
848 push( @installer::globals::logfileinfo, $infoline);
850 my $filename = "Media.idt";
851 if ( ! -f $filename ) { installer::exiter::exit_program("ERROR: Could not find file \"$filename\" in \"$workdir\" !", "change_media_table"); }
853 my $filecontent = installer::files::read_file($filename);
854 my $mediafile = analyze_media_file($filecontent, $workdir);
855 set_current_last_sequence($mediafile);
857 if ( $installer::globals::fix_number_of_cab_files )
859 # Determining the line with the highest sequencenumber. That file needs to be updated.
860 my $lastdiskid = get_last_diskid($mediafile);
861 if ( $installer::globals::lastcabfilename eq "" ) { set_last_cabfile_name($mediafile, $lastdiskid); }
862 my $newmaxsequencenumber = $installer::globals::lastsequence_before_merge + $mergemodulehash->{'filenumber'};
864 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
866 if ( $i <= 2 ) { next; } # ignoring first three lines
867 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
868 if ( ${$filecontent}[$i] =~ /^\s*(\Q$lastdiskid\E\t)\Q$installer::globals::lastsequence_before_merge\E(\t.*)$/ )
870 my $start = $1;
871 my $final = $2;
872 $infoline = "Merge: Old line in media table: ${$filecontent}[$i]\n";
873 push( @installer::globals::logfileinfo, $infoline);
874 my $newline = $start . $newmaxsequencenumber . $final . "\n";
875 ${$filecontent}[$i] = $newline;
876 $infoline = "Merge: Changed line in media table: ${$filecontent}[$i]\n";
877 push( @installer::globals::logfileinfo, $infoline);
881 else
883 # the new line is identical for all localized databases, but has to be created for each MergeModule ($mergemodulegid)
884 if ( ! exists($installer::globals::merge_media_line{$mergemodulegid}) )
886 $installer::globals::merge_media_line{$mergemodulegid} = create_new_media_line($mergemodulehash, $mediafile, $allupdatelastsequences, $allupdatediskids);
889 $infoline = "Adding line: $installer::globals::merge_media_line{$mergemodulegid}\n";
890 push( @installer::globals::logfileinfo, $infoline);
892 # adding new line
893 push(@{$filecontent}, $installer::globals::merge_media_line{$mergemodulegid});
896 # saving file
897 installer::files::save_file($filename, $filecontent);
900 #########################################################################
901 # Putting the directory table content into a hash.
902 #########################################################################
904 sub analyze_directorytable_file
906 my ($filecontent, $idtfilename) = @_;
908 my %dirhash = ();
909 # Iterating over the file content
910 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
912 if ( $i <= 2 ) { next; } # ignoring first three lines
913 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
914 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\s*$/ )
916 my %line = ();
917 # Format: Directory Directory_Parent DefaultDir
918 $line{'Directory'} = $1;
919 $line{'Directory_Parent'} = $2;
920 $line{'DefaultDir'} = $3;
921 $line{'linenumber'} = $i; # saving also the line number for direct access
923 my $uniquekey = $line{'Directory'};
924 $dirhash{$uniquekey} = \%line;
926 else
928 my $linecount = $i + 1;
929 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "analyze_directorytable_file");
933 return \%dirhash;
936 #########################################################################
937 # Putting the msi assembly table content into a hash.
938 #########################################################################
940 sub analyze_msiassemblytable_file
942 my ($filecontent, $idtfilename) = @_;
944 my %assemblyhash = ();
945 # Iterating over the file content
946 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
948 if ( $i <= 2 ) { next; } # ignoring first three lines
949 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
950 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.*?)\t(.*?)\s*$/ )
952 my %line = ();
953 # Format: Component_ Feature_ File_Manifest File_Application Attributes
954 $line{'Component'} = $1;
955 $line{'Feature'} = $2;
956 $line{'File_Manifest'} = $3;
957 $line{'File_Application'} = $4;
958 $line{'Attributes'} = $5;
959 $line{'linenumber'} = $i; # saving also the line number for direct access
961 my $uniquekey = $line{'Component'};
962 $assemblyhash{$uniquekey} = \%line;
964 else
966 my $linecount = $i + 1;
967 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "analyze_msiassemblytable_file");
971 return \%assemblyhash;
974 #########################################################################
975 # Putting the file table content into a hash.
976 #########################################################################
978 sub analyze_filetable_file
980 my ( $filecontent, $idtfilename ) = @_;
982 my %filehash = ();
983 # Iterating over the file content
984 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
986 if ( $i <= 2 ) { next; } # ignoring first three lines
987 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
988 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.+?)\s*$/ )
990 my %line = ();
991 # Format: File Component_ FileName FileSize Version Language Attributes Sequence
992 $line{'File'} = $1;
993 $line{'Component'} = $2;
994 $line{'FileName'} = $3;
995 $line{'FileSize'} = $4;
996 $line{'Version'} = $5;
997 $line{'Language'} = $6;
998 $line{'Attributes'} = $7;
999 $line{'Sequence'} = $8;
1000 $line{'linenumber'} = $i; # saving also the line number for direct access
1002 my $uniquekey = $line{'File'};
1003 $filehash{$uniquekey} = \%line;
1005 else
1007 my $linecount = $i + 1;
1008 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "analyze_filetable_file");
1012 return \%filehash;
1015 #########################################################################
1016 # Creating a new line for the directory table.
1017 #########################################################################
1019 sub get_new_line_for_directory_table
1021 my ($dir) = @_;
1023 my $newline = "$dir->{'Directory'}\t$dir->{'Directory_Parent'}\t$dir->{'DefaultDir'}\n";
1025 return $newline;
1028 #########################################################################
1029 # Creating a new line for the file table.
1030 #########################################################################
1032 sub get_new_line_for_file_table
1034 my ($file) = @_;
1036 my $newline = "$file->{'File'}\t$file->{'Component'}\t$file->{'FileName'}\t$file->{'FileSize'}\t$file->{'Version'}\t$file->{'Language'}\t$file->{'Attributes'}\t$file->{'Sequence'}\n";
1038 return $newline;
1041 #########################################################################
1042 # Creating a new line for the msiassembly table.
1043 #########################################################################
1045 sub get_new_line_for_msiassembly_table
1047 my ($assembly) = @_;
1049 my $newline = "$assembly->{'Component'}\t$assembly->{'Feature'}\t$assembly->{'File_Manifest'}\t$assembly->{'File_Application'}\t$assembly->{'Attributes'}\n";
1051 return $newline;
1054 #########################################################################
1055 # Sorting the files collector, if there are files, following
1056 # the merge module files.
1057 #########################################################################
1059 sub sort_files_collector_for_sequence
1061 my ($filesref) = @_;
1063 my @sortarray = ();
1064 my %helphash = ();
1066 for ( my $i = 0; $i <= $#{$filesref}; $i++ )
1068 my $onefile = ${$filesref}[$i];
1069 if ( ! exists($onefile->{'sequencenumber'}) ) { installer::exiter::exit_program("ERROR: Could not find sequencenumber for file: $onefile->{'uniquename'} !", "sort_files_collector_for_sequence"); }
1070 my $sequence = $onefile->{'sequencenumber'};
1071 $helphash{$sequence} = $onefile;
1074 foreach my $seq ( sort { $a <=> $b } keys %helphash ) { push(@sortarray, $helphash{$seq}); }
1076 return \@sortarray;
1079 #########################################################################
1080 # In the file table "Sequence" and "Attributes" have to be changed.
1081 #########################################################################
1083 sub change_file_table
1085 my ($mergemodulehash, $workdir, $allupdatesequenceshashref, $includepatharrayref, $filesref, $mergemodulegid) = @_;
1087 my $infoline = "Changing content of table \"File\"\n";
1088 push( @installer::globals::logfileinfo, $infoline);
1090 my $idtfilename = "File.idt";
1091 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_file_table"); }
1093 my $filecontent = installer::files::read_file($idtfilename);
1095 # If File.idt needed to be removed before the msm database was merged into the msi database,
1096 # now it is time to add the content into File.idt
1097 if ( $mergemodulehash->{'removefiletable'} )
1099 for ( my $i = 0; $i <= $#{$mergemodulehash->{'fileidtcontent'}}; $i++ )
1101 push(@{$filecontent}, ${$mergemodulehash->{'fileidtcontent'}}[$i]);
1105 # Unpacking the MergeModule.CABinet (only once)
1106 # Unpacking into temp directory. Warning: expand.exe has problems with very long unpack directories.
1108 my $empty = "";
1109 my $unpackdir = installer::systemactions::create_directories("cab", \$empty);
1110 push(@installer::globals::removedirs, $unpackdir);
1111 $unpackdir = $unpackdir . $installer::globals::separator . $mergemodulegid;
1113 my %newfileshash = ();
1114 if (( $installer::globals::fix_number_of_cab_files ) && ( ! $installer::globals::mergefiles_added_into_collector ))
1116 if ( ! -d $unpackdir ) { installer::systemactions::create_directory($unpackdir); }
1118 # changing directory
1119 my $from = cwd();
1120 my $to = $mergemodulehash->{'workdir'};
1121 if ( $^O =~ /cygwin/i ) {
1122 $to = qx(cygpath -u "$to");
1123 chomp $to;
1126 chdir($to) || die "Could not chdir to \"$to\"\n";
1128 # Unpack the cab file, so that in can be included into the last office cabinet file.
1129 # Not using cabarc.exe from cabsdk for unpacking cabinet files, but "expand.exe" that
1130 # should be available on every Windows system.
1132 $infoline = "Unpacking cabinet file: $mergemodulehash->{'cabinetfile'}\n";
1133 push( @installer::globals::logfileinfo, $infoline);
1135 # Avoid the Cygwin expand command
1136 my $expandfile = "expand.exe"; # Has to be in the path
1137 if ( $^O =~ /cygwin/i ) {
1138 $expandfile = qx(cygpath -u "$ENV{WINDIR}"/System32/expand.exe);
1139 chomp $expandfile;
1142 my $cabfilename = "MergeModule.CABinet";
1144 my $systemcall = "";
1145 if ( $^O =~ /cygwin/i ) {
1146 my $localunpackdir = qx(cygpath -m "$unpackdir");
1147 chomp $localunpackdir;
1148 $systemcall = $expandfile . " " . $cabfilename . " -F:\\\* " . $localunpackdir;
1150 else
1152 $systemcall = $expandfile . " " . $cabfilename . " -F:\* " . $unpackdir . " 2\>\&1";
1155 my $returnvalue = system($systemcall);
1157 $infoline = "Systemcall: $systemcall\n";
1158 push( @installer::globals::logfileinfo, $infoline);
1160 if ($returnvalue)
1162 $infoline = "ERROR: Could not execute $systemcall !\n";
1163 push( @installer::globals::logfileinfo, $infoline);
1164 installer::exiter::exit_program("ERROR: Could not extract cabinet file: $mergemodulehash->{'cabinetfile'} !", "change_file_table");
1166 else
1168 $infoline = "Success: Executed $systemcall successfully!\n";
1169 push( @installer::globals::logfileinfo, $infoline);
1172 chdir($from);
1175 # For performance reasons creating a hash with file names and rows
1176 # The content of File.idt is changed after every merge -> content cannot be saved in global hash
1177 my $merge_filetablehashref = analyze_filetable_file($filecontent, $idtfilename);
1179 my $attributes = "16384"; # Always
1181 my $filename;
1182 foreach $filename (keys %{$mergemodulehash->{'mergefilesequence'}} )
1184 my $mergefilesequence = $mergemodulehash->{'mergefilesequence'}->{$filename};
1186 if ( ! exists($merge_filetablehashref->{$filename}) ) { installer::exiter::exit_program("ERROR: Could not find file \"$filename\" in \"$idtfilename\" !", "change_file_table"); }
1187 my $filehash = $merge_filetablehashref->{$filename};
1188 my $linenumber = $filehash->{'linenumber'};
1190 # <- this line has to be changed concerning "Sequence" and "Attributes"
1191 $filehash->{'Attributes'} = $attributes;
1193 # If this is an update process, the sequence numbers have to be reused.
1194 if ( $installer::globals::updatedatabase )
1196 if ( ! exists($allupdatesequenceshashref->{$filehash->{'File'}}) ) { installer::exiter::exit_program("ERROR: Sequence not defined for file \"$filehash->{'File'}\" !", "change_file_table"); }
1197 $filehash->{'Sequence'} = $allupdatesequenceshashref->{$filehash->{'File'}};
1198 # Saving all mergemodule sequence numbers. This is important for creating ddf files
1199 $installer::globals::allmergemodulefilesequences{$filehash->{'Sequence'}} = 1;
1201 else
1203 # Important saved data: $installer::globals::lastsequence_before_merge.
1204 # This mechanism keeps the correct order inside the new cabinet file.
1205 $filehash->{'Sequence'} = $filehash->{'Sequence'} + $installer::globals::lastsequence_before_merge;
1208 my $oldline = ${$filecontent}[$linenumber];
1209 my $newline = get_new_line_for_file_table($filehash);
1210 ${$filecontent}[$linenumber] = $newline;
1212 $infoline = "Merge, replacing line:\n";
1213 push( @installer::globals::logfileinfo, $infoline);
1214 $infoline = "Old: $oldline\n";
1215 push( @installer::globals::logfileinfo, $infoline);
1216 $infoline = "New: $newline\n";
1217 push( @installer::globals::logfileinfo, $infoline);
1219 # Adding files to the files collector (but only once)
1220 if (( $installer::globals::fix_number_of_cab_files ) && ( ! $installer::globals::mergefiles_added_into_collector ))
1222 # If the number of cabinet files is kept constant,
1223 # all files from the mergemodule cabinet files will
1224 # be integrated into the last office cabinet file
1225 # (installer::globals::lastcabfilename).
1226 # Therefore the files must now be added to the filescollector,
1227 # so that they will be integrated into the ddf files.
1229 # Problem with very long filenames -> copying to shorter filenames
1230 my $newfilename = "f" . $filehash->{'Sequence'};
1231 my $completesource = $unpackdir . $installer::globals::separator . $filehash->{'File'};
1232 my $completedest = $unpackdir . $installer::globals::separator . $newfilename;
1233 installer::systemactions::copy_one_file($completesource, $completedest);
1235 my $locallastcabfilename = $installer::globals::lastcabfilename;
1236 if ( $locallastcabfilename =~ /^\s*\#/ ) { $locallastcabfilename =~ s/^\s*\#//; } # removing beginning hashes
1238 # Create new file hash for file collector
1239 my %newfile = ();
1240 $newfile{'sequencenumber'} = $filehash->{'Sequence'};
1241 $newfile{'assignedsequencenumber'} = $filehash->{'Sequence'};
1242 $newfile{'cabinet'} = $locallastcabfilename;
1243 $newfile{'sourcepath'} = $completedest;
1244 $newfile{'componentname'} = $filehash->{'Component'};
1245 $newfile{'uniquename'} = $filehash->{'File'};
1246 $newfile{'Name'} = $filehash->{'File'};
1248 # Saving in globals sequence hash
1249 $installer::globals::uniquefilenamesequence{$filehash->{'File'}} = $filehash->{'Sequence'};
1251 if ( ! -f $newfile{'sourcepath'} ) { installer::exiter::exit_program("ERROR: File \"$newfile{'sourcepath'}\" must exist!", "change_file_table"); }
1253 # Collecting all new files. Attention: This files must be included into files collector in correct order!
1254 $newfileshash{$filehash->{'Sequence'}} = \%newfile;
1255 # push(@{$filesref}, \%newfile); -> this is not the correct order
1259 # Now the files can be added to the files collector
1260 # In the case of an update process, there can be new files, that have to be added after the merge module files.
1261 # Warning: In multilingual installation sets, the files only have to be added once to the files collector!
1263 if ( ! $installer::globals::mergefiles_added_into_collector )
1265 foreach my $localsequence ( sort { $a <=> $b } keys %newfileshash ) { push(@{$filesref}, $newfileshash{$localsequence}); }
1266 if ( $installer::globals::newfilesexist ) { $filesref = sort_files_collector_for_sequence($filesref); }
1267 # $installer::globals::mergefiles_added_into_collector = 1; -> Not yet. Only if all mergemodules are merged for one language.
1270 # Saving the idt file (for every language)
1271 installer::files::save_file($idtfilename, $filecontent);
1273 return $filesref;
1276 #########################################################################
1277 # Reading the file "Director.idt". The Directory, that is defined in scp
1278 # has to be defined in this table.
1279 #########################################################################
1281 sub collect_directories
1283 my $idtfilename = "Director.idt";
1284 my $filecontent = installer::files::read_file($idtfilename);
1286 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
1288 if ( $i <= 2 ) { next; } # ignoring first three lines
1289 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
1290 # Format: Directory Directory_Parent DefaultDir
1291 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\s*$/ )
1293 $installer::globals::merge_alldirectory_hash{$1} = 1;
1295 else
1297 my $linecount = $i + 1;
1298 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "collect_directories");
1303 #########################################################################
1304 # Reading the file "Feature.idt". The Feature, that is defined in scp
1305 # has to be defined in this table.
1306 #########################################################################
1308 sub collect_feature
1310 my ($workdir) = @_;
1311 my $idtfilename = "Feature.idt";
1312 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "collect_feature"); }
1313 my $filecontent = installer::files::read_file($idtfilename);
1315 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
1317 if ( $i <= 2 ) { next; } # ignoring first three lines
1318 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
1319 # Format: Feature Feature_Parent Title Description Display Level Directory_ Attributes
1320 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
1322 $installer::globals::merge_allfeature_hash{$1} = 1;
1324 else
1326 my $linecount = $i + 1;
1327 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "collect_feature");
1332 #########################################################################
1333 # In the featurecomponent table, the new connections have to be added.
1334 #########################################################################
1336 sub change_featurecomponent_table
1338 my ($mergemodulehash, $workdir) = @_;
1340 my $infoline = "Changing content of table \"FeatureComponents\"\n";
1341 push( @installer::globals::logfileinfo, $infoline);
1343 my $idtfilename = "FeatureC.idt";
1344 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_featurecomponent_table"); }
1346 my $filecontent = installer::files::read_file($idtfilename);
1348 # Simply adding for each new component one line. The Feature has to be defined in scp project.
1349 my $feature = $mergemodulehash->{'feature'};
1351 if ( ! $installer::globals::mergefeaturecollected )
1353 collect_feature($workdir); # putting content into hash %installer::globals::merge_allfeature_hash
1354 $installer::globals::mergefeaturecollected = 1;
1357 if ( ! exists($installer::globals::merge_allfeature_hash{$feature}) )
1359 installer::exiter::exit_program("ERROR: Unknown feature defined in scp: \"$feature\" . Not defined in table \"Feature\" !", "change_featurecomponent_table");
1362 my $component;
1363 foreach $component ( keys %{$mergemodulehash->{'componentnames'}} )
1365 my $line = "$feature\t$component\n";
1366 push(@{$filecontent}, $line);
1367 $infoline = "Adding line: $line\n";
1368 push( @installer::globals::logfileinfo, $infoline);
1371 # saving file
1372 installer::files::save_file($idtfilename, $filecontent);
1375 ###############################################################################
1376 # In the components table, the conditions or attributes of merge modules should be updated
1377 ###############################################################################
1379 sub change_component_table
1381 my ($mergemodulehash, $workdir) = @_;
1383 my $infoline = "Changing content of table \"Component\"\n";
1384 push( @installer::globals::logfileinfo, $infoline);
1386 my $idtfilename = "Componen.idt";
1387 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_component_table"); }
1389 my $filecontent = installer::files::read_file($idtfilename);
1391 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
1393 my $component;
1394 foreach $component ( keys %{$mergemodulehash->{'componentnames'}} )
1396 if ( my ( $comp_, $compid_, $dir_, $attr_, $cond_, $keyp_ ) = ${$filecontent}[$i] =~ /^\s*($component)\t(.*?)\t(.+?)\t(.+?)\t(.*?)\t(.*?)\s*$/)
1398 my $newattr_ = ( $attr_ =~ /^\s*0x/ ) ? hex($attr_) : $attr_;
1399 if ( $mergemodulehash->{'attributes_add'} )
1401 $infoline = "Adding attribute(s) ($mergemodulehash->{'attributes_add'}) from scp2 to component $comp_\n";
1402 push( @installer::globals::logfileinfo, $infoline);
1403 if ( $mergemodulehash->{'attributes_add'} =~ /^\s*0x/ )
1405 $newattr_ = $newattr_ | hex($mergemodulehash->{'attributes_add'});
1407 else
1409 $newattr_ = $newattr_ | $mergemodulehash->{'attributes_add'};
1411 $infoline = "Old attribute(s): $attr_\nNew attribute(s): $newattr_\n";
1412 push( @installer::globals::logfileinfo, $infoline);
1414 my $newcond_ = $cond_;
1415 if ( $mergemodulehash->{'componentcondition'} )
1417 $infoline = "Adding condition ($mergemodulehash->{'componentcondition'}) from scp2 to component $comp_\n";
1418 push( @installer::globals::logfileinfo, $infoline);
1419 if ($cond_)
1421 $newcond_ = "($cond_) AND ($mergemodulehash->{'componentcondition'})";
1423 else
1425 $newcond_ = "$mergemodulehash->{'componentcondition'}";
1427 $infoline = "Old condition: $cond_\nNew condition: $newcond_\n";
1428 push( @installer::globals::logfileinfo, $infoline);
1430 ${$filecontent}[$i] = "$comp_\t$compid_\t$dir_\t$newattr_\t$newcond_\t$keyp_\n";
1435 # saving file
1436 installer::files::save_file($idtfilename, $filecontent);
1439 #########################################################################
1440 # In the directory table, the directory parent has to be changed,
1441 # if it is not TARGETDIR.
1442 #########################################################################
1444 sub change_directory_table
1446 my ($mergemodulehash, $workdir) = @_;
1448 # directory for MergeModule has to be defined in scp project
1449 my $scpdirectory = $mergemodulehash->{'rootdir'};
1451 if ( $scpdirectory ne "TARGETDIR" ) # TARGETDIR works fine, when using msidb.exe
1453 my $infoline = "Changing content of table \"Directory\"\n";
1454 push( @installer::globals::logfileinfo, $infoline);
1456 my $idtfilename = "Director.idt";
1457 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_directory_table"); }
1459 my $filecontent = installer::files::read_file($idtfilename);
1461 if ( ! $installer::globals::mergedirectoriescollected )
1463 collect_directories(); # putting content into %installer::globals::merge_alldirectory_hash, only first column!
1464 $installer::globals::mergedirectoriescollected = 1;
1467 if ( ! exists($installer::globals::merge_alldirectory_hash{$scpdirectory}) )
1469 installer::exiter::exit_program("ERROR: Unknown directory defined in scp: \"$scpdirectory\" . Not defined in table \"Directory\" !", "change_directory_table");
1472 # If the definition in scp is okay, now the complete content of "Director.idt" can be analyzed
1473 my $merge_directorytablehashref = analyze_directorytable_file($filecontent, $idtfilename);
1475 my $directory;
1476 foreach $directory (keys %{$mergemodulehash->{'mergedirectories'}} )
1478 if ( ! exists($merge_directorytablehashref->{$directory}) ) { installer::exiter::exit_program("ERROR: Could not find directory \"$directory\" in \"$idtfilename\" !", "change_directory_table"); }
1479 my $dirhash = $merge_directorytablehashref->{$directory};
1480 my $linenumber = $dirhash->{'linenumber'};
1482 # <- this line has to be changed concerning "Directory_Parent",
1483 # if the current value is "TARGETDIR", which is the default value from msidb.exe
1485 if ( $dirhash->{'Directory_Parent'} eq "TARGETDIR" )
1487 $dirhash->{'Directory_Parent'} = $scpdirectory;
1489 my $oldline = ${$filecontent}[$linenumber];
1490 my $newline = get_new_line_for_directory_table($dirhash);
1491 ${$filecontent}[$linenumber] = $newline;
1493 $infoline = "Merge, replacing line:\n";
1494 push( @installer::globals::logfileinfo, $infoline);
1495 $infoline = "Old: $oldline\n";
1496 push( @installer::globals::logfileinfo, $infoline);
1497 $infoline = "New: $newline\n";
1498 push( @installer::globals::logfileinfo, $infoline);
1502 # saving file
1503 installer::files::save_file($idtfilename, $filecontent);
1507 #########################################################################
1508 # In the msiassembly table, the feature has to be changed.
1509 #########################################################################
1511 sub change_msiassembly_table
1513 my ($mergemodulehash, $workdir) = @_;
1515 my $infoline = "Changing content of table \"MsiAssembly\"\n";
1516 push( @installer::globals::logfileinfo, $infoline);
1518 my $idtfilename = "MsiAssem.idt";
1519 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_msiassembly_table"); }
1521 my $filecontent = installer::files::read_file($idtfilename);
1523 # feature has to be defined in scp project
1524 my $feature = $mergemodulehash->{'feature'};
1526 if ( ! $installer::globals::mergefeaturecollected )
1528 collect_feature($workdir); # putting content into hash %installer::globals::merge_allfeature_hash
1529 $installer::globals::mergefeaturecollected = 1;
1532 if ( ! exists($installer::globals::merge_allfeature_hash{$feature}) )
1534 installer::exiter::exit_program("ERROR: Unknown feature defined in scp: \"$feature\" . Not defined in table \"Feature\" !", "change_msiassembly_table");
1537 my $merge_msiassemblytablehashref = analyze_msiassemblytable_file($filecontent, $idtfilename);
1539 my $component;
1540 foreach $component (keys %{$mergemodulehash->{'mergeassemblies'}} )
1542 if ( ! exists($merge_msiassemblytablehashref->{$component}) ) { installer::exiter::exit_program("ERROR: Could not find component \"$component\" in \"$idtfilename\" !", "change_msiassembly_table"); }
1543 my $assemblyhash = $merge_msiassemblytablehashref->{$component};
1544 my $linenumber = $assemblyhash->{'linenumber'};
1546 # <- this line has to be changed concerning "Feature"
1547 $assemblyhash->{'Feature'} = $feature;
1549 my $oldline = ${$filecontent}[$linenumber];
1550 my $newline = get_new_line_for_msiassembly_table($assemblyhash);
1551 ${$filecontent}[$linenumber] = $newline;
1553 $infoline = "Merge, replacing line:\n";
1554 push( @installer::globals::logfileinfo, $infoline);
1555 $infoline = "Old: $oldline\n";
1556 push( @installer::globals::logfileinfo, $infoline);
1557 $infoline = "New: $newline\n";
1558 push( @installer::globals::logfileinfo, $infoline);
1561 # saving file
1562 installer::files::save_file($idtfilename, $filecontent);
1565 #########################################################################
1566 # Creating file content hash
1567 #########################################################################
1569 sub make_executeidtcontent_hash
1571 my ($filecontent, $idtfilename) = @_;
1573 my %newhash = ();
1575 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
1577 if ( $i <= 2 ) { next; } # ignoring first three lines
1578 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
1579 # Format for all sequence tables: Action Condition Sequence
1580 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\s*$/ )
1582 my %onehash = ();
1583 $onehash{'Action'} = $1;
1584 $onehash{'Condition'} = $2;
1585 $onehash{'Sequence'} = $3;
1586 $newhash{$onehash{'Action'}} = \%onehash;
1588 else
1590 my $linecount = $i + 1;
1591 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "make_executeidtcontent_hash");
1595 return \%newhash;
1598 #########################################################################
1599 # Creating file content hash
1600 #########################################################################
1602 sub make_moduleexecuteidtcontent_hash
1604 my ($filecontent, $idtfilename) = @_;
1606 my %newhash = ();
1608 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
1610 if ( $i <= 2 ) { next; } # ignoring first three lines
1611 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
1612 # Format for all module sequence tables: Action Sequence BaseAction After Condition
1613 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
1615 my %onehash = ();
1616 $onehash{'Action'} = $1;
1617 $onehash{'Sequence'} = $2;
1618 $onehash{'BaseAction'} = $3;
1619 $onehash{'After'} = $4;
1620 $onehash{'Condition'} = $5;
1621 $newhash{$onehash{'Action'}} = \%onehash;
1623 else
1625 my $linecount = $i + 1;
1626 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "make_executeidtcontent_hash");
1630 return \%newhash;
1633 #########################################################################
1634 # ExecuteSequence tables need to be merged with
1635 # ModuleExecuteSequence tables created by msidb.exe.
1636 #########################################################################
1638 sub change_executesequence_table
1640 my ($mergemodulehash, $workdir, $idtfilename, $moduleidtfilename) = @_;
1642 my $infoline = "Changing content of table \"$idtfilename\"\n";
1643 push( @installer::globals::logfileinfo, $infoline);
1645 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_executesequence_table"); }
1646 if ( ! -f $moduleidtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$moduleidtfilename\" in \"$workdir\" !", "change_executesequence_table"); }
1648 # Reading file content
1649 my $idtfilecontent = installer::files::read_file($idtfilename);
1650 my $moduleidtfilecontent = installer::files::read_file($moduleidtfilename);
1652 # Converting to hash
1653 my $idtcontenthash = make_executeidtcontent_hash($idtfilecontent, $idtfilename);
1654 my $moduleidtcontenthash = make_moduleexecuteidtcontent_hash($moduleidtfilecontent, $moduleidtfilename);
1656 # Merging
1657 foreach my $action ( keys %{$moduleidtcontenthash} )
1659 if ( exists($idtcontenthash->{$action}) ) { next; } # Action already exists, can be ignored
1661 if (( $idtfilename eq "InstallU.idt" ) && ( ! ( $action =~ /^\s*WindowsFolder\./ ))) { next; } # Only "WindowsFolder.*" CustomActions for UI Sequence table
1663 my $actionhashref = $moduleidtcontenthash->{$action};
1664 if ( $actionhashref->{'Sequence'} ne "" )
1666 # Format for all sequence tables: Action Condition Sequence
1667 my $newline = $actionhashref->{'Action'} . "\t" . $actionhashref->{'Condition'} . "\t" . $actionhashref->{'Sequence'} . "\n";
1668 # Adding to table
1669 push(@{$idtfilecontent}, $newline);
1670 # Also adding to hash
1671 my %idttablehash = ();
1672 $idttablehash{'Action'} = $actionhashref->{'Action'};
1673 $idttablehash{'Condition'} = $actionhashref->{'Condition'};
1674 $idttablehash{'Sequence'} = $actionhashref->{'Sequence'};
1675 $idtcontenthash->{$action} = \%idttablehash;
1678 else # no sequence defined, using syntax "BaseAction" and "After"
1680 my $baseactionname = $actionhashref->{'BaseAction'};
1681 # If this baseactionname is not defined in execute idt file, it is not possible to merge
1682 if ( ! exists($idtcontenthash->{$baseactionname}) ) { installer::exiter::exit_program("ERROR: Merge problem: Could not find action \"$baseactionname\" in file \"$idtfilename\" !", "change_executesequence_table"); }
1684 my $baseaction = $idtcontenthash->{$baseactionname};
1685 my $sequencenumber = $baseaction->{'Sequence'};
1686 if ( $actionhashref->{'After'} == 1 ) { $sequencenumber = $sequencenumber + 1; }
1687 else { $sequencenumber = $sequencenumber - 1; }
1689 # Format for all sequence tables: Action Condition Sequence
1690 my $newline = $actionhashref->{'Action'} . "\t" . $actionhashref->{'Condition'} . "\t" . $sequencenumber . "\n";
1691 # Adding to table
1692 push(@{$idtfilecontent}, $newline);
1693 # Also adding to hash
1694 my %idttablehash = ();
1695 $idttablehash{'Action'} = $actionhashref->{'Action'};
1696 $idttablehash{'Condition'} = $actionhashref->{'Condition'};
1697 $idttablehash{'Sequence'} = $sequencenumber;
1698 $idtcontenthash->{$action} = \%idttablehash;
1702 # saving file
1703 installer::files::save_file($idtfilename, $idtfilecontent);