Branch libreoffice-5-0-4
[LibreOffice.git] / solenv / bin / modules / installer / windows / mergemodule.pm
blob68bb203f1053576a676b25046bde184ececdabcd
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 Cwd;
22 use Digest::MD5;
23 use installer::converter;
24 use installer::exiter;
25 use installer::files;
26 use installer::globals;
27 use installer::logger;
28 use installer::pathanalyzer;
29 use installer::remover;
30 use installer::scriptitems;
31 use installer::systemactions;
32 use installer::worker;
33 use installer::windows::idtglobal;
34 use installer::windows::language;
36 #################################################################
37 # Merging the Windows MergeModules into the msi database.
38 #################################################################
40 sub merge_mergemodules_into_msi_database
42 my ($mergemodules, $filesref, $msifilename, $languagestringref, $allvariables, $includepatharrayref, $allupdatesequences, $allupdatelastsequences, $allupdatediskids) = @_;
44 my $domerge = 0;
45 if (( $#{$mergemodules} > -1 ) && ( ! $installer::globals::languagepack ) && ( ! $installer::globals::helppack )) { $domerge = 1; }
47 if ( $domerge )
49 installer::logger::include_header_into_logfile("Merging merge modules into msi database");
50 installer::logger::print_message( "... merging msm files into msi database ... \n" );
51 installer::logger::include_timestamp_into_logfile("\nPerformance Info: MergeModule into msi database, start");
53 my $msidb = "msidb.exe"; # Has to be in the path
54 my $cabinetfile = "MergeModule.CABinet"; # the name of each cabinet file in a merge file
55 my $infoline = "";
56 my $systemcall = "";
57 my $returnvalue = "";
59 # 1. Analyzing the MergeModule (has only to be done once)
60 # a. -> Extracting cabinet file: msidb.exe -d <msmfile> -x MergeModule.CABinet
61 # b. -> Number of files in cabinet file: msidb.exe -d <msmfile> -f <directory> -e File
62 # c. -> List of components: msidb.exe -d <msmfile> -f <directory> -e Component
64 if ( ! $installer::globals::mergemodules_analyzed )
66 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Analyzing MergeModules, start");
67 $infoline = "Analyzing all Merge Modules\n\n";
68 push( @installer::globals::logfileinfo, $infoline);
70 %installer::globals::mergemodules = ();
72 my $mergemoduledir = installer::systemactions::create_directories("mergefiles", $languagestringref);
74 my $mergemodule;
75 foreach $mergemodule ( @{$mergemodules} )
77 my $filename = $mergemodule->{'Name'};
78 my $mergefile = $ENV{'MSM_PATH'} . $filename;
80 if ( ! -f $mergefile ) { installer::exiter::exit_program("ERROR: msm file not found: $filename ($mergefile)!", "merge_mergemodules_into_msi_database"); }
81 my $completesource = $mergefile;
83 my $mergegid = $mergemodule->{'gid'};
84 my $workdir = $mergemoduledir . $installer::globals::separator . $mergegid;
85 if ( ! -d $workdir ) { installer::systemactions::create_directory($workdir); }
87 $infoline = "Analyzing Merge Module: $filename\n";
88 push( @installer::globals::logfileinfo, $infoline);
90 # copy msm file into working directory
91 my $completedest = $workdir . $installer::globals::separator . $filename;
92 installer::systemactions::copy_one_file($completesource, $completedest);
93 if ( ! -f $completedest ) { installer::exiter::exit_program("ERROR: msm file not found: $completedest !", "merge_mergemodules_into_msi_database"); }
95 # changing directory
96 my $from = cwd();
97 my $to = $workdir;
98 chdir($to);
100 # remove an existing cabinet file
101 if ( -f $cabinetfile ) { unlink($cabinetfile); }
103 # exclude cabinet file
104 $systemcall = $msidb . " -d " . $filename . " -x " . $cabinetfile;
105 $returnvalue = system($systemcall);
107 $infoline = "Systemcall: $systemcall\n";
108 push( @installer::globals::logfileinfo, $infoline);
110 if ($returnvalue)
112 $infoline = "ERROR: Could not execute $systemcall !\n";
113 push( @installer::globals::logfileinfo, $infoline);
114 installer::exiter::exit_program("ERROR: Could not extract cabinet file from merge file: $completedest !", "merge_mergemodules_into_msi_database");
116 else
118 $infoline = "Success: Executed $systemcall successfully!\n";
119 push( @installer::globals::logfileinfo, $infoline);
122 # exclude tables from mergefile
123 # Attention: All listed tables have to exist in the database. If they not exist, an error window pops up
124 # and the return value of msidb.exe is not zero. The error window makes it impossible to check the existence
125 # of a table with the help of the return value.
126 # Solution: Export of all tables by using "*" . Some tables must exist (File Component Directory), other
127 # tables do not need to exist (MsiAssembly).
129 if ( $^O =~ /cygwin/i ) {
130 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
131 my $localworkdir = $workdir;
132 $localworkdir =~ s/\//\\\\/g;
133 $systemcall = $msidb . " -d " . $filename . " -f " . $localworkdir . " -e \\\*";
135 else
137 $systemcall = $msidb . " -d " . $filename . " -f " . $workdir . " -e \*";
140 $returnvalue = system($systemcall);
142 $infoline = "Systemcall: $systemcall\n";
143 push( @installer::globals::logfileinfo, $infoline);
145 if ($returnvalue)
147 $infoline = "ERROR: Could not execute $systemcall !\n";
148 push( @installer::globals::logfileinfo, $infoline);
149 installer::exiter::exit_program("ERROR: Could not exclude tables from merge file: $completedest !", "merge_mergemodules_into_msi_database");
151 else
153 $infoline = "Success: Executed $systemcall successfully!\n";
154 push( @installer::globals::logfileinfo, $infoline);
157 # Determining files
158 my $idtfilename = "File.idt"; # must exist
159 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: File \"$idtfilename\" not found in directory \"$workdir\" !", "merge_mergemodules_into_msi_database"); }
160 my $filecontent = installer::files::read_file($idtfilename);
161 my @file_idt_content = ();
162 my $filecounter = 0;
163 my %mergefilesequence = ();
164 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
166 if ( $i <= 2 ) { next; } # ignoring first three lines
167 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
168 $filecounter++;
169 push(@file_idt_content, ${$filecontent}[$i]);
170 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.+?)\t(.*?)\t(.*?)\t(.*?)\t(\d+?)\s*$/ )
172 my $filename = $1;
173 my $filesequence = $8;
174 $mergefilesequence{$filename} = $filesequence;
176 else
178 my $linecount = $i + 1;
179 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "merge_mergemodules_into_msi_database");
183 # Determining components
184 $idtfilename = "Component.idt"; # must exist
185 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: File \"$idtfilename\" not found in directory \"$workdir\" !", "merge_mergemodules_into_msi_database"); }
186 $filecontent = installer::files::read_file($idtfilename);
187 my %componentnames = ();
188 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
190 if ( $i <= 2 ) { next; } # ignoring first three lines
191 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
192 if ( ${$filecontent}[$i] =~ /^\s*(\S+)\s+/ ) { $componentnames{$1} = 1; }
195 # Determining directories
196 $idtfilename = "Directory.idt"; # must exist
197 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: File \"$idtfilename\" not found in directory \"$workdir\" !", "merge_mergemodules_into_msi_database"); }
198 $filecontent = installer::files::read_file($idtfilename);
199 my %mergedirectories = ();
200 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
202 if ( $i <= 2 ) { next; } # ignoring first three lines
203 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
204 if ( ${$filecontent}[$i] =~ /^\s*(\S+)\s+/ ) { $mergedirectories{$1} = 1; }
207 # Determining assemblies
208 $idtfilename = "MsiAssembly.idt"; # does not need to exist
209 my $hasmsiassemblies = 0;
210 my %mergeassemblies = ();
211 if ( -f $idtfilename )
213 $filecontent = installer::files::read_file($idtfilename);
214 $hasmsiassemblies = 1;
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+/ ) { $mergeassemblies{$1} = 1; }
223 # It is possible, that other tables have to be checked here. This happens, if tables in the
224 # merge module have to know the "Feature" or the "Directory", under which the content of the
225 # msm file is integrated into the msi database.
227 # Determining name of cabinet file in installation set
228 my $cabfilename = $mergemodule->{'Cabfilename'};
229 if ( $cabfilename ) { installer::packagelist::resolve_packagevariables(\$cabfilename, $allvariables, 0); }
231 # Analyzing styles
232 # Flag REMOVE_FILE_TABLE is required for msvc9 Merge-Module, because otherwise msidb.exe
233 # fails during integration of msm file into msi database.
235 my $styles = "";
236 my $removefiletable = 0;
237 if ( $mergemodule->{'Styles'} ) { $styles = $mergemodule->{'Styles'}; }
238 if ( $styles =~ /\bREMOVE_FILE_TABLE\b/ ) { $removefiletable = 1; }
240 if ( $removefiletable )
242 my $removeworkdir = $workdir . $installer::globals::separator . "remove_file_idt";
243 if ( ! -d $removeworkdir ) { installer::systemactions::create_directory($removeworkdir); }
244 my $completeremovedest = $removeworkdir . $installer::globals::separator . $filename;
245 installer::systemactions::copy_one_file($completedest, $completeremovedest);
246 if ( ! -f $completeremovedest ) { installer::exiter::exit_program("ERROR: msm file not found: $completeremovedest !", "merge_mergemodules_into_msi_database"); }
248 # Unpacking msm file
249 if ( $^O =~ /cygwin/i ) {
250 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
251 my $localcompleteremovedest = $completeremovedest;
252 my $localremoveworkdir = $removeworkdir;
253 $localcompleteremovedest =~ s/\//\\\\/g;
254 $localremoveworkdir =~ s/\//\\\\/g;
255 $systemcall = $msidb . " -d " . $localcompleteremovedest . " -f " . $localremoveworkdir . " -e \\\*";
257 else
259 $systemcall = $msidb . " -d " . $completeremovedest . " -f " . $removeworkdir . " -e \*";
262 $returnvalue = system($systemcall);
264 my $idtfilename = $removeworkdir . $installer::globals::separator . "File.idt";
265 if ( -f $idtfilename ) { unlink $idtfilename; }
266 unlink $completeremovedest;
268 # Packing msm file without "File.idt"
269 if ( $^O =~ /cygwin/i ) {
270 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
271 my $localcompleteremovedest = $completeremovedest;
272 my $localremoveworkdir = $removeworkdir;
273 $localcompleteremovedest =~ s/\//\\\\/g;
274 $localremoveworkdir =~ s/\//\\\\/g;
275 $systemcall = $msidb . " -c -d " . $localcompleteremovedest . " -f " . $localremoveworkdir . " -i \\\*";
277 else
279 $systemcall = $msidb . " -c -d " . $completeremovedest . " -f " . $removeworkdir . " -i \*";
281 $returnvalue = system($systemcall);
283 # Using this msm file for merging
284 if ( -f $completeremovedest ) { $completedest = $completeremovedest; }
285 else { installer::exiter::exit_program("ERROR: Could not find msm file without File.idt: $completeremovedest !", "merge_mergemodules_into_msi_database"); }
288 # Saving MergeModule info
290 my %onemergemodulehash = ();
291 $onemergemodulehash{'mergefilepath'} = $completedest;
292 $onemergemodulehash{'workdir'} = $workdir;
293 $onemergemodulehash{'cabinetfile'} = $workdir . $installer::globals::separator . $cabinetfile;
294 $onemergemodulehash{'filenumber'} = $filecounter;
295 $onemergemodulehash{'componentnames'} = \%componentnames;
296 $onemergemodulehash{'componentcondition'} = $mergemodule->{'ComponentCondition'};
297 $onemergemodulehash{'cabfilename'} = $cabfilename;
298 $onemergemodulehash{'feature'} = $mergemodule->{'Feature'};
299 $onemergemodulehash{'rootdir'} = $mergemodule->{'RootDir'};
300 $onemergemodulehash{'name'} = $mergemodule->{'Name'};
301 $onemergemodulehash{'mergefilesequence'} = \%mergefilesequence;
302 $onemergemodulehash{'mergeassemblies'} = \%mergeassemblies;
303 $onemergemodulehash{'mergedirectories'} = \%mergedirectories;
304 $onemergemodulehash{'hasmsiassemblies'} = $hasmsiassemblies;
305 $onemergemodulehash{'removefiletable'} = $removefiletable;
306 $onemergemodulehash{'fileidtcontent'} = \@file_idt_content;
308 $installer::globals::mergemodules{$mergegid} = \%onemergemodulehash;
310 # Collecting all cab files, to copy them into installation set
311 if ( $cabfilename ) { $installer::globals::copy_msm_files{$cabfilename} = $onemergemodulehash{'cabinetfile'}; }
313 chdir($from);
316 $infoline = "All Merge Modules successfully analyzed\n";
317 push( @installer::globals::logfileinfo, $infoline);
319 $installer::globals::mergemodules_analyzed = 1;
320 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Analyzing MergeModules, stop");
322 $infoline = "\n";
323 push( @installer::globals::logfileinfo, $infoline);
326 # 2. Change msi database (has to be done for every msi database -> for every language)
327 # a. Merge msm file into msi database: msidb.exe -d <msifile> -m <mergefile>
328 # b. Extracting tables from msi database: msidb.exe -d <msifile> -f <directory> -e File Media, ...
329 # c. Changing content of msi database in tables: File, Media, Directory, FeatureComponent
330 # d. Including tables into msi database: msidb.exe -d <msifile> -f <directory> -i File Media, ...
331 # e. Copying cabinet file into installation set (later)
333 my $counter = 0;
334 my $mergemodulegid;
335 foreach $mergemodulegid (keys %installer::globals::mergemodules)
337 my $mergemodulehash = $installer::globals::mergemodules{$mergemodulegid};
338 $counter++;
340 installer::logger::include_header_into_logfile("Merging Module: $mergemodulehash->{'name'}");
341 installer::logger::print_message( "\t... $mergemodulehash->{'name'} ... \n" );
343 $msifilename = installer::converter::make_path_conform($msifilename);
344 my $workdir = $msifilename;
345 installer::pathanalyzer::get_path_from_fullqualifiedname(\$workdir);
347 # changing directory
348 my $from = cwd();
349 my $to = $workdir;
350 chdir($to);
352 # Saving original msi database
353 installer::systemactions::copy_one_file($msifilename, "$msifilename\.$counter");
355 # Merging msm file, this is the "real" merge command
357 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Before merging database");
359 if ( $^O =~ /cygwin/i ) {
360 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
361 my $localmergemodulepath = $mergemodulehash->{'mergefilepath'};
362 my $localmsifilename = $msifilename;
363 $localmergemodulepath =~ s/\//\\\\/g;
364 $localmsifilename =~ s/\//\\\\/g;
365 $systemcall = $msidb . " -d " . $localmsifilename . " -m " . $localmergemodulepath;
367 else
369 $systemcall = $msidb . " -d " . $msifilename . " -m " . $mergemodulehash->{'mergefilepath'};
371 $returnvalue = system($systemcall);
373 $infoline = "Systemcall: $systemcall\n";
374 push( @installer::globals::logfileinfo, $infoline);
376 if ($returnvalue)
378 $infoline = "ERROR: Could not execute $systemcall . Returnvalue: $returnvalue!\n";
379 push( @installer::globals::logfileinfo, $infoline);
380 installer::exiter::exit_program("Could not merge msm file into database: $mergemodulehash->{'mergefilepath'}\n$infoline", "merge_mergemodules_into_msi_database");
382 else
384 $infoline = "Success: Executed $systemcall successfully!\n";
385 push( @installer::globals::logfileinfo, $infoline);
388 installer::logger::include_timestamp_into_logfile("\nPerformance Info: After merging database");
390 # Saving original idt files
391 if ( -f "File.idt" ) { installer::systemactions::rename_one_file("File.idt", "old.File.idt.$counter"); }
392 if ( -f "Media.idt" ) { installer::systemactions::rename_one_file("Media.idt", "old.Media.idt.$counter"); }
393 if ( -f "Directory.idt" ) { installer::systemactions::rename_one_file("Directory.idt", "old.Directory.idt.$counter"); }
394 if ( -f "Director.idt" ) { installer::systemactions::rename_one_file("Director.idt", "old.Director.idt.$counter"); }
395 if ( -f "FeatureComponents.idt" ) { installer::systemactions::rename_one_file("FeatureComponents.idt", "old.FeatureComponents.idt.$counter"); }
396 if ( -f "FeatureC.idt" ) { installer::systemactions::rename_one_file("FeatureC.idt", "old.FeatureC.idt.$counter"); }
397 if ( -f "MsiAssembly.idt" ) { installer::systemactions::rename_one_file("MsiAssembly.idt", "old.MsiAssembly.idt.$counter"); }
398 if ( -f "MsiAssem.idt" ) { installer::systemactions::rename_one_file("MsiAssem.idt", "old.MsiAssem.idt.$counter"); }
399 if ( -f "Componen.idt" ) { installer::systemactions::rename_one_file("Componen.idt", "old.Componen.idt.$counter"); }
401 # Extracting tables
403 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Before extracting tables");
405 my $workingtables = "File Media Directory FeatureComponents"; # required tables
406 # Optional tables can be added now
407 if ( $mergemodulehash->{'hasmsiassemblies'} ) { $workingtables = $workingtables . " MsiAssembly"; }
408 if ( $mergemodulehash->{'componentcondition'} ) { $workingtables = $workingtables . " Component"; }
410 # Table "Feature" has to be exported, but it is not necessary to import it.
411 if ( $^O =~ /cygwin/i ) {
412 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
413 my $localmsifilename = $msifilename;
414 my $localworkdir = $workdir;
415 $localmsifilename =~ s/\//\\\\/g;
416 $localworkdir =~ s/\//\\\\/g;
417 $systemcall = $msidb . " -d " . $localmsifilename . " -f " . $localworkdir . " -e " . "Feature " . $workingtables;
419 else
421 $systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -e " . "Feature " . $workingtables;
423 $returnvalue = system($systemcall);
425 $infoline = "Systemcall: $systemcall\n";
426 push( @installer::globals::logfileinfo, $infoline);
428 if ($returnvalue)
430 $infoline = "ERROR: Could not execute $systemcall !\n";
431 push( @installer::globals::logfileinfo, $infoline);
432 installer::exiter::exit_program("ERROR: Could not exclude tables from msi database: $msifilename !", "merge_mergemodules_into_msi_database");
434 else
436 $infoline = "Success: Executed $systemcall successfully!\n";
437 push( @installer::globals::logfileinfo, $infoline);
440 installer::logger::include_timestamp_into_logfile("\nPerformance Info: After extracting tables");
442 # Using 8+3 table names, that are used, when tables are integrated into database. The export of tables
443 # creates idt-files, that have long names.
445 if ( -f "Directory.idt" ) { installer::systemactions::rename_one_file("Directory.idt", "Director.idt"); }
446 if ( -f "FeatureComponents.idt" ) { installer::systemactions::rename_one_file("FeatureComponents.idt", "FeatureC.idt"); }
447 if ( -f "MsiAssembly.idt" ) { installer::systemactions::rename_one_file("MsiAssembly.idt", "MsiAssem.idt"); }
448 if ( -f "Component.idt" ) { installer::systemactions::rename_one_file("Component.idt", "Componen.idt"); }
450 # Changing content of tables: File, Media, Directory, FeatureComponent, MsiAssembly, Component
451 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing Media table");
452 change_media_table($mergemodulehash, $workdir, $mergemodulegid, $allupdatelastsequences, $allupdatediskids);
453 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing File table");
454 $filesref = change_file_table($mergemodulehash, $workdir, $allupdatesequences, $includepatharrayref, $filesref, $mergemodulegid);
455 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing FeatureComponent table");
456 change_featurecomponent_table($mergemodulehash, $workdir);
457 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing Directory table");
458 change_directory_table($mergemodulehash, $workdir);
459 if ( $mergemodulehash->{'hasmsiassemblies'} )
461 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing MsiAssembly table");
462 change_msiassembly_table($mergemodulehash, $workdir);
465 if ( $mergemodulehash->{'componentcondition'} )
467 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing Component table");
468 change_component_table($mergemodulehash, $workdir);
471 # msidb.exe does not merge InstallExecuteSequence, AdminExecuteSequence and AdvtExecuteSequence. Instead it creates
472 # new tables ModuleInstallExecuteSequence, ModuleAdminExecuteSequence and ModuleAdvtExecuteSequence that need to be
473 # merged into the three ExecuteSequences with the following process (also into InstallUISequence.idt).
475 # Saving original idt files
476 if ( -f "InstallE.idt" ) { installer::systemactions::rename_one_file("InstallE.idt", "old.InstallE.idt.$counter"); }
477 if ( -f "InstallU.idt" ) { installer::systemactions::rename_one_file("InstallU.idt", "old.InstallU.idt.$counter"); }
478 if ( -f "AdminExe.idt" ) { installer::systemactions::rename_one_file("AdminExe.idt", "old.AdminExe.idt.$counter"); }
479 if ( -f "AdvtExec.idt" ) { installer::systemactions::rename_one_file("AdvtExec.idt", "old.AdvtExec.idt.$counter"); }
480 if ( -f "ModuleInstallExecuteSequence.idt" ) { installer::systemactions::rename_one_file("ModuleInstallExecuteSequence.idt", "old.ModuleInstallExecuteSequence.idt.$counter"); }
481 if ( -f "ModuleAdminExecuteSequence.idt" ) { installer::systemactions::rename_one_file("ModuleAdminExecuteSequence.idt", "old.ModuleAdminExecuteSequence.idt.$counter"); }
482 if ( -f "ModuleAdvtExecuteSequence.idt" ) { installer::systemactions::rename_one_file("ModuleAdvtExecuteSequence.idt", "old.ModuleAdvtExecuteSequence.idt.$counter"); }
484 # Extracting tables
485 my $moduleexecutetables = "ModuleInstallExecuteSequence ModuleAdminExecuteSequence ModuleAdvtExecuteSequence"; # new tables
486 my $executetables = "InstallExecuteSequence InstallUISequence AdminExecuteSequence AdvtExecuteSequence"; # tables to be merged
489 if ( $^O =~ /cygwin/i ) {
490 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
491 my $localmsifilename = $msifilename;
492 my $localworkdir = $workdir;
493 $localmsifilename =~ s/\//\\\\/g;
494 $localworkdir =~ s/\//\\\\/g;
495 $systemcall = $msidb . " -d " . $localmsifilename . " -f " . $localworkdir . " -e " . "Feature " . $moduleexecutetables;
497 else
499 $systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -e " . "Feature " . $moduleexecutetables;
501 $returnvalue = system($systemcall);
503 if ( $^O =~ /cygwin/i ) {
504 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
505 my $localmsifilename = $msifilename;
506 my $localworkdir = $workdir;
507 $localmsifilename =~ s/\//\\\\/g;
508 $localworkdir =~ s/\//\\\\/g;
509 $systemcall = $msidb . " -d " . $localmsifilename . " -f " . $localworkdir . " -e " . "Feature " . $executetables;
511 else
513 $systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -e " . "Feature " . $executetables;
515 $returnvalue = system($systemcall);
517 # Using 8+3 table names, that are used, when tables are integrated into database. The export of tables
518 # creates idt-files, that have long names.
520 if ( -f "InstallExecuteSequence.idt" ) { installer::systemactions::rename_one_file("InstallExecuteSequence.idt", "InstallE.idt"); }
521 if ( -f "InstallUISequence.idt" ) { installer::systemactions::rename_one_file("InstallUISequence.idt", "InstallU.idt"); }
522 if ( -f "AdminExecuteSequence.idt" ) { installer::systemactions::rename_one_file("AdminExecuteSequence.idt", "AdminExe.idt"); }
523 if ( -f "AdvtExecuteSequence.idt" ) { installer::systemactions::rename_one_file("AdvtExecuteSequence.idt", "AdvtExec.idt"); }
525 # Merging content of tables ModuleInstallExecuteSequence, ModuleAdminExecuteSequence and ModuleAdvtExecuteSequence
526 # into tables InstallExecuteSequence, AdminExecuteSequence and AdvtExecuteSequence
527 if ( -f "ModuleInstallExecuteSequence.idt" )
529 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing InstallExecuteSequence table");
530 change_executesequence_table($mergemodulehash, $workdir, "InstallE.idt", "ModuleInstallExecuteSequence.idt");
531 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing InstallUISequence table");
532 change_executesequence_table($mergemodulehash, $workdir, "InstallU.idt", "ModuleInstallExecuteSequence.idt");
535 if ( -f "ModuleAdminExecuteSequence.idt" )
537 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing AdminExecuteSequence table");
538 change_executesequence_table($mergemodulehash, $workdir, "AdminExe.idt", "ModuleAdminExecuteSequence.idt");
541 if ( -f "ModuleAdvtExecuteSequence.idt" )
543 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing AdvtExecuteSequence table");
544 change_executesequence_table($mergemodulehash, $workdir, "AdvtExec.idt", "ModuleAdvtExecuteSequence.idt");
547 installer::logger::include_timestamp_into_logfile("\nPerformance Info: All tables edited");
549 # Including tables into msi database
551 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Before including tables");
553 if ( $^O =~ /cygwin/i ) {
554 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
555 my $localmsifilename = $msifilename;
556 my $localworkdir = $workdir;
557 $localmsifilename =~ s/\//\\\\/g;
558 $localworkdir =~ s/\//\\\\/g;
559 foreach $table (split / /, $workingtables . ' ' . $executetables) {
560 $systemcall = $msidb . " -d " . $localmsifilename . " -f " . $localworkdir . " -i " . $table;
561 my $retval = system($systemcall);
562 $infoline = "Systemcall returned $retval: $systemcall\n";
563 push( @installer::globals::logfileinfo, $infoline);
564 $returnvalue |= $retval;
567 else
569 $systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -i " . $workingtables. " " . $executetables;
570 $returnvalue = system($systemcall);
571 $infoline = "Systemcall: $systemcall\n";
572 push( @installer::globals::logfileinfo, $infoline);
576 if ($returnvalue)
578 $infoline = "ERROR: Could not execute $systemcall !\n";
579 push( @installer::globals::logfileinfo, $infoline);
580 installer::exiter::exit_program("ERROR: Could not include tables into msi database: $msifilename !", "merge_mergemodules_into_msi_database");
582 else
584 $infoline = "Success: Executed $systemcall successfully!\n";
585 push( @installer::globals::logfileinfo, $infoline);
588 installer::logger::include_timestamp_into_logfile("\nPerformance Info: After including tables");
590 chdir($from);
593 if ( ! $installer::globals::mergefiles_added_into_collector ) { $installer::globals::mergefiles_added_into_collector = 1; } # Now all mergemodules are merged for one language.
595 installer::logger::include_timestamp_into_logfile("\nPerformance Info: MergeModule into msi database, stop");
598 return $filesref;
601 #########################################################################
602 # Analyzing the content of the media table.
603 #########################################################################
605 sub analyze_media_file
607 my ($filecontent, $workdir) = @_;
609 my %filehash = ();
610 my $linecount = 0;
611 my $counter = 0;
612 my $filename = "Media.idt";
614 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
616 if ( $i <= 2 ) { next; } # ignoring first three lines
617 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
618 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.+?)\t(.+?)\t(.*?)\s*$/ )
620 my %line = ();
621 # Format: DiskId LastSequence DiskPrompt Cabinet VolumeLabel Source
622 $line{'DiskId'} = $1;
623 $line{'LastSequence'} = $2;
624 $line{'DiskPrompt'} = $3;
625 $line{'Cabinet'} = $4;
626 $line{'VolumeLabel'} = $5;
627 $line{'Source'} = $6;
629 $counter++;
630 $filehash{$counter} = \%line;
632 else
634 $linecount = $i + 1;
635 installer::exiter::exit_program("ERROR: Unknown line format in table \"$filename\" in \"$workdir\" (line $linecount) !", "analyze_media_file");
639 return \%filehash;
642 #########################################################################
643 # Setting the DiskID for the new cabinet file
644 #########################################################################
646 sub get_diskid
648 my ($mediafile, $allupdatediskids, $cabfilename) = @_;
650 my $diskid = 0;
651 my $line;
653 if (( $installer::globals::updatedatabase ) && ( exists($allupdatediskids->{$cabfilename}) ))
655 $diskid = $allupdatediskids->{$cabfilename};
657 else
659 foreach $line ( keys %{$mediafile} )
661 if ( $mediafile->{$line}->{'DiskId'} > $diskid ) { $diskid = $mediafile->{$line}->{'DiskId'}; }
664 $diskid++;
667 return $diskid;
670 #########################################################################
671 # Setting the global LastSequence variable
672 #########################################################################
674 sub set_current_last_sequence
676 my ($mediafile) = @_;
678 my $lastsequence = 0;
679 my $line;
680 foreach $line ( keys %{$mediafile} )
682 if ( $mediafile->{$line}->{'LastSequence'} > $lastsequence ) { $lastsequence = $mediafile->{$line}->{'LastSequence'}; }
685 $installer::globals::lastsequence_before_merge = $lastsequence;
688 #########################################################################
689 # Setting the LastSequence for the new cabinet file
690 #########################################################################
692 sub get_lastsequence
694 my ($mergemodulehash, $allupdatelastsequences) = @_;
696 my $lastsequence = 0;
698 if (( $installer::globals::updatedatabase ) && ( exists($allupdatelastsequences->{$mergemodulehash->{'cabfilename'}}) ))
700 $lastsequence = $allupdatelastsequences->{$mergemodulehash->{'cabfilename'}};
702 else
704 $lastsequence = $installer::globals::lastsequence_before_merge + $mergemodulehash->{'filenumber'};
707 return $lastsequence;
710 #########################################################################
711 # Setting the DiskPrompt for the new cabinet file
712 #########################################################################
714 sub get_diskprompt
716 my ($mediafile) = @_;
718 my $diskprompt = "";
719 my $line;
720 foreach $line ( keys %{$mediafile} )
722 if ( exists($mediafile->{$line}->{'DiskPrompt'}) )
724 $diskprompt = $mediafile->{$line}->{'DiskPrompt'};
725 last;
729 return $diskprompt;
732 #########################################################################
733 # Setting the VolumeLabel for the new cabinet file
734 #########################################################################
736 sub get_volumelabel
738 my ($mediafile) = @_;
740 my $volumelabel = "";
741 my $line;
742 foreach $line ( keys %{$mediafile} )
744 if ( exists($mediafile->{$line}->{'VolumeLabel'}) )
746 $volumelabel = $mediafile->{$line}->{'VolumeLabel'};
747 last;
751 return $volumelabel;
754 #########################################################################
755 # Setting the Source for the new cabinet file
756 #########################################################################
758 sub get_source
760 my ($mediafile) = @_;
762 my $source = "";
763 my $line;
764 foreach $line ( keys %{$mediafile} )
766 if ( exists($mediafile->{$line}->{'Source'}) )
768 $diskprompt = $mediafile->{$line}->{'Source'};
769 last;
773 return $source;
776 #########################################################################
777 # For each Merge Module one new line has to be included into the
778 # media table.
779 #########################################################################
781 sub create_new_media_line
783 my ($mergemodulehash, $mediafile, $allupdatelastsequences, $allupdatediskids) = @_;
785 my $diskid = get_diskid($mediafile, $allupdatediskids, $mergemodulehash->{'cabfilename'});
786 my $lastsequence = get_lastsequence($mergemodulehash, $allupdatelastsequences);
787 my $diskprompt = get_diskprompt($mediafile);
788 my $cabinet = $mergemodulehash->{'cabfilename'};
789 my $volumelabel = get_volumelabel($mediafile);
790 my $source = get_source($mediafile);
792 if ( $installer::globals::include_cab_in_msi ) { $cabinet = "\#" . $cabinet; }
794 my $newline = "$diskid\t$lastsequence\t$diskprompt\t$cabinet\t$volumelabel\t$source\n";
796 return $newline;
799 #########################################################################
800 # Setting the last diskid in media table.
801 #########################################################################
803 sub get_last_diskid
805 my ($mediafile) = @_;
807 my $lastdiskid = 0;
808 my $line;
809 foreach $line ( keys %{$mediafile} )
811 if ( $mediafile->{$line}->{'DiskId'} > $lastdiskid ) { $lastdiskid = $mediafile->{$line}->{'DiskId'}; }
814 return $lastdiskid;
817 #########################################################################
818 # Setting global variable for last cab file name.
819 #########################################################################
821 sub set_last_cabfile_name
823 my ($mediafile, $lastdiskid) = @_;
825 my $line;
826 foreach $line ( keys %{$mediafile} )
828 if ( $mediafile->{$line}->{'DiskId'} == $lastdiskid ) { $installer::globals::lastcabfilename = $mediafile->{$line}->{'Cabinet'}; }
830 my $infoline = "Setting last cabinet file: $installer::globals::lastcabfilename\n";
831 push( @installer::globals::logfileinfo, $infoline);
834 #########################################################################
835 # In the media table the new cabinet file has to be added or the
836 # number of the last cabinet file has to be increased.
837 #########################################################################
839 sub change_media_table
841 my ( $mergemodulehash, $workdir, $mergemodulegid, $allupdatelastsequences, $allupdatediskids ) = @_;
843 my $infoline = "Changing content of table \"Media\"\n";
844 push( @installer::globals::logfileinfo, $infoline);
846 my $filename = "Media.idt";
847 if ( ! -f $filename ) { installer::exiter::exit_program("ERROR: Could not find file \"$filename\" in \"$workdir\" !", "change_media_table"); }
849 my $filecontent = installer::files::read_file($filename);
850 my $mediafile = analyze_media_file($filecontent, $workdir);
851 set_current_last_sequence($mediafile);
853 if ( $installer::globals::fix_number_of_cab_files )
855 # Determining the line with the highest sequencenumber. That file needs to be updated.
856 my $lastdiskid = get_last_diskid($mediafile);
857 if ( $installer::globals::lastcabfilename eq "" ) { set_last_cabfile_name($mediafile, $lastdiskid); }
858 my $newmaxsequencenumber = $installer::globals::lastsequence_before_merge + $mergemodulehash->{'filenumber'};
860 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
862 if ( $i <= 2 ) { next; } # ignoring first three lines
863 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
864 if ( ${$filecontent}[$i] =~ /^\s*(\Q$lastdiskid\E\t)\Q$installer::globals::lastsequence_before_merge\E(\t.*)$/ )
866 my $start = $1;
867 my $final = $2;
868 $infoline = "Merge: Old line in media table: ${$filecontent}[$i]\n";
869 push( @installer::globals::logfileinfo, $infoline);
870 my $newline = $start . $newmaxsequencenumber . $final . "\n";
871 ${$filecontent}[$i] = $newline;
872 $infoline = "Merge: Changed line in media table: ${$filecontent}[$i]\n";
873 push( @installer::globals::logfileinfo, $infoline);
877 else
879 # the new line is identical for all localized databases, but has to be created for each MergeModule ($mergemodulegid)
880 if ( ! exists($installer::globals::merge_media_line{$mergemodulegid}) )
882 $installer::globals::merge_media_line{$mergemodulegid} = create_new_media_line($mergemodulehash, $mediafile, $allupdatelastsequences, $allupdatediskids);
885 $infoline = "Adding line: $installer::globals::merge_media_line{$mergemodulegid}\n";
886 push( @installer::globals::logfileinfo, $infoline);
888 # adding new line
889 push(@{$filecontent}, $installer::globals::merge_media_line{$mergemodulegid});
892 # saving file
893 installer::files::save_file($filename, $filecontent);
896 #########################################################################
897 # Putting the directory table content into a hash.
898 #########################################################################
900 sub analyze_directorytable_file
902 my ($filecontent, $idtfilename) = @_;
904 my %dirhash = ();
905 # Iterating over the file content
906 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
908 if ( $i <= 2 ) { next; } # ignoring first three lines
909 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
910 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\s*$/ )
912 my %line = ();
913 # Format: Directory Directory_Parent DefaultDir
914 $line{'Directory'} = $1;
915 $line{'Directory_Parent'} = $2;
916 $line{'DefaultDir'} = $3;
917 $line{'linenumber'} = $i; # saving also the line number for direct access
919 my $uniquekey = $line{'Directory'};
920 $dirhash{$uniquekey} = \%line;
922 else
924 my $linecount = $i + 1;
925 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "analyze_directorytable_file");
929 return \%dirhash;
932 #########################################################################
933 # Putting the msi assembly table content into a hash.
934 #########################################################################
936 sub analyze_msiassemblytable_file
938 my ($filecontent, $idtfilename) = @_;
940 my %assemblyhash = ();
941 # Iterating over the file content
942 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
944 if ( $i <= 2 ) { next; } # ignoring first three lines
945 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
946 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.*?)\t(.*?)\s*$/ )
948 my %line = ();
949 # Format: Component_ Feature_ File_Manifest File_Application Attributes
950 $line{'Component'} = $1;
951 $line{'Feature'} = $2;
952 $line{'File_Manifest'} = $3;
953 $line{'File_Application'} = $4;
954 $line{'Attributes'} = $5;
955 $line{'linenumber'} = $i; # saving also the line number for direct access
957 my $uniquekey = $line{'Component'};
958 $assemblyhash{$uniquekey} = \%line;
960 else
962 my $linecount = $i + 1;
963 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "analyze_msiassemblytable_file");
967 return \%assemblyhash;
970 #########################################################################
971 # Putting the file table content into a hash.
972 #########################################################################
974 sub analyze_filetable_file
976 my ( $filecontent, $idtfilename ) = @_;
978 my %filehash = ();
979 # Iterating over the file content
980 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
982 if ( $i <= 2 ) { next; } # ignoring first three lines
983 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
984 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.+?)\s*$/ )
986 my %line = ();
987 # Format: File Component_ FileName FileSize Version Language Attributes Sequence
988 $line{'File'} = $1;
989 $line{'Component'} = $2;
990 $line{'FileName'} = $3;
991 $line{'FileSize'} = $4;
992 $line{'Version'} = $5;
993 $line{'Language'} = $6;
994 $line{'Attributes'} = $7;
995 $line{'Sequence'} = $8;
996 $line{'linenumber'} = $i; # saving also the line number for direct access
998 my $uniquekey = $line{'File'};
999 $filehash{$uniquekey} = \%line;
1001 else
1003 my $linecount = $i + 1;
1004 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "analyze_filetable_file");
1008 return \%filehash;
1011 #########################################################################
1012 # Creating a new line for the directory table.
1013 #########################################################################
1015 sub get_new_line_for_directory_table
1017 my ($dir) = @_;
1019 my $newline = "$dir->{'Directory'}\t$dir->{'Directory_Parent'}\t$dir->{'DefaultDir'}\n";
1021 return $newline;
1024 #########################################################################
1025 # Creating a new line for the file table.
1026 #########################################################################
1028 sub get_new_line_for_file_table
1030 my ($file) = @_;
1032 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";
1034 return $newline;
1037 #########################################################################
1038 # Creating a new line for the msiassembly table.
1039 #########################################################################
1041 sub get_new_line_for_msiassembly_table
1043 my ($assembly) = @_;
1045 my $newline = "$assembly->{'Component'}\t$assembly->{'Feature'}\t$assembly->{'File_Manifest'}\t$assembly->{'File_Application'}\t$assembly->{'Attributes'}\n";
1047 return $newline;
1050 #########################################################################
1051 # Sorting the files collector, if there are files, following
1052 # the merge module files.
1053 #########################################################################
1055 sub sort_files_collector_for_sequence
1057 my ($filesref) = @_;
1059 my @sortarray = ();
1060 my %helphash = ();
1062 for ( my $i = 0; $i <= $#{$filesref}; $i++ )
1064 my $onefile = ${$filesref}[$i];
1065 if ( ! exists($onefile->{'sequencenumber'}) ) { installer::exiter::exit_program("ERROR: Could not find sequencenumber for file: $onefile->{'uniquename'} !", "sort_files_collector_for_sequence"); }
1066 my $sequence = $onefile->{'sequencenumber'};
1067 $helphash{$sequence} = $onefile;
1070 foreach my $seq ( sort { $a <=> $b } keys %helphash ) { push(@sortarray, $helphash{$seq}); }
1072 return \@sortarray;
1075 #########################################################################
1076 # In the file table "Sequence" and "Attributes" have to be changed.
1077 #########################################################################
1079 sub change_file_table
1081 my ($mergemodulehash, $workdir, $allupdatesequenceshashref, $includepatharrayref, $filesref, $mergemodulegid) = @_;
1083 my $infoline = "Changing content of table \"File\"\n";
1084 push( @installer::globals::logfileinfo, $infoline);
1086 my $idtfilename = "File.idt";
1087 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_file_table"); }
1089 my $filecontent = installer::files::read_file($idtfilename);
1091 # If File.idt needed to be removed before the msm database was merged into the msi database,
1092 # now it is time to add the content into File.idt
1093 if ( $mergemodulehash->{'removefiletable'} )
1095 for ( my $i = 0; $i <= $#{$mergemodulehash->{'fileidtcontent'}}; $i++ )
1097 push(@{$filecontent}, ${$mergemodulehash->{'fileidtcontent'}}[$i]);
1101 # Unpacking the MergeModule.CABinet (only once)
1102 # Unpacking into temp directory. Warning: expand.exe has problems with very long unpack directories.
1104 my $empty = "";
1105 my $unpackdir = installer::systemactions::create_directories("cab", \$empty);
1106 push(@installer::globals::removedirs, $unpackdir);
1107 $unpackdir = $unpackdir . $installer::globals::separator . $mergemodulegid;
1109 my %newfileshash = ();
1110 if (( $installer::globals::fix_number_of_cab_files ) && ( ! $installer::globals::mergefiles_added_into_collector ))
1112 if ( ! -d $unpackdir ) { installer::systemactions::create_directory($unpackdir); }
1114 # changing directory
1115 my $from = cwd();
1116 my $to = $mergemodulehash->{'workdir'};
1117 if ( $^O =~ /cygwin/i ) {
1118 $to = qx(cygpath -u "$to");
1119 chomp $to;
1122 chdir($to) || die "Could not chdir to \"$to\"\n";
1124 # Unpack the cab file, so that in can be included into the last office cabinet file.
1125 # Not using cabarc.exe from cabsdk for unpacking cabinet files, but "expand.exe" that
1126 # should be available on every Windows system.
1128 $infoline = "Unpacking cabinet file: $mergemodulehash->{'cabinetfile'}\n";
1129 push( @installer::globals::logfileinfo, $infoline);
1131 # Avoid the Cygwin expand command
1132 my $expandfile = "expand.exe"; # Has to be in the path
1133 if ( $^O =~ /cygwin/i ) {
1134 $expandfile = qx(cygpath -u "$ENV{WINDIR}"/System32/expand.exe);
1135 chomp $expandfile;
1138 my $cabfilename = "MergeModule.CABinet";
1140 my $systemcall = "";
1141 if ( $^O =~ /cygwin/i ) {
1142 my $localunpackdir = qx(cygpath -m "$unpackdir");
1143 chomp $localunpackdir;
1144 $systemcall = $expandfile . " " . $cabfilename . " -F:\\\* " . $localunpackdir;
1146 else
1148 $systemcall = $expandfile . " " . $cabfilename . " -F:\* " . $unpackdir . " 2\>\&1";
1151 my $returnvalue = system($systemcall);
1153 $infoline = "Systemcall: $systemcall\n";
1154 push( @installer::globals::logfileinfo, $infoline);
1156 if ($returnvalue)
1158 $infoline = "ERROR: Could not execute $systemcall !\n";
1159 push( @installer::globals::logfileinfo, $infoline);
1160 installer::exiter::exit_program("ERROR: Could not extract cabinet file: $mergemodulehash->{'cabinetfile'} !", "change_file_table");
1162 else
1164 $infoline = "Success: Executed $systemcall successfully!\n";
1165 push( @installer::globals::logfileinfo, $infoline);
1168 chdir($from);
1171 # For performance reasons creating a hash with file names and rows
1172 # The content of File.idt is changed after every merge -> content cannot be saved in global hash
1173 $merge_filetablehashref = analyze_filetable_file($filecontent, $idtfilename);
1175 my $attributes = "16384"; # Always
1177 my $filename;
1178 foreach $filename (keys %{$mergemodulehash->{'mergefilesequence'}} )
1180 my $mergefilesequence = $mergemodulehash->{'mergefilesequence'}->{$filename};
1182 if ( ! exists($merge_filetablehashref->{$filename}) ) { installer::exiter::exit_program("ERROR: Could not find file \"$filename\" in \"$idtfilename\" !", "change_file_table"); }
1183 my $filehash = $merge_filetablehashref->{$filename};
1184 my $linenumber = $filehash->{'linenumber'};
1186 # <- this line has to be changed concerning "Sequence" and "Attributes"
1187 $filehash->{'Attributes'} = $attributes;
1189 # If this is an update process, the sequence numbers have to be reused.
1190 if ( $installer::globals::updatedatabase )
1192 if ( ! exists($allupdatesequenceshashref->{$filehash->{'File'}}) ) { installer::exiter::exit_program("ERROR: Sequence not defined for file \"$filehash->{'File'}\" !", "change_file_table"); }
1193 $filehash->{'Sequence'} = $allupdatesequenceshashref->{$filehash->{'File'}};
1194 # Saving all mergemodule sequence numbers. This is important for creating ddf files
1195 $installer::globals::allmergemodulefilesequences{$filehash->{'Sequence'}} = 1;
1197 else
1199 # Important saved data: $installer::globals::lastsequence_before_merge.
1200 # This mechanism keeps the correct order inside the new cabinet file.
1201 $filehash->{'Sequence'} = $filehash->{'Sequence'} + $installer::globals::lastsequence_before_merge;
1204 my $oldline = ${$filecontent}[$linenumber];
1205 my $newline = get_new_line_for_file_table($filehash);
1206 ${$filecontent}[$linenumber] = $newline;
1208 $infoline = "Merge, replacing line:\n";
1209 push( @installer::globals::logfileinfo, $infoline);
1210 $infoline = "Old: $oldline\n";
1211 push( @installer::globals::logfileinfo, $infoline);
1212 $infoline = "New: $newline\n";
1213 push( @installer::globals::logfileinfo, $infoline);
1215 # Adding files to the files collector (but only once)
1216 if (( $installer::globals::fix_number_of_cab_files ) && ( ! $installer::globals::mergefiles_added_into_collector ))
1218 # If the number of cabinet files is kept constant,
1219 # all files from the mergemodule cabinet files will
1220 # be integrated into the last office cabinet file
1221 # (installer::globals::lastcabfilename).
1222 # Therefore the files must now be added to the filescollector,
1223 # so that they will be integrated into the ddf files.
1225 # Problem with very long filenames -> copying to shorter filenames
1226 my $newfilename = "f" . $filehash->{'Sequence'};
1227 my $completesource = $unpackdir . $installer::globals::separator . $filehash->{'File'};
1228 my $completedest = $unpackdir . $installer::globals::separator . $newfilename;
1229 installer::systemactions::copy_one_file($completesource, $completedest);
1231 my $locallastcabfilename = $installer::globals::lastcabfilename;
1232 if ( $locallastcabfilename =~ /^\s*\#/ ) { $locallastcabfilename =~ s/^\s*\#//; } # removing beginning hashes
1234 # Create new file hash for file collector
1235 my %newfile = ();
1236 $newfile{'sequencenumber'} = $filehash->{'Sequence'};
1237 $newfile{'assignedsequencenumber'} = $filehash->{'Sequence'};
1238 $newfile{'cabinet'} = $locallastcabfilename;
1239 $newfile{'sourcepath'} = $completedest;
1240 $newfile{'componentname'} = $filehash->{'Component'};
1241 $newfile{'uniquename'} = $filehash->{'File'};
1242 $newfile{'Name'} = $filehash->{'File'};
1244 # Saving in globals sequence hash
1245 $installer::globals::uniquefilenamesequence{$filehash->{'File'}} = $filehash->{'Sequence'};
1247 if ( ! -f $newfile{'sourcepath'} ) { installer::exiter::exit_program("ERROR: File \"$newfile{'sourcepath'}\" must exist!", "change_file_table"); }
1249 # Collecting all new files. Attention: This files must be included into files collector in correct order!
1250 $newfileshash{$filehash->{'Sequence'}} = \%newfile;
1251 # push(@{$filesref}, \%newfile); -> this is not the correct order
1255 # Now the files can be added to the files collector
1256 # In the case of an update process, there can be new files, that have to be added after the merge module files.
1257 # Warning: In multilingual installation sets, the files only have to be added once to the files collector!
1259 if ( ! $installer::globals::mergefiles_added_into_collector )
1261 foreach my $localsequence ( sort { $a <=> $b } keys %newfileshash ) { push(@{$filesref}, $newfileshash{$localsequence}); }
1262 if ( $installer::globals::newfilesexist ) { $filesref = sort_files_collector_for_sequence($filesref); }
1263 # $installer::globals::mergefiles_added_into_collector = 1; -> Not yet. Only if all mergemodules are merged for one language.
1266 # Saving the idt file (for every language)
1267 installer::files::save_file($idtfilename, $filecontent);
1269 return $filesref;
1272 #########################################################################
1273 # Reading the file "Director.idt". The Directory, that is defined in scp
1274 # has to be defined in this table.
1275 #########################################################################
1277 sub collect_directories
1279 my $idtfilename = "Director.idt";
1280 my $filecontent = installer::files::read_file($idtfilename);
1282 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
1284 if ( $i <= 2 ) { next; } # ignoring first three lines
1285 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
1286 # Format: Directory Directory_Parent DefaultDir
1287 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\s*$/ )
1289 $installer::globals::merge_alldirectory_hash{$1} = 1;
1291 else
1293 my $linecount = $i + 1;
1294 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "collect_directories");
1299 #########################################################################
1300 # Reading the file "Feature.idt". The Feature, that is defined in scp
1301 # has to be defined in this table.
1302 #########################################################################
1304 sub collect_feature
1306 my $idtfilename = "Feature.idt";
1307 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "collect_feature"); }
1308 my $filecontent = installer::files::read_file($idtfilename);
1310 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
1312 if ( $i <= 2 ) { next; } # ignoring first three lines
1313 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
1314 # Format: Feature Feature_Parent Title Description Display Level Directory_ Attributes
1315 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
1317 $installer::globals::merge_allfeature_hash{$1} = 1;
1319 else
1321 my $linecount = $i + 1;
1322 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "collect_feature");
1327 #########################################################################
1328 # In the featurecomponent table, the new connections have to be added.
1329 #########################################################################
1331 sub change_featurecomponent_table
1333 my ($mergemodulehash, $workdir) = @_;
1335 my $infoline = "Changing content of table \"FeatureComponents\"\n";
1336 push( @installer::globals::logfileinfo, $infoline);
1338 my $idtfilename = "FeatureC.idt";
1339 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_featurecomponent_table"); }
1341 my $filecontent = installer::files::read_file($idtfilename);
1343 # Simply adding for each new component one line. The Feature has to be defined in scp project.
1344 my $feature = $mergemodulehash->{'feature'};
1346 if ( ! $installer::globals::mergefeaturecollected )
1348 collect_feature(); # putting content into hash %installer::globals::merge_allfeature_hash
1349 $installer::globals::mergefeaturecollected = 1;
1352 if ( ! exists($installer::globals::merge_allfeature_hash{$feature}) )
1354 installer::exiter::exit_program("ERROR: Unknown feature defined in scp: \"$feature\" . Not defined in table \"Feature\" !", "change_featurecomponent_table");
1357 my $component;
1358 foreach $component ( keys %{$mergemodulehash->{'componentnames'}} )
1360 my $line = "$feature\t$component\n";
1361 push(@{$filecontent}, $line);
1362 $infoline = "Adding line: $line\n";
1363 push( @installer::globals::logfileinfo, $infoline);
1366 # saving file
1367 installer::files::save_file($idtfilename, $filecontent);
1370 ###############################################################################
1371 # In the components table, the conditions of merge modules should be updated
1372 ###############################################################################
1374 sub change_component_table
1376 my ($mergemodulehash, $workdir) = @_;
1378 my $infoline = "Changing content of table \"Component\"\n";
1379 push( @installer::globals::logfileinfo, $infoline);
1381 my $idtfilename = "Componen.idt";
1382 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_component_table"); }
1384 my $filecontent = installer::files::read_file($idtfilename);
1386 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
1388 my $component;
1389 foreach $component ( keys %{$mergemodulehash->{'componentnames'}} )
1391 if ( ${$filecontent}[$i] =~ /^\s*$component/)
1393 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.+?)\t(.+?)\t(.*?)\t(.*?)\s*$/ )
1395 $infoline = "Adding condition ($mergemodulehash->{'componentcondition'}) from scp2 to component $1\n";
1396 push( @installer::globals::logfileinfo, $infoline);
1397 if ($5)
1399 $infoline = "Old condition: $5\nNew condition: ($5) AND ($mergemodulehash->{'componentcondition'})\n";
1400 push( @installer::globals::logfileinfo, $infoline);
1401 ${$filecontent}[$i] = "$1\t$2\t$3\t$4\t($5) AND ($mergemodulehash->{'componentcondition'})\t$6\n";
1403 else
1405 $infoline = "Old condition: <none>\nNew condition: $mergemodulehash->{'componentcondition'}\n";
1406 push( @installer::globals::logfileinfo, $infoline);
1407 ${$filecontent}[$i] = "$1\t$2\t$3\t$4\t$mergemodulehash->{'componentcondition'}\t$6\n";
1414 # saving file
1415 installer::files::save_file($idtfilename, $filecontent);
1418 #########################################################################
1419 # In the directory table, the directory parent has to be changed,
1420 # if it is not TARGETDIR.
1421 #########################################################################
1423 sub change_directory_table
1425 my ($mergemodulehash, $workdir) = @_;
1427 # directory for MergeModule has to be defined in scp project
1428 my $scpdirectory = $mergemodulehash->{'rootdir'};
1430 if ( $scpdirectory ne "TARGETDIR" ) # TARGETDIR works fine, when using msidb.exe
1432 my $infoline = "Changing content of table \"Directory\"\n";
1433 push( @installer::globals::logfileinfo, $infoline);
1435 my $idtfilename = "Director.idt";
1436 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_directory_table"); }
1438 my $filecontent = installer::files::read_file($idtfilename);
1440 if ( ! $installer::globals::mergedirectoriescollected )
1442 collect_directories(); # putting content into %installer::globals::merge_alldirectory_hash, only first column!
1443 $installer::globals::mergedirectoriescollected = 1;
1446 if ( ! exists($installer::globals::merge_alldirectory_hash{$scpdirectory}) )
1448 installer::exiter::exit_program("ERROR: Unknown directory defined in scp: \"$scpdirectory\" . Not defined in table \"Directory\" !", "change_directory_table");
1451 # If the definition in scp is okay, now the complete content of "Director.idt" can be analyzed
1452 my $merge_directorytablehashref = analyze_directorytable_file($filecontent, $idtfilename);
1454 my $directory;
1455 foreach $directory (keys %{$mergemodulehash->{'mergedirectories'}} )
1457 if ( ! exists($merge_directorytablehashref->{$directory}) ) { installer::exiter::exit_program("ERROR: Could not find directory \"$directory\" in \"$idtfilename\" !", "change_directory_table"); }
1458 my $dirhash = $merge_directorytablehashref->{$directory};
1459 my $linenumber = $dirhash->{'linenumber'};
1461 # <- this line has to be changed concerning "Directory_Parent",
1462 # if the current value is "TARGETDIR", which is the default value from msidb.exe
1464 if ( $dirhash->{'Directory_Parent'} eq "TARGETDIR" )
1466 $dirhash->{'Directory_Parent'} = $scpdirectory;
1468 my $oldline = ${$filecontent}[$linenumber];
1469 my $newline = get_new_line_for_directory_table($dirhash);
1470 ${$filecontent}[$linenumber] = $newline;
1472 $infoline = "Merge, replacing line:\n";
1473 push( @installer::globals::logfileinfo, $infoline);
1474 $infoline = "Old: $oldline\n";
1475 push( @installer::globals::logfileinfo, $infoline);
1476 $infoline = "New: $newline\n";
1477 push( @installer::globals::logfileinfo, $infoline);
1481 # saving file
1482 installer::files::save_file($idtfilename, $filecontent);
1486 #########################################################################
1487 # In the msiassembly table, the feature has to be changed.
1488 #########################################################################
1490 sub change_msiassembly_table
1492 my ($mergemodulehash, $workdir) = @_;
1494 my $infoline = "Changing content of table \"MsiAssembly\"\n";
1495 push( @installer::globals::logfileinfo, $infoline);
1497 my $idtfilename = "MsiAssem.idt";
1498 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_msiassembly_table"); }
1500 my $filecontent = installer::files::read_file($idtfilename);
1502 # feature has to be defined in scp project
1503 my $feature = $mergemodulehash->{'feature'};
1505 if ( ! $installer::globals::mergefeaturecollected )
1507 collect_feature(); # putting content into hash %installer::globals::merge_allfeature_hash
1508 $installer::globals::mergefeaturecollected = 1;
1511 if ( ! exists($installer::globals::merge_allfeature_hash{$feature}) )
1513 installer::exiter::exit_program("ERROR: Unknown feature defined in scp: \"$feature\" . Not defined in table \"Feature\" !", "change_msiassembly_table");
1516 my $merge_msiassemblytablehashref = analyze_msiassemblytable_file($filecontent, $idtfilename);
1518 my $component;
1519 foreach $component (keys %{$mergemodulehash->{'mergeassemblies'}} )
1521 if ( ! exists($merge_msiassemblytablehashref->{$component}) ) { installer::exiter::exit_program("ERROR: Could not find component \"$component\" in \"$idtfilename\" !", "change_msiassembly_table"); }
1522 my $assemblyhash = $merge_msiassemblytablehashref->{$component};
1523 my $linenumber = $assemblyhash->{'linenumber'};
1525 # <- this line has to be changed concerning "Feature"
1526 $assemblyhash->{'Feature'} = $feature;
1528 my $oldline = ${$filecontent}[$linenumber];
1529 my $newline = get_new_line_for_msiassembly_table($assemblyhash);
1530 ${$filecontent}[$linenumber] = $newline;
1532 $infoline = "Merge, replacing line:\n";
1533 push( @installer::globals::logfileinfo, $infoline);
1534 $infoline = "Old: $oldline\n";
1535 push( @installer::globals::logfileinfo, $infoline);
1536 $infoline = "New: $newline\n";
1537 push( @installer::globals::logfileinfo, $infoline);
1540 # saving file
1541 installer::files::save_file($idtfilename, $filecontent);
1544 #########################################################################
1545 # Creating file content hash
1546 #########################################################################
1548 sub make_executeidtcontent_hash
1550 my ($filecontent, $idtfilename) = @_;
1552 my %newhash = ();
1554 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
1556 if ( $i <= 2 ) { next; } # ignoring first three lines
1557 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
1558 # Format for all sequence tables: Action Condition Sequence
1559 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\s*$/ )
1561 my %onehash = ();
1562 $onehash{'Action'} = $1;
1563 $onehash{'Condition'} = $2;
1564 $onehash{'Sequence'} = $3;
1565 $newhash{$onehash{'Action'}} = \%onehash;
1567 else
1569 my $linecount = $i + 1;
1570 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "make_executeidtcontent_hash");
1574 return \%newhash;
1577 #########################################################################
1578 # Creating file content hash
1579 #########################################################################
1581 sub make_moduleexecuteidtcontent_hash
1583 my ($filecontent, $idtfilename) = @_;
1585 my %newhash = ();
1587 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
1589 if ( $i <= 2 ) { next; } # ignoring first three lines
1590 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
1591 # Format for all module sequence tables: Action Sequence BaseAction After Condition
1592 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
1594 my %onehash = ();
1595 $onehash{'Action'} = $1;
1596 $onehash{'Sequence'} = $2;
1597 $onehash{'BaseAction'} = $3;
1598 $onehash{'After'} = $4;
1599 $onehash{'Condition'} = $5;
1600 $newhash{$onehash{'Action'}} = \%onehash;
1602 else
1604 my $linecount = $i + 1;
1605 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "make_executeidtcontent_hash");
1609 return \%newhash;
1612 #########################################################################
1613 # ExecuteSequence tables need to be merged with
1614 # ModuleExecuteSequence tables created by msidb.exe.
1615 #########################################################################
1617 sub change_executesequence_table
1619 my ($mergemodulehash, $workdir, $idtfilename, $moduleidtfilename) = @_;
1621 my $infoline = "Changing content of table \"$idtfilename\"\n";
1622 push( @installer::globals::logfileinfo, $infoline);
1624 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_executesequence_table"); }
1625 if ( ! -f $moduleidtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$moduleidtfilename\" in \"$workdir\" !", "change_executesequence_table"); }
1627 # Reading file content
1628 my $idtfilecontent = installer::files::read_file($idtfilename);
1629 my $moduleidtfilecontent = installer::files::read_file($moduleidtfilename);
1631 # Converting to hash
1632 my $idtcontenthash = make_executeidtcontent_hash($idtfilecontent, $idtfilename);
1633 my $moduleidtcontenthash = make_moduleexecuteidtcontent_hash($moduleidtfilecontent, $moduleidtfilename);
1635 # Merging
1636 foreach my $action ( keys %{$moduleidtcontenthash} )
1638 if ( exists($idtcontenthash->{$action}) ) { next; } # Action already exists, can be ignored
1640 if (( $idtfilename eq "InstallU.idt" ) && ( ! ( $action =~ /^\s*WindowsFolder\./ ))) { next; } # Only "WindowsFolder.*" CustomActions for UI Sequence table
1642 my $actionhashref = $moduleidtcontenthash->{$action};
1643 if ( $actionhashref->{'Sequence'} ne "" )
1645 # Format for all sequence tables: Action Condition Sequence
1646 my $newline = $actionhashref->{'Action'} . "\t" . $actionhashref->{'Condition'} . "\t" . $actionhashref->{'Sequence'} . "\n";
1647 # Adding to table
1648 push(@{$idtfilecontent}, $newline);
1649 # Also adding to hash
1650 my %idttablehash = ();
1651 $idttablehash{'Action'} = $actionhashref->{'Action'};
1652 $idttablehash{'Condition'} = $actionhashref->{'Condition'};
1653 $idttablehash{'Sequence'} = $actionhashref->{'Sequence'};
1654 $idtcontenthash->{$action} = \%idttablehash;
1657 else # no sequence defined, using syntax "BaseAction" and "After"
1659 my $baseactionname = $actionhashref->{'BaseAction'};
1660 # If this baseactionname is not defined in execute idt file, it is not possible to merge
1661 if ( ! exists($idtcontenthash->{$baseactionname}) ) { installer::exiter::exit_program("ERROR: Merge problem: Could not find action \"$baseactionname\" in file \"$idtfilename\" !", "change_executesequence_table"); }
1663 my $baseaction = $idtcontenthash->{$baseactionname};
1664 my $sequencenumber = $baseaction->{'Sequence'};
1665 if ( $actionhashref->{'After'} == 1 ) { $sequencenumber = $sequencenumber + 1; }
1666 else { $sequencenumber = $sequencenumber - 1; }
1668 # Format for all sequence tables: Action Condition Sequence
1669 my $newline = $actionhashref->{'Action'} . "\t" . $actionhashref->{'Condition'} . "\t" . $sequencenumber . "\n";
1670 # Adding to table
1671 push(@{$idtfilecontent}, $newline);
1672 # Also adding to hash
1673 my %idttablehash = ();
1674 $idttablehash{'Action'} = $actionhashref->{'Action'};
1675 $idttablehash{'Condition'} = $actionhashref->{'Condition'};
1676 $idttablehash{'Sequence'} = $sequencenumber;
1677 $idtcontenthash->{$action} = \%idttablehash;
1681 # saving file
1682 installer::files::save_file($idtfilename, $idtfilecontent);