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