1#
2# This file is part of the LibreOffice project.
3#
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/.
7#
8# This file incorporates work covered by the following license notice:
9#
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 .
17#
18
19package installer::windows::mergemodule;
20
21use Cwd;
22use Digest::MD5;
23use installer::converter;
24use installer::exiter;
25use installer::files;
26use installer::globals;
27use installer::logger;
28use installer::pathanalyzer;
29use installer::remover;
30use installer::scriptitems;
31use installer::systemactions;
32use installer::worker;
33use installer::windows::idtglobal;
34use installer::windows::language;
35
36#################################################################
37# Merging the Windows MergeModules into the msi database.
38#################################################################
39
40sub merge_mergemodules_into_msi_database
41{
42    my ($mergemodules, $filesref, $msifilename, $languagestringref, $allvariables, $includepatharrayref, $allupdatesequences, $allupdatelastsequences, $allupdatediskids) = @_;
43
44    my $domerge = 0;
45    if (( $#{$mergemodules} > -1 ) && ( ! $installer::globals::languagepack ) && ( ! $installer::globals::helppack )) { $domerge = 1; }
46
47    if ( $domerge )
48    {
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");
52
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 = "";
58
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
63
64        if ( ! $installer::globals::mergemodules_analyzed )
65        {
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);
69
70            %installer::globals::mergemodules = ();
71
72            my $mergemoduledir = installer::systemactions::create_directories("mergefiles", $languagestringref);
73
74            my $mergemodule;
75            foreach $mergemodule ( @{$mergemodules} )
76            {
77                my $filename = $mergemodule->{'Name'};
78                my $mergefile = $ENV{'MSM_PATH'} . $filename;
79
80                if ( ! -f $mergefile ) { installer::exiter::exit_program("ERROR: msm file not found: $filename ($mergefile)!", "merge_mergemodules_into_msi_database"); }
81                my $completesource = $mergefile;
82
83                my $mergegid = $mergemodule->{'gid'};
84                my $workdir = $mergemoduledir . $installer::globals::separator . $mergegid;
85                if ( ! -d $workdir ) { installer::systemactions::create_directory($workdir); }
86
87                $infoline = "Analyzing Merge Module: $filename\n";
88                push( @installer::globals::logfileinfo, $infoline);
89
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"); }
94
95                # changing directory
96                my $from = cwd();
97                my $to = $workdir;
98                chdir($to);
99
100                # remove an existing cabinet file
101                if ( -f $cabinetfile ) { unlink($cabinetfile); }
102
103                # exclude cabinet file
104                $systemcall = $msidb . " -d " . $filename . " -x " . $cabinetfile;
105                $returnvalue = system($systemcall);
106
107                $infoline = "Systemcall: $systemcall\n";
108                push( @installer::globals::logfileinfo, $infoline);
109
110                if ($returnvalue)
111                {
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");
115                }
116                else
117                {
118                    $infoline = "Success: Executed $systemcall successfully!\n";
119                    push( @installer::globals::logfileinfo, $infoline);
120                }
121
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).
128
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 \\\*";
134                }
135                else
136                {
137                    $systemcall = $msidb . " -d " . $filename . " -f " . $workdir . " -e \*";
138                }
139
140                $returnvalue = system($systemcall);
141
142                $infoline = "Systemcall: $systemcall\n";
143                push( @installer::globals::logfileinfo, $infoline);
144
145                if ($returnvalue)
146                {
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");
150                }
151                else
152                {
153                    $infoline = "Success: Executed $systemcall successfully!\n";
154                    push( @installer::globals::logfileinfo, $infoline);
155                }
156
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++ )
165                {
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*$/ )
171                    {
172                        my $filename = $1;
173                        my $filesequence = $8;
174                        $mergefilesequence{$filename} = $filesequence;
175                    }
176                    else
177                    {
178                        my $linecount = $i + 1;
179                        installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "merge_mergemodules_into_msi_database");
180                    }
181                }
182
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++ )
189                {
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; }
193                }
194
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++ )
201                {
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; }
205                }
206
207                # Determining assemblies
208                $idtfilename = "MsiAssembly.idt"; # does not need to exist
209                my $hasmsiassemblies = 0;
210                my %mergeassemblies = ();
211                if ( -f $idtfilename )
212                {
213                    $filecontent = installer::files::read_file($idtfilename);
214                    $hasmsiassemblies = 1;
215                    for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
216                    {
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; }
220                    }
221                }
222
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.
226
227                # Determining name of cabinet file in installation set
228                my $cabfilename = $mergemodule->{'Cabfilename'};
229                if ( $cabfilename ) { installer::packagelist::resolve_packagevariables(\$cabfilename, $allvariables, 0); }
230
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.
234
235                my $styles = "";
236                my $removefiletable = 0;
237                if ( $mergemodule->{'Styles'} ) { $styles = $mergemodule->{'Styles'}; }
238                if ( $styles =~ /\bREMOVE_FILE_TABLE\b/ ) { $removefiletable = 1; }
239
240                if ( $removefiletable )
241                {
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"); }
247
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 \\\*";
256                    }
257                    else
258                    {
259                        $systemcall = $msidb . " -d " . $completeremovedest . " -f " . $removeworkdir . " -e \*";
260                    }
261
262                    $returnvalue = system($systemcall);
263
264                    my $idtfilename = $removeworkdir . $installer::globals::separator . "File.idt";
265                    if ( -f $idtfilename ) { unlink $idtfilename; }
266                    unlink $completeremovedest;
267
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 \\\*";
276                    }
277                    else
278                    {
279                        $systemcall = $msidb . " -c -d " . $completeremovedest . " -f " . $removeworkdir . " -i \*";
280                    }
281                    $returnvalue = system($systemcall);
282
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"); }
286                }
287
288                # Saving MergeModule info
289
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{'attributes_add'} = $mergemodule->{'Attributes_Add'};
298                $onemergemodulehash{'cabfilename'} = $cabfilename;
299                $onemergemodulehash{'feature'} = $mergemodule->{'Feature'};
300                $onemergemodulehash{'rootdir'} = $mergemodule->{'RootDir'};
301                $onemergemodulehash{'name'} = $mergemodule->{'Name'};
302                $onemergemodulehash{'mergefilesequence'} = \%mergefilesequence;
303                $onemergemodulehash{'mergeassemblies'} = \%mergeassemblies;
304                $onemergemodulehash{'mergedirectories'} = \%mergedirectories;
305                $onemergemodulehash{'hasmsiassemblies'} = $hasmsiassemblies;
306                $onemergemodulehash{'removefiletable'} = $removefiletable;
307                $onemergemodulehash{'fileidtcontent'} = \@file_idt_content;
308
309                $installer::globals::mergemodules{$mergegid} = \%onemergemodulehash;
310
311                # Collecting all cab files, to copy them into installation set
312                if ( $cabfilename ) { $installer::globals::copy_msm_files{$cabfilename} = $onemergemodulehash{'cabinetfile'}; }
313
314                chdir($from);
315            }
316
317            $infoline = "All Merge Modules successfully analyzed\n";
318            push( @installer::globals::logfileinfo, $infoline);
319
320            $installer::globals::mergemodules_analyzed = 1;
321            installer::logger::include_timestamp_into_logfile("\nPerformance Info: Analyzing MergeModules, stop");
322
323            $infoline = "\n";
324            push( @installer::globals::logfileinfo, $infoline);
325        }
326
327        # 2. Change msi database (has to be done for every msi database -> for every language)
328        #   a. Merge msm file into msi database: msidb.exe -d <msifile> -m <mergefile>
329        #   b. Extracting tables from msi database: msidb.exe -d <msifile> -f <directory> -e File Media, ...
330        #   c. Changing content of msi database in tables: File, Media, Directory, FeatureComponent
331        #   d. Including tables into msi database: msidb.exe -d <msifile> -f <directory> -i File Media, ...
332        #   e. Copying cabinet file into installation set (later)
333
334        my $counter = 0;
335        my $mergemodulegid;
336        foreach $mergemodulegid (keys %installer::globals::mergemodules)
337        {
338            my $mergemodulehash = $installer::globals::mergemodules{$mergemodulegid};
339            $counter++;
340
341            installer::logger::include_header_into_logfile("Merging Module: $mergemodulehash->{'name'}");
342            installer::logger::print_message( "\t... $mergemodulehash->{'name'} ... \n" );
343
344            $msifilename = installer::converter::make_path_conform($msifilename);
345            my $workdir = $msifilename;
346            installer::pathanalyzer::get_path_from_fullqualifiedname(\$workdir);
347
348            # changing directory
349            my $from = cwd();
350            my $to = $workdir;
351            chdir($to);
352
353            # Saving original msi database
354            installer::systemactions::copy_one_file($msifilename, "$msifilename\.$counter");
355
356            # Merging msm file, this is the "real" merge command
357
358            installer::logger::include_timestamp_into_logfile("\nPerformance Info: Before merging database");
359
360            if ( $^O =~ /cygwin/i ) {
361                # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
362                my $localmergemodulepath = $mergemodulehash->{'mergefilepath'};
363                my $localmsifilename = $msifilename;
364                $localmergemodulepath =~ s/\//\\\\/g;
365                $localmsifilename =~ s/\//\\\\/g;
366                $systemcall = $msidb . " -d " . $localmsifilename . " -m " . $localmergemodulepath;
367            }
368            else
369            {
370                $systemcall = $msidb . " -d " . $msifilename . " -m " . $mergemodulehash->{'mergefilepath'};
371            }
372            $returnvalue = system($systemcall);
373
374            $infoline = "Systemcall: $systemcall\n";
375            push( @installer::globals::logfileinfo, $infoline);
376
377            if ($returnvalue)
378            {
379                $infoline = "ERROR: Could not execute $systemcall . Returnvalue: $returnvalue!\n";
380                push( @installer::globals::logfileinfo, $infoline);
381                installer::exiter::exit_program("Could not merge msm file into database: $mergemodulehash->{'mergefilepath'}\n$infoline", "merge_mergemodules_into_msi_database");
382            }
383            else
384            {
385                $infoline = "Success: Executed $systemcall successfully!\n";
386                push( @installer::globals::logfileinfo, $infoline);
387            }
388
389            installer::logger::include_timestamp_into_logfile("\nPerformance Info: After merging database");
390
391            # Saving original idt files
392            if ( -f "File.idt" ) { installer::systemactions::rename_one_file("File.idt", "old.File.idt.$counter"); }
393            if ( -f "Media.idt" ) { installer::systemactions::rename_one_file("Media.idt", "old.Media.idt.$counter"); }
394            if ( -f "Directory.idt" ) { installer::systemactions::rename_one_file("Directory.idt", "old.Directory.idt.$counter"); }
395            if ( -f "Director.idt" ) { installer::systemactions::rename_one_file("Director.idt", "old.Director.idt.$counter"); }
396            if ( -f "FeatureComponents.idt" ) { installer::systemactions::rename_one_file("FeatureComponents.idt", "old.FeatureComponents.idt.$counter"); }
397            if ( -f "FeatureC.idt" ) { installer::systemactions::rename_one_file("FeatureC.idt", "old.FeatureC.idt.$counter"); }
398            if ( -f "MsiAssembly.idt" ) { installer::systemactions::rename_one_file("MsiAssembly.idt", "old.MsiAssembly.idt.$counter"); }
399            if ( -f "MsiAssem.idt" ) { installer::systemactions::rename_one_file("MsiAssem.idt", "old.MsiAssem.idt.$counter"); }
400            if ( -f "Componen.idt" ) { installer::systemactions::rename_one_file("Componen.idt", "old.Componen.idt.$counter"); }
401
402            # Extracting tables
403
404            installer::logger::include_timestamp_into_logfile("\nPerformance Info: Before extracting tables");
405
406            my $workingtables = "File Media Directory FeatureComponents"; # required tables
407            # Optional tables can be added now
408            if ( $mergemodulehash->{'hasmsiassemblies'} ) { $workingtables = $workingtables . " MsiAssembly"; }
409            if ( ( $mergemodulehash->{'componentcondition'} ) || ( $mergemodulehash->{'attributes_add'} ) ) { $workingtables = $workingtables . " Component"; }
410
411            # Table "Feature" has to be exported, but it is not necessary to import it.
412            if ( $^O =~ /cygwin/i ) {
413                # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
414                my $localmsifilename = $msifilename;
415                my $localworkdir = $workdir;
416                $localmsifilename =~ s/\//\\\\/g;
417                $localworkdir =~ s/\//\\\\/g;
418                $systemcall = $msidb . " -d " . $localmsifilename . " -f " . $localworkdir . " -e " . "Feature " . $workingtables;
419            }
420            else
421            {
422                $systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -e " . "Feature " . $workingtables;
423            }
424            $returnvalue = system($systemcall);
425
426            $infoline = "Systemcall: $systemcall\n";
427            push( @installer::globals::logfileinfo, $infoline);
428
429            if ($returnvalue)
430            {
431                $infoline = "ERROR: Could not execute $systemcall !\n";
432                push( @installer::globals::logfileinfo, $infoline);
433                installer::exiter::exit_program("ERROR: Could not exclude tables from msi database: $msifilename !", "merge_mergemodules_into_msi_database");
434            }
435            else
436            {
437                $infoline = "Success: Executed $systemcall successfully!\n";
438                push( @installer::globals::logfileinfo, $infoline);
439            }
440
441            installer::logger::include_timestamp_into_logfile("\nPerformance Info: After extracting tables");
442
443            # Using 8+3 table names, that are used, when tables are integrated into database. The export of tables
444            # creates idt-files, that have long names.
445
446            if ( -f "Directory.idt" ) { installer::systemactions::rename_one_file("Directory.idt", "Director.idt"); }
447            if ( -f "FeatureComponents.idt" ) { installer::systemactions::rename_one_file("FeatureComponents.idt", "FeatureC.idt"); }
448            if ( -f "MsiAssembly.idt" ) { installer::systemactions::rename_one_file("MsiAssembly.idt", "MsiAssem.idt"); }
449            if ( -f "Component.idt" ) { installer::systemactions::rename_one_file("Component.idt", "Componen.idt"); }
450
451            # Changing content of tables: File, Media, Directory, FeatureComponent, MsiAssembly, Component
452            installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing Media table");
453            change_media_table($mergemodulehash, $workdir, $mergemodulegid, $allupdatelastsequences, $allupdatediskids);
454            installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing File table");
455            $filesref = change_file_table($mergemodulehash, $workdir, $allupdatesequences, $includepatharrayref, $filesref, $mergemodulegid);
456            installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing FeatureComponent table");
457            change_featurecomponent_table($mergemodulehash, $workdir);
458            installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing Directory table");
459            change_directory_table($mergemodulehash, $workdir);
460            if ( $mergemodulehash->{'hasmsiassemblies'} )
461            {
462                installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing MsiAssembly table");
463                change_msiassembly_table($mergemodulehash, $workdir);
464            }
465
466            if ( ( $mergemodulehash->{'componentcondition'} ) || ( $mergemodulehash->{'attributes_add'} ) )
467            {
468                installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing Component table");
469                change_component_table($mergemodulehash, $workdir);
470            }
471
472            # msidb.exe does not merge InstallExecuteSequence, AdminExecuteSequence and AdvtExecuteSequence. Instead it creates
473            # new tables ModuleInstallExecuteSequence, ModuleAdminExecuteSequence and ModuleAdvtExecuteSequence that need to be
474            # merged into the three ExecuteSequences with the following process (also into InstallUISequence.idt).
475
476            # Saving original idt files
477            if ( -f "InstallE.idt" ) { installer::systemactions::rename_one_file("InstallE.idt", "old.InstallE.idt.$counter"); }
478            if ( -f "InstallU.idt" ) { installer::systemactions::rename_one_file("InstallU.idt", "old.InstallU.idt.$counter"); }
479            if ( -f "AdminExe.idt" ) { installer::systemactions::rename_one_file("AdminExe.idt", "old.AdminExe.idt.$counter"); }
480            if ( -f "AdvtExec.idt" ) { installer::systemactions::rename_one_file("AdvtExec.idt", "old.AdvtExec.idt.$counter"); }
481            if ( -f "ModuleInstallExecuteSequence.idt" ) { installer::systemactions::rename_one_file("ModuleInstallExecuteSequence.idt", "old.ModuleInstallExecuteSequence.idt.$counter"); }
482            if ( -f "ModuleAdminExecuteSequence.idt" ) { installer::systemactions::rename_one_file("ModuleAdminExecuteSequence.idt", "old.ModuleAdminExecuteSequence.idt.$counter"); }
483            if ( -f "ModuleAdvtExecuteSequence.idt" ) { installer::systemactions::rename_one_file("ModuleAdvtExecuteSequence.idt", "old.ModuleAdvtExecuteSequence.idt.$counter"); }
484
485            # Extracting tables
486            my $moduleexecutetables = "ModuleInstallExecuteSequence ModuleAdminExecuteSequence ModuleAdvtExecuteSequence"; # new tables
487            my $executetables = "InstallExecuteSequence InstallUISequence AdminExecuteSequence AdvtExecuteSequence"; # tables to be merged
488
489
490            if ( $^O =~ /cygwin/i ) {
491                # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
492                my $localmsifilename = $msifilename;
493                my $localworkdir = $workdir;
494                $localmsifilename =~ s/\//\\\\/g;
495                $localworkdir =~ s/\//\\\\/g;
496                $systemcall = $msidb . " -d " . $localmsifilename . " -f " . $localworkdir . " -e " . "Feature " . $moduleexecutetables;
497            }
498            else
499            {
500                $systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -e " . "Feature " . $moduleexecutetables;
501            }
502            $returnvalue = system($systemcall);
503
504            if ( $^O =~ /cygwin/i ) {
505                # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
506                my $localmsifilename = $msifilename;
507                my $localworkdir = $workdir;
508                $localmsifilename =~ s/\//\\\\/g;
509                $localworkdir =~ s/\//\\\\/g;
510                $systemcall = $msidb . " -d " . $localmsifilename . " -f " . $localworkdir . " -e " . "Feature " . $executetables;
511            }
512            else
513            {
514                $systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -e " . "Feature " . $executetables;
515            }
516            $returnvalue = system($systemcall);
517
518            # Using 8+3 table names, that are used, when tables are integrated into database. The export of tables
519            # creates idt-files, that have long names.
520
521            if ( -f "InstallExecuteSequence.idt" ) { installer::systemactions::rename_one_file("InstallExecuteSequence.idt", "InstallE.idt"); }
522            if ( -f "InstallUISequence.idt" ) { installer::systemactions::rename_one_file("InstallUISequence.idt", "InstallU.idt"); }
523            if ( -f "AdminExecuteSequence.idt" ) { installer::systemactions::rename_one_file("AdminExecuteSequence.idt", "AdminExe.idt"); }
524            if ( -f "AdvtExecuteSequence.idt" ) { installer::systemactions::rename_one_file("AdvtExecuteSequence.idt", "AdvtExec.idt"); }
525
526            # Merging content of tables ModuleInstallExecuteSequence, ModuleAdminExecuteSequence and ModuleAdvtExecuteSequence
527            # into tables InstallExecuteSequence, AdminExecuteSequence and AdvtExecuteSequence
528            if ( -f "ModuleInstallExecuteSequence.idt" )
529            {
530                installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing InstallExecuteSequence table");
531                change_executesequence_table($mergemodulehash, $workdir, "InstallE.idt", "ModuleInstallExecuteSequence.idt");
532                installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing InstallUISequence table");
533                change_executesequence_table($mergemodulehash, $workdir, "InstallU.idt", "ModuleInstallExecuteSequence.idt");
534            }
535
536            if ( -f "ModuleAdminExecuteSequence.idt" )
537            {
538                installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing AdminExecuteSequence table");
539                change_executesequence_table($mergemodulehash, $workdir, "AdminExe.idt", "ModuleAdminExecuteSequence.idt");
540            }
541
542            if ( -f "ModuleAdvtExecuteSequence.idt" )
543            {
544                installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing AdvtExecuteSequence table");
545                change_executesequence_table($mergemodulehash, $workdir, "AdvtExec.idt", "ModuleAdvtExecuteSequence.idt");
546            }
547
548            installer::logger::include_timestamp_into_logfile("\nPerformance Info: All tables edited");
549
550            # Including tables into msi database
551
552            installer::logger::include_timestamp_into_logfile("\nPerformance Info: Before including tables");
553
554            if ( $^O =~ /cygwin/i ) {
555                # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
556                my $localmsifilename = $msifilename;
557                my $localworkdir = $workdir;
558                $localmsifilename =~ s/\//\\\\/g;
559                $localworkdir =~ s/\//\\\\/g;
560        foreach $table (split / /, $workingtables . ' ' . $executetables) {
561          $systemcall = $msidb . " -d " . $localmsifilename . " -f " . $localworkdir . " -i " . $table;
562          my $retval = system($systemcall);
563          $infoline = "Systemcall returned $retval: $systemcall\n";
564          push( @installer::globals::logfileinfo, $infoline);
565          $returnvalue |= $retval;
566        }
567            }
568            else
569            {
570                $systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -i " . $workingtables. " " . $executetables;
571        $returnvalue = system($systemcall);
572        $infoline = "Systemcall: $systemcall\n";
573        push( @installer::globals::logfileinfo, $infoline);
574
575            }
576
577            if ($returnvalue)
578            {
579                $infoline = "ERROR: Could not execute $systemcall !\n";
580                push( @installer::globals::logfileinfo, $infoline);
581                installer::exiter::exit_program("ERROR: Could not include tables into msi database: $msifilename !", "merge_mergemodules_into_msi_database");
582            }
583            else
584            {
585                $infoline = "Success: Executed $systemcall successfully!\n";
586                push( @installer::globals::logfileinfo, $infoline);
587            }
588
589            installer::logger::include_timestamp_into_logfile("\nPerformance Info: After including tables");
590
591            chdir($from);
592        }
593
594        if ( ! $installer::globals::mergefiles_added_into_collector ) { $installer::globals::mergefiles_added_into_collector = 1; } # Now all mergemodules are merged for one language.
595
596        installer::logger::include_timestamp_into_logfile("\nPerformance Info: MergeModule into msi database, stop");
597    }
598
599    return $filesref;
600}
601
602#########################################################################
603# Analyzing the content of the media table.
604#########################################################################
605
606sub analyze_media_file
607{
608    my ($filecontent, $workdir) = @_;
609
610    my %filehash = ();
611    my $linecount = 0;
612    my $counter = 0;
613    my $filename = "Media.idt";
614
615    for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
616    {
617        if ( $i <= 2 ) { next; }                        # ignoring first three lines
618        if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
619        if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.+?)\t(.+?)\t(.*?)\s*$/ )
620        {
621            my %line = ();
622            # Format: DiskId    LastSequence    DiskPrompt  Cabinet VolumeLabel Source
623            $line{'DiskId'} = $1;
624            $line{'LastSequence'} = $2;
625            $line{'DiskPrompt'} = $3;
626            $line{'Cabinet'} = $4;
627            $line{'VolumeLabel'} = $5;
628            $line{'Source'} = $6;
629
630            $counter++;
631            $filehash{$counter} = \%line;
632        }
633        else
634        {
635            $linecount = $i + 1;
636            installer::exiter::exit_program("ERROR: Unknown line format in table \"$filename\" in \"$workdir\" (line $linecount) !", "analyze_media_file");
637        }
638    }
639
640    return \%filehash;
641}
642
643#########################################################################
644# Setting the DiskID for the new cabinet file
645#########################################################################
646
647sub get_diskid
648{
649    my ($mediafile, $allupdatediskids, $cabfilename) = @_;
650
651    my $diskid = 0;
652    my $line;
653
654    if (( $installer::globals::updatedatabase ) && ( exists($allupdatediskids->{$cabfilename}) ))
655    {
656        $diskid = $allupdatediskids->{$cabfilename};
657    }
658    else
659    {
660        foreach $line ( keys %{$mediafile} )
661        {
662            if ( $mediafile->{$line}->{'DiskId'} > $diskid ) { $diskid = $mediafile->{$line}->{'DiskId'}; }
663        }
664
665        $diskid++;
666    }
667
668    return $diskid;
669}
670
671#########################################################################
672# Setting the global LastSequence variable
673#########################################################################
674
675sub set_current_last_sequence
676{
677    my ($mediafile) = @_;
678
679    my $lastsequence = 0;
680    my $line;
681    foreach $line ( keys %{$mediafile} )
682    {
683        if ( $mediafile->{$line}->{'LastSequence'} > $lastsequence ) { $lastsequence = $mediafile->{$line}->{'LastSequence'}; }
684    }
685
686    $installer::globals::lastsequence_before_merge = $lastsequence;
687}
688
689#########################################################################
690# Setting the LastSequence for the new cabinet file
691#########################################################################
692
693sub get_lastsequence
694{
695    my ($mergemodulehash, $allupdatelastsequences) = @_;
696
697    my $lastsequence = 0;
698
699    if (( $installer::globals::updatedatabase ) && ( exists($allupdatelastsequences->{$mergemodulehash->{'cabfilename'}}) ))
700    {
701        $lastsequence = $allupdatelastsequences->{$mergemodulehash->{'cabfilename'}};
702    }
703    else
704    {
705        $lastsequence = $installer::globals::lastsequence_before_merge + $mergemodulehash->{'filenumber'};
706    }
707
708    return $lastsequence;
709}
710
711#########################################################################
712# Setting the DiskPrompt for the new cabinet file
713#########################################################################
714
715sub get_diskprompt
716{
717    my ($mediafile) = @_;
718
719    my $diskprompt = "";
720    my $line;
721    foreach $line ( keys %{$mediafile} )
722    {
723        if ( exists($mediafile->{$line}->{'DiskPrompt'}) )
724        {
725            $diskprompt = $mediafile->{$line}->{'DiskPrompt'};
726            last;
727        }
728    }
729
730    return $diskprompt;
731}
732
733#########################################################################
734# Setting the VolumeLabel for the new cabinet file
735#########################################################################
736
737sub get_volumelabel
738{
739    my ($mediafile) = @_;
740
741    my $volumelabel = "";
742    my $line;
743    foreach $line ( keys %{$mediafile} )
744    {
745        if ( exists($mediafile->{$line}->{'VolumeLabel'}) )
746        {
747            $volumelabel = $mediafile->{$line}->{'VolumeLabel'};
748            last;
749        }
750    }
751
752    return $volumelabel;
753}
754
755#########################################################################
756# Setting the Source for the new cabinet file
757#########################################################################
758
759sub get_source
760{
761    my ($mediafile) = @_;
762
763    my $source = "";
764    my $line;
765    foreach $line ( keys %{$mediafile} )
766    {
767        if ( exists($mediafile->{$line}->{'Source'}) )
768        {
769            $diskprompt = $mediafile->{$line}->{'Source'};
770            last;
771        }
772    }
773
774    return $source;
775}
776
777#########################################################################
778# For each Merge Module one new line has to be included into the
779# media table.
780#########################################################################
781
782sub create_new_media_line
783{
784    my ($mergemodulehash, $mediafile, $allupdatelastsequences, $allupdatediskids) = @_;
785
786    my $diskid = get_diskid($mediafile, $allupdatediskids, $mergemodulehash->{'cabfilename'});
787    my $lastsequence = get_lastsequence($mergemodulehash, $allupdatelastsequences);
788    my $diskprompt = get_diskprompt($mediafile);
789    my $cabinet = $mergemodulehash->{'cabfilename'};
790    my $volumelabel = get_volumelabel($mediafile);
791    my $source = get_source($mediafile);
792
793    if ( $installer::globals::include_cab_in_msi ) { $cabinet = "\#" . $cabinet; }
794
795    my $newline = "$diskid\t$lastsequence\t$diskprompt\t$cabinet\t$volumelabel\t$source\n";
796
797    return $newline;
798}
799
800#########################################################################
801# Setting the last diskid in media table.
802#########################################################################
803
804sub get_last_diskid
805{
806    my ($mediafile) = @_;
807
808    my $lastdiskid = 0;
809    my $line;
810    foreach $line ( keys %{$mediafile} )
811    {
812        if ( $mediafile->{$line}->{'DiskId'} > $lastdiskid ) { $lastdiskid = $mediafile->{$line}->{'DiskId'}; }
813    }
814
815    return $lastdiskid;
816}
817
818#########################################################################
819# Setting global variable for last cab file name.
820#########################################################################
821
822sub set_last_cabfile_name
823{
824    my ($mediafile, $lastdiskid) = @_;
825
826    my $line;
827    foreach $line ( keys %{$mediafile} )
828    {
829        if ( $mediafile->{$line}->{'DiskId'} == $lastdiskid ) { $installer::globals::lastcabfilename = $mediafile->{$line}->{'Cabinet'}; }
830    }
831    my $infoline = "Setting last cabinet file: $installer::globals::lastcabfilename\n";
832    push( @installer::globals::logfileinfo, $infoline);
833}
834
835#########################################################################
836# In the media table the new cabinet file has to be added or the
837# number of the last cabinet file has to be increased.
838#########################################################################
839
840sub change_media_table
841{
842    my ( $mergemodulehash, $workdir, $mergemodulegid, $allupdatelastsequences, $allupdatediskids ) = @_;
843
844    my $infoline = "Changing content of table \"Media\"\n";
845    push( @installer::globals::logfileinfo, $infoline);
846
847    my $filename = "Media.idt";
848    if ( ! -f $filename ) { installer::exiter::exit_program("ERROR: Could not find file \"$filename\" in \"$workdir\" !", "change_media_table"); }
849
850    my $filecontent = installer::files::read_file($filename);
851    my $mediafile = analyze_media_file($filecontent, $workdir);
852    set_current_last_sequence($mediafile);
853
854    if ( $installer::globals::fix_number_of_cab_files )
855    {
856        # Determining the line with the highest sequencenumber. That file needs to be updated.
857        my $lastdiskid = get_last_diskid($mediafile);
858        if ( $installer::globals::lastcabfilename eq "" ) { set_last_cabfile_name($mediafile, $lastdiskid); }
859        my $newmaxsequencenumber = $installer::globals::lastsequence_before_merge + $mergemodulehash->{'filenumber'};
860
861        for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
862        {
863            if ( $i <= 2 ) { next; }                        # ignoring first three lines
864            if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
865            if ( ${$filecontent}[$i] =~ /^\s*(\Q$lastdiskid\E\t)\Q$installer::globals::lastsequence_before_merge\E(\t.*)$/ )
866            {
867                my $start = $1;
868                my $final = $2;
869                $infoline = "Merge: Old line in media table: ${$filecontent}[$i]\n";
870                push( @installer::globals::logfileinfo, $infoline);
871                my $newline = $start . $newmaxsequencenumber . $final . "\n";
872                ${$filecontent}[$i] = $newline;
873                $infoline = "Merge: Changed line in media table: ${$filecontent}[$i]\n";
874                push( @installer::globals::logfileinfo, $infoline);
875            }
876        }
877    }
878    else
879    {
880        # the new line is identical for all localized databases, but has to be created for each MergeModule ($mergemodulegid)
881        if ( ! exists($installer::globals::merge_media_line{$mergemodulegid}) )
882        {
883            $installer::globals::merge_media_line{$mergemodulegid} = create_new_media_line($mergemodulehash, $mediafile, $allupdatelastsequences, $allupdatediskids);
884        }
885
886        $infoline = "Adding line: $installer::globals::merge_media_line{$mergemodulegid}\n";
887        push( @installer::globals::logfileinfo, $infoline);
888
889        # adding new line
890        push(@{$filecontent}, $installer::globals::merge_media_line{$mergemodulegid});
891    }
892
893    # saving file
894    installer::files::save_file($filename, $filecontent);
895}
896
897#########################################################################
898# Putting the directory table content into a hash.
899#########################################################################
900
901sub analyze_directorytable_file
902{
903    my ($filecontent, $idtfilename) = @_;
904
905    my %dirhash = ();
906    # Iterating over the file content
907    for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
908    {
909        if ( $i <= 2 ) { next; }                        # ignoring first three lines
910        if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
911        if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\s*$/ )
912        {
913            my %line = ();
914            # Format: Directory Directory_Parent    DefaultDir
915            $line{'Directory'} = $1;
916            $line{'Directory_Parent'} = $2;
917            $line{'DefaultDir'} = $3;
918            $line{'linenumber'} = $i; # saving also the line number for direct access
919
920            my $uniquekey = $line{'Directory'};
921            $dirhash{$uniquekey} = \%line;
922        }
923        else
924        {
925            my $linecount = $i + 1;
926            installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "analyze_directorytable_file");
927        }
928    }
929
930    return \%dirhash;
931}
932
933#########################################################################
934# Putting the msi assembly table content into a hash.
935#########################################################################
936
937sub analyze_msiassemblytable_file
938{
939    my ($filecontent, $idtfilename) = @_;
940
941    my %assemblyhash = ();
942    # Iterating over the file content
943    for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
944    {
945        if ( $i <= 2 ) { next; }                        # ignoring first three lines
946        if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
947        if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.*?)\t(.*?)\s*$/ )
948        {
949            my %line = ();
950            # Format: Component_    Feature_    File_Manifest   File_Application    Attributes
951            $line{'Component'} = $1;
952            $line{'Feature'} = $2;
953            $line{'File_Manifest'} = $3;
954            $line{'File_Application'} = $4;
955            $line{'Attributes'} = $5;
956            $line{'linenumber'} = $i; # saving also the line number for direct access
957
958            my $uniquekey = $line{'Component'};
959            $assemblyhash{$uniquekey} = \%line;
960        }
961        else
962        {
963            my $linecount = $i + 1;
964            installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "analyze_msiassemblytable_file");
965        }
966    }
967
968    return \%assemblyhash;
969}
970
971#########################################################################
972# Putting the file table content into a hash.
973#########################################################################
974
975sub analyze_filetable_file
976{
977    my ( $filecontent, $idtfilename ) = @_;
978
979    my %filehash = ();
980    # Iterating over the file content
981    for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
982    {
983        if ( $i <= 2 ) { next; }                        # ignoring first three lines
984        if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
985        if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.+?)\s*$/ )
986        {
987            my %line = ();
988            # Format: File  Component_  FileName    FileSize    Version Language    Attributes  Sequence
989            $line{'File'} = $1;
990            $line{'Component'} = $2;
991            $line{'FileName'} = $3;
992            $line{'FileSize'} = $4;
993            $line{'Version'} = $5;
994            $line{'Language'} = $6;
995            $line{'Attributes'} = $7;
996            $line{'Sequence'} = $8;
997            $line{'linenumber'} = $i; # saving also the line number for direct access
998
999            my $uniquekey = $line{'File'};
1000            $filehash{$uniquekey} = \%line;
1001        }
1002        else
1003        {
1004            my $linecount = $i + 1;
1005            installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "analyze_filetable_file");
1006        }
1007    }
1008
1009    return \%filehash;
1010}
1011
1012#########################################################################
1013# Creating a new line for the directory table.
1014#########################################################################
1015
1016sub get_new_line_for_directory_table
1017{
1018    my ($dir) = @_;
1019
1020    my $newline = "$dir->{'Directory'}\t$dir->{'Directory_Parent'}\t$dir->{'DefaultDir'}\n";
1021
1022    return $newline;
1023}
1024
1025#########################################################################
1026# Creating a new line for the file table.
1027#########################################################################
1028
1029sub get_new_line_for_file_table
1030{
1031    my ($file) = @_;
1032
1033    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
1035    return $newline;
1036}
1037
1038#########################################################################
1039# Creating a new line for the msiassembly table.
1040#########################################################################
1041
1042sub get_new_line_for_msiassembly_table
1043{
1044    my ($assembly) = @_;
1045
1046    my $newline = "$assembly->{'Component'}\t$assembly->{'Feature'}\t$assembly->{'File_Manifest'}\t$assembly->{'File_Application'}\t$assembly->{'Attributes'}\n";
1047
1048    return $newline;
1049}
1050
1051#########################################################################
1052# Sorting the files collector, if there are files, following
1053# the merge module files.
1054#########################################################################
1055
1056sub sort_files_collector_for_sequence
1057{
1058    my ($filesref) = @_;
1059
1060    my @sortarray = ();
1061    my %helphash = ();
1062
1063    for ( my $i = 0; $i <= $#{$filesref}; $i++ )
1064    {
1065        my $onefile = ${$filesref}[$i];
1066        if ( ! exists($onefile->{'sequencenumber'}) ) { installer::exiter::exit_program("ERROR: Could not find sequencenumber for file: $onefile->{'uniquename'} !", "sort_files_collector_for_sequence"); }
1067        my $sequence = $onefile->{'sequencenumber'};
1068        $helphash{$sequence} = $onefile;
1069    }
1070
1071    foreach my $seq ( sort { $a <=> $b } keys %helphash ) { push(@sortarray, $helphash{$seq}); }
1072
1073    return \@sortarray;
1074}
1075
1076#########################################################################
1077# In the file table "Sequence" and "Attributes" have to be changed.
1078#########################################################################
1079
1080sub change_file_table
1081{
1082    my ($mergemodulehash, $workdir, $allupdatesequenceshashref, $includepatharrayref, $filesref, $mergemodulegid) = @_;
1083
1084    my $infoline = "Changing content of table \"File\"\n";
1085    push( @installer::globals::logfileinfo, $infoline);
1086
1087    my $idtfilename = "File.idt";
1088    if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_file_table"); }
1089
1090    my $filecontent = installer::files::read_file($idtfilename);
1091
1092    # If File.idt needed to be removed before the msm database was merged into the msi database,
1093    # now it is time to add the content into File.idt
1094    if ( $mergemodulehash->{'removefiletable'} )
1095    {
1096        for ( my $i = 0; $i <= $#{$mergemodulehash->{'fileidtcontent'}}; $i++ )
1097        {
1098            push(@{$filecontent}, ${$mergemodulehash->{'fileidtcontent'}}[$i]);
1099        }
1100    }
1101
1102    # Unpacking the MergeModule.CABinet (only once)
1103    # Unpacking into temp directory. Warning: expand.exe has problems with very long unpack directories.
1104
1105    my $empty = "";
1106    my $unpackdir = installer::systemactions::create_directories("cab", \$empty);
1107    push(@installer::globals::removedirs, $unpackdir);
1108    $unpackdir = $unpackdir . $installer::globals::separator . $mergemodulegid;
1109
1110    my %newfileshash = ();
1111    if (( $installer::globals::fix_number_of_cab_files ) && ( ! $installer::globals::mergefiles_added_into_collector ))
1112    {
1113        if ( ! -d $unpackdir ) { installer::systemactions::create_directory($unpackdir); }
1114
1115        # changing directory
1116        my $from = cwd();
1117        my $to = $mergemodulehash->{'workdir'};
1118         if ( $^O =~ /cygwin/i ) {
1119            $to = qx(cygpath -u "$to");
1120            chomp $to;
1121        }
1122
1123        chdir($to) || die "Could not chdir to \"$to\"\n";
1124
1125        # Unpack the cab file, so that in can be included into the last office cabinet file.
1126        # Not using cabarc.exe from cabsdk for unpacking cabinet files, but "expand.exe" that
1127        # should be available on every Windows system.
1128
1129        $infoline = "Unpacking cabinet file: $mergemodulehash->{'cabinetfile'}\n";
1130        push( @installer::globals::logfileinfo, $infoline);
1131
1132        # Avoid the Cygwin expand command
1133        my $expandfile = "expand.exe";  # Has to be in the path
1134         if ( $^O =~ /cygwin/i ) {
1135            $expandfile = qx(cygpath -u "$ENV{WINDIR}"/System32/expand.exe);
1136            chomp $expandfile;
1137        }
1138
1139        my $cabfilename = "MergeModule.CABinet";
1140
1141        my $systemcall = "";
1142        if ( $^O =~ /cygwin/i ) {
1143            my $localunpackdir = qx(cygpath -m "$unpackdir");
1144            chomp $localunpackdir;
1145            $systemcall = $expandfile . " " . $cabfilename . " -F:\\\* " . $localunpackdir;
1146        }
1147        else
1148        {
1149            $systemcall = $expandfile . " " . $cabfilename . " -F:\* " . $unpackdir . " 2\>\&1";
1150        }
1151
1152        my $returnvalue = system($systemcall);
1153
1154        $infoline = "Systemcall: $systemcall\n";
1155        push( @installer::globals::logfileinfo, $infoline);
1156
1157        if ($returnvalue)
1158        {
1159            $infoline = "ERROR: Could not execute $systemcall !\n";
1160            push( @installer::globals::logfileinfo, $infoline);
1161            installer::exiter::exit_program("ERROR: Could not extract cabinet file: $mergemodulehash->{'cabinetfile'} !", "change_file_table");
1162        }
1163        else
1164        {
1165            $infoline = "Success: Executed $systemcall successfully!\n";
1166            push( @installer::globals::logfileinfo, $infoline);
1167        }
1168
1169        chdir($from);
1170    }
1171
1172    # For performance reasons creating a hash with file names and rows
1173    # The content of File.idt is changed after every merge -> content cannot be saved in global hash
1174    $merge_filetablehashref = analyze_filetable_file($filecontent, $idtfilename);
1175
1176    my $attributes = "16384"; # Always
1177
1178    my $filename;
1179    foreach $filename (keys %{$mergemodulehash->{'mergefilesequence'}} )
1180    {
1181        my $mergefilesequence = $mergemodulehash->{'mergefilesequence'}->{$filename};
1182
1183        if ( ! exists($merge_filetablehashref->{$filename}) ) { installer::exiter::exit_program("ERROR: Could not find file \"$filename\" in \"$idtfilename\" !", "change_file_table"); }
1184        my $filehash = $merge_filetablehashref->{$filename};
1185        my $linenumber = $filehash->{'linenumber'};
1186
1187        # <- this line has to be changed concerning "Sequence" and "Attributes"
1188        $filehash->{'Attributes'} = $attributes;
1189
1190        # If this is an update process, the sequence numbers have to be reused.
1191        if ( $installer::globals::updatedatabase )
1192        {
1193            if ( ! exists($allupdatesequenceshashref->{$filehash->{'File'}}) ) { installer::exiter::exit_program("ERROR: Sequence not defined for file \"$filehash->{'File'}\" !", "change_file_table"); }
1194            $filehash->{'Sequence'} = $allupdatesequenceshashref->{$filehash->{'File'}};
1195            # Saving all mergemodule sequence numbers. This is important for creating ddf files
1196            $installer::globals::allmergemodulefilesequences{$filehash->{'Sequence'}} = 1;
1197        }
1198        else
1199        {
1200            # Important saved data: $installer::globals::lastsequence_before_merge.
1201            # This mechanism keeps the correct order inside the new cabinet file.
1202            $filehash->{'Sequence'} = $filehash->{'Sequence'} + $installer::globals::lastsequence_before_merge;
1203        }
1204
1205        my $oldline = ${$filecontent}[$linenumber];
1206        my $newline = get_new_line_for_file_table($filehash);
1207        ${$filecontent}[$linenumber] = $newline;
1208
1209        $infoline = "Merge, replacing line:\n";
1210        push( @installer::globals::logfileinfo, $infoline);
1211        $infoline = "Old: $oldline\n";
1212        push( @installer::globals::logfileinfo, $infoline);
1213        $infoline = "New: $newline\n";
1214        push( @installer::globals::logfileinfo, $infoline);
1215
1216        # Adding files to the files collector (but only once)
1217        if (( $installer::globals::fix_number_of_cab_files ) && ( ! $installer::globals::mergefiles_added_into_collector ))
1218        {
1219            # If the number of cabinet files is kept constant,
1220            # all files from the mergemodule cabinet files will
1221            # be integrated into the last office cabinet file
1222            # (installer::globals::lastcabfilename).
1223            # Therefore the files must now be added to the filescollector,
1224            # so that they will be integrated into the ddf files.
1225
1226            # Problem with very long filenames -> copying to shorter filenames
1227            my $newfilename = "f" . $filehash->{'Sequence'};
1228            my $completesource = $unpackdir . $installer::globals::separator . $filehash->{'File'};
1229            my $completedest = $unpackdir . $installer::globals::separator . $newfilename;
1230            installer::systemactions::copy_one_file($completesource, $completedest);
1231
1232            my $locallastcabfilename = $installer::globals::lastcabfilename;
1233            if ( $locallastcabfilename =~ /^\s*\#/ ) { $locallastcabfilename =~ s/^\s*\#//; }  # removing beginning hashes
1234
1235            # Create new file hash for file collector
1236            my %newfile = ();
1237            $newfile{'sequencenumber'} = $filehash->{'Sequence'};
1238            $newfile{'assignedsequencenumber'} = $filehash->{'Sequence'};
1239            $newfile{'cabinet'} = $locallastcabfilename;
1240            $newfile{'sourcepath'} = $completedest;
1241            $newfile{'componentname'} = $filehash->{'Component'};
1242            $newfile{'uniquename'} = $filehash->{'File'};
1243            $newfile{'Name'} = $filehash->{'File'};
1244
1245            # Saving in globals sequence hash
1246            $installer::globals::uniquefilenamesequence{$filehash->{'File'}} = $filehash->{'Sequence'};
1247
1248            if ( ! -f $newfile{'sourcepath'} ) { installer::exiter::exit_program("ERROR: File \"$newfile{'sourcepath'}\" must exist!", "change_file_table"); }
1249
1250            # Collecting all new files. Attention: This files must be included into files collector in correct order!
1251            $newfileshash{$filehash->{'Sequence'}} = \%newfile;
1252            # push(@{$filesref}, \%newfile); -> this is not the correct order
1253        }
1254    }
1255
1256    # Now the files can be added to the files collector
1257    # In the case of an update process, there can be new files, that have to be added after the merge module files.
1258    # Warning: In multilingual installation sets, the files only have to be added once to the files collector!
1259
1260    if ( ! $installer::globals::mergefiles_added_into_collector )
1261    {
1262        foreach my $localsequence ( sort { $a <=> $b } keys %newfileshash ) { push(@{$filesref}, $newfileshash{$localsequence}); }
1263        if ( $installer::globals::newfilesexist ) { $filesref = sort_files_collector_for_sequence($filesref); }
1264        # $installer::globals::mergefiles_added_into_collector = 1; -> Not yet. Only if all mergemodules are merged for one language.
1265    }
1266
1267    # Saving the idt file (for every language)
1268    installer::files::save_file($idtfilename, $filecontent);
1269
1270    return $filesref;
1271}
1272
1273#########################################################################
1274# Reading the file "Director.idt". The Directory, that is defined in scp
1275# has to be defined in this table.
1276#########################################################################
1277
1278sub collect_directories
1279{
1280    my $idtfilename = "Director.idt";
1281    my $filecontent = installer::files::read_file($idtfilename);
1282
1283    for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
1284    {
1285        if ( $i <= 2 ) { next; }                        # ignoring first three lines
1286        if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
1287        # Format: Directory Directory_Parent    DefaultDir
1288        if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\s*$/ )
1289        {
1290            $installer::globals::merge_alldirectory_hash{$1} = 1;
1291        }
1292        else
1293        {
1294            my $linecount = $i + 1;
1295            installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "collect_directories");
1296        }
1297    }
1298}
1299
1300#########################################################################
1301# Reading the file "Feature.idt". The Feature, that is defined in scp
1302# has to be defined in this table.
1303#########################################################################
1304
1305sub collect_feature
1306{
1307    my $idtfilename = "Feature.idt";
1308    if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "collect_feature"); }
1309    my $filecontent = installer::files::read_file($idtfilename);
1310
1311    for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
1312    {
1313        if ( $i <= 2 ) { next; }                        # ignoring first three lines
1314        if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
1315        # Format: Feature   Feature_Parent  Title   Description Display Level   Directory_  Attributes
1316        if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
1317        {
1318            $installer::globals::merge_allfeature_hash{$1} = 1;
1319        }
1320        else
1321        {
1322            my $linecount = $i + 1;
1323            installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "collect_feature");
1324        }
1325    }
1326}
1327
1328#########################################################################
1329# In the featurecomponent table, the new connections have to be added.
1330#########################################################################
1331
1332sub change_featurecomponent_table
1333{
1334    my ($mergemodulehash, $workdir) = @_;
1335
1336    my $infoline = "Changing content of table \"FeatureComponents\"\n";
1337    push( @installer::globals::logfileinfo, $infoline);
1338
1339    my $idtfilename = "FeatureC.idt";
1340    if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_featurecomponent_table"); }
1341
1342    my $filecontent = installer::files::read_file($idtfilename);
1343
1344    # Simply adding for each new component one line. The Feature has to be defined in scp project.
1345    my $feature = $mergemodulehash->{'feature'};
1346
1347    if ( ! $installer::globals::mergefeaturecollected )
1348    {
1349        collect_feature(); # putting content into hash %installer::globals::merge_allfeature_hash
1350        $installer::globals::mergefeaturecollected = 1;
1351    }
1352
1353    if ( ! exists($installer::globals::merge_allfeature_hash{$feature}) )
1354    {
1355        installer::exiter::exit_program("ERROR: Unknown feature defined in scp: \"$feature\" . Not defined in table \"Feature\" !", "change_featurecomponent_table");
1356    }
1357
1358    my $component;
1359    foreach $component ( keys %{$mergemodulehash->{'componentnames'}} )
1360    {
1361        my $line = "$feature\t$component\n";
1362        push(@{$filecontent}, $line);
1363        $infoline = "Adding line: $line\n";
1364        push( @installer::globals::logfileinfo, $infoline);
1365    }
1366
1367    # saving file
1368    installer::files::save_file($idtfilename, $filecontent);
1369}
1370
1371###############################################################################
1372# In the components table, the conditions or attributes of merge modules should be updated
1373###############################################################################
1374
1375sub change_component_table
1376{
1377    my ($mergemodulehash, $workdir) = @_;
1378
1379    my $infoline = "Changing content of table \"Component\"\n";
1380    push( @installer::globals::logfileinfo, $infoline);
1381
1382    my $idtfilename = "Componen.idt";
1383    if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_component_table"); }
1384
1385    my $filecontent = installer::files::read_file($idtfilename);
1386
1387    for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
1388    {
1389        my $component;
1390        foreach $component ( keys %{$mergemodulehash->{'componentnames'}} )
1391        {
1392            if ( my ( $comp_, $compid_, $dir_, $attr_, $cond_, $keyp_ ) = ${$filecontent}[$i] =~ /^\s*($component)\t(.*?)\t(.+?)\t(.+?)\t(.*?)\t(.*?)\s*$/)
1393            {
1394                my $newattr_ = ( $attr_ =~ /^\s*0x/ ) ? hex($attr_) : $attr_;
1395                if ( $mergemodulehash->{'attributes_add'} )
1396                {
1397                    $infoline = "Adding attribute(s) ($mergemodulehash->{'attributes_add'}) from scp2 to component $comp_\n";
1398                    push( @installer::globals::logfileinfo, $infoline);
1399                    if ( $mergemodulehash->{'attributes_add'} =~ /^\s*0x/ )
1400                    {
1401                        $newattr_ = $newattr_ | hex($mergemodulehash->{'attributes_add'});
1402                    }
1403                    else
1404                    {
1405                        $newattr_ = $newattr_ | $mergemodulehash->{'attributes_add'};
1406                    }
1407                    $infoline = "Old attribute(s): $attr_\nNew attribute(s): $newattr_\n";
1408                    push( @installer::globals::logfileinfo, $infoline);
1409                }
1410                my $newcond_ = $cond_;
1411                if ( $mergemodulehash->{'componentcondition'} )
1412                {
1413                    $infoline = "Adding condition ($mergemodulehash->{'componentcondition'}) from scp2 to component $comp_\n";
1414                    push( @installer::globals::logfileinfo, $infoline);
1415                    if ($cond_)
1416                    {
1417                        $newcond_ = "($cond_) AND ($mergemodulehash->{'componentcondition'})";
1418                    }
1419                    else
1420                    {
1421                        $newcond_ = "$mergemodulehash->{'componentcondition'}";
1422                    }
1423                    $infoline = "Old condition: $cond_\nNew condition: $newcond_\n";
1424                    push( @installer::globals::logfileinfo, $infoline);
1425                }
1426                ${$filecontent}[$i] = "$comp_\t$compid_\t$dir_\t$newattr_\t$newcond_\t$keyp_\n";
1427            }
1428        }
1429    }
1430
1431    # saving file
1432    installer::files::save_file($idtfilename, $filecontent);
1433}
1434
1435#########################################################################
1436# In the directory table, the directory parent has to be changed,
1437# if it is not TARGETDIR.
1438#########################################################################
1439
1440sub change_directory_table
1441{
1442    my ($mergemodulehash, $workdir) = @_;
1443
1444    # directory for MergeModule has to be defined in scp project
1445    my $scpdirectory = $mergemodulehash->{'rootdir'};
1446
1447    if ( $scpdirectory ne "TARGETDIR" )  # TARGETDIR works fine, when using msidb.exe
1448    {
1449        my $infoline = "Changing content of table \"Directory\"\n";
1450        push( @installer::globals::logfileinfo, $infoline);
1451
1452        my $idtfilename = "Director.idt";
1453        if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_directory_table"); }
1454
1455        my $filecontent = installer::files::read_file($idtfilename);
1456
1457        if ( ! $installer::globals::mergedirectoriescollected )
1458        {
1459            collect_directories(); # putting content into %installer::globals::merge_alldirectory_hash, only first column!
1460            $installer::globals::mergedirectoriescollected = 1;
1461        }
1462
1463        if ( ! exists($installer::globals::merge_alldirectory_hash{$scpdirectory}) )
1464        {
1465            installer::exiter::exit_program("ERROR: Unknown directory defined in scp: \"$scpdirectory\" . Not defined in table \"Directory\" !", "change_directory_table");
1466        }
1467
1468        # If the definition in scp is okay, now the complete content of "Director.idt" can be analyzed
1469        my $merge_directorytablehashref = analyze_directorytable_file($filecontent, $idtfilename);
1470
1471        my $directory;
1472        foreach $directory (keys %{$mergemodulehash->{'mergedirectories'}} )
1473        {
1474            if ( ! exists($merge_directorytablehashref->{$directory}) ) { installer::exiter::exit_program("ERROR: Could not find directory \"$directory\" in \"$idtfilename\" !", "change_directory_table"); }
1475            my $dirhash = $merge_directorytablehashref->{$directory};
1476            my $linenumber = $dirhash->{'linenumber'};
1477
1478            # <- this line has to be changed concerning "Directory_Parent",
1479            # if the current value is "TARGETDIR", which is the default value from msidb.exe
1480
1481            if ( $dirhash->{'Directory_Parent'} eq "TARGETDIR" )
1482            {
1483                $dirhash->{'Directory_Parent'} = $scpdirectory;
1484
1485                my $oldline = ${$filecontent}[$linenumber];
1486                my $newline = get_new_line_for_directory_table($dirhash);
1487                ${$filecontent}[$linenumber] = $newline;
1488
1489                $infoline = "Merge, replacing line:\n";
1490                push( @installer::globals::logfileinfo, $infoline);
1491                $infoline = "Old: $oldline\n";
1492                push( @installer::globals::logfileinfo, $infoline);
1493                $infoline = "New: $newline\n";
1494                push( @installer::globals::logfileinfo, $infoline);
1495            }
1496        }
1497
1498        # saving file
1499        installer::files::save_file($idtfilename, $filecontent);
1500    }
1501}
1502
1503#########################################################################
1504# In the msiassembly table, the feature has to be changed.
1505#########################################################################
1506
1507sub change_msiassembly_table
1508{
1509    my ($mergemodulehash, $workdir) = @_;
1510
1511    my $infoline = "Changing content of table \"MsiAssembly\"\n";
1512    push( @installer::globals::logfileinfo, $infoline);
1513
1514    my $idtfilename = "MsiAssem.idt";
1515    if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_msiassembly_table"); }
1516
1517    my $filecontent = installer::files::read_file($idtfilename);
1518
1519    # feature has to be defined in scp project
1520    my $feature = $mergemodulehash->{'feature'};
1521
1522    if ( ! $installer::globals::mergefeaturecollected )
1523    {
1524        collect_feature();  # putting content into hash %installer::globals::merge_allfeature_hash
1525        $installer::globals::mergefeaturecollected = 1;
1526    }
1527
1528    if ( ! exists($installer::globals::merge_allfeature_hash{$feature}) )
1529    {
1530        installer::exiter::exit_program("ERROR: Unknown feature defined in scp: \"$feature\" . Not defined in table \"Feature\" !", "change_msiassembly_table");
1531    }
1532
1533    my $merge_msiassemblytablehashref = analyze_msiassemblytable_file($filecontent, $idtfilename);
1534
1535    my $component;
1536    foreach $component (keys %{$mergemodulehash->{'mergeassemblies'}} )
1537    {
1538        if ( ! exists($merge_msiassemblytablehashref->{$component}) ) { installer::exiter::exit_program("ERROR: Could not find component \"$component\" in \"$idtfilename\" !", "change_msiassembly_table"); }
1539        my $assemblyhash = $merge_msiassemblytablehashref->{$component};
1540        my $linenumber = $assemblyhash->{'linenumber'};
1541
1542        # <- this line has to be changed concerning "Feature"
1543        $assemblyhash->{'Feature'} = $feature;
1544
1545        my $oldline = ${$filecontent}[$linenumber];
1546        my $newline = get_new_line_for_msiassembly_table($assemblyhash);
1547        ${$filecontent}[$linenumber] = $newline;
1548
1549        $infoline = "Merge, replacing line:\n";
1550        push( @installer::globals::logfileinfo, $infoline);
1551        $infoline = "Old: $oldline\n";
1552        push( @installer::globals::logfileinfo, $infoline);
1553        $infoline = "New: $newline\n";
1554        push( @installer::globals::logfileinfo, $infoline);
1555    }
1556
1557    # saving file
1558    installer::files::save_file($idtfilename, $filecontent);
1559}
1560
1561#########################################################################
1562# Creating file content hash
1563#########################################################################
1564
1565sub make_executeidtcontent_hash
1566{
1567    my ($filecontent, $idtfilename) = @_;
1568
1569    my %newhash = ();
1570
1571    for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
1572    {
1573        if ( $i <= 2 ) { next; }                        # ignoring first three lines
1574        if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
1575        # Format for all sequence tables: Action    Condition   Sequence
1576        if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\s*$/ )
1577        {
1578            my %onehash = ();
1579            $onehash{'Action'} = $1;
1580            $onehash{'Condition'} = $2;
1581            $onehash{'Sequence'} = $3;
1582            $newhash{$onehash{'Action'}} = \%onehash;
1583        }
1584        else
1585        {
1586            my $linecount = $i + 1;
1587            installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "make_executeidtcontent_hash");
1588        }
1589    }
1590
1591    return \%newhash;
1592}
1593
1594#########################################################################
1595# Creating file content hash
1596#########################################################################
1597
1598sub make_moduleexecuteidtcontent_hash
1599{
1600    my ($filecontent, $idtfilename) = @_;
1601
1602    my %newhash = ();
1603
1604    for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
1605    {
1606        if ( $i <= 2 ) { next; }                        # ignoring first three lines
1607        if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
1608        # Format for all module sequence tables: Action Sequence    BaseAction  After Condition
1609        if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
1610        {
1611            my %onehash = ();
1612            $onehash{'Action'} = $1;
1613            $onehash{'Sequence'} = $2;
1614            $onehash{'BaseAction'} = $3;
1615            $onehash{'After'} = $4;
1616            $onehash{'Condition'} = $5;
1617            $newhash{$onehash{'Action'}} = \%onehash;
1618        }
1619        else
1620        {
1621            my $linecount = $i + 1;
1622            installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "make_executeidtcontent_hash");
1623        }
1624    }
1625
1626    return \%newhash;
1627}
1628
1629#########################################################################
1630# ExecuteSequence tables need to be merged with
1631# ModuleExecuteSequence tables created by msidb.exe.
1632#########################################################################
1633
1634sub change_executesequence_table
1635{
1636    my ($mergemodulehash, $workdir, $idtfilename, $moduleidtfilename) = @_;
1637
1638    my $infoline = "Changing content of table \"$idtfilename\"\n";
1639    push( @installer::globals::logfileinfo, $infoline);
1640
1641    if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_executesequence_table"); }
1642    if ( ! -f $moduleidtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$moduleidtfilename\" in \"$workdir\" !", "change_executesequence_table"); }
1643
1644    # Reading file content
1645    my $idtfilecontent = installer::files::read_file($idtfilename);
1646    my $moduleidtfilecontent = installer::files::read_file($moduleidtfilename);
1647
1648    # Converting to hash
1649    my $idtcontenthash = make_executeidtcontent_hash($idtfilecontent, $idtfilename);
1650    my $moduleidtcontenthash = make_moduleexecuteidtcontent_hash($moduleidtfilecontent, $moduleidtfilename);
1651
1652    # Merging
1653    foreach my $action ( keys %{$moduleidtcontenthash} )
1654    {
1655        if ( exists($idtcontenthash->{$action}) ) { next; } # Action already exists, can be ignored
1656
1657        if (( $idtfilename eq "InstallU.idt" ) && ( ! ( $action =~ /^\s*WindowsFolder\./ ))) { next; } # Only "WindowsFolder.*" CustomActions for UI Sequence table
1658
1659        my $actionhashref = $moduleidtcontenthash->{$action};
1660        if ( $actionhashref->{'Sequence'} ne "" )
1661        {
1662            # Format for all sequence tables: Action Condition Sequence
1663            my $newline = $actionhashref->{'Action'} . "\t" . $actionhashref->{'Condition'} . "\t" . $actionhashref->{'Sequence'} . "\n";
1664            # Adding to table
1665            push(@{$idtfilecontent}, $newline);
1666            # Also adding to hash
1667            my %idttablehash = ();
1668            $idttablehash{'Action'} = $actionhashref->{'Action'};
1669            $idttablehash{'Condition'} = $actionhashref->{'Condition'};
1670            $idttablehash{'Sequence'} = $actionhashref->{'Sequence'};
1671            $idtcontenthash->{$action} = \%idttablehash;
1672
1673        }
1674        else    # no sequence defined, using syntax "BaseAction" and "After"
1675        {
1676            my $baseactionname = $actionhashref->{'BaseAction'};
1677            # If this baseactionname is not defined in execute idt file, it is not possible to merge
1678            if ( ! exists($idtcontenthash->{$baseactionname}) ) { installer::exiter::exit_program("ERROR: Merge problem: Could not find action \"$baseactionname\" in file \"$idtfilename\" !", "change_executesequence_table"); }
1679
1680            my $baseaction = $idtcontenthash->{$baseactionname};
1681            my $sequencenumber = $baseaction->{'Sequence'};
1682            if ( $actionhashref->{'After'} == 1 ) { $sequencenumber = $sequencenumber + 1; }
1683            else { $sequencenumber = $sequencenumber - 1; }
1684
1685            # Format for all sequence tables: Action Condition Sequence
1686            my $newline = $actionhashref->{'Action'} . "\t" . $actionhashref->{'Condition'} . "\t" . $sequencenumber . "\n";
1687            # Adding to table
1688            push(@{$idtfilecontent}, $newline);
1689            # Also adding to hash
1690            my %idttablehash = ();
1691            $idttablehash{'Action'} = $actionhashref->{'Action'};
1692            $idttablehash{'Condition'} = $actionhashref->{'Condition'};
1693            $idttablehash{'Sequence'} = $sequencenumber;
1694            $idtcontenthash->{$action} = \%idttablehash;
1695        }
1696    }
1697
1698    # saving file
1699    installer::files::save_file($idtfilename, $idtfilecontent);
1700}
1701
1702
17031;
1704