merge the formfield patch from ooo-build
[ooovba.git] / solenv / bin / modules / installer / windows / mergemodule.pm
blob28638df952358d1bcf6070a37e05a875b6757bd1
1 #*************************************************************************
3 # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 #
5 # Copyright 2008 by Sun Microsystems, Inc.
7 # OpenOffice.org - a multi-platform office productivity suite
9 # $RCSfile: mergemodule.pm,v $
11 # $Revision: 1.8 $
13 # This file is part of OpenOffice.org.
15 # OpenOffice.org is free software: you can redistribute it and/or modify
16 # it under the terms of the GNU Lesser General Public License version 3
17 # only, as published by the Free Software Foundation.
19 # OpenOffice.org is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU Lesser General Public License version 3 for more details
23 # (a copy is included in the LICENSE file that accompanied this code).
25 # You should have received a copy of the GNU Lesser General Public License
26 # version 3 along with OpenOffice.org. If not, see
27 # <http://www.openoffice.org/license.html>
28 # for a copy of the LGPLv3 License.
30 #*************************************************************************
32 package installer::windows::mergemodule;
34 use Cwd;
35 use Digest::MD5;
36 use installer::converter;
37 use installer::exiter;
38 use installer::files;
39 use installer::globals;
40 use installer::logger;
41 use installer::pathanalyzer;
42 use installer::remover;
43 use installer::scriptitems;
44 use installer::systemactions;
45 use installer::worker;
46 use installer::windows::idtglobal;
47 use installer::windows::language;
49 #################################################################
50 # Merging the Windows MergeModules into the msi database.
51 #################################################################
53 sub merge_mergemodules_into_msi_database
55 my ($mergemodules, $filesref, $msifilename, $languagestringref, $language, $languagefile, $allvariables, $includepatharrayref, $allupdatesequences, $allupdatelastsequences, $allupdatediskids) = @_;
57 my $domerge = 0;
58 if (( $#{$mergemodules} > -1 ) && ( ! $installer::globals::patch ) && ( ! $installer::globals::languagepack )) { $domerge = 1; }
60 if ( $domerge )
62 installer::logger::include_header_into_logfile("Merging merge modules into msi database");
63 installer::logger::print_message( "... merging msm files into msi database ... \n" );
64 installer::logger::include_timestamp_into_logfile("\nPerformance Info: MergeModule into msi database, start");
66 my $msidb = "msidb.exe"; # Has to be in the path
67 my $cabinetfile = "MergeModule.CABinet"; # the name of each cabinet file in a merge file
68 my $infoline = "";
69 my $systemcall = "";
70 my $returnvalue = "";
72 # 1. Analyzing the MergeModule (has only to be done once)
73 # a. -> Extracting cabinet file: msidb.exe -d <msmfile> -x MergeModule.CABinet
74 # b. -> Number of files in cabinet file: msidb.exe -d <msmfile> -f <directory> -e File
75 # c. -> List of components: msidb.exe -d <msmfile> -f <directory> -e Component
77 if ( ! $installer::globals::mergemodules_analyzed )
79 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Analyzing MergeModules, start");
80 $infoline = "Analyzing all Merge Modules\n\n";
81 push( @installer::globals::logfileinfo, $infoline);
83 %installer::globals::mergemodules = ();
85 my $mergemoduledir = installer::systemactions::create_directories("mergefiles", $languagestringref);
86 # push(@installer::globals::removedirs, $mergemoduledir);
88 my $mergemodule;
89 foreach $mergemodule ( @{$mergemodules} )
91 my $filename = $mergemodule->{'Name'};
92 my $mergefile = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$filename, $includepatharrayref, 1);
94 if ( ! -f $$mergefile ) { installer::exiter::exit_program("ERROR: msm file not found: $filename !", "merge_mergemodules_into_msi_database"); }
95 my $completesource = $$mergefile;
97 my $mergegid = $mergemodule->{'gid'};
98 my $workdir = $mergemoduledir . $installer::globals::separator . $mergegid;
99 if ( ! -d $workdir ) { installer::systemactions::create_directory($workdir); }
101 $infoline = "Analyzing Merge Module: $filename\n";
102 push( @installer::globals::logfileinfo, $infoline);
104 # copy msm file into working directory
105 my $completedest = $workdir . $installer::globals::separator . $filename;
106 installer::systemactions::copy_one_file($completesource, $completedest);
107 if ( ! -f $completedest ) { installer::exiter::exit_program("ERROR: msm file not found: $completedest !", "merge_mergemodules_into_msi_database"); }
109 # changing directory
110 my $from = cwd();
111 my $to = $workdir;
112 chdir($to);
114 # remove an existing cabinet file
115 if ( -f $cabinetfile ) { unlink($cabinetfile); }
117 # exclude cabinet file
118 $systemcall = $msidb . " -d " . $filename . " -x " . $cabinetfile;
119 $returnvalue = system($systemcall);
121 $infoline = "Systemcall: $systemcall\n";
122 push( @installer::globals::logfileinfo, $infoline);
124 if ($returnvalue)
126 $infoline = "ERROR: Could not execute $systemcall !\n";
127 push( @installer::globals::logfileinfo, $infoline);
128 installer::exiter::exit_program("ERROR: Could not extract cabinet file from merge file: $completedest !", "merge_mergemodules_into_msi_database");
130 else
132 $infoline = "Success: Executed $systemcall successfully!\n";
133 push( @installer::globals::logfileinfo, $infoline);
136 # exclude tables from mergefile
137 # Attention: All listed tables have to exist in the database. If they not exist, an error window pops up
138 # and the return value of msidb.exe is not zero. The error window makes it impossible to check the existence
139 # of a table with the help of the return value.
140 # Solution: Export of all tables by using "*" . Some tables must exist (File Component Directory), other
141 # tables do not need to exist (MsiAssembly).
143 if ( $^O =~ /cygwin/i ) {
144 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
145 my $localworkdir = $workdir;
146 $localworkdir =~ s/\//\\\\/g;
147 $systemcall = $msidb . " -d " . $filename . " -f " . $localworkdir . " -e \\\*";
149 else
151 # $systemcall = $msidb . " -d " . $filename . " -f " . $workdir . " -e File Component MsiAssembly Directory";
152 $systemcall = $msidb . " -d " . $filename . " -f " . $workdir . " -e \*";
155 $returnvalue = system($systemcall);
157 $infoline = "Systemcall: $systemcall\n";
158 push( @installer::globals::logfileinfo, $infoline);
160 if ($returnvalue)
162 $infoline = "ERROR: Could not execute $systemcall !\n";
163 push( @installer::globals::logfileinfo, $infoline);
164 installer::exiter::exit_program("ERROR: Could not exclude tables from merge file: $completedest !", "merge_mergemodules_into_msi_database");
166 else
168 $infoline = "Success: Executed $systemcall successfully!\n";
169 push( @installer::globals::logfileinfo, $infoline);
172 # Determining files
173 my $idtfilename = "File.idt"; # must exist
174 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: File \"$idtfilename\" not found in directory \"$workdir\" !", "merge_mergemodules_into_msi_database"); }
175 my $filecontent = installer::files::read_file($idtfilename);
176 my @file_idt_content = ();
177 my $filecounter = 0;
178 my %mergefilesequence = ();
179 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
181 if ( $i <= 2 ) { next; } # ignoring first three lines
182 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
183 $filecounter++;
184 push(@file_idt_content, ${$filecontent}[$i]);
185 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.+?)\t(.*?)\t(.*?)\t(.*?)\t(\d+?)\s*$/ )
187 my $filename = $1;
188 my $filesequence = $8;
189 $mergefilesequence{$filename} = $filesequence;
191 else
193 my $linecount = $i + 1;
194 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "merge_mergemodules_into_msi_database");
198 # Determining components
199 $idtfilename = "Component.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 %componentnames = ();
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+/ ) { $componentnames{$1} = 1; }
210 # Determining directories
211 $idtfilename = "Directory.idt"; # must exist
212 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: File \"$idtfilename\" not found in directory \"$workdir\" !", "merge_mergemodules_into_msi_database"); }
213 $filecontent = installer::files::read_file($idtfilename);
214 my %mergedirectories = ();
215 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
217 if ( $i <= 2 ) { next; } # ignoring first three lines
218 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
219 if ( ${$filecontent}[$i] =~ /^\s*(\S+)\s+/ ) { $mergedirectories{$1} = 1; }
222 # Determining assemblies
223 $idtfilename = "MsiAssembly.idt"; # does not need to exist
224 my $hasmsiassemblies = 0;
225 my %mergeassemblies = ();
226 if ( -f $idtfilename )
228 $filecontent = installer::files::read_file($idtfilename);
229 $hasmsiassemblies = 1;
230 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
232 if ( $i <= 2 ) { next; } # ignoring first three lines
233 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
234 if ( ${$filecontent}[$i] =~ /^\s*(\S+)\s+/ ) { $mergeassemblies{$1} = 1; }
238 # It is possible, that other tables have to be checked here. This happens, if tables in the
239 # merge module have to know the "Feature" or the "Directory", under which the content of the
240 # msm file is integrated into the msi database.
242 # Determining name of cabinet file in installation set
243 my $cabfilename = $mergemodule->{'Cabfilename'};
244 installer::packagelist::resolve_packagevariables(\$cabfilename, $allvariables, 0);
246 # Analyzing styles
247 # Flag REMOVE_FILE_TABLE is required for msvc9 Merge-Module, because otherwise msidb.exe
248 # fails during integration of msm file into msi database.
250 my $styles = "";
251 my $removefiletable = 0;
252 if ( $mergemodule->{'Styles'} ) { $styles = $mergemodule->{'Styles'}; }
253 if ( $styles =~ /\bREMOVE_FILE_TABLE\b/ ) { $removefiletable = 1; }
255 if ( $removefiletable )
257 my $removeworkdir = $workdir . $installer::globals::separator . "remove_file_idt";
258 if ( ! -d $removeworkdir ) { installer::systemactions::create_directory($removeworkdir); }
259 my $completeremovedest = $removeworkdir . $installer::globals::separator . $filename;
260 installer::systemactions::copy_one_file($completedest, $completeremovedest);
261 if ( ! -f $completeremovedest ) { installer::exiter::exit_program("ERROR: msm file not found: $completeremovedest !", "merge_mergemodules_into_msi_database"); }
263 # Unpacking msm file
264 if ( $^O =~ /cygwin/i ) {
265 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
266 my $localcompleteremovedest = $completeremovedest;
267 my $localremoveworkdir = $removeworkdir;
268 $localcompleteremovedest =~ s/\//\\\\/g;
269 $localremoveworkdir =~ s/\//\\\\/g;
270 $systemcall = $msidb . " -d " . $localcompleteremovedest . " -f " . $localremoveworkdir . " -e \\\*";
272 else
274 $systemcall = $msidb . " -d " . $completeremovedest . " -f " . $removeworkdir . " -e \*";
277 $returnvalue = system($systemcall);
279 my $idtfilename = $removeworkdir . $installer::globals::separator . "File.idt";
280 if ( -f $idtfilename ) { unlink $idtfilename; }
281 unlink $completeremovedest;
283 # Packing msm file without "File.idt"
284 if ( $^O =~ /cygwin/i ) {
285 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
286 my $localcompleteremovedest = $completeremovedest;
287 my $localremoveworkdir = $removeworkdir;
288 $localcompleteremovedest =~ s/\//\\\\/g;
289 $localremoveworkdir =~ s/\//\\\\/g;
290 $systemcall = $msidb . " -c -d " . $localcompleteremovedest . " -f " . $localremoveworkdir . " -i \\\*";
292 else
294 $systemcall = $msidb . " -c -d " . $completeremovedest . " -f " . $removeworkdir . " -i \*";
296 $returnvalue = system($systemcall);
298 # Using this msm file for merging
299 if ( -f $completeremovedest ) { $completedest = $completeremovedest; }
300 else { installer::exiter::exit_program("ERROR: Could not find msm file without File.idt: $completeremovedest !", "merge_mergemodules_into_msi_database"); }
303 # Saving MergeModule info
305 my %onemergemodulehash = ();
306 $onemergemodulehash{'mergefilepath'} = $completedest;
307 $onemergemodulehash{'workdir'} = $workdir;
308 $onemergemodulehash{'cabinetfile'} = $workdir . $installer::globals::separator . $cabinetfile;
309 $onemergemodulehash{'filenumber'} = $filecounter;
310 $onemergemodulehash{'componentnames'} = \%componentnames;
311 $onemergemodulehash{'cabfilename'} = $cabfilename;
312 $onemergemodulehash{'feature'} = $mergemodule->{'Feature'};
313 $onemergemodulehash{'rootdir'} = $mergemodule->{'RootDir'};
314 $onemergemodulehash{'name'} = $mergemodule->{'Name'};
315 $onemergemodulehash{'mergefilesequence'} = \%mergefilesequence;
316 $onemergemodulehash{'mergeassemblies'} = \%mergeassemblies;
317 $onemergemodulehash{'mergedirectories'} = \%mergedirectories;
318 $onemergemodulehash{'hasmsiassemblies'} = $hasmsiassemblies;
319 $onemergemodulehash{'removefiletable'} = $removefiletable;
320 $onemergemodulehash{'fileidtcontent'} = \@file_idt_content;
322 $installer::globals::mergemodules{$mergegid} = \%onemergemodulehash;
324 # Collecting all cab files, to copy them into installation set
325 $installer::globals::copy_msm_files{$cabfilename} = $onemergemodulehash{'cabinetfile'};
327 chdir($from);
330 $infoline = "All Merge Modules successfully analyzed\n";
331 push( @installer::globals::logfileinfo, $infoline);
333 $installer::globals::mergemodules_analyzed = 1;
334 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Analyzing MergeModules, stop");
336 $infoline = "\n";
337 push( @installer::globals::logfileinfo, $infoline);
340 # 2. Change msi database (has to be done for every msi database -> for every language)
341 # a. Merge msm file into msi database: msidb.exe -d <msifile> -m <mergefile>
342 # b. Extracting tables from msi database: msidb.exe -d <msifile> -f <directory> -e File Media, ...
343 # c. Changing content of msi database in tables: File, Media, Directory, FeatureComponent
344 # d. Including tables into msi database: msidb.exe -d <msifile> -f <directory> -i File Media, ...
345 # e. Copying cabinet file into installation set (later)
347 my $counter = 0;
348 my $mergemodulegid;
349 foreach $mergemodulegid (keys %installer::globals::mergemodules)
351 my $mergemodulehash = $installer::globals::mergemodules{$mergemodulegid};
352 $counter++;
354 installer::logger::include_header_into_logfile("Merging Module: $mergemodulehash->{'name'}");
355 installer::logger::print_message( "\t... $mergemodulehash->{'name'} ... \n" );
357 $msifilename = installer::converter::make_path_conform($msifilename);
358 my $workdir = $msifilename;
359 installer::pathanalyzer::get_path_from_fullqualifiedname(\$workdir);
361 # changing directory
362 my $from = cwd();
363 my $to = $workdir;
364 chdir($to);
366 # Saving original msi database
367 installer::systemactions::copy_one_file($msifilename, "$msifilename\.$counter");
369 # Merging msm file, this is the "real" merge command
371 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Before merging database");
373 if ( $^O =~ /cygwin/i ) {
374 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
375 my $localmergemodulepath = $mergemodulehash->{'mergefilepath'};
376 my $localmsifilename = $msifilename;
377 $localmergemodulepath =~ s/\//\\\\/g;
378 $localmsifilename =~ s/\//\\\\/g;
379 $systemcall = $msidb . " -d " . $localmsifilename . " -m " . $localmergemodulepath;
381 else
383 $systemcall = $msidb . " -d " . $msifilename . " -m " . $mergemodulehash->{'mergefilepath'};
385 $returnvalue = system($systemcall);
387 $infoline = "Systemcall: $systemcall\n";
388 push( @installer::globals::logfileinfo, $infoline);
390 if ($returnvalue)
392 $infoline = "ERROR: Could not execute $systemcall . Returnvalue: $returnvalue!\n";
393 push( @installer::globals::logfileinfo, $infoline);
394 installer::exiter::exit_program("ERROR: Could not merge msm file into database: $mergemodulehash->{'mergefilepath'} !", "merge_mergemodules_into_msi_database");
396 else
398 $infoline = "Success: Executed $systemcall successfully!\n";
399 push( @installer::globals::logfileinfo, $infoline);
402 installer::logger::include_timestamp_into_logfile("\nPerformance Info: After merging database");
404 # Saving original idt files
405 if ( -f "File.idt" ) { installer::systemactions::rename_one_file("File.idt", "File.idt.$counter"); }
406 if ( -f "Media.idt" ) { installer::systemactions::rename_one_file("Media.idt", "Media.idt.$counter"); }
407 if ( -f "Directory.idt" ) { installer::systemactions::rename_one_file("Directory.idt", "Directory.idt.$counter"); }
408 if ( -f "Director.idt" ) { installer::systemactions::rename_one_file("Director.idt", "Director.idt.$counter"); }
409 if ( -f "FeatureComponents.idt" ) { installer::systemactions::rename_one_file("FeatureComponents.idt", "FeatureComponents.idt.$counter"); }
410 if ( -f "FeatureC.idt" ) { installer::systemactions::rename_one_file("FeatureC.idt", "FeatureC.idt.$counter"); }
411 if ( -f "MsiAssembly.idt" ) { installer::systemactions::rename_one_file("MsiAssembly.idt", "MsiAssembly.idt.$counter"); }
412 if ( -f "MsiAssem.idt" ) { installer::systemactions::rename_one_file("MsiAssem.idt", "MsiAssem.idt.$counter"); }
414 # Extracting tables
416 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Before extracting tables");
418 my $workingtables = "File Media Directory FeatureComponents"; # required tables
419 # Optional tables can be added now
420 if ( $mergemodulehash->{'hasmsiassemblies'} ) { $workingtables = $workingtables . " MsiAssembly"; }
422 # Table "Feature" has to be exported, but it is not necessary to import it.
423 if ( $^O =~ /cygwin/i ) {
424 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
425 my $localmsifilename = $msifilename;
426 my $localworkdir = $workdir;
427 $localmsifilename =~ s/\//\\\\/g;
428 $localworkdir =~ s/\//\\\\/g;
429 $systemcall = $msidb . " -d " . $localmsifilename . " -f " . $localworkdir . " -e " . "Feature " . $workingtables;
431 else
433 $systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -e " . "Feature " . $workingtables;
435 $returnvalue = system($systemcall);
437 $infoline = "Systemcall: $systemcall\n";
438 push( @installer::globals::logfileinfo, $infoline);
440 if ($returnvalue)
442 $infoline = "ERROR: Could not execute $systemcall !\n";
443 push( @installer::globals::logfileinfo, $infoline);
444 installer::exiter::exit_program("ERROR: Could not exclude tables from msi database: $msifilename !", "merge_mergemodules_into_msi_database");
446 else
448 $infoline = "Success: Executed $systemcall successfully!\n";
449 push( @installer::globals::logfileinfo, $infoline);
452 installer::logger::include_timestamp_into_logfile("\nPerformance Info: After extracting tables");
454 # Using 8+3 table names, that are used, when tables are integrated into database. The export of tables
455 # creates idt-files, that have long names.
457 if ( -f "Directory.idt" ) { installer::systemactions::rename_one_file("Directory.idt", "Director.idt"); }
458 if ( -f "FeatureComponents.idt" ) { installer::systemactions::rename_one_file("FeatureComponents.idt", "FeatureC.idt"); }
459 if ( -f "MsiAssembly.idt" ) { installer::systemactions::rename_one_file("MsiAssembly.idt", "MsiAssem.idt"); }
461 # Changing content of tables: File, Media, Directory, FeatureComponent, MsiAssembly
462 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing Media table");
463 change_media_table($mergemodulehash, $workdir, $mergemodulegid, $allupdatelastsequences, $allupdatediskids);
464 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing File table");
465 $filesref = change_file_table($mergemodulehash, $workdir, $allupdatesequences, $includepatharrayref, $filesref, $mergemodulegid);
466 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing FeatureComponent table");
467 change_featurecomponent_table($mergemodulehash, $workdir);
468 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing Directory table");
469 change_directory_table($mergemodulehash, $workdir);
470 if ( $mergemodulehash->{'hasmsiassemblies'} )
472 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing MsiAssembly table");
473 change_msiassembly_table($mergemodulehash, $workdir);
476 # msidb.exe does not merge InstallExecuteSequence, AdminExecuteSequence and AdvtExecuteSequence. Instead it creates
477 # new tables ModuleInstallExecuteSequence, ModuleAdminExecuteSequence and ModuleAdvtExecuteSequence that need to be
478 # merged into the three ExecuteSequences with the following process (also into InstallUISequence.idt).
480 # Saving original idt files
481 if ( -f "InstallE.idt" ) { installer::systemactions::rename_one_file("InstallE.idt", "InstallE.idt.$counter"); }
482 if ( -f "InstallU.idt" ) { installer::systemactions::rename_one_file("InstallU.idt", "InstallU.idt.$counter"); }
483 if ( -f "AdminExe.idt" ) { installer::systemactions::rename_one_file("AdminExe.idt", "AdminExe.idt.$counter"); }
484 if ( -f "AdvtExec.idt" ) { installer::systemactions::rename_one_file("AdvtExec.idt", "AdvtExec.idt.$counter"); }
485 if ( -f "ModuleInstallExecuteSequence.idt" ) { installer::systemactions::rename_one_file("ModuleInstallExecuteSequence.idt", "ModuleInstallExecuteSequence.idt.$counter"); }
486 if ( -f "ModuleAdminExecuteSequence.idt" ) { installer::systemactions::rename_one_file("ModuleAdminExecuteSequence.idt", "ModuleAdminExecuteSequence.idt.$counter"); }
487 if ( -f "ModuleAdvtExecuteSequence.idt" ) { installer::systemactions::rename_one_file("ModuleAdvtExecuteSequence.idt", "ModuleAdvtExecuteSequence.idt.$counter"); }
489 # Extracting tables
490 my $moduleexecutetables = "ModuleInstallExecuteSequence ModuleAdminExecuteSequence ModuleAdvtExecuteSequence"; # new tables
491 my $executetables = "InstallExecuteSequence InstallUISequence AdminExecuteSequence AdvtExecuteSequence"; # tables to be merged
494 if ( $^O =~ /cygwin/i ) {
495 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
496 my $localmsifilename = $msifilename;
497 my $localworkdir = $workdir;
498 $localmsifilename =~ s/\//\\\\/g;
499 $localworkdir =~ s/\//\\\\/g;
500 $systemcall = $msidb . " -d " . $localmsifilename . " -f " . $localworkdir . " -e " . "Feature " . $moduleexecutetables;
502 else
504 $systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -e " . "Feature " . $moduleexecutetables;
506 $returnvalue = system($systemcall);
508 if ( $^O =~ /cygwin/i ) {
509 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
510 my $localmsifilename = $msifilename;
511 my $localworkdir = $workdir;
512 $localmsifilename =~ s/\//\\\\/g;
513 $localworkdir =~ s/\//\\\\/g;
514 $systemcall = $msidb . " -d " . $localmsifilename . " -f " . $localworkdir . " -e " . "Feature " . $executetables;
516 else
518 $systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -e " . "Feature " . $executetables;
520 $returnvalue = system($systemcall);
522 # Using 8+3 table names, that are used, when tables are integrated into database. The export of tables
523 # creates idt-files, that have long names.
525 if ( -f "InstallExecuteSequence.idt" ) { installer::systemactions::rename_one_file("InstallExecuteSequence.idt", "InstallE.idt"); }
526 if ( -f "InstallUISequence.idt" ) { installer::systemactions::rename_one_file("InstallUISequence.idt", "InstallU.idt"); }
527 if ( -f "AdminExecuteSequence.idt" ) { installer::systemactions::rename_one_file("AdminExecuteSequence.idt", "AdminExe.idt"); }
528 if ( -f "AdvtExecuteSequence.idt" ) { installer::systemactions::rename_one_file("AdvtExecuteSequence.idt", "AdvtExec.idt"); }
530 # Merging content of tables ModuleInstallExecuteSequence, ModuleAdminExecuteSequence and ModuleAdvtExecuteSequence
531 # into tables InstallExecuteSequence, AdminExecuteSequence and AdvtExecuteSequence
532 if ( -f "ModuleInstallExecuteSequence.idt" )
534 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing InstallExecuteSequence table");
535 change_executesequence_table($mergemodulehash, $workdir, "InstallE.idt", "ModuleInstallExecuteSequence.idt");
536 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing InstallUISequence table");
537 change_executesequence_table($mergemodulehash, $workdir, "InstallU.idt", "ModuleInstallExecuteSequence.idt");
540 if ( -f "ModuleAdminExecuteSequence.idt" )
542 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing AdminExecuteSequence table");
543 change_executesequence_table($mergemodulehash, $workdir, "AdminExe.idt", "ModuleAdminExecuteSequence.idt");
546 if ( -f "ModuleAdvtExecuteSequence.idt" )
548 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing AdvtExecuteSequence table");
549 change_executesequence_table($mergemodulehash, $workdir, "AdvtExec.idt", "ModuleAdvtExecuteSequence.idt");
552 installer::logger::include_timestamp_into_logfile("\nPerformance Info: All tables edited");
554 # Including tables into msi database
556 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Before including tables");
558 if ( $^O =~ /cygwin/i ) {
559 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
560 my $localmsifilename = $msifilename;
561 my $localworkdir = $workdir;
562 $localmsifilename =~ s/\//\\\\/g;
563 $localworkdir =~ s/\//\\\\/g;
564 $systemcall = $msidb . " -d " . $localmsifilename . " -f " . $localworkdir . " -i " . $workingtables. " " . $executetables;
566 else
568 $systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -i " . $workingtables. " " . $executetables;
570 $returnvalue = system($systemcall);
572 $infoline = "Systemcall: $systemcall\n";
573 push( @installer::globals::logfileinfo, $infoline);
575 if ($returnvalue)
577 $infoline = "ERROR: Could not execute $systemcall !\n";
578 push( @installer::globals::logfileinfo, $infoline);
579 installer::exiter::exit_program("ERROR: Could not include tables into msi database: $msifilename !", "merge_mergemodules_into_msi_database");
581 else
583 $infoline = "Success: Executed $systemcall successfully!\n";
584 push( @installer::globals::logfileinfo, $infoline);
587 installer::logger::include_timestamp_into_logfile("\nPerformance Info: After including tables");
589 chdir($from);
592 if ( ! $installer::globals::mergefiles_added_into_collector ) { $installer::globals::mergefiles_added_into_collector = 1; } # Now all mergemodules are merged for one language.
594 installer::logger::include_timestamp_into_logfile("\nPerformance Info: MergeModule into msi database, stop");
597 return $filesref;
600 #########################################################################
601 # Analyzing the content of the media table.
602 #########################################################################
604 sub analyze_media_file
606 my ($filecontent, $workdir) = @_;
608 my %filehash = ();
609 my $linecount = 0;
610 my $counter = 0;
611 my $filename = "Media.idt";
613 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
615 if ( $i <= 2 ) { next; } # ignoring first three lines
616 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
617 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.+?)\t(.+?)\t(.*?)\s*$/ )
619 my %line = ();
620 # Format: DiskId LastSequence DiskPrompt Cabinet VolumeLabel Source
621 $line{'DiskId'} = $1;
622 $line{'LastSequence'} = $2;
623 $line{'DiskPrompt'} = $3;
624 $line{'Cabinet'} = $4;
625 $line{'VolumeLabel'} = $5;
626 $line{'Source'} = $6;
628 $counter++;
629 $filehash{$counter} = \%line;
631 else
633 $linecount = $i + 1;
634 installer::exiter::exit_program("ERROR: Unknown line format in table \"$filename\" in \"$workdir\" (line $linecount) !", "analyze_media_file");
638 return \%filehash;
641 #########################################################################
642 # Setting the DiskID for the new cabinet file
643 #########################################################################
645 sub get_diskid
647 my ($mediafile, $allupdatediskids, $cabfilename) = @_;
649 my $diskid = 0;
650 my $line;
652 if (( $installer::globals::updatedatabase ) && ( exists($allupdatediskids->{$cabfilename}) ))
654 $diskid = $allupdatediskids->{$cabfilename};
656 else
658 foreach $line ( keys %{$mediafile} )
660 if ( $mediafile->{$line}->{'DiskId'} > $diskid ) { $diskid = $mediafile->{$line}->{'DiskId'}; }
663 $diskid++;
666 return $diskid;
669 #########################################################################
670 # Setting the global LastSequence variable
671 #########################################################################
673 sub set_current_last_sequence
675 my ($mediafile) = @_;
677 my $lastsequence = 0;
678 my $line;
679 foreach $line ( keys %{$mediafile} )
681 if ( $mediafile->{$line}->{'LastSequence'} > $lastsequence ) { $lastsequence = $mediafile->{$line}->{'LastSequence'}; }
684 $installer::globals::lastsequence_before_merge = $lastsequence;
687 #########################################################################
688 # Setting the LastSequence for the new cabinet file
689 #########################################################################
691 sub get_lastsequence
693 my ($mergemodulehash, $allupdatelastsequences) = @_;
695 my $lastsequence = 0;
697 if (( $installer::globals::updatedatabase ) && ( exists($allupdatelastsequences->{$mergemodulehash->{'cabfilename'}}) ))
699 $lastsequence = $allupdatelastsequences->{$mergemodulehash->{'cabfilename'}};
701 else
703 $lastsequence = $installer::globals::lastsequence_before_merge + $mergemodulehash->{'filenumber'};
706 return $lastsequence;
709 #########################################################################
710 # Setting the DiskPrompt for the new cabinet file
711 #########################################################################
713 sub get_diskprompt
715 my ($mediafile) = @_;
717 my $diskprompt = "";
718 my $line;
719 foreach $line ( keys %{$mediafile} )
721 if ( exists($mediafile->{$line}->{'DiskPrompt'}) )
723 $diskprompt = $mediafile->{$line}->{'DiskPrompt'};
724 last;
728 return $diskprompt;
731 #########################################################################
732 # Setting the VolumeLabel for the new cabinet file
733 #########################################################################
735 sub get_volumelabel
737 my ($mediafile) = @_;
739 my $volumelabel = "";
740 my $line;
741 foreach $line ( keys %{$mediafile} )
743 if ( exists($mediafile->{$line}->{'VolumeLabel'}) )
745 $volumelabel = $mediafile->{$line}->{'VolumeLabel'};
746 last;
750 return $volumelabel;
753 #########################################################################
754 # Setting the Source for the new cabinet file
755 #########################################################################
757 sub get_source
759 my ($mediafile) = @_;
761 my $source = "";
762 my $line;
763 foreach $line ( keys %{$mediafile} )
765 if ( exists($mediafile->{$line}->{'Source'}) )
767 $diskprompt = $mediafile->{$line}->{'Source'};
768 last;
772 return $source;
775 #########################################################################
776 # For each Merge Module one new line has to be included into the
777 # media table.
778 #########################################################################
780 sub create_new_media_line
782 my ($mergemodulehash, $mediafile, $allupdatelastsequences, $allupdatediskids) = @_;
784 my $diskid = get_diskid($mediafile, $allupdatediskids, $mergemodulehash->{'cabfilename'});
785 my $lastsequence = get_lastsequence($mergemodulehash, $allupdatelastsequences);
786 my $diskprompt = get_diskprompt($mediafile);
787 my $cabinet = $mergemodulehash->{'cabfilename'};
788 my $volumelabel = get_volumelabel($mediafile);
789 my $source = get_source($mediafile);
791 if ( $installer::globals::include_cab_in_msi ) { $cabinet = "\#" . $cabinet; }
793 my $newline = "$diskid\t$lastsequence\t$diskprompt\t$cabinet\t$volumelabel\t$source\n";
795 return $newline;
798 #########################################################################
799 # Setting the last diskid in media table.
800 #########################################################################
802 sub get_last_diskid
804 my ($mediafile) = @_;
806 my $lastdiskid = 0;
807 my $line;
808 foreach $line ( keys %{$mediafile} )
810 if ( $mediafile->{$line}->{'DiskId'} > $lastdiskid ) { $lastdiskid = $mediafile->{$line}->{'DiskId'}; }
813 return $lastdiskid;
816 #########################################################################
817 # Setting global variable for last cab file name.
818 #########################################################################
820 sub set_last_cabfile_name
822 my ($mediafile, $lastdiskid) = @_;
824 my $line;
825 foreach $line ( keys %{$mediafile} )
827 if ( $mediafile->{$line}->{'DiskId'} == $lastdiskid ) { $installer::globals::lastcabfilename = $mediafile->{$line}->{'Cabinet'}; }
829 my $infoline = "Setting last cabinet file: $installer::globals::lastcabfilename\n";
830 push( @installer::globals::logfileinfo, $infoline);
833 #########################################################################
834 # In the media table the new cabinet file has to be added or the
835 # number of the last cabinet file has to be increased.
836 #########################################################################
838 sub change_media_table
840 my ( $mergemodulehash, $workdir, $mergemodulegid, $allupdatelastsequences, $allupdatediskids ) = @_;
842 my $infoline = "Changing content of table \"Media\"\n";
843 push( @installer::globals::logfileinfo, $infoline);
845 my $filename = "Media.idt";
846 if ( ! -f $filename ) { installer::exiter::exit_program("ERROR: Could not find file \"$filename\" in \"$workdir\" !", "change_media_table"); }
848 my $filecontent = installer::files::read_file($filename);
849 my $mediafile = analyze_media_file($filecontent, $workdir);
850 set_current_last_sequence($mediafile);
852 if ( $installer::globals::fix_number_of_cab_files )
854 # Determining the line with the highest sequencenumber. That file needs to be updated.
855 my $lastdiskid = get_last_diskid($mediafile);
856 if ( $installer::globals::lastcabfilename eq "" ) { set_last_cabfile_name($mediafile, $lastdiskid); }
857 my $newmaxsequencenumber = $installer::globals::lastsequence_before_merge + $mergemodulehash->{'filenumber'};
859 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
861 if ( $i <= 2 ) { next; } # ignoring first three lines
862 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
863 if ( ${$filecontent}[$i] =~ /^\s*(\Q$lastdiskid\E\t)\Q$installer::globals::lastsequence_before_merge\E(\t.*)$/ )
865 my $start = $1;
866 my $final = $2;
867 $infoline = "Merge: Old line in media table: ${$filecontent}[$i]\n";
868 push( @installer::globals::logfileinfo, $infoline);
869 my $newline = $start . $newmaxsequencenumber . $final . "\n";
870 ${$filecontent}[$i] = $newline;
871 $infoline = "Merge: Changed line in media table: ${$filecontent}[$i]\n";
872 push( @installer::globals::logfileinfo, $infoline);
876 else
878 # the new line is identical for all localized databases, but has to be created for each MergeModule ($mergemodulegid)
879 if ( ! exists($installer::globals::merge_media_line{$mergemodulegid}) )
881 $installer::globals::merge_media_line{$mergemodulegid} = create_new_media_line($mergemodulehash, $mediafile, $allupdatelastsequences, $allupdatediskids);
884 $infoline = "Adding line: $installer::globals::merge_media_line{$mergemodulegid}\n";
885 push( @installer::globals::logfileinfo, $infoline);
887 # adding new line
888 push(@{$filecontent}, $installer::globals::merge_media_line{$mergemodulegid});
891 # saving file
892 installer::files::save_file($filename, $filecontent);
895 #########################################################################
896 # Putting the directory table content into a hash.
897 #########################################################################
899 sub analyze_directorytable_file
901 my ($filecontent, $idtfilename) = @_;
903 my %dirhash = ();
904 # Iterating over the file content
905 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
907 if ( $i <= 2 ) { next; } # ignoring first three lines
908 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
909 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\s*$/ )
911 my %line = ();
912 # Format: Directory Directory_Parent DefaultDir
913 $line{'Directory'} = $1;
914 $line{'Directory_Parent'} = $2;
915 $line{'DefaultDir'} = $3;
916 $line{'linenumber'} = $i; # saving also the line number for direct access
918 my $uniquekey = $line{'Directory'};
919 $dirhash{$uniquekey} = \%line;
921 else
923 my $linecount = $i + 1;
924 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "analyze_directorytable_file");
928 return \%dirhash;
931 #########################################################################
932 # Putting the msi assembly table content into a hash.
933 #########################################################################
935 sub analyze_msiassemblytable_file
937 my ($filecontent, $idtfilename) = @_;
939 my %assemblyhash = ();
940 # Iterating over the file content
941 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
943 if ( $i <= 2 ) { next; } # ignoring first three lines
944 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
945 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.*?)\t(.*?)\s*$/ )
947 my %line = ();
948 # Format: Component_ Feature_ File_Manifest File_Application Attributes
949 $line{'Component'} = $1;
950 $line{'Feature'} = $2;
951 $line{'File_Manifest'} = $3;
952 $line{'File_Application'} = $4;
953 $line{'Attributes'} = $5;
954 $line{'linenumber'} = $i; # saving also the line number for direct access
956 my $uniquekey = $line{'Component'};
957 $assemblyhash{$uniquekey} = \%line;
959 else
961 my $linecount = $i + 1;
962 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "analyze_msiassemblytable_file");
966 return \%assemblyhash;
969 #########################################################################
970 # Putting the file table content into a hash.
971 #########################################################################
973 sub analyze_filetable_file
975 my ( $filecontent, $idtfilename ) = @_;
977 my %filehash = ();
978 # Iterating over the file content
979 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
981 if ( $i <= 2 ) { next; } # ignoring first three lines
982 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
983 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.+?)\s*$/ )
985 my %line = ();
986 # Format: File Component_ FileName FileSize Version Language Attributes Sequence
987 $line{'File'} = $1;
988 $line{'Component'} = $2;
989 $line{'FileName'} = $3;
990 $line{'FileSize'} = $4;
991 $line{'Version'} = $5;
992 $line{'Language'} = $6;
993 $line{'Attributes'} = $7;
994 $line{'Sequence'} = $8;
995 $line{'linenumber'} = $i; # saving also the line number for direct access
997 my $uniquekey = $line{'File'};
998 $filehash{$uniquekey} = \%line;
1000 else
1002 my $linecount = $i + 1;
1003 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "analyze_filetable_file");
1007 return \%filehash;
1010 #########################################################################
1011 # Creating a new line for the directory table.
1012 #########################################################################
1014 sub get_new_line_for_directory_table
1016 my ($dir) = @_;
1018 my $newline = "$dir->{'Directory'}\t$dir->{'Directory_Parent'}\t$dir->{'DefaultDir'}\n";
1020 return $newline;
1023 #########################################################################
1024 # Creating a new line for the file table.
1025 #########################################################################
1027 sub get_new_line_for_file_table
1029 my ($file) = @_;
1031 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";
1033 return $newline;
1036 #########################################################################
1037 # Creating a new line for the msiassembly table.
1038 #########################################################################
1040 sub get_new_line_for_msiassembly_table
1042 my ($assembly) = @_;
1044 my $newline = "$assembly->{'Component'}\t$assembly->{'Feature'}\t$assembly->{'File_Manifest'}\t$assembly->{'File_Application'}\t$assembly->{'Attributes'}\n";
1046 return $newline;
1049 #########################################################################
1050 # Sorting the files collector, if there are files, following
1051 # the merge module files.
1052 #########################################################################
1054 sub sort_files_collector_for_sequence
1056 my ($filesref) = @_;
1058 my @sortarray = ();
1059 my %helphash = ();
1061 for ( my $i = 0; $i <= $#{$filesref}; $i++ )
1063 my $onefile = ${$filesref}[$i];
1064 if ( ! exists($onefile->{'sequencenumber'}) ) { installer::exiter::exit_program("ERROR: Could not find sequencenumber for file: $onefile->{'uniquename'} !", "sort_files_collector_for_sequence"); }
1065 my $sequence = $onefile->{'sequencenumber'};
1066 $helphash{$sequence} = $onefile;
1069 foreach my $seq ( sort { $a <=> $b } keys %helphash ) { push(@sortarray, $helphash{$seq}); }
1071 return \@sortarray;
1074 #########################################################################
1075 # In the file table "Sequence" and "Attributes" have to be changed.
1076 #########################################################################
1078 sub change_file_table
1080 my ($mergemodulehash, $workdir, $allupdatesequenceshashref, $includepatharrayref, $filesref, $mergemodulegid) = @_;
1082 my $infoline = "Changing content of table \"File\"\n";
1083 push( @installer::globals::logfileinfo, $infoline);
1085 my $idtfilename = "File.idt";
1086 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_file_table"); }
1088 my $filecontent = installer::files::read_file($idtfilename);
1090 # If File.idt needed to be removed before the msm database was merged into the msi database,
1091 # now it is time to add the content into File.idt
1092 if ( $mergemodulehash->{'removefiletable'} )
1094 for ( my $i = 0; $i <= $#{$mergemodulehash->{'fileidtcontent'}}; $i++ )
1096 push(@{$filecontent}, ${$mergemodulehash->{'fileidtcontent'}}[$i]);
1100 # Unpacking the MergeModule.CABinet (only once)
1101 # Unpacking into temp directory. Warning: expand.exe has problems with very long unpack directories.
1103 my $unpackdir = installer::systemactions::create_directories("cab", "");
1104 push(@installer::globals::removedirs, $unpackdir);
1105 $unpackdir = $unpackdir . $installer::globals::separator . $mergemodulegid;
1107 my %newfileshash = ();
1108 if (( $installer::globals::fix_number_of_cab_files ) && ( ! $installer::globals::mergefiles_added_into_collector ))
1110 if ( ! -d $unpackdir ) { installer::systemactions::create_directory($unpackdir); }
1112 # Unpack the cab file, so that in can be included into the last office cabinet file. Attention: cararc.exe from cabsdk required.
1113 # cabarc.exe -o X <fullcabfilepath>
1115 # my $cabarcfilename = "cabarc.exe";
1116 # my $cabarcfile = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$cabarcfilename, $includepatharrayref, 1);
1118 # if ( ! -f $$cabarcfile )
1120 # $cabarcfilename = "CABARC.EXE";
1121 # $cabarcfile = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$cabarcfilename, $includepatharrayref, 1);
1122 # if ( ! -f $$cabarcfile )
1124 # installer::exiter::exit_program("ERROR: cabarc.exe not found !", "change_file_table");
1127 # my $cabarc = $$cabarcfile;
1129 # changing directory
1130 my $from = cwd();
1131 my $to = $mergemodulehash->{'workdir'};
1132 if ( $^O =~ /cygwin/i ) {
1133 $to = qx(cygpath -u "$to");
1134 chomp $to;
1137 chdir($to) || die "Could not chdir to \"$to\"\n";
1139 # Unpack the cab file, so that in can be included into the last office cabinet file.
1140 # Not using cabarc.exe from cabsdk for unpacking cabinet files, but "expand.exe" that
1141 # should be available on every Windows system.
1143 $infoline = "Unpacking cabinet file: $mergemodulehash->{'cabinetfile'}\n";
1144 push( @installer::globals::logfileinfo, $infoline);
1146 # Avoid the Cygwin expand command
1147 my $expandfile = "expand.exe"; # Has to be in the path
1148 if ( $^O =~ /cygwin/i ) {
1149 $expandfile = qx(cygpath -u "$ENV{WINDIR}"/System32/expand.exe);
1150 chomp $expandfile;
1153 my $cabfilename = "MergeModule.CABinet";
1155 # exclude cabinet file
1156 # my $systemcall = $cabarc . " -o X " . $mergemodulehash->{'cabinetfile'};
1158 my $systemcall = "";
1159 if ( $^O =~ /cygwin/i ) {
1160 my $localunpackdir = qx(cygpath -m "$unpackdir");
1161 chomp $localunpackdir;
1162 $systemcall = $expandfile . " " . $cabfilename . " -F:\\\* " . $localunpackdir;
1164 else
1166 $systemcall = $expandfile . " " . $cabfilename . " -F:\* " . $unpackdir . " 2\>\&1";
1169 my $returnvalue = system($systemcall);
1171 $infoline = "Systemcall: $systemcall\n";
1172 push( @installer::globals::logfileinfo, $infoline);
1174 if ($returnvalue)
1176 $infoline = "ERROR: Could not execute $systemcall !\n";
1177 push( @installer::globals::logfileinfo, $infoline);
1178 installer::exiter::exit_program("ERROR: Could not extract cabinet file: $mergemodulehash->{'cabinetfile'} !", "change_file_table");
1180 else
1182 $infoline = "Success: Executed $systemcall successfully!\n";
1183 push( @installer::globals::logfileinfo, $infoline);
1186 chdir($from);
1189 # For performance reasons creating a hash with file names and rows
1190 # The content of File.idt is changed after every merge -> content cannot be saved in global hash
1191 $merge_filetablehashref = analyze_filetable_file($filecontent, $idtfilename);
1193 my $attributes = "16384"; # Always
1195 my $filename;
1196 foreach $filename (keys %{$mergemodulehash->{'mergefilesequence'}} )
1198 my $mergefilesequence = $mergemodulehash->{'mergefilesequence'}->{$filename};
1200 if ( ! exists($merge_filetablehashref->{$filename}) ) { installer::exiter::exit_program("ERROR: Could not find file \"$filename\" in \"$idtfilename\" !", "change_file_table"); }
1201 my $filehash = $merge_filetablehashref->{$filename};
1202 my $linenumber = $filehash->{'linenumber'};
1204 # <- this line has to be changed concerning "Sequence" and "Attributes"
1205 $filehash->{'Attributes'} = $attributes;
1207 # If this is an update process, the sequence numbers have to be reused.
1208 if ( $installer::globals::updatedatabase )
1210 if ( ! exists($allupdatesequenceshashref->{$filehash->{'File'}}) ) { installer::exiter::exit_program("ERROR: Sequence not defined for file \"$filehash->{'File'}\" !", "change_file_table"); }
1211 $filehash->{'Sequence'} = $allupdatesequenceshashref->{$filehash->{'File'}};
1212 # Saving all mergemodule sequence numbers. This is important for creating ddf files
1213 $installer::globals::allmergemodulefilesequences{$filehash->{'Sequence'}} = 1;
1215 else
1217 # Important saved data: $installer::globals::lastsequence_before_merge.
1218 # This mechanism keeps the correct order inside the new cabinet file.
1219 $filehash->{'Sequence'} = $filehash->{'Sequence'} + $installer::globals::lastsequence_before_merge;
1222 my $oldline = ${$filecontent}[$linenumber];
1223 my $newline = get_new_line_for_file_table($filehash);
1224 ${$filecontent}[$linenumber] = $newline;
1226 $infoline = "Merge, replacing line:\n";
1227 push( @installer::globals::logfileinfo, $infoline);
1228 $infoline = "Old: $oldline\n";
1229 push( @installer::globals::logfileinfo, $infoline);
1230 $infoline = "New: $newline\n";
1231 push( @installer::globals::logfileinfo, $infoline);
1233 # Adding files to the files collector (but only once)
1234 if (( $installer::globals::fix_number_of_cab_files ) && ( ! $installer::globals::mergefiles_added_into_collector ))
1236 # If the number of cabinet files is kept constant,
1237 # all files from the mergemodule cabinet files will
1238 # be integrated into the last office cabinet file
1239 # (installer::globals::lastcabfilename).
1240 # Therefore the files must now be added to the filescollector,
1241 # so that they will be integrated into the ddf files.
1243 # Problem with very long filenames -> copying to shorter filenames
1244 my $newfilename = "f" . $filehash->{'Sequence'};
1245 my $completesource = $unpackdir . $installer::globals::separator . $filehash->{'File'};
1246 my $completedest = $unpackdir . $installer::globals::separator . $newfilename;
1247 installer::systemactions::copy_one_file($completesource, $completedest);
1249 my $locallastcabfilename = $installer::globals::lastcabfilename;
1250 if ( $locallastcabfilename =~ /^\s*\#/ ) { $locallastcabfilename =~ s/^\s*\#//; } # removing beginning hashes
1252 # Create new file hash for file collector
1253 my %newfile = ();
1254 $newfile{'sequencenumber'} = $filehash->{'Sequence'};
1255 $newfile{'assignedsequencenumber'} = $filehash->{'Sequence'};
1256 $newfile{'cabinet'} = $locallastcabfilename;
1257 $newfile{'sourcepath'} = $completedest;
1258 $newfile{'componentname'} = $filehash->{'Component'};
1259 $newfile{'uniquename'} = $filehash->{'File'};
1260 $newfile{'Name'} = $filehash->{'File'};
1262 # Saving in globals sequence hash
1263 $installer::globals::uniquefilenamesequence{$filehash->{'File'}} = $filehash->{'Sequence'};
1265 if ( ! -f $newfile{'sourcepath'} ) { installer::exiter::exit_program("ERROR: File \"$newfile{'sourcepath'}\" must exist!", "change_file_table"); }
1267 # Collecting all new files. Attention: This files must be included into files collector in correct order!
1268 $newfileshash{$filehash->{'Sequence'}} = \%newfile;
1269 # push(@{$filesref}, \%newfile); -> this is not the correct order
1273 # Now the files can be added to the files collector
1274 # In the case of an update process, there can be new files, that have to be added after the merge module files.
1275 # Warning: In multilingual installation sets, the files only have to be added once to the files collector!
1277 if ( ! $installer::globals::mergefiles_added_into_collector )
1279 foreach my $localsequence ( sort { $a <=> $b } keys %newfileshash ) { push(@{$filesref}, $newfileshash{$localsequence}); }
1280 if ( $installer::globals::newfilesexist ) { $filesref = sort_files_collector_for_sequence($filesref); }
1281 # $installer::globals::mergefiles_added_into_collector = 1; -> Not yet. Only if all mergemodules are merged for one language.
1284 # Saving the idt file (for every language)
1285 installer::files::save_file($idtfilename, $filecontent);
1287 return $filesref;
1290 #########################################################################
1291 # Reading the file "Director.idt". The Directory, that is defined in scp
1292 # has to be defined in this table.
1293 #########################################################################
1295 sub collect_directories
1297 my $idtfilename = "Director.idt";
1298 my $filecontent = installer::files::read_file($idtfilename);
1300 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
1302 if ( $i <= 2 ) { next; } # ignoring first three lines
1303 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
1304 # Format: Directory Directory_Parent DefaultDir
1305 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\s*$/ )
1307 $installer::globals::merge_alldirectory_hash{$1} = 1;
1309 else
1311 my $linecount = $i + 1;
1312 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "collect_directories");
1317 #########################################################################
1318 # Reading the file "Feature.idt". The Feature, that is defined in scp
1319 # has to be defined in this table.
1320 #########################################################################
1322 sub collect_feature
1324 my $idtfilename = "Feature.idt";
1325 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "collect_feature"); }
1326 my $filecontent = installer::files::read_file($idtfilename);
1328 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
1330 if ( $i <= 2 ) { next; } # ignoring first three lines
1331 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
1332 # Format: Feature Feature_Parent Title Description Display Level Directory_ Attributes
1333 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
1335 $installer::globals::merge_allfeature_hash{$1} = 1;
1337 else
1339 my $linecount = $i + 1;
1340 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "collect_feature");
1345 #########################################################################
1346 # In the featurecomponent table, the new connections have to be added.
1347 #########################################################################
1349 sub change_featurecomponent_table
1351 my ($mergemodulehash, $workdir) = @_;
1353 my $infoline = "Changing content of table \"FeatureComponents\"\n";
1354 push( @installer::globals::logfileinfo, $infoline);
1356 my $idtfilename = "FeatureC.idt";
1357 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_featurecomponent_table"); }
1359 my $filecontent = installer::files::read_file($idtfilename);
1361 # Simply adding for each new component one line. The Feature has to be defined in scp project.
1362 my $feature = $mergemodulehash->{'feature'};
1364 if ( ! $installer::globals::mergefeaturecollected )
1366 collect_feature(); # putting content into hash %installer::globals::merge_allfeature_hash
1367 $installer::globals::mergefeaturecollected = 1;
1370 if ( ! exists($installer::globals::merge_allfeature_hash{$feature}) )
1372 installer::exiter::exit_program("ERROR: Unknown feature defined in scp: \"$feature\" . Not defined in table \"Feature\" !", "change_featurecomponent_table");
1375 my $component;
1376 foreach $component ( keys %{$mergemodulehash->{'componentnames'}} )
1378 my $line = "$feature\t$component\n";
1379 push(@{$filecontent}, $line);
1380 $infoline = "Adding line: $line\n";
1381 push( @installer::globals::logfileinfo, $infoline);
1384 # saving file
1385 installer::files::save_file($idtfilename, $filecontent);
1388 #########################################################################
1389 # In the directory table, the directory parent has to be changed,
1390 # if it is not TARGETDIR.
1391 #########################################################################
1393 sub change_directory_table
1395 my ($mergemodulehash, $workdir) = @_;
1397 # directory for MergeModule has to be defined in scp project
1398 my $scpdirectory = $mergemodulehash->{'rootdir'};
1400 if ( $scpdirectory ne "TARGETDIR" ) # TARGETDIR works fine, when using msidb.exe
1402 my $infoline = "Changing content of table \"Directory\"\n";
1403 push( @installer::globals::logfileinfo, $infoline);
1405 my $idtfilename = "Director.idt";
1406 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_directory_table"); }
1408 my $filecontent = installer::files::read_file($idtfilename);
1410 if ( ! $installer::globals::mergedirectoriescollected )
1412 collect_directories(); # putting content into %installer::globals::merge_alldirectory_hash, only first column!
1413 $installer::globals::mergedirectoriescollected = 1;
1416 if ( ! exists($installer::globals::merge_alldirectory_hash{$scpdirectory}) )
1418 installer::exiter::exit_program("ERROR: Unknown directory defined in scp: \"$scpdirectory\" . Not defined in table \"Directory\" !", "change_directory_table");
1421 # If the definition in scp is okay, now the complete content of "Director.idt" can be analyzed
1422 my $merge_directorytablehashref = analyze_directorytable_file($filecontent, $idtfilename);
1424 my $directory;
1425 foreach $directory (keys %{$mergemodulehash->{'mergedirectories'}} )
1427 if ( ! exists($merge_directorytablehashref->{$directory}) ) { installer::exiter::exit_program("ERROR: Could not find directory \"$directory\" in \"$idtfilename\" !", "change_directory_table"); }
1428 my $dirhash = $merge_directorytablehashref->{$directory};
1429 my $linenumber = $dirhash->{'linenumber'};
1431 # <- this line has to be changed concerning "Directory_Parent",
1432 # if the current value is "TARGETDIR", which is the default value from msidb.exe
1434 if ( $dirhash->{'Directory_Parent'} eq "TARGETDIR" )
1436 $dirhash->{'Directory_Parent'} = $scpdirectory;
1438 my $oldline = ${$filecontent}[$linenumber];
1439 my $newline = get_new_line_for_directory_table($dirhash);
1440 ${$filecontent}[$linenumber] = $newline;
1442 $infoline = "Merge, replacing line:\n";
1443 push( @installer::globals::logfileinfo, $infoline);
1444 $infoline = "Old: $oldline\n";
1445 push( @installer::globals::logfileinfo, $infoline);
1446 $infoline = "New: $newline\n";
1447 push( @installer::globals::logfileinfo, $infoline);
1451 # saving file
1452 installer::files::save_file($idtfilename, $filecontent);
1456 #########################################################################
1457 # In the msiassembly table, the feature has to be changed.
1458 #########################################################################
1460 sub change_msiassembly_table
1462 my ($mergemodulehash, $workdir) = @_;
1464 my $infoline = "Changing content of table \"MsiAssembly\"\n";
1465 push( @installer::globals::logfileinfo, $infoline);
1467 my $idtfilename = "MsiAssem.idt";
1468 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_msiassembly_table"); }
1470 my $filecontent = installer::files::read_file($idtfilename);
1472 # feature has to be defined in scp project
1473 my $feature = $mergemodulehash->{'feature'};
1475 if ( ! $installer::globals::mergefeaturecollected )
1477 collect_feature(); # putting content into hash %installer::globals::merge_allfeature_hash
1478 $installer::globals::mergefeaturecollected = 1;
1481 if ( ! exists($installer::globals::merge_allfeature_hash{$feature}) )
1483 installer::exiter::exit_program("ERROR: Unknown feature defined in scp: \"$feature\" . Not defined in table \"Feature\" !", "change_msiassembly_table");
1486 my $merge_msiassemblytablehashref = analyze_msiassemblytable_file($filecontent, $idtfilename);
1488 my $component;
1489 foreach $component (keys %{$mergemodulehash->{'mergeassemblies'}} )
1491 if ( ! exists($merge_msiassemblytablehashref->{$component}) ) { installer::exiter::exit_program("ERROR: Could not find component \"$component\" in \"$idtfilename\" !", "change_msiassembly_table"); }
1492 my $assemblyhash = $merge_msiassemblytablehashref->{$component};
1493 my $linenumber = $assemblyhash->{'linenumber'};
1495 # <- this line has to be changed concerning "Feature"
1496 $assemblyhash->{'Feature'} = $feature;
1498 my $oldline = ${$filecontent}[$linenumber];
1499 my $newline = get_new_line_for_msiassembly_table($assemblyhash);
1500 ${$filecontent}[$linenumber] = $newline;
1502 $infoline = "Merge, replacing line:\n";
1503 push( @installer::globals::logfileinfo, $infoline);
1504 $infoline = "Old: $oldline\n";
1505 push( @installer::globals::logfileinfo, $infoline);
1506 $infoline = "New: $newline\n";
1507 push( @installer::globals::logfileinfo, $infoline);
1510 # saving file
1511 installer::files::save_file($idtfilename, $filecontent);
1514 #########################################################################
1515 # Creating file content hash
1516 #########################################################################
1518 sub make_executeidtcontent_hash
1520 my ($filecontent, $idtfilename) = @_;
1522 my %newhash = ();
1524 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
1526 if ( $i <= 2 ) { next; } # ignoring first three lines
1527 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
1528 # Format for all sequence tables: Action Condition Sequence
1529 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\s*$/ )
1531 my %onehash = ();
1532 $onehash{'Action'} = $1;
1533 $onehash{'Condition'} = $2;
1534 $onehash{'Sequence'} = $3;
1535 $newhash{$onehash{'Action'}} = \%onehash;
1537 else
1539 my $linecount = $i + 1;
1540 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "make_executeidtcontent_hash");
1544 return \%newhash;
1547 #########################################################################
1548 # Creating file content hash
1549 #########################################################################
1551 sub make_moduleexecuteidtcontent_hash
1553 my ($filecontent, $idtfilename) = @_;
1555 my %newhash = ();
1557 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
1559 if ( $i <= 2 ) { next; } # ignoring first three lines
1560 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
1561 # Format for all module sequence tables: Action Sequence BaseAction After Condition
1562 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
1564 my %onehash = ();
1565 $onehash{'Action'} = $1;
1566 $onehash{'Sequence'} = $2;
1567 $onehash{'BaseAction'} = $3;
1568 $onehash{'After'} = $4;
1569 $onehash{'Condition'} = $5;
1570 $newhash{$onehash{'Action'}} = \%onehash;
1572 else
1574 my $linecount = $i + 1;
1575 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "make_executeidtcontent_hash");
1579 return \%newhash;
1582 #########################################################################
1583 # ExecuteSequence tables need to be merged with
1584 # ModuleExecuteSequence tables created by msidb.exe.
1585 #########################################################################
1587 sub change_executesequence_table
1589 my ($mergemodulehash, $workdir, $idtfilename, $moduleidtfilename) = @_;
1591 my $infoline = "Changing content of table \"$idtfilename\"\n";
1592 push( @installer::globals::logfileinfo, $infoline);
1594 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_executesequence_table"); }
1595 if ( ! -f $moduleidtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$moduleidtfilename\" in \"$workdir\" !", "change_executesequence_table"); }
1597 # Reading file content
1598 my $idtfilecontent = installer::files::read_file($idtfilename);
1599 my $moduleidtfilecontent = installer::files::read_file($moduleidtfilename);
1601 # Converting to hash
1602 my $idtcontenthash = make_executeidtcontent_hash($idtfilecontent, $idtfilename);
1603 my $moduleidtcontenthash = make_moduleexecuteidtcontent_hash($moduleidtfilecontent, $moduleidtfilename);
1605 # Merging
1606 foreach my $action ( keys %{$moduleidtcontenthash} )
1608 if ( exists($idtcontenthash->{$action}) ) { next; } # Action already exists, can be ignored
1610 if (( $idtfilename eq "InstallU.idt" ) && ( ! ( $action =~ /^\s*WindowsFolder\./ ))) { next; } # Only "WindowsFolder.*" CustomActions for UI Sequence table
1612 my $actionhashref = $moduleidtcontenthash->{$action};
1613 if ( $actionhashref->{'Sequence'} ne "" )
1615 # Format for all sequence tables: Action Condition Sequence
1616 my $newline = $actionhashref->{'Action'} . "\t" . $actionhashref->{'Condition'} . "\t" . $actionhashref->{'Sequence'} . "\n";
1617 # Adding to table
1618 push(@{$idtfilecontent}, $newline);
1619 # Also adding to hash
1620 my %idttablehash = ();
1621 $idttablehash{'Action'} = $actionhashref->{'Action'};
1622 $idttablehash{'Condition'} = $actionhashref->{'Condition'};
1623 $idttablehash{'Sequence'} = $actionhashref->{'Sequence'};
1624 $idtcontenthash->{$action} = \%idttablehash;
1627 else # no sequence defined, using syntax "BaseAction" and "After"
1629 my $baseactionname = $actionhashref->{'BaseAction'};
1630 # If this baseactionname is not defined in execute idt file, it is not possible to merge
1631 if ( ! exists($idtcontenthash->{$baseactionname}) ) { installer::exiter::exit_program("ERROR: Merge problem: Could not find action \"$baseactionname\" in file \"$idtfilename\" !", "change_executesequence_table"); }
1633 my $baseaction = $idtcontenthash->{$baseactionname};
1634 my $sequencenumber = $baseaction->{'Sequence'};
1635 if ( $actionhashref->{'After'} == 1 ) { $sequencenumber = $sequencenumber + 1; }
1636 else { $sequencenumber = $sequencenumber - 1; }
1638 # Format for all sequence tables: Action Condition Sequence
1639 my $newline = $actionhashref->{'Action'} . "\t" . $actionhashref->{'Condition'} . "\t" . $sequencenumber . "\n";
1640 # Adding to table
1641 push(@{$idtfilecontent}, $newline);
1642 # Also adding to hash
1643 my %idttablehash = ();
1644 $idttablehash{'Action'} = $actionhashref->{'Action'};
1645 $idttablehash{'Condition'} = $actionhashref->{'Condition'};
1646 $idttablehash{'Sequence'} = $sequencenumber;
1647 $idtcontenthash->{$action} = \%idttablehash;
1651 # saving file
1652 installer::files::save_file($idtfilename, $idtfilecontent);