cid#1607171 Data race condition
[LibreOffice.git] / solenv / bin / modules / installer / windows / mergemodule.pm
blob2fc8a98c0211d40f7e05633ac6ae608fdd5c0163
2 # This file is part of the LibreOffice project.
4 # This Source Code Form is subject to the terms of the Mozilla Public
5 # License, v. 2.0. If a copy of the MPL was not distributed with this
6 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 # This file incorporates work covered by the following license notice:
10 # Licensed to the Apache Software Foundation (ASF) under one or more
11 # contributor license agreements. See the NOTICE file distributed
12 # with this work for additional information regarding copyright
13 # ownership. The ASF licenses this file to you under the Apache
14 # License, Version 2.0 (the "License"); you may not use this file
15 # except in compliance with the License. You may obtain a copy of
16 # the License at http://www.apache.org/licenses/LICENSE-2.0 .
19 package installer::windows::mergemodule;
21 use strict;
22 use warnings;
24 use Cwd;
25 use Digest::MD5;
26 use installer::converter;
27 use installer::exiter;
28 use installer::files;
29 use installer::globals;
30 use installer::logger;
31 use installer::pathanalyzer;
32 use installer::remover;
33 use installer::scriptitems;
34 use installer::systemactions;
35 use installer::worker;
36 use installer::windows::idtglobal;
37 use installer::windows::language;
39 #################################################################
40 # Merging the Windows MergeModules into the msi database.
41 #################################################################
43 sub merge_mergemodules_into_msi_database
45 my ($mergemodules, $filesref, $msifilename, $languagestringref, $allvariables, $includepatharrayref, $allupdatesequences, $allupdatelastsequences, $allupdatediskids) = @_;
47 my $domerge = 0;
48 if (( $#{$mergemodules} > -1 ) && ( ! $installer::globals::languagepack ) && ( ! $installer::globals::helppack )) { $domerge = 1; }
50 if ( $domerge )
52 installer::logger::include_header_into_logfile("Merging merge modules into msi database");
53 installer::logger::print_message( "... merging msm files into msi database ... \n" );
54 installer::logger::include_timestamp_into_logfile("\nPerformance Info: MergeModule into msi database, start");
56 my $msidb = "msidb.exe"; # Has to be in the path
57 my $cabinetfile = "MergeModule.CABinet"; # the name of each cabinet file in a merge file
58 my $infoline = "";
59 my $systemcall = "";
60 my $systemcall_output = "";
61 my $returnvalue = "";
62 # in cygwin the * glob needs to be escaped when passing it to msidb
63 my $globescape = "";
64 $globescape = "\\" if ( $^O =~ /cygwin/i );
66 # 1. Analyzing the MergeModule (has only to be done once)
67 # a. -> Extracting cabinet file: msidb.exe -d <msmfile> -x MergeModule.CABinet
68 # b. -> Number of files in cabinet file: msidb.exe -d <msmfile> -f <directory> -e File
69 # c. -> List of components: msidb.exe -d <msmfile> -f <directory> -e Component
71 if ( ! $installer::globals::mergemodules_analyzed )
73 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Analyzing MergeModules, start");
74 $infoline = "Analyzing all Merge Modules\n\n";
75 push( @installer::globals::logfileinfo, $infoline);
77 %installer::globals::mergemodules = ();
79 my $mergemoduledir = installer::systemactions::create_directories("mergefiles", $languagestringref);
81 my $mergemodule;
82 foreach $mergemodule ( @{$mergemodules} )
84 my $filename = $mergemodule->{'Name'};
85 my $mergefile = $ENV{'MSM_PATH'} . $filename;
87 if ( ! -f $mergefile ) { installer::exiter::exit_program("ERROR: msm file not found: $filename ($mergefile)!", "merge_mergemodules_into_msi_database"); }
88 my $completesource = $mergefile;
90 my $mergegid = $mergemodule->{'gid'};
91 my $workdir = $mergemoduledir . $installer::globals::separator . $mergegid;
92 if ( ! -d $workdir ) { installer::systemactions::create_directory($workdir); }
94 $infoline = "Analyzing Merge Module: $filename\n";
95 push( @installer::globals::logfileinfo, $infoline);
97 # copy msm file into working directory
98 my $completedest = $workdir . $installer::globals::separator . $filename;
99 installer::systemactions::copy_one_file($completesource, $completedest);
100 if ( ! -f $completedest ) { installer::exiter::exit_program("ERROR: msm file not found: $completedest !", "merge_mergemodules_into_msi_database"); }
102 # changing directory
103 my $from = cwd();
104 my $to = $workdir;
105 chdir($to);
107 # remove an existing cabinet file
108 if ( -f $cabinetfile ) { unlink($cabinetfile); }
110 # export cabinet file
111 $systemcall = $msidb . " -d " . $filename . " -x " . $cabinetfile;
112 $systemcall_output = `$systemcall`;
113 $returnvalue = $? >> 8;
115 if ($returnvalue)
117 $infoline = "ERROR: Could not execute $systemcall - returncode: $returnvalue - output: $systemcall_output\n";
118 push( @installer::globals::logfileinfo, $infoline);
119 installer::exiter::exit_program("ERROR: Could not extract cabinet file from merge file: $completedest !", "merge_mergemodules_into_msi_database");
121 else
123 $infoline = "Success: Executed $systemcall successfully!\n";
124 push( @installer::globals::logfileinfo, $infoline);
127 # export tables from mergefile
128 # Attention: All listed tables have to exist in the database. If they not exist, an error window pops up
129 # and the return value of msidb.exe is not zero. The error window makes it impossible to check the existence
130 # of a table with the help of the return value.
131 # Solution: Export of all tables by using "*" . Some tables must exist (File Component Directory), other
132 # tables do not need to exist (MsiAssembly).
134 $systemcall = $msidb . " -d " . $filename . " -f " . $workdir . " -e $globescape*";
135 # msidb.exe really wants backslashes
136 $systemcall =~ s/\//\\\\/g;
138 $systemcall_output = `$systemcall`;
139 $returnvalue = $? >> 8;
141 if ($returnvalue)
143 $infoline = "ERROR: Could not execute $systemcall - returncode: $returnvalue - output: $systemcall_output\n";
144 push( @installer::globals::logfileinfo, $infoline);
145 installer::exiter::exit_program("ERROR: Could not export tables from merge file: $completedest !", "merge_mergemodules_into_msi_database");
147 else
149 $infoline = "Success: Executed $systemcall successfully!\n";
150 push( @installer::globals::logfileinfo, $infoline);
153 # Determining files
154 my $idtfilename = "File.idt"; # must exist
155 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: File \"$idtfilename\" not found in directory \"$workdir\" !", "merge_mergemodules_into_msi_database"); }
156 my $filecontent = installer::files::read_file($idtfilename);
157 my @file_idt_content = ();
158 my $filecounter = 0;
159 my %mergefilesequence = ();
160 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
162 if ( $i <= 2 ) { next; } # ignoring first three lines
163 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
164 $filecounter++;
165 push(@file_idt_content, ${$filecontent}[$i]);
166 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.+?)\t(.*?)\t(.*?)\t(.*?)\t(\d+?)\s*$/ )
168 my $filename = $1;
169 my $filesequence = $8;
170 $mergefilesequence{$filename} = $filesequence;
172 else
174 my $linecount = $i + 1;
175 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "merge_mergemodules_into_msi_database");
179 # Determining components
180 $idtfilename = "Component.idt"; # must exist
181 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: File \"$idtfilename\" not found in directory \"$workdir\" !", "merge_mergemodules_into_msi_database"); }
182 $filecontent = installer::files::read_file($idtfilename);
183 my %componentnames = ();
184 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
186 if ( $i <= 2 ) { next; } # ignoring first three lines
187 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
188 if ( ${$filecontent}[$i] =~ /^\s*(\S+)\s+/ ) { $componentnames{$1} = 1; }
191 # Determining directories
192 $idtfilename = "Directory.idt"; # must exist
193 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: File \"$idtfilename\" not found in directory \"$workdir\" !", "merge_mergemodules_into_msi_database"); }
194 $filecontent = installer::files::read_file($idtfilename);
195 my %mergedirectories = ();
196 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
198 if ( $i <= 2 ) { next; } # ignoring first three lines
199 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
200 if ( ${$filecontent}[$i] =~ /^\s*(\S+)\s+/ ) { $mergedirectories{$1} = 1; }
203 # Determining assemblies
204 $idtfilename = "MsiAssembly.idt"; # does not need to exist
205 my $hasmsiassemblies = 0;
206 my %mergeassemblies = ();
207 if ( -f $idtfilename )
209 $filecontent = installer::files::read_file($idtfilename);
210 $hasmsiassemblies = 1;
211 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
213 if ( $i <= 2 ) { next; } # ignoring first three lines
214 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
215 if ( ${$filecontent}[$i] =~ /^\s*(\S+)\s+/ ) { $mergeassemblies{$1} = 1; }
219 # It is possible, that other tables have to be checked here. This happens, if tables in the
220 # merge module have to know the "Feature" or the "Directory", under which the content of the
221 # msm file is integrated into the msi database.
223 # Determining name of cabinet file in installation set
224 my $cabfilename = $mergemodule->{'Cabfilename'};
225 if ( $cabfilename ) { installer::packagelist::resolve_packagevariables(\$cabfilename, $allvariables, 0); }
227 # Analyzing styles
228 # Flag REMOVE_FILE_TABLE is required for msvc9 Merge-Module, because otherwise msidb.exe
229 # fails during integration of msm file into msi database.
231 my $styles = "";
232 my $removefiletable = 0;
233 if ( $mergemodule->{'Styles'} ) { $styles = $mergemodule->{'Styles'}; }
234 if ( $styles =~ /\bREMOVE_FILE_TABLE\b/ ) { $removefiletable = 1; }
236 if ( $removefiletable )
238 my $removeworkdir = $workdir . $installer::globals::separator . "remove_file_idt";
239 if ( ! -d $removeworkdir ) { installer::systemactions::create_directory($removeworkdir); }
240 my $completeremovedest = $removeworkdir . $installer::globals::separator . $filename;
241 installer::systemactions::copy_one_file($completedest, $completeremovedest);
242 if ( ! -f $completeremovedest ) { installer::exiter::exit_program("ERROR: msm file not found: $completeremovedest !", "merge_mergemodules_into_msi_database"); }
244 # Unpacking msm file
245 $systemcall = $msidb . " -d " . $completeremovedest . " -f " . $removeworkdir . " -e $globescape*";
246 # msidb.exe really wants backslashes
247 $systemcall =~ s/\//\\\\/g;
249 $systemcall_output = `$systemcall`;
250 $returnvalue = $? >> 8;
252 if ($returnvalue) {
253 $infoline = "ERROR: Could not execute $systemcall - returncode: $returnvalue - output: $systemcall_output\n";
254 push( @installer::globals::logfileinfo, $infoline);
255 installer::exiter::exit_program("ERROR: $systemcall failed!", "merge_mergemodules_into_msi_database");
256 } else {
257 $infoline = "Success: Executed $systemcall successfully!\n";
258 push(@installer::globals::logfileinfo, $infoline);
261 my $idtfilename = $removeworkdir . $installer::globals::separator . "File.idt";
262 if ( -f $idtfilename ) { unlink $idtfilename; }
263 unlink $completeremovedest;
265 # Packing msm file without "File.idt"
266 $systemcall = $msidb . " -c -d " . $completeremovedest . " -f " . $removeworkdir . " -i $globescape*";
267 # msidb.exe really wants backslashes
268 $systemcall =~ s/\//\\\\/g;
270 $systemcall_output = `$systemcall`;
271 $returnvalue = $? >> 8;
273 if ($returnvalue) {
274 $infoline = "ERROR: Could not execute $systemcall - returncode: $returnvalue - output: $systemcall_output\n";
275 push( @installer::globals::logfileinfo, $infoline);
276 installer::exiter::exit_program("ERROR: $systemcall failed!", "merge_mergemodules_into_msi_database");
277 } else {
278 $infoline = "Success: Executed $systemcall successfully!\n";
279 push( @installer::globals::logfileinfo, $infoline);
282 # Using this msm file for merging
283 if ( -f $completeremovedest ) { $completedest = $completeremovedest; }
284 else { installer::exiter::exit_program("ERROR: Could not find msm file without File.idt: $completeremovedest !", "merge_mergemodules_into_msi_database"); }
287 # Saving MergeModule info
289 my %onemergemodulehash = ();
290 $onemergemodulehash{'mergefilepath'} = $completedest;
291 $onemergemodulehash{'workdir'} = $workdir;
292 $onemergemodulehash{'cabinetfile'} = $workdir . $installer::globals::separator . $cabinetfile;
293 $onemergemodulehash{'filenumber'} = $filecounter;
294 $onemergemodulehash{'componentnames'} = \%componentnames;
295 $onemergemodulehash{'componentcondition'} = $mergemodule->{'ComponentCondition'};
296 $onemergemodulehash{'attributes_add'} = $mergemodule->{'Attributes_Add'};
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 $systemcall = $msidb . " -d " . $msifilename . " -m " . $mergemodulehash->{'mergefilepath'};
360 # msidb.exe really wants backslashes
361 $systemcall =~ s/\//\\\\/g;
363 $systemcall_output = `$systemcall`;
364 $returnvalue = $? >> 8;
366 if ($returnvalue)
368 $infoline = "ERROR: Could not execute $systemcall - returncode: $returnvalue - output: $systemcall_output\n";
369 push( @installer::globals::logfileinfo, $infoline);
370 installer::exiter::exit_program("Could not merge msm file into database: $mergemodulehash->{'mergefilepath'}\n$infoline", "merge_mergemodules_into_msi_database");
372 else
374 $infoline = "Success: Executed $systemcall successfully!\n";
375 push( @installer::globals::logfileinfo, $infoline);
378 installer::logger::include_timestamp_into_logfile("\nPerformance Info: After merging database");
380 # Saving original idt files
381 if ( -f "File.idt" ) { installer::systemactions::rename_one_file("File.idt", "old.File.idt.$counter"); }
382 if ( -f "Media.idt" ) { installer::systemactions::rename_one_file("Media.idt", "old.Media.idt.$counter"); }
383 if ( -f "Directory.idt" ) { installer::systemactions::rename_one_file("Directory.idt", "old.Directory.idt.$counter"); }
384 if ( -f "Director.idt" ) { installer::systemactions::rename_one_file("Director.idt", "old.Director.idt.$counter"); }
385 if ( -f "FeatureComponents.idt" ) { installer::systemactions::rename_one_file("FeatureComponents.idt", "old.FeatureComponents.idt.$counter"); }
386 if ( -f "FeatureC.idt" ) { installer::systemactions::rename_one_file("FeatureC.idt", "old.FeatureC.idt.$counter"); }
387 if ( -f "MsiAssembly.idt" ) { installer::systemactions::rename_one_file("MsiAssembly.idt", "old.MsiAssembly.idt.$counter"); }
388 if ( -f "MsiAssem.idt" ) { installer::systemactions::rename_one_file("MsiAssem.idt", "old.MsiAssem.idt.$counter"); }
389 if ( -f "Componen.idt" ) { installer::systemactions::rename_one_file("Componen.idt", "old.Componen.idt.$counter"); }
391 # Extracting tables
393 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Before extracting tables");
395 my $workingtables = "File Media Directory FeatureComponents"; # required tables
396 # Optional tables can be added now
397 if ( $mergemodulehash->{'hasmsiassemblies'} ) { $workingtables = $workingtables . " MsiAssembly"; }
398 if ( ( $mergemodulehash->{'componentcondition'} ) || ( $mergemodulehash->{'attributes_add'} ) ) { $workingtables = $workingtables . " Component"; }
400 # Table "Feature" has to be exported, but it is not necessary to import it.
401 $systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -e " . "Feature " . $workingtables;
402 # msidb.exe really wants backslashes
403 $systemcall =~ s/\//\\\\/g;
405 $systemcall_output = `$systemcall`;
406 $returnvalue = $? >> 8;
408 if ($returnvalue)
410 $infoline = "ERROR: Could not execute $systemcall - returncode: $returnvalue - output: $systemcall_output\n";
411 push( @installer::globals::logfileinfo, $infoline);
412 installer::exiter::exit_program("ERROR: Could not exclude tables from msi database: $msifilename !", "merge_mergemodules_into_msi_database");
414 else
416 $infoline = "Success: Executed $systemcall successfully!\n";
417 push( @installer::globals::logfileinfo, $infoline);
420 installer::logger::include_timestamp_into_logfile("\nPerformance Info: After extracting tables");
422 # Using 8+3 table names, that are used, when tables are integrated into database. The export of tables
423 # creates idt-files, that have long names.
425 if ( -f "Directory.idt" ) { installer::systemactions::rename_one_file("Directory.idt", "Director.idt"); }
426 if ( -f "FeatureComponents.idt" ) { installer::systemactions::rename_one_file("FeatureComponents.idt", "FeatureC.idt"); }
427 if ( -f "MsiAssembly.idt" ) { installer::systemactions::rename_one_file("MsiAssembly.idt", "MsiAssem.idt"); }
428 if ( -f "Component.idt" ) { installer::systemactions::rename_one_file("Component.idt", "Componen.idt"); }
430 # Changing content of tables: File, Media, Directory, FeatureComponent, MsiAssembly, Component
431 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing Media table");
432 change_media_table($mergemodulehash, $workdir, $mergemodulegid, $allupdatelastsequences, $allupdatediskids);
433 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing File table");
434 $filesref = change_file_table($mergemodulehash, $workdir, $allupdatesequences, $includepatharrayref, $filesref, $mergemodulegid);
435 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing FeatureComponent table");
436 change_featurecomponent_table($mergemodulehash, $workdir);
437 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing Directory table");
438 change_directory_table($mergemodulehash, $workdir);
439 if ( $mergemodulehash->{'hasmsiassemblies'} )
441 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing MsiAssembly table");
442 change_msiassembly_table($mergemodulehash, $workdir);
445 if ( ( $mergemodulehash->{'componentcondition'} ) || ( $mergemodulehash->{'attributes_add'} ) )
447 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing Component table");
448 change_component_table($mergemodulehash, $workdir);
451 # msidb.exe does not merge InstallExecuteSequence, AdminExecuteSequence and AdvtExecuteSequence. Instead it creates
452 # new tables ModuleInstallExecuteSequence, ModuleAdminExecuteSequence and ModuleAdvtExecuteSequence that need to be
453 # merged into the three ExecuteSequences with the following process (also into InstallUISequence.idt).
455 # Saving original idt files
456 if ( -f "InstallE.idt" ) { installer::systemactions::rename_one_file("InstallE.idt", "old.InstallE.idt.$counter"); }
457 if ( -f "InstallU.idt" ) { installer::systemactions::rename_one_file("InstallU.idt", "old.InstallU.idt.$counter"); }
458 if ( -f "AdminExe.idt" ) { installer::systemactions::rename_one_file("AdminExe.idt", "old.AdminExe.idt.$counter"); }
459 if ( -f "AdvtExec.idt" ) { installer::systemactions::rename_one_file("AdvtExec.idt", "old.AdvtExec.idt.$counter"); }
460 if ( -f "ModuleInstallExecuteSequence.idt" ) { installer::systemactions::rename_one_file("ModuleInstallExecuteSequence.idt", "old.ModuleInstallExecuteSequence.idt.$counter"); }
461 if ( -f "ModuleAdminExecuteSequence.idt" ) { installer::systemactions::rename_one_file("ModuleAdminExecuteSequence.idt", "old.ModuleAdminExecuteSequence.idt.$counter"); }
462 if ( -f "ModuleAdvtExecuteSequence.idt" ) { installer::systemactions::rename_one_file("ModuleAdvtExecuteSequence.idt", "old.ModuleAdvtExecuteSequence.idt.$counter"); }
464 # Extracting tables
465 my $moduleexecutetables = "ModuleInstallExecuteSequence ModuleAdminExecuteSequence ModuleAdvtExecuteSequence"; # new tables
466 my $executetables = "InstallExecuteSequence InstallUISequence AdminExecuteSequence AdvtExecuteSequence"; # tables to be merged
468 $systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -e " . "Feature " . $moduleexecutetables;
469 # msidb.exe really wants backslashes
470 $systemcall =~ s/\//\\\\/g;
472 $systemcall_output = `$systemcall`;
473 $returnvalue = $? >> 8;
475 if ($returnvalue) {
476 # the exit status of this command had not been checked in the past, it fails because
477 # there is no ModuleAdminExecuteSequence table
478 $infoline = "IGNORING: Could not execute $systemcall - returncode: $returnvalue - output: $systemcall_output\n";
479 push( @installer::globals::logfileinfo, $infoline);
480 #installer::exiter::exit_program("ERROR: $infoline", "merge_mergemodules_into_msi_database");
481 } else {
482 $infoline = "Success: Executed $systemcall successfully\n";
483 push( @installer::globals::logfileinfo, $infoline);
486 $systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -e " . "Feature " . $executetables;
487 # msidb.exe really wants backslashes
488 $systemcall =~ s/\//\\\\/g;
490 $systemcall_output = `$systemcall`;
491 $returnvalue = $? >> 8;
493 if ($returnvalue) {
494 $infoline = "ERROR: Could not execute $systemcall - returncode: $returnvalue - output: $systemcall_output\n";
495 push( @installer::globals::logfileinfo, $infoline);
496 installer::exiter::exit_program("$infoline", "merge_mergemodules_into_msi_database");
497 } else {
498 $infoline = "Success: Executed $systemcall successfully\n";
499 push( @installer::globals::logfileinfo, $infoline);
502 # Using 8+3 table names, that are used, when tables are integrated into database. The export of tables
503 # creates idt-files, that have long names.
505 if ( -f "InstallExecuteSequence.idt" ) { installer::systemactions::rename_one_file("InstallExecuteSequence.idt", "InstallE.idt"); }
506 if ( -f "InstallUISequence.idt" ) { installer::systemactions::rename_one_file("InstallUISequence.idt", "InstallU.idt"); }
507 if ( -f "AdminExecuteSequence.idt" ) { installer::systemactions::rename_one_file("AdminExecuteSequence.idt", "AdminExe.idt"); }
508 if ( -f "AdvtExecuteSequence.idt" ) { installer::systemactions::rename_one_file("AdvtExecuteSequence.idt", "AdvtExec.idt"); }
510 # Merging content of tables ModuleInstallExecuteSequence, ModuleAdminExecuteSequence and ModuleAdvtExecuteSequence
511 # into tables InstallExecuteSequence, AdminExecuteSequence and AdvtExecuteSequence
512 if ( -f "ModuleInstallExecuteSequence.idt" )
514 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing InstallExecuteSequence table");
515 change_executesequence_table($mergemodulehash, $workdir, "InstallE.idt", "ModuleInstallExecuteSequence.idt");
516 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing InstallUISequence table");
517 change_executesequence_table($mergemodulehash, $workdir, "InstallU.idt", "ModuleInstallExecuteSequence.idt");
520 if ( -f "ModuleAdminExecuteSequence.idt" )
522 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing AdminExecuteSequence table");
523 change_executesequence_table($mergemodulehash, $workdir, "AdminExe.idt", "ModuleAdminExecuteSequence.idt");
526 if ( -f "ModuleAdvtExecuteSequence.idt" )
528 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing AdvtExecuteSequence table");
529 change_executesequence_table($mergemodulehash, $workdir, "AdvtExec.idt", "ModuleAdvtExecuteSequence.idt");
532 installer::logger::include_timestamp_into_logfile("\nPerformance Info: All tables edited");
534 # Including tables into msi database
536 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Before including tables");
538 $systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -i " . $workingtables. " " . $executetables;
539 # msidb.exe really wants backslashes
540 $systemcall =~ s/\//\\\\/g;
542 $systemcall_output = `$systemcall`;
543 $returnvalue = $? >> 8;
545 if ($returnvalue)
547 $infoline = "ERROR: Could not execute $systemcall - returncode: $returnvalue - output: $systemcall_output\n";
548 push( @installer::globals::logfileinfo, $infoline);
549 installer::exiter::exit_program("ERROR: Could not include tables into msi database: $msifilename !", "merge_mergemodules_into_msi_database");
551 else
553 $infoline = "Success: Executed $systemcall successfully!\n";
554 push( @installer::globals::logfileinfo, $infoline);
557 installer::logger::include_timestamp_into_logfile("\nPerformance Info: After including tables");
559 chdir($from);
562 if ( ! $installer::globals::mergefiles_added_into_collector ) { $installer::globals::mergefiles_added_into_collector = 1; } # Now all mergemodules are merged for one language.
564 installer::logger::include_timestamp_into_logfile("\nPerformance Info: MergeModule into msi database, stop");
567 return $filesref;
570 #########################################################################
571 # Analyzing the content of the media table.
572 #########################################################################
574 sub analyze_media_file
576 my ($filecontent, $workdir) = @_;
578 my %filehash = ();
579 my $linecount = 0;
580 my $counter = 0;
581 my $filename = "Media.idt";
583 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
585 if ( $i <= 2 ) { next; } # ignoring first three lines
586 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
587 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.+?)\t(.+?)\t(.*?)\s*$/ )
589 my %line = ();
590 # Format: DiskId LastSequence DiskPrompt Cabinet VolumeLabel Source
591 $line{'DiskId'} = $1;
592 $line{'LastSequence'} = $2;
593 $line{'DiskPrompt'} = $3;
594 $line{'Cabinet'} = $4;
595 $line{'VolumeLabel'} = $5;
596 $line{'Source'} = $6;
598 $counter++;
599 $filehash{$counter} = \%line;
601 else
603 $linecount = $i + 1;
604 installer::exiter::exit_program("ERROR: Unknown line format in table \"$filename\" in \"$workdir\" (line $linecount) !", "analyze_media_file");
608 return \%filehash;
611 #########################################################################
612 # Setting the DiskID for the new cabinet file
613 #########################################################################
615 sub get_diskid
617 my ($mediafile, $allupdatediskids, $cabfilename) = @_;
619 my $diskid = 0;
620 my $line;
622 if (( $installer::globals::updatedatabase ) && ( exists($allupdatediskids->{$cabfilename}) ))
624 $diskid = $allupdatediskids->{$cabfilename};
626 else
628 foreach $line ( keys %{$mediafile} )
630 if ( $mediafile->{$line}->{'DiskId'} > $diskid ) { $diskid = $mediafile->{$line}->{'DiskId'}; }
633 $diskid++;
636 return $diskid;
639 #########################################################################
640 # Setting the global LastSequence variable
641 #########################################################################
643 sub set_current_last_sequence
645 my ($mediafile) = @_;
647 my $lastsequence = 0;
648 my $line;
649 foreach $line ( keys %{$mediafile} )
651 if ( $mediafile->{$line}->{'LastSequence'} > $lastsequence ) { $lastsequence = $mediafile->{$line}->{'LastSequence'}; }
654 $installer::globals::lastsequence_before_merge = $lastsequence;
657 #########################################################################
658 # Setting the LastSequence for the new cabinet file
659 #########################################################################
661 sub get_lastsequence
663 my ($mergemodulehash, $allupdatelastsequences) = @_;
665 my $lastsequence = 0;
667 if (( $installer::globals::updatedatabase ) && ( exists($allupdatelastsequences->{$mergemodulehash->{'cabfilename'}}) ))
669 $lastsequence = $allupdatelastsequences->{$mergemodulehash->{'cabfilename'}};
671 else
673 $lastsequence = $installer::globals::lastsequence_before_merge + $mergemodulehash->{'filenumber'};
676 return $lastsequence;
679 #########################################################################
680 # Setting the DiskPrompt for the new cabinet file
681 #########################################################################
683 sub get_diskprompt
685 my ($mediafile) = @_;
687 my $diskprompt = "";
688 my $line;
689 foreach $line ( keys %{$mediafile} )
691 if ( exists($mediafile->{$line}->{'DiskPrompt'}) )
693 $diskprompt = $mediafile->{$line}->{'DiskPrompt'};
694 last;
698 return $diskprompt;
701 #########################################################################
702 # Setting the VolumeLabel for the new cabinet file
703 #########################################################################
705 sub get_volumelabel
707 my ($mediafile) = @_;
709 my $volumelabel = "";
710 my $line;
711 foreach $line ( keys %{$mediafile} )
713 if ( exists($mediafile->{$line}->{'VolumeLabel'}) )
715 $volumelabel = $mediafile->{$line}->{'VolumeLabel'};
716 last;
720 return $volumelabel;
723 #########################################################################
724 # Setting the Source for the new cabinet file
725 #########################################################################
727 sub get_source
729 my ($mediafile) = @_;
731 my $source = "";
732 my $line;
733 foreach $line ( keys %{$mediafile} )
735 if ( exists($mediafile->{$line}->{'Source'}) )
737 $source = $mediafile->{$line}->{'Source'};
738 last;
742 return $source;
745 #########################################################################
746 # For each Merge Module one new line has to be included into the
747 # media table.
748 #########################################################################
750 sub create_new_media_line
752 my ($mergemodulehash, $mediafile, $allupdatelastsequences, $allupdatediskids) = @_;
754 my $diskid = get_diskid($mediafile, $allupdatediskids, $mergemodulehash->{'cabfilename'});
755 my $lastsequence = get_lastsequence($mergemodulehash, $allupdatelastsequences);
756 my $diskprompt = get_diskprompt($mediafile);
757 my $cabinet = $mergemodulehash->{'cabfilename'};
758 my $volumelabel = get_volumelabel($mediafile);
759 my $source = get_source($mediafile);
761 if ( $installer::globals::include_cab_in_msi ) { $cabinet = "\#" . $cabinet; }
763 my $newline = "$diskid\t$lastsequence\t$diskprompt\t$cabinet\t$volumelabel\t$source\n";
765 return $newline;
768 #########################################################################
769 # Setting the last diskid in media table.
770 #########################################################################
772 sub get_last_diskid
774 my ($mediafile) = @_;
776 my $lastdiskid = 0;
777 my $line;
778 foreach $line ( keys %{$mediafile} )
780 if ( $mediafile->{$line}->{'DiskId'} > $lastdiskid ) { $lastdiskid = $mediafile->{$line}->{'DiskId'}; }
783 return $lastdiskid;
786 #########################################################################
787 # Setting global variable for last cab file name.
788 #########################################################################
790 sub set_last_cabfile_name
792 my ($mediafile, $lastdiskid) = @_;
794 my $line;
795 foreach $line ( keys %{$mediafile} )
797 if ( $mediafile->{$line}->{'DiskId'} == $lastdiskid ) { $installer::globals::lastcabfilename = $mediafile->{$line}->{'Cabinet'}; }
799 my $infoline = "Setting last cabinet file: $installer::globals::lastcabfilename\n";
800 push( @installer::globals::logfileinfo, $infoline);
803 #########################################################################
804 # In the media table the new cabinet file has to be added or the
805 # number of the last cabinet file has to be increased.
806 #########################################################################
808 sub change_media_table
810 my ( $mergemodulehash, $workdir, $mergemodulegid, $allupdatelastsequences, $allupdatediskids ) = @_;
812 my $infoline = "Changing content of table \"Media\"\n";
813 push( @installer::globals::logfileinfo, $infoline);
815 my $filename = "Media.idt";
816 if ( ! -f $filename ) { installer::exiter::exit_program("ERROR: Could not find file \"$filename\" in \"$workdir\" !", "change_media_table"); }
818 my $filecontent = installer::files::read_file($filename);
819 my $mediafile = analyze_media_file($filecontent, $workdir);
820 set_current_last_sequence($mediafile);
822 if ( $installer::globals::fix_number_of_cab_files )
824 # Determining the line with the highest sequencenumber. That file needs to be updated.
825 my $lastdiskid = get_last_diskid($mediafile);
826 if ( $installer::globals::lastcabfilename eq "" ) { set_last_cabfile_name($mediafile, $lastdiskid); }
827 my $newmaxsequencenumber = $installer::globals::lastsequence_before_merge + $mergemodulehash->{'filenumber'};
829 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
831 if ( $i <= 2 ) { next; } # ignoring first three lines
832 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
833 if ( ${$filecontent}[$i] =~ /^\s*(\Q$lastdiskid\E\t)\Q$installer::globals::lastsequence_before_merge\E(\t.*)$/ )
835 my $start = $1;
836 my $final = $2;
837 $infoline = "Merge: Old line in media table: ${$filecontent}[$i]\n";
838 push( @installer::globals::logfileinfo, $infoline);
839 my $newline = $start . $newmaxsequencenumber . $final . "\n";
840 ${$filecontent}[$i] = $newline;
841 $infoline = "Merge: Changed line in media table: ${$filecontent}[$i]\n";
842 push( @installer::globals::logfileinfo, $infoline);
846 else
848 # the new line is identical for all localized databases, but has to be created for each MergeModule ($mergemodulegid)
849 if ( ! exists($installer::globals::merge_media_line{$mergemodulegid}) )
851 $installer::globals::merge_media_line{$mergemodulegid} = create_new_media_line($mergemodulehash, $mediafile, $allupdatelastsequences, $allupdatediskids);
854 $infoline = "Adding line: $installer::globals::merge_media_line{$mergemodulegid}\n";
855 push( @installer::globals::logfileinfo, $infoline);
857 # adding new line
858 push(@{$filecontent}, $installer::globals::merge_media_line{$mergemodulegid});
861 # saving file
862 installer::files::save_file($filename, $filecontent);
865 #########################################################################
866 # Putting the directory table content into a hash.
867 #########################################################################
869 sub analyze_directorytable_file
871 my ($filecontent, $idtfilename) = @_;
873 my %dirhash = ();
874 # Iterating over the file content
875 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
877 if ( $i <= 2 ) { next; } # ignoring first three lines
878 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
879 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\s*$/ )
881 my %line = ();
882 # Format: Directory Directory_Parent DefaultDir
883 $line{'Directory'} = $1;
884 $line{'Directory_Parent'} = $2;
885 $line{'DefaultDir'} = $3;
886 $line{'linenumber'} = $i; # saving also the line number for direct access
888 my $uniquekey = $line{'Directory'};
889 $dirhash{$uniquekey} = \%line;
891 else
893 my $linecount = $i + 1;
894 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "analyze_directorytable_file");
898 return \%dirhash;
901 #########################################################################
902 # Putting the msi assembly table content into a hash.
903 #########################################################################
905 sub analyze_msiassemblytable_file
907 my ($filecontent, $idtfilename) = @_;
909 my %assemblyhash = ();
910 # Iterating over the file content
911 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
913 if ( $i <= 2 ) { next; } # ignoring first three lines
914 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
915 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.*?)\t(.*?)\s*$/ )
917 my %line = ();
918 # Format: Component_ Feature_ File_Manifest File_Application Attributes
919 $line{'Component'} = $1;
920 $line{'Feature'} = $2;
921 $line{'File_Manifest'} = $3;
922 $line{'File_Application'} = $4;
923 $line{'Attributes'} = $5;
924 $line{'linenumber'} = $i; # saving also the line number for direct access
926 my $uniquekey = $line{'Component'};
927 $assemblyhash{$uniquekey} = \%line;
929 else
931 my $linecount = $i + 1;
932 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "analyze_msiassemblytable_file");
936 return \%assemblyhash;
939 #########################################################################
940 # Putting the file table content into a hash.
941 #########################################################################
943 sub analyze_filetable_file
945 my ( $filecontent, $idtfilename ) = @_;
947 my %filehash = ();
948 # Iterating over the file content
949 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
951 if ( $i <= 2 ) { next; } # ignoring first three lines
952 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
953 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.+?)\s*$/ )
955 my %line = ();
956 # Format: File Component_ FileName FileSize Version Language Attributes Sequence
957 $line{'File'} = $1;
958 $line{'Component'} = $2;
959 $line{'FileName'} = $3;
960 $line{'FileSize'} = $4;
961 $line{'Version'} = $5;
962 $line{'Language'} = $6;
963 $line{'Attributes'} = $7;
964 $line{'Sequence'} = $8;
965 $line{'linenumber'} = $i; # saving also the line number for direct access
967 my $uniquekey = $line{'File'};
968 $filehash{$uniquekey} = \%line;
970 else
972 my $linecount = $i + 1;
973 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "analyze_filetable_file");
977 return \%filehash;
980 #########################################################################
981 # Creating a new line for the directory table.
982 #########################################################################
984 sub get_new_line_for_directory_table
986 my ($dir) = @_;
988 my $newline = "$dir->{'Directory'}\t$dir->{'Directory_Parent'}\t$dir->{'DefaultDir'}\n";
990 return $newline;
993 #########################################################################
994 # Creating a new line for the file table.
995 #########################################################################
997 sub get_new_line_for_file_table
999 my ($file) = @_;
1001 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";
1003 return $newline;
1006 #########################################################################
1007 # Creating a new line for the msiassembly table.
1008 #########################################################################
1010 sub get_new_line_for_msiassembly_table
1012 my ($assembly) = @_;
1014 my $newline = "$assembly->{'Component'}\t$assembly->{'Feature'}\t$assembly->{'File_Manifest'}\t$assembly->{'File_Application'}\t$assembly->{'Attributes'}\n";
1016 return $newline;
1019 #########################################################################
1020 # Sorting the files collector, if there are files, following
1021 # the merge module files.
1022 #########################################################################
1024 sub sort_files_collector_for_sequence
1026 my ($filesref) = @_;
1028 my @sortarray = ();
1029 my %helphash = ();
1031 for ( my $i = 0; $i <= $#{$filesref}; $i++ )
1033 my $onefile = ${$filesref}[$i];
1034 if ( ! exists($onefile->{'sequencenumber'}) ) { installer::exiter::exit_program("ERROR: Could not find sequencenumber for file: $onefile->{'uniquename'} !", "sort_files_collector_for_sequence"); }
1035 my $sequence = $onefile->{'sequencenumber'};
1036 $helphash{$sequence} = $onefile;
1039 foreach my $seq ( sort { $a <=> $b } keys %helphash ) { push(@sortarray, $helphash{$seq}); }
1041 return \@sortarray;
1044 #########################################################################
1045 # In the file table "Sequence" and "Attributes" have to be changed.
1046 #########################################################################
1048 sub change_file_table
1050 my ($mergemodulehash, $workdir, $allupdatesequenceshashref, $includepatharrayref, $filesref, $mergemodulegid) = @_;
1052 my $infoline = "Changing content of table \"File\"\n";
1053 push( @installer::globals::logfileinfo, $infoline);
1055 my $globescape = "";
1056 $globescape = "\\" if ( $^O =~ /cygwin/i );
1058 my $idtfilename = "File.idt";
1059 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_file_table"); }
1061 my $filecontent = installer::files::read_file($idtfilename);
1063 # If File.idt needed to be removed before the msm database was merged into the msi database,
1064 # now it is time to add the content into File.idt
1065 if ( $mergemodulehash->{'removefiletable'} )
1067 for ( my $i = 0; $i <= $#{$mergemodulehash->{'fileidtcontent'}}; $i++ )
1069 push(@{$filecontent}, ${$mergemodulehash->{'fileidtcontent'}}[$i]);
1073 # Unpacking the MergeModule.CABinet (only once)
1074 # Unpacking into temp directory. Warning: expand.exe has problems with very long unpack directories.
1076 my $empty = "";
1077 my $unpackdir = installer::systemactions::create_directories("cab", \$empty);
1078 $unpackdir = qx(cygpath -m "$unpackdir");
1079 chomp $unpackdir;
1080 push(@installer::globals::removedirs, $unpackdir);
1081 $unpackdir = $unpackdir . $installer::globals::separator . $mergemodulegid;
1083 my %newfileshash = ();
1084 if (( $installer::globals::fix_number_of_cab_files ) && ( ! $installer::globals::mergefiles_added_into_collector ))
1086 if ( ! -d $unpackdir ) { installer::systemactions::create_directory($unpackdir); }
1088 # changing directory
1089 my $from = cwd();
1090 my $to = $mergemodulehash->{'workdir'};
1091 if ( $^O =~ /cygwin/i ) {
1092 $to = qx(cygpath -u "$to");
1093 chomp $to;
1096 chdir($to) || die "Could not chdir to \"$to\"\n";
1098 # Unpack the cab file, so that in can be included into the last office cabinet file.
1099 # Not using cabarc.exe from cabsdk for unpacking cabinet files, but "expand.exe" that
1100 # should be available on every Windows system.
1102 $infoline = "Unpacking cabinet file: $mergemodulehash->{'cabinetfile'}\n";
1103 push( @installer::globals::logfileinfo, $infoline);
1105 # Avoid the Cygwin expand command
1106 my $expandfile = qx(cygpath -m "$ENV{WINDIR}"/System32/expand.exe);
1107 chomp $expandfile;
1109 my $cabfilename = "MergeModule.CABinet";
1111 my $systemcall = $expandfile . " " . $cabfilename . " -F:$globescape* " . $unpackdir . " 2\>\&1";
1113 my $systemcall_output = `$systemcall`;
1114 my $returnvalue = $? >> 8;
1116 if ($returnvalue)
1118 $infoline = "ERROR: Could not execute $systemcall - returncode: $returnvalue - output: $systemcall_output\n";
1119 push( @installer::globals::logfileinfo, $infoline);
1120 installer::exiter::exit_program("ERROR: extracting $cabfilename failed using $systemcall", "change_file_table");
1122 else
1124 $infoline = "Success: Executed $systemcall successfully!\n";
1125 push( @installer::globals::logfileinfo, $infoline);
1128 chdir($from);
1131 # For performance reasons creating a hash with file names and rows
1132 # The content of File.idt is changed after every merge -> content cannot be saved in global hash
1133 my $merge_filetablehashref = analyze_filetable_file($filecontent, $idtfilename);
1135 my $attributes = "16384"; # Always
1137 my $filename;
1138 foreach $filename (keys %{$mergemodulehash->{'mergefilesequence'}} )
1140 my $mergefilesequence = $mergemodulehash->{'mergefilesequence'}->{$filename};
1142 if ( ! exists($merge_filetablehashref->{$filename}) ) { installer::exiter::exit_program("ERROR: Could not find file \"$filename\" in \"$idtfilename\" !", "change_file_table"); }
1143 my $filehash = $merge_filetablehashref->{$filename};
1144 my $linenumber = $filehash->{'linenumber'};
1146 # <- this line has to be changed concerning "Sequence" and "Attributes"
1147 $filehash->{'Attributes'} = $attributes;
1149 # If this is an update process, the sequence numbers have to be reused.
1150 if ( $installer::globals::updatedatabase )
1152 if ( ! exists($allupdatesequenceshashref->{$filehash->{'File'}}) ) { installer::exiter::exit_program("ERROR: Sequence not defined for file \"$filehash->{'File'}\" !", "change_file_table"); }
1153 $filehash->{'Sequence'} = $allupdatesequenceshashref->{$filehash->{'File'}};
1154 # Saving all mergemodule sequence numbers. This is important for creating ddf files
1155 $installer::globals::allmergemodulefilesequences{$filehash->{'Sequence'}} = 1;
1157 else
1159 # Important saved data: $installer::globals::lastsequence_before_merge.
1160 # This mechanism keeps the correct order inside the new cabinet file.
1161 $filehash->{'Sequence'} = $filehash->{'Sequence'} + $installer::globals::lastsequence_before_merge;
1164 my $oldline = ${$filecontent}[$linenumber];
1165 my $newline = get_new_line_for_file_table($filehash);
1166 ${$filecontent}[$linenumber] = $newline;
1168 $infoline = "Merge, replacing line:\n";
1169 push( @installer::globals::logfileinfo, $infoline);
1170 $infoline = "Old: $oldline\n";
1171 push( @installer::globals::logfileinfo, $infoline);
1172 $infoline = "New: $newline\n";
1173 push( @installer::globals::logfileinfo, $infoline);
1175 # Adding files to the files collector (but only once)
1176 if (( $installer::globals::fix_number_of_cab_files ) && ( ! $installer::globals::mergefiles_added_into_collector ))
1178 # If the number of cabinet files is kept constant,
1179 # all files from the mergemodule cabinet files will
1180 # be integrated into the last office cabinet file
1181 # (installer::globals::lastcabfilename).
1182 # Therefore the files must now be added to the filescollector,
1183 # so that they will be integrated into the ddf files.
1185 # Problem with very long filenames -> copying to shorter filenames
1186 my $newfilename = "f" . $filehash->{'Sequence'};
1187 my $completesource = $unpackdir . $installer::globals::separator . $filehash->{'File'};
1188 my $completedest = $unpackdir . $installer::globals::separator . $newfilename;
1189 installer::systemactions::copy_one_file($completesource, $completedest);
1191 my $locallastcabfilename = $installer::globals::lastcabfilename;
1192 if ( $locallastcabfilename =~ /^\s*\#/ ) { $locallastcabfilename =~ s/^\s*\#//; } # removing beginning hashes
1194 # Create new file hash for file collector
1195 my %newfile = ();
1196 $newfile{'sequencenumber'} = $filehash->{'Sequence'};
1197 $newfile{'assignedsequencenumber'} = $filehash->{'Sequence'};
1198 $newfile{'cabinet'} = $locallastcabfilename;
1199 $newfile{'sourcepath'} = $completedest;
1200 $newfile{'componentname'} = $filehash->{'Component'};
1201 $newfile{'uniquename'} = $filehash->{'File'};
1202 $newfile{'Name'} = $filehash->{'File'};
1204 # Saving in globals sequence hash
1205 $installer::globals::uniquefilenamesequence{$filehash->{'File'}} = $filehash->{'Sequence'};
1207 if ( ! -f $newfile{'sourcepath'} ) { installer::exiter::exit_program("ERROR: File \"$newfile{'sourcepath'}\" must exist!", "change_file_table"); }
1209 # Collecting all new files. Attention: This files must be included into files collector in correct order!
1210 $newfileshash{$filehash->{'Sequence'}} = \%newfile;
1211 # push(@{$filesref}, \%newfile); -> this is not the correct order
1215 # Now the files can be added to the files collector
1216 # In the case of an update process, there can be new files, that have to be added after the merge module files.
1217 # Warning: In multilingual installation sets, the files only have to be added once to the files collector!
1219 if ( ! $installer::globals::mergefiles_added_into_collector )
1221 foreach my $localsequence ( sort { $a <=> $b } keys %newfileshash ) { push(@{$filesref}, $newfileshash{$localsequence}); }
1222 if ( $installer::globals::newfilesexist ) { $filesref = sort_files_collector_for_sequence($filesref); }
1223 # $installer::globals::mergefiles_added_into_collector = 1; -> Not yet. Only if all mergemodules are merged for one language.
1226 # Saving the idt file (for every language)
1227 installer::files::save_file($idtfilename, $filecontent);
1229 return $filesref;
1232 #########################################################################
1233 # Reading the file "Director.idt". The Directory, that is defined in scp
1234 # has to be defined in this table.
1235 #########################################################################
1237 sub collect_directories
1239 my $idtfilename = "Director.idt";
1240 my $filecontent = installer::files::read_file($idtfilename);
1242 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
1244 if ( $i <= 2 ) { next; } # ignoring first three lines
1245 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
1246 # Format: Directory Directory_Parent DefaultDir
1247 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\s*$/ )
1249 $installer::globals::merge_alldirectory_hash{$1} = 1;
1251 else
1253 my $linecount = $i + 1;
1254 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "collect_directories");
1259 #########################################################################
1260 # Reading the file "Feature.idt". The Feature, that is defined in scp
1261 # has to be defined in this table.
1262 #########################################################################
1264 sub collect_feature
1266 my ($workdir) = @_;
1267 my $idtfilename = "Feature.idt";
1268 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "collect_feature"); }
1269 my $filecontent = installer::files::read_file($idtfilename);
1271 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
1273 if ( $i <= 2 ) { next; } # ignoring first three lines
1274 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
1275 # Format: Feature Feature_Parent Title Description Display Level Directory_ Attributes
1276 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
1278 $installer::globals::merge_allfeature_hash{$1} = 1;
1280 else
1282 my $linecount = $i + 1;
1283 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "collect_feature");
1288 #########################################################################
1289 # In the featurecomponent table, the new connections have to be added.
1290 #########################################################################
1292 sub change_featurecomponent_table
1294 my ($mergemodulehash, $workdir) = @_;
1296 my $infoline = "Changing content of table \"FeatureComponents\"\n";
1297 push( @installer::globals::logfileinfo, $infoline);
1299 my $idtfilename = "FeatureC.idt";
1300 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_featurecomponent_table"); }
1302 my $filecontent = installer::files::read_file($idtfilename);
1304 # Simply adding for each new component one line. The Feature has to be defined in scp project.
1305 my $feature = $mergemodulehash->{'feature'};
1307 if ( ! $installer::globals::mergefeaturecollected )
1309 collect_feature($workdir); # putting content into hash %installer::globals::merge_allfeature_hash
1310 $installer::globals::mergefeaturecollected = 1;
1313 if ( ! exists($installer::globals::merge_allfeature_hash{$feature}) )
1315 installer::exiter::exit_program("ERROR: Unknown feature defined in scp: \"$feature\" . Not defined in table \"Feature\" !", "change_featurecomponent_table");
1318 my $component;
1319 foreach $component ( keys %{$mergemodulehash->{'componentnames'}} )
1321 my $line = "$feature\t$component\n";
1322 push(@{$filecontent}, $line);
1323 $infoline = "Adding line: $line\n";
1324 push( @installer::globals::logfileinfo, $infoline);
1327 # saving file
1328 installer::files::save_file($idtfilename, $filecontent);
1331 ###############################################################################
1332 # In the components table, the conditions or attributes of merge modules should be updated
1333 ###############################################################################
1335 sub change_component_table
1337 my ($mergemodulehash, $workdir) = @_;
1339 my $infoline = "Changing content of table \"Component\"\n";
1340 push( @installer::globals::logfileinfo, $infoline);
1342 my $idtfilename = "Componen.idt";
1343 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_component_table"); }
1345 my $filecontent = installer::files::read_file($idtfilename);
1347 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
1349 my $component;
1350 foreach $component ( keys %{$mergemodulehash->{'componentnames'}} )
1352 if ( my ( $comp_, $compid_, $dir_, $attr_, $cond_, $keyp_ ) = ${$filecontent}[$i] =~ /^\s*($component)\t(.*?)\t(.+?)\t(.+?)\t(.*?)\t(.*?)\s*$/)
1354 my $newattr_ = ( $attr_ =~ /^\s*0x/ ) ? hex($attr_) : $attr_;
1355 if ( $mergemodulehash->{'attributes_add'} )
1357 $infoline = "Adding attribute(s) ($mergemodulehash->{'attributes_add'}) from scp2 to component $comp_\n";
1358 push( @installer::globals::logfileinfo, $infoline);
1359 if ( $mergemodulehash->{'attributes_add'} =~ /^\s*0x/ )
1361 $newattr_ = $newattr_ | hex($mergemodulehash->{'attributes_add'});
1363 else
1365 $newattr_ = $newattr_ | $mergemodulehash->{'attributes_add'};
1367 $infoline = "Old attribute(s): $attr_\nNew attribute(s): $newattr_\n";
1368 push( @installer::globals::logfileinfo, $infoline);
1370 my $newcond_ = $cond_;
1371 if ( $mergemodulehash->{'componentcondition'} )
1373 $infoline = "Adding condition ($mergemodulehash->{'componentcondition'}) from scp2 to component $comp_\n";
1374 push( @installer::globals::logfileinfo, $infoline);
1375 if ($cond_)
1377 $newcond_ = "($cond_) AND ($mergemodulehash->{'componentcondition'})";
1379 else
1381 $newcond_ = "$mergemodulehash->{'componentcondition'}";
1383 $infoline = "Old condition: $cond_\nNew condition: $newcond_\n";
1384 push( @installer::globals::logfileinfo, $infoline);
1386 ${$filecontent}[$i] = "$comp_\t$compid_\t$dir_\t$newattr_\t$newcond_\t$keyp_\n";
1391 # saving file
1392 installer::files::save_file($idtfilename, $filecontent);
1395 #########################################################################
1396 # In the directory table, the directory parent has to be changed,
1397 # if it is not TARGETDIR.
1398 #########################################################################
1400 sub change_directory_table
1402 my ($mergemodulehash, $workdir) = @_;
1404 # directory for MergeModule has to be defined in scp project
1405 my $scpdirectory = $mergemodulehash->{'rootdir'};
1407 if ( $scpdirectory ne "TARGETDIR" ) # TARGETDIR works fine, when using msidb.exe
1409 my $infoline = "Changing content of table \"Directory\"\n";
1410 push( @installer::globals::logfileinfo, $infoline);
1412 my $idtfilename = "Director.idt";
1413 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_directory_table"); }
1415 my $filecontent = installer::files::read_file($idtfilename);
1417 if ( ! $installer::globals::mergedirectoriescollected )
1419 collect_directories(); # putting content into %installer::globals::merge_alldirectory_hash, only first column!
1420 $installer::globals::mergedirectoriescollected = 1;
1423 if ( ! exists($installer::globals::merge_alldirectory_hash{$scpdirectory}) )
1425 installer::exiter::exit_program("ERROR: Unknown directory defined in scp: \"$scpdirectory\" . Not defined in table \"Directory\" !", "change_directory_table");
1428 # If the definition in scp is okay, now the complete content of "Director.idt" can be analyzed
1429 my $merge_directorytablehashref = analyze_directorytable_file($filecontent, $idtfilename);
1431 my $directory;
1432 foreach $directory (keys %{$mergemodulehash->{'mergedirectories'}} )
1434 if ( ! exists($merge_directorytablehashref->{$directory}) ) { installer::exiter::exit_program("ERROR: Could not find directory \"$directory\" in \"$idtfilename\" !", "change_directory_table"); }
1435 my $dirhash = $merge_directorytablehashref->{$directory};
1436 my $linenumber = $dirhash->{'linenumber'};
1438 # <- this line has to be changed concerning "Directory_Parent",
1439 # if the current value is "TARGETDIR", which is the default value from msidb.exe
1441 if ( $dirhash->{'Directory_Parent'} eq "TARGETDIR" )
1443 $dirhash->{'Directory_Parent'} = $scpdirectory;
1445 my $oldline = ${$filecontent}[$linenumber];
1446 my $newline = get_new_line_for_directory_table($dirhash);
1447 ${$filecontent}[$linenumber] = $newline;
1449 $infoline = "Merge, replacing line:\n";
1450 push( @installer::globals::logfileinfo, $infoline);
1451 $infoline = "Old: $oldline\n";
1452 push( @installer::globals::logfileinfo, $infoline);
1453 $infoline = "New: $newline\n";
1454 push( @installer::globals::logfileinfo, $infoline);
1458 # saving file
1459 installer::files::save_file($idtfilename, $filecontent);
1463 #########################################################################
1464 # In the msiassembly table, the feature has to be changed.
1465 #########################################################################
1467 sub change_msiassembly_table
1469 my ($mergemodulehash, $workdir) = @_;
1471 my $infoline = "Changing content of table \"MsiAssembly\"\n";
1472 push( @installer::globals::logfileinfo, $infoline);
1474 my $idtfilename = "MsiAssem.idt";
1475 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_msiassembly_table"); }
1477 my $filecontent = installer::files::read_file($idtfilename);
1479 # feature has to be defined in scp project
1480 my $feature = $mergemodulehash->{'feature'};
1482 if ( ! $installer::globals::mergefeaturecollected )
1484 collect_feature($workdir); # putting content into hash %installer::globals::merge_allfeature_hash
1485 $installer::globals::mergefeaturecollected = 1;
1488 if ( ! exists($installer::globals::merge_allfeature_hash{$feature}) )
1490 installer::exiter::exit_program("ERROR: Unknown feature defined in scp: \"$feature\" . Not defined in table \"Feature\" !", "change_msiassembly_table");
1493 my $merge_msiassemblytablehashref = analyze_msiassemblytable_file($filecontent, $idtfilename);
1495 my $component;
1496 foreach $component (keys %{$mergemodulehash->{'mergeassemblies'}} )
1498 if ( ! exists($merge_msiassemblytablehashref->{$component}) ) { installer::exiter::exit_program("ERROR: Could not find component \"$component\" in \"$idtfilename\" !", "change_msiassembly_table"); }
1499 my $assemblyhash = $merge_msiassemblytablehashref->{$component};
1500 my $linenumber = $assemblyhash->{'linenumber'};
1502 # <- this line has to be changed concerning "Feature"
1503 $assemblyhash->{'Feature'} = $feature;
1505 my $oldline = ${$filecontent}[$linenumber];
1506 my $newline = get_new_line_for_msiassembly_table($assemblyhash);
1507 ${$filecontent}[$linenumber] = $newline;
1509 $infoline = "Merge, replacing line:\n";
1510 push( @installer::globals::logfileinfo, $infoline);
1511 $infoline = "Old: $oldline\n";
1512 push( @installer::globals::logfileinfo, $infoline);
1513 $infoline = "New: $newline\n";
1514 push( @installer::globals::logfileinfo, $infoline);
1517 # saving file
1518 installer::files::save_file($idtfilename, $filecontent);
1521 #########################################################################
1522 # Creating file content hash
1523 #########################################################################
1525 sub make_executeidtcontent_hash
1527 my ($filecontent, $idtfilename) = @_;
1529 my %newhash = ();
1531 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
1533 if ( $i <= 2 ) { next; } # ignoring first three lines
1534 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
1535 # Format for all sequence tables: Action Condition Sequence
1536 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\s*$/ )
1538 my %onehash = ();
1539 $onehash{'Action'} = $1;
1540 $onehash{'Condition'} = $2;
1541 $onehash{'Sequence'} = $3;
1542 $newhash{$onehash{'Action'}} = \%onehash;
1544 else
1546 my $linecount = $i + 1;
1547 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "make_executeidtcontent_hash");
1551 return \%newhash;
1554 #########################################################################
1555 # Creating file content hash
1556 #########################################################################
1558 sub make_moduleexecuteidtcontent_hash
1560 my ($filecontent, $idtfilename) = @_;
1562 my %newhash = ();
1564 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
1566 if ( $i <= 2 ) { next; } # ignoring first three lines
1567 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
1568 # Format for all module sequence tables: Action Sequence BaseAction After Condition
1569 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
1571 my %onehash = ();
1572 $onehash{'Action'} = $1;
1573 $onehash{'Sequence'} = $2;
1574 $onehash{'BaseAction'} = $3;
1575 $onehash{'After'} = $4;
1576 $onehash{'Condition'} = $5;
1577 $newhash{$onehash{'Action'}} = \%onehash;
1579 else
1581 my $linecount = $i + 1;
1582 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "make_executeidtcontent_hash");
1586 return \%newhash;
1589 #########################################################################
1590 # ExecuteSequence tables need to be merged with
1591 # ModuleExecuteSequence tables created by msidb.exe.
1592 #########################################################################
1594 sub change_executesequence_table
1596 my ($mergemodulehash, $workdir, $idtfilename, $moduleidtfilename) = @_;
1598 my $infoline = "Changing content of table \"$idtfilename\"\n";
1599 push( @installer::globals::logfileinfo, $infoline);
1601 if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_executesequence_table"); }
1602 if ( ! -f $moduleidtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$moduleidtfilename\" in \"$workdir\" !", "change_executesequence_table"); }
1604 # Reading file content
1605 my $idtfilecontent = installer::files::read_file($idtfilename);
1606 my $moduleidtfilecontent = installer::files::read_file($moduleidtfilename);
1608 # Converting to hash
1609 my $idtcontenthash = make_executeidtcontent_hash($idtfilecontent, $idtfilename);
1610 my $moduleidtcontenthash = make_moduleexecuteidtcontent_hash($moduleidtfilecontent, $moduleidtfilename);
1612 # Merging
1613 foreach my $action ( keys %{$moduleidtcontenthash} )
1615 if ( exists($idtcontenthash->{$action}) ) { next; } # Action already exists, can be ignored
1617 if (( $idtfilename eq "InstallU.idt" ) && ( ! ( $action =~ /^\s*WindowsFolder\./ ))) { next; } # Only "WindowsFolder.*" CustomActions for UI Sequence table
1619 my $actionhashref = $moduleidtcontenthash->{$action};
1620 if ( $actionhashref->{'Sequence'} ne "" )
1622 # Format for all sequence tables: Action Condition Sequence
1623 my $newline = $actionhashref->{'Action'} . "\t" . $actionhashref->{'Condition'} . "\t" . $actionhashref->{'Sequence'} . "\n";
1624 # Adding to table
1625 push(@{$idtfilecontent}, $newline);
1626 # Also adding to hash
1627 my %idttablehash = ();
1628 $idttablehash{'Action'} = $actionhashref->{'Action'};
1629 $idttablehash{'Condition'} = $actionhashref->{'Condition'};
1630 $idttablehash{'Sequence'} = $actionhashref->{'Sequence'};
1631 $idtcontenthash->{$action} = \%idttablehash;
1634 else # no sequence defined, using syntax "BaseAction" and "After"
1636 my $baseactionname = $actionhashref->{'BaseAction'};
1637 # If this baseactionname is not defined in execute idt file, it is not possible to merge
1638 if ( ! exists($idtcontenthash->{$baseactionname}) ) { installer::exiter::exit_program("ERROR: Merge problem: Could not find action \"$baseactionname\" in file \"$idtfilename\" !", "change_executesequence_table"); }
1640 my $baseaction = $idtcontenthash->{$baseactionname};
1641 my $sequencenumber = $baseaction->{'Sequence'};
1642 if ( $actionhashref->{'After'} == 1 ) { $sequencenumber = $sequencenumber + 1; }
1643 else { $sequencenumber = $sequencenumber - 1; }
1645 # Format for all sequence tables: Action Condition Sequence
1646 my $newline = $actionhashref->{'Action'} . "\t" . $actionhashref->{'Condition'} . "\t" . $sequencenumber . "\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'} = $sequencenumber;
1654 $idtcontenthash->{$action} = \%idttablehash;
1658 # saving file
1659 installer::files::save_file($idtfilename, $idtfilecontent);