617 lines
21 KiB
Perl
617 lines
21 KiB
Perl
#
|
|
# This file is part of the LibreOffice project.
|
|
#
|
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
#
|
|
# This file incorporates work covered by the following license notice:
|
|
#
|
|
# Licensed to the Apache Software Foundation (ASF) under one or more
|
|
# contributor license agreements. See the NOTICE file distributed
|
|
# with this work for additional information regarding copyright
|
|
# ownership. The ASF licenses this file to you under the Apache
|
|
# License, Version 2.0 (the "License"); you may not use this file
|
|
# except in compliance with the License. You may obtain a copy of
|
|
# the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
|
#
|
|
|
|
package installer::windows::update;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use installer::converter;
|
|
use installer::exiter;
|
|
use installer::files;
|
|
use installer::globals;
|
|
use installer::pathanalyzer;
|
|
use installer::systemactions;
|
|
|
|
#################################################################################
|
|
# Extracting all tables from an msi database
|
|
#################################################################################
|
|
|
|
sub extract_all_tables_from_msidatabase
|
|
{
|
|
my ($fulldatabasepath, $workdir) = @_;
|
|
|
|
my $msidb = "msidb.exe"; # Has to be in the path
|
|
my $infoline = "";
|
|
my $systemcall = "";
|
|
my $returnvalue = "";
|
|
my $extraslash = ""; # Has to be set for non-ActiveState perl
|
|
$extraslash = "\\" if ( $^O =~ /cygwin/i );
|
|
|
|
# Export of all tables by using "*"
|
|
$systemcall = $msidb . " -d " . $fulldatabasepath . " -f " . $workdir . " -e " . $extraslash . "*";
|
|
# msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
|
|
$systemcall =~ s/\//\\\\/g;
|
|
|
|
my $systemcall_output = `$systemcall`;
|
|
my $returnvalue = $? >> 8;
|
|
|
|
$infoline = "Systemcall: $systemcall\n";
|
|
push( @installer::globals::logfileinfo, $infoline);
|
|
|
|
if ($returnvalue)
|
|
{
|
|
$infoline = "ERROR: Could not execute $systemcall - returncode: $returnvalue - output: $systemcall_output\n";
|
|
push( @installer::globals::logfileinfo, $infoline);
|
|
installer::exiter::exit_program("ERROR: Could not exclude tables from msi database: $fulldatabasepath !", "extract_all_tables_from_msidatabase");
|
|
}
|
|
else
|
|
{
|
|
$infoline = "Success: Executed $systemcall successfully!\n";
|
|
push( @installer::globals::logfileinfo, $infoline);
|
|
}
|
|
}
|
|
|
|
#################################################################################
|
|
# Collecting the keys from the first line of the idt file
|
|
#################################################################################
|
|
|
|
sub collect_all_keys
|
|
{
|
|
my ($line) = @_;
|
|
|
|
my @allkeys = ();
|
|
my $rownumber = 0;
|
|
my $onekey = "";
|
|
|
|
while ( $line =~ /^\s*(\S+?)\t(.*)$/ )
|
|
{
|
|
$onekey = $1;
|
|
$line = $2;
|
|
$rownumber++;
|
|
push(@allkeys, $onekey);
|
|
}
|
|
|
|
# and the last key
|
|
|
|
$onekey = $line;
|
|
$onekey =~ s/^\s*//g;
|
|
$onekey =~ s/\s*$//g;
|
|
|
|
$rownumber++;
|
|
push(@allkeys, $onekey);
|
|
|
|
return (\@allkeys, $rownumber);
|
|
}
|
|
|
|
#################################################################################
|
|
# Analyzing the content of one line of an idt file
|
|
#################################################################################
|
|
|
|
sub get_oneline_hash
|
|
{
|
|
my ($line, $allkeys, $rownumber) = @_;
|
|
|
|
my $counter = 0;
|
|
my %linehash = ();
|
|
|
|
$line =~ s/^\s*//;
|
|
$line =~ s/\s*$//;
|
|
|
|
my $value = "";
|
|
my $onekey = "";
|
|
|
|
while ( $line =~ /^(.*?)\t(.*)$/ )
|
|
{
|
|
$value = $1;
|
|
$line = $2;
|
|
$onekey = ${$allkeys}[$counter];
|
|
$linehash{$onekey} = $value;
|
|
$counter++;
|
|
}
|
|
|
|
# the last column
|
|
|
|
$value = $line;
|
|
$onekey = ${$allkeys}[$counter];
|
|
|
|
$linehash{$onekey} = $value;
|
|
|
|
return \%linehash;
|
|
}
|
|
|
|
#################################################################################
|
|
# Analyzing the content of an idt file
|
|
#################################################################################
|
|
|
|
sub analyze_idt_file
|
|
{
|
|
my ($filecontent) = @_;
|
|
|
|
my %table = ();
|
|
# keys are written in first line
|
|
my ($allkeys, $rownumber) = collect_all_keys(${$filecontent}[0]);
|
|
|
|
for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
|
|
{
|
|
if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; }
|
|
|
|
my $onelinehash = get_oneline_hash(${$filecontent}[$i], $allkeys, $rownumber);
|
|
my $linekey = $i - 2; # ! : The linenumber is the unique key !? Always decrease by two, because of removed first three lines.
|
|
$table{$linekey} = $onelinehash;
|
|
}
|
|
|
|
return \%table;
|
|
}
|
|
|
|
#################################################################################
|
|
# Reading all idt files in a specified directory
|
|
#################################################################################
|
|
|
|
sub read_all_tables_from_msidatabase
|
|
{
|
|
my ($workdir) = @_;
|
|
|
|
my %database = ();
|
|
|
|
my $ext = "idt";
|
|
|
|
my $allidtfiles = installer::systemactions::find_file_with_file_extension($ext, $workdir);
|
|
|
|
for ( my $i = 0; $i <= $#{$allidtfiles}; $i++ )
|
|
{
|
|
my $onefilename = ${$allidtfiles}[$i];
|
|
my $longonefilename = $workdir . $installer::globals::separator . $onefilename;
|
|
if ( ! -f $longonefilename ) { installer::exiter::exit_program("ERROR: Could not find idt file: $longonefilename!", "read_all_tables_from_msidatabase"); }
|
|
my $filecontent = installer::files::read_file($longonefilename);
|
|
my $idtcontent = analyze_idt_file($filecontent);
|
|
if ($onefilename eq "Directory.idt") {
|
|
collect_directories($filecontent, $longonefilename);
|
|
}
|
|
my $key = $onefilename;
|
|
$key =~ s/\.idt\s*$//;
|
|
$database{$key} = $idtcontent;
|
|
}
|
|
|
|
return \%database;
|
|
}
|
|
|
|
#################################################################################
|
|
# Checking, if this is the correct database.
|
|
#################################################################################
|
|
|
|
sub correct_database
|
|
{
|
|
my ($product, $pro, $langs, $languagestringref) = @_;
|
|
|
|
my $correct_database = 0;
|
|
|
|
# Comparing $product with $installer::globals::product and
|
|
# $pro with $installer::globals::pro and
|
|
# $langs with $languagestringref
|
|
|
|
my $product_is_good = 0;
|
|
|
|
my $localproduct = $installer::globals::product;
|
|
if ( $installer::globals::languagepack ) { $localproduct = $localproduct . "LanguagePack"; }
|
|
elsif ( $installer::globals::helppack ) { $localproduct = $localproduct . "HelpPack"; }
|
|
|
|
if ( $product eq $localproduct ) { $product_is_good = 1; }
|
|
|
|
if ( $product_is_good )
|
|
{
|
|
my $pro_is_good = 0;
|
|
|
|
if ((( $pro eq "pro" ) && ( $installer::globals::pro )) || (( $pro eq "nonpro" ) && ( ! $installer::globals::pro ))) { $pro_is_good = 1; }
|
|
|
|
if ( $pro_is_good )
|
|
{
|
|
my $langlisthash = installer::converter::convert_stringlist_into_hash(\$langs, ",");
|
|
my $langstringhash = installer::converter::convert_stringlist_into_hash($languagestringref, "_");
|
|
|
|
my $not_included = 0;
|
|
foreach my $onelang ( keys %{$langlisthash} )
|
|
{
|
|
if ( ! exists($langstringhash->{$onelang}) )
|
|
{
|
|
$not_included = 1;
|
|
last;
|
|
}
|
|
}
|
|
|
|
if ( ! $not_included )
|
|
{
|
|
foreach my $onelanguage ( keys %{$langstringhash} )
|
|
{
|
|
if ( ! exists($langlisthash->{$onelanguage}) )
|
|
{
|
|
$not_included = 1;
|
|
last;
|
|
}
|
|
}
|
|
|
|
if ( ! $not_included ) { $correct_database = 1; }
|
|
}
|
|
}
|
|
}
|
|
|
|
return $correct_database;
|
|
}
|
|
|
|
#################################################################################
|
|
# Searching for the path to the reference database for this special product.
|
|
#################################################################################
|
|
|
|
sub get_databasename_from_list
|
|
{
|
|
my ($filecontent, $languagestringref, $filename) = @_;
|
|
|
|
my $databasepath = "";
|
|
|
|
for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
|
|
{
|
|
my $line = ${$filecontent}[$i];
|
|
if ( $line =~ /^\s*$/ ) { next; } # empty line
|
|
if ( $line =~ /^\s*\#/ ) { next; } # comment line
|
|
|
|
if ( $line =~ /^\s*(.+?)\s*\t+\s*(.+?)\s*\t+\s*(.+?)\s*\t+\s*(.+?)\s*$/ )
|
|
{
|
|
my $product = $1;
|
|
my $pro = $2;
|
|
my $langs = $3;
|
|
my $path = $4;
|
|
|
|
if (( $pro ne "pro" ) && ( $pro ne "nonpro" )) { installer::exiter::exit_program("ERROR: Wrong syntax in file: $filename. Only \"pro\" or \"nonpro\" allowed in column 1! Line: \"$line\"", "get_databasename_from_list"); }
|
|
|
|
if ( correct_database($product, $pro, $langs, $languagestringref) )
|
|
{
|
|
$databasepath = $path;
|
|
last;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
installer::exiter::exit_program("ERROR: Wrong syntax in file: $filename! Line: \"$line\"", "get_databasename_from_list");
|
|
}
|
|
}
|
|
|
|
return $databasepath;
|
|
}
|
|
|
|
#################################################################################
|
|
# Reading an existing database completely
|
|
#################################################################################
|
|
|
|
sub readdatabase
|
|
{
|
|
my ($allvariables, $languagestringref, $includepatharrayref) = @_;
|
|
|
|
my $database = "";
|
|
my $infoline = "";
|
|
|
|
if ( ! $allvariables->{'UPDATE_DATABASE_LISTNAME'} ) { installer::exiter::exit_program("ERROR: If \"UPDATE_DATABASE\" is set, \"UPDATE_DATABASE_LISTNAME\" is required.", "Main"); }
|
|
my $listfilename = $allvariables->{'UPDATE_DATABASE_LISTNAME'};
|
|
|
|
# Searching the list in the include paths
|
|
my $listname = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$listfilename, $includepatharrayref, 1);
|
|
if ( $$listname eq "" ) { installer::exiter::exit_program("ERROR: List file not found: $listfilename !", "readdatabase"); }
|
|
my $completelistname = $$listname;
|
|
|
|
# Reading list file
|
|
my $listfile = installer::files::read_file($completelistname);
|
|
|
|
# Get name and path of reference database
|
|
my $databasename = get_databasename_from_list($listfile, $languagestringref, $completelistname);
|
|
|
|
# If the correct database was not found, this is not necessarily an error. But in this case, this is not an update packaging process!
|
|
if (( $databasename ) && ( $databasename ne "" )) # This is an update packaging process!
|
|
{
|
|
$installer::globals::updatedatabase = 1;
|
|
installer::logger::print_message( "... update process, using database $databasename ...\n" );
|
|
$infoline = "\nDatabase found in $completelistname: \"$databasename\"\n\n";
|
|
# Saving in global variable
|
|
$installer::globals::updatedatabasepath = $databasename;
|
|
}
|
|
else
|
|
{
|
|
$infoline = "\nNo database found in $completelistname. This is no update process!\n\n";
|
|
}
|
|
push( @installer::globals::logfileinfo, $infoline);
|
|
|
|
if ( $installer::globals::updatedatabase )
|
|
{
|
|
if ( ! -f $databasename ) { installer::exiter::exit_program("ERROR: Could not find reference database: $databasename!", "readdatabase"); }
|
|
|
|
my $msifilename = $databasename;
|
|
installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$msifilename);
|
|
|
|
installer::logger::include_timestamp_into_logfile("Performance Info: readdatabase start");
|
|
|
|
# create directory for unpacking
|
|
my $databasedir = installer::systemactions::create_directories("database", $languagestringref);
|
|
|
|
# copy database
|
|
my $fulldatabasepath = $databasedir . $installer::globals::separator . $msifilename;
|
|
installer::systemactions::copy_one_file($databasename, $fulldatabasepath);
|
|
|
|
installer::logger::include_timestamp_into_logfile("Performance Info: readdatabase: before extracting tables");
|
|
|
|
# extract all tables from database
|
|
extract_all_tables_from_msidatabase($fulldatabasepath, $databasedir);
|
|
|
|
installer::logger::include_timestamp_into_logfile("Performance Info: readdatabase: before reading tables");
|
|
|
|
# read all tables
|
|
$database = read_all_tables_from_msidatabase($databasedir);
|
|
|
|
# Test output:
|
|
|
|
# foreach my $key1 ( keys %{$database} )
|
|
# {
|
|
# print "Test1: $key1\n";
|
|
# foreach my $key2 ( keys %{$database->{$key1}} )
|
|
# {
|
|
# print "\tTest2: $key2\n";
|
|
# foreach my $key3 ( keys %{$database->{$key1}->{$key2}} )
|
|
# {
|
|
# print "\t\tTest3: $key3: $database->{$key1}->{$key2}->{$key3}\n";
|
|
# }
|
|
# }
|
|
# }
|
|
|
|
# Example: File table
|
|
|
|
# my $filetable = $database->{'File'};
|
|
# foreach my $linenumber ( keys %{$filetable} )
|
|
# {
|
|
# print "Test Filenumber: $linenumber\n";
|
|
# foreach my $key ( keys %{$filetable->{$linenumber}} )
|
|
# {
|
|
# print "\t\tTest: $key: $filetable->{$linenumber}->{$key}\n";
|
|
# }
|
|
# }
|
|
|
|
# Example: Searching for ProductCode in table Property
|
|
|
|
# my $column1 = "Property";
|
|
# my $column2 = "Value";
|
|
# my $searchkey = "ProductCode";
|
|
# my $propertytable = $database->{'Property'};
|
|
# foreach my $linenumber ( keys %{$propertytable} )
|
|
# {
|
|
# if ( $propertytable->{$linenumber}->{$column1} eq $searchkey )
|
|
# {
|
|
# print("Test: $searchkey : $propertytable->{$linenumber}->{$column2}\n");
|
|
# }
|
|
# }
|
|
|
|
installer::logger::include_timestamp_into_logfile("Performance Info: readdatabase end");
|
|
}
|
|
|
|
return $database;
|
|
}
|
|
|
|
#########################################################################
|
|
# Reading the file "Directory.idt".
|
|
#########################################################################
|
|
|
|
sub collect_directories
|
|
{
|
|
my ($filecontent, $idtfilename) = @_;
|
|
|
|
for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
|
|
{
|
|
if ( $i <= 2 ) { next; } # ignoring first three lines
|
|
if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
|
|
# Format: Directory Directory_Parent DefaultDir
|
|
if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\s*$/ )
|
|
{
|
|
$installer::globals::merge_directory_hash{$1} = 1;
|
|
}
|
|
else
|
|
{
|
|
my $linecount = $i + 1;
|
|
installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "collect_directories");
|
|
}
|
|
}
|
|
}
|
|
|
|
#################################################################################
|
|
# Files can be included in merge modules. This is also important for update.
|
|
#################################################################################
|
|
|
|
sub readmergedatabase
|
|
{
|
|
my ( $mergemodules, $languagestringref, $includepatharrayref ) = @_;
|
|
|
|
installer::logger::include_timestamp_into_logfile("Performance Info: readmergedatabase start");
|
|
|
|
my $mergemoduledir = installer::systemactions::create_directories("mergedatabase", $languagestringref);
|
|
|
|
my %allmergefiles = ();
|
|
|
|
foreach my $mergemodule ( @{$mergemodules} )
|
|
{
|
|
my $filename = $mergemodule->{'Name'};
|
|
my $mergefile = $ENV{'MSM_PATH'} . $filename;
|
|
|
|
if ( ! -f $mergefile ) { installer::exiter::exit_program("ERROR: msm file not found: $mergefile !", "readmergedatabase"); }
|
|
my $completesource = $mergefile;
|
|
|
|
my $mergegid = $mergemodule->{'gid'};
|
|
my $workdir = $mergemoduledir . $installer::globals::separator . $mergegid;
|
|
if ( ! -d $workdir ) { installer::systemactions::create_directory($workdir); }
|
|
|
|
my $completedest = $workdir . $installer::globals::separator . $filename;
|
|
installer::systemactions::copy_one_file($completesource, $completedest);
|
|
if ( ! -f $completedest ) { installer::exiter::exit_program("ERROR: msm file not found: $completedest !", "readmergedatabase"); }
|
|
|
|
# extract all tables from database
|
|
extract_all_tables_from_msidatabase($completedest, $workdir);
|
|
|
|
# read all tables
|
|
my $onemergefile = read_all_tables_from_msidatabase($workdir);
|
|
|
|
$allmergefiles{$mergegid} = $onemergefile;
|
|
}
|
|
|
|
foreach my $mergefilegid ( keys %allmergefiles )
|
|
{
|
|
my $onemergefile = $allmergefiles{$mergefilegid};
|
|
my $filetable = $onemergefile->{'File'};
|
|
|
|
foreach my $linenumber ( keys %{$filetable} )
|
|
{
|
|
# Collecting all files from merge modules in global hash
|
|
$installer::globals::mergemodulefiles{$filetable->{$linenumber}->{'File'}} = 1;
|
|
}
|
|
}
|
|
|
|
installer::logger::include_timestamp_into_logfile("Performance Info: readmergedatabase end");
|
|
}
|
|
|
|
#################################################################################
|
|
# Creating several useful hashes from old database
|
|
#################################################################################
|
|
|
|
sub create_database_hashes
|
|
{
|
|
my ( $database ) = @_;
|
|
|
|
# 1. Hash ( Component -> UniqueFileName ), required in File table.
|
|
# Read from File table.
|
|
|
|
my %uniquefilename = ();
|
|
my %allupdatesequences = ();
|
|
my %allupdatecomponents = ();
|
|
my %allupdatefileorder = ();
|
|
my %allupdatecomponentorder = ();
|
|
my %revuniquefilename = ();
|
|
my %revshortfilename = ();
|
|
my %shortdirname = ();
|
|
my %componentid = ();
|
|
my %componentidkeypath = ();
|
|
my %alloldproperties = ();
|
|
my %allupdatelastsequences = ();
|
|
my %allupdatediskids = ();
|
|
|
|
my $filetable = $database->{'File'};
|
|
|
|
foreach my $linenumber ( keys %{$filetable} )
|
|
{
|
|
my $comp = $filetable->{$linenumber}->{'Component_'};
|
|
my $uniquename = $filetable->{$linenumber}->{'File'};
|
|
my $filename = $filetable->{$linenumber}->{'FileName'};
|
|
my $sequence = $filetable->{$linenumber}->{'Sequence'};
|
|
|
|
my $shortname = "";
|
|
if ( $filename =~ /^\s*(.*?)\|\s*(.*?)\s*$/ )
|
|
{
|
|
$shortname = $1;
|
|
$filename = $2;
|
|
}
|
|
|
|
# unique is the combination of $component and $filename
|
|
my $key = "$comp/$filename";
|
|
|
|
if ( exists($uniquefilename{$key}) ) { installer::exiter::exit_program("ERROR: Component/FileName \"$key\" is not unique in table \"File\" !", "create_database_hashes"); }
|
|
|
|
my $value = $uniquename;
|
|
if ( $shortname ne "" ) { $value = "$uniquename;$shortname"; }
|
|
$uniquefilename{$key} = $value; # saving the unique keys and short names in hash
|
|
|
|
# Saving reverse keys too
|
|
$revuniquefilename{$uniquename} = $key;
|
|
if ( $shortname ne "" ) { $revshortfilename{$shortname} = $key; }
|
|
|
|
# Saving Sequences for unique names (and also components)
|
|
$allupdatesequences{$uniquename} = $sequence;
|
|
$allupdatecomponents{$uniquename} = $comp;
|
|
|
|
# Saving unique names and components for sequences
|
|
$allupdatefileorder{$sequence} = $uniquename;
|
|
$allupdatecomponentorder{$sequence} = $comp;
|
|
}
|
|
|
|
# 2. Hash, required in Directory table.
|
|
|
|
my $dirtable = $database->{'Directory'};
|
|
|
|
foreach my $linenumber ( keys %{$dirtable} )
|
|
{
|
|
my $dir = $dirtable->{$linenumber}->{'Directory'}; # this is a unique name
|
|
my $defaultdir = $dirtable->{$linenumber}->{'DefaultDir'};
|
|
|
|
my $shortname = "";
|
|
if ( $defaultdir =~ /^\s*(.*?)\|\s*(.*?)\s*$/ )
|
|
{
|
|
$shortname = $1;
|
|
$shortdirname{$dir} = $shortname; # collecting only the short names
|
|
}
|
|
}
|
|
|
|
# 3. Hash, collecting info from Component table.
|
|
# ComponentID and KeyPath have to be reused.
|
|
|
|
my $comptable = $database->{'Component'};
|
|
|
|
foreach my $linenumber ( keys %{$comptable} )
|
|
{
|
|
my $comp = $comptable->{$linenumber}->{'Component'};
|
|
my $compid = $comptable->{$linenumber}->{'ComponentId'};
|
|
my $keypath = $comptable->{$linenumber}->{'KeyPath'};
|
|
|
|
$componentid{$comp} = $compid;
|
|
$componentidkeypath{$comp} = $keypath;
|
|
}
|
|
|
|
# 4. Hash, property table, required for ProductCode and Installlocation.
|
|
|
|
my $proptable = $database->{'Property'};
|
|
|
|
foreach my $linenumber ( keys %{$proptable} )
|
|
{
|
|
my $prop = $proptable->{$linenumber}->{'Property'};
|
|
my $value = $proptable->{$linenumber}->{'Value'};
|
|
|
|
$alloldproperties{$prop} = $value;
|
|
}
|
|
|
|
# 5. Media table, getting last sequence
|
|
|
|
my $mediatable = $database->{'Media'};
|
|
$installer::globals::updatelastsequence = 0;
|
|
|
|
foreach my $linenumber ( keys %{$mediatable} )
|
|
{
|
|
my $cabname = $mediatable->{$linenumber}->{'Cabinet'};
|
|
my $lastsequence = $mediatable->{$linenumber}->{'LastSequence'};
|
|
my $diskid = $mediatable->{$linenumber}->{'DiskId'};
|
|
$allupdatelastsequences{$cabname} = $lastsequence;
|
|
$allupdatediskids{$cabname} = $diskid;
|
|
|
|
if ( $lastsequence > $installer::globals::updatelastsequence ) { $installer::globals::updatelastsequence = $lastsequence; }
|
|
}
|
|
|
|
$installer::globals::updatesequencecounter = $installer::globals::updatelastsequence;
|
|
|
|
return (\%uniquefilename, \%revuniquefilename, \%revshortfilename, \%allupdatesequences, \%allupdatecomponents, \%allupdatefileorder, \%allupdatecomponentorder, \%shortdirname, \%componentid, \%componentidkeypath, \%alloldproperties, \%allupdatelastsequences, \%allupdatediskids);
|
|
}
|
|
|
|
|
|
1;
|