Inno Setup:从 INI 文件中读取名称并为每个名称创建安装的单一安装程序
Inno Setup: Single installer that reads names from an INI file and creates an installation for each name
我正在使用 Pascal Script 函数来读取和检测文件中数组中的名称数量。当它确定了名称的数量后,我想要一个 #for
循环来迭代该数量。
我可以使用 Pascal 脚本读取和检测名称的数量。问题是,我不知道如何在名称计数过程之后设置 numberOfElements
变量的值。它必须设置为刚刚使用 Pascal Script 函数读取的名称数。
下面是一些示例代码:
#define numberOfElements
#sub CreateSubInstallation
[Languages]
//code ommitted
[Files]
//code ommitted
[Run]
//code ommitted
#endsub
#for {i = 0; i < numberOfElements; i++} CreateSubInstallation
换一种方法也行。我只想从一个文件中读取一些名称,然后复制安装的名称数量。因此每个名称都有其自己的安装。更详细地说,每个名称都有它自己的:目录、子目录和文件中的变量,这些文件将把名称 "injected" 放入其中。
这是 INI 文件的格式:
[Customer]
customers={"customerName1","customerName2"}
这是使用 Pascal 脚本读取和检测名称的代码:
{ Get the customer names from file }
function GetCustomersFromFile(fileName : string): string;
var
lines: TArrayOfString;
amountOfLines,i: integer;
tempResult: string;
begin
tempResult := '';
if LoadStringsFromFile(fileName, lines) then
begin
amountOfLines := GetArrayLength(amountOfLines);
{ Keep reading lines until the end of the file is reached }
for i := 0 to amountOfLines - 1 do
begin
if (Pos('customers', lines[i]) = 1) then
tempResult := lines[i];
end;
{ if not found return -1 }
if (tempResult = '') then
{ this result is for debugging and }
{ will have no impact on the array population process }
tempResult := '-1';
Result := tempResult;
end;
end;
{ Count the number of elements present in the array in the file }
{ Done for tempArray initilization }
function CountNumberOfStringElements(line : string): integer;
const
chars = ['0'..'9', 'a'..'z', 'A'..'Z'];
var
ignoreCommas: Boolean;
numElements, numValidText: integer;
i: integer;
begin
ignoreCommas := false;
numValidText := 0;
numElements := 0;
{ Loop through text }
for i := 0 to Length(line) - 1 do
begin
if (line[i] = '"') then
ignoreCommas := (NOT ignoreCommas);
if ((line[i]) IN chars AND (ignoreCommas)) then
Inc(numValidText);
if((line[i] = ',') AND (NOT ignoreCommas) )then
Inc(numElements);
end;
if (numElements >= 1) then
result := numElements + 1
else if ((numElements = 0) AND (numValidText > 0)) then
result := 1
else if ((numElements = 0) AND (numValidText = 0)) then
result := 0;
end;
这基本上就是我希望安装程序执行的操作,只是一个非常精简的版本。
[Setup]
AppName=My Program
AppVersion=1.5
WizardStyle=modern
DefaultDirName={autopf}\My Program
DefaultGroupName=My Program
UninstallDisplayIcon={app}\MyProg.exe
Compression=lzma2
SolidCompression=yes
OutputDir=userdocs:Inno Setup Examples Output
ArchitecturesAllowed=x64
ArchitecturesInstallIn64BitMode=x64
[Files]
Source: "MyProg-x64.exe"; DestDir: "{app}/customer1/"; DestName: "MyProg.exe"
Source: "MyProg.chm"; DestDir: "{app}/customer1/"
Source: "Readme.txt"; DestDir: "{app}/customer1/"; Flags: isreadme
Source: "MyProg-x64.exe"; DestDir: "{app}/customer2/"; DestName: "MyProg.exe"
Source: "MyProg.chm"; DestDir: "{app}/customer2/"
Source: "Readme.txt"; DestDir: "{app}/customer2/"; Flags: isreadme
[Icons]
Name: "{group}\My Program"; Filename: "{app}\MyProg.exe"
请注意之所以结构如此,是因为它们是服务。每项服务最终都会包含更多与客户相关的内容。整个安装过程必须使用 .exe 完成,删除过程必须使用不同但也是单一的 .exe。
你的问题很不清楚。但我会尽量给你一些答案。
如果我没理解错的话,您想在 INI 文件中为每个客户部署相同的文件集。如果您需要在 run/install 时间读取 INI 文件(如果您在编译时读取 INI 文件,这是可能的)。
如果您需要在 run/install 时间克隆文件,您所能做的就是将它们安装到一个临时文件夹,然后使用 Pascal 脚本将它们复制过来。
[Files]
Source: "MyProg.exe"; DestDir: "{tmp}"
[Code]
procedure CurStepChanged(CurStep: TSetupStep);
var
I: Integer;
SourcePath: string;
TargetPath: string;
begin
if CurStep = ssPostInstall then
begin
for I := 0 to NumberOfCustomers - 1 then
begin
SourcePath := ExpandConstant('{tmp}\MyProg.exe');
TargetPath := GetPathForCustomer(I) + '\MyProg.exe';
if FileCopy(SourcePath, TargetPath, False) then
begin
Log(Format('Installed for customer %d', [I]));
end
else
begin
Log(Format('Failed to install for customer %d', [I]));
end;
end;
end;
end;
(您必须用您的实现替换 NumberOfCustomers
和 GetPathForCustomer
)
虽然这样,您将不会看到任何进度条,并且您会失去 Inno Setup 的所有内置错误处理。您还必须在 Pascal 脚本中实施卸载。
编译时读取INI文件肯定更好。这意味着每次更改 INI 文件时都必须重新生成安装程序。但这可以通过单击一次命令行编译器来完成。
尽管使用预处理器解析 INI 文件并不容易。
另一个 hackish 解决方案是在 [Files]
部分生成大量相同的条目,然后可以在 run/install 时间与客户动态关联。它不是通用的,因为总会有一个上限。但是,如果您知道您永远不会拥有比例如更多的东西。 100 个客户,这是一个可行的选择。并且进度条,错误处理和卸载都可以。
我不明白,[Languages]
部分与INI文件有什么关系,所以我跳过了。
旁注:您的 GetCustomersFromFile
和 GetCustomersFromFile
可以使用 GetIniString
和 TStringList.CommaText
替换为几行代码。但这是一个单独的问题。
我正在使用 Pascal Script 函数来读取和检测文件中数组中的名称数量。当它确定了名称的数量后,我想要一个 #for
循环来迭代该数量。
我可以使用 Pascal 脚本读取和检测名称的数量。问题是,我不知道如何在名称计数过程之后设置 numberOfElements
变量的值。它必须设置为刚刚使用 Pascal Script 函数读取的名称数。
下面是一些示例代码:
#define numberOfElements
#sub CreateSubInstallation
[Languages]
//code ommitted
[Files]
//code ommitted
[Run]
//code ommitted
#endsub
#for {i = 0; i < numberOfElements; i++} CreateSubInstallation
换一种方法也行。我只想从一个文件中读取一些名称,然后复制安装的名称数量。因此每个名称都有其自己的安装。更详细地说,每个名称都有它自己的:目录、子目录和文件中的变量,这些文件将把名称 "injected" 放入其中。
这是 INI 文件的格式:
[Customer]
customers={"customerName1","customerName2"}
这是使用 Pascal 脚本读取和检测名称的代码:
{ Get the customer names from file }
function GetCustomersFromFile(fileName : string): string;
var
lines: TArrayOfString;
amountOfLines,i: integer;
tempResult: string;
begin
tempResult := '';
if LoadStringsFromFile(fileName, lines) then
begin
amountOfLines := GetArrayLength(amountOfLines);
{ Keep reading lines until the end of the file is reached }
for i := 0 to amountOfLines - 1 do
begin
if (Pos('customers', lines[i]) = 1) then
tempResult := lines[i];
end;
{ if not found return -1 }
if (tempResult = '') then
{ this result is for debugging and }
{ will have no impact on the array population process }
tempResult := '-1';
Result := tempResult;
end;
end;
{ Count the number of elements present in the array in the file }
{ Done for tempArray initilization }
function CountNumberOfStringElements(line : string): integer;
const
chars = ['0'..'9', 'a'..'z', 'A'..'Z'];
var
ignoreCommas: Boolean;
numElements, numValidText: integer;
i: integer;
begin
ignoreCommas := false;
numValidText := 0;
numElements := 0;
{ Loop through text }
for i := 0 to Length(line) - 1 do
begin
if (line[i] = '"') then
ignoreCommas := (NOT ignoreCommas);
if ((line[i]) IN chars AND (ignoreCommas)) then
Inc(numValidText);
if((line[i] = ',') AND (NOT ignoreCommas) )then
Inc(numElements);
end;
if (numElements >= 1) then
result := numElements + 1
else if ((numElements = 0) AND (numValidText > 0)) then
result := 1
else if ((numElements = 0) AND (numValidText = 0)) then
result := 0;
end;
这基本上就是我希望安装程序执行的操作,只是一个非常精简的版本。
[Setup]
AppName=My Program
AppVersion=1.5
WizardStyle=modern
DefaultDirName={autopf}\My Program
DefaultGroupName=My Program
UninstallDisplayIcon={app}\MyProg.exe
Compression=lzma2
SolidCompression=yes
OutputDir=userdocs:Inno Setup Examples Output
ArchitecturesAllowed=x64
ArchitecturesInstallIn64BitMode=x64
[Files]
Source: "MyProg-x64.exe"; DestDir: "{app}/customer1/"; DestName: "MyProg.exe"
Source: "MyProg.chm"; DestDir: "{app}/customer1/"
Source: "Readme.txt"; DestDir: "{app}/customer1/"; Flags: isreadme
Source: "MyProg-x64.exe"; DestDir: "{app}/customer2/"; DestName: "MyProg.exe"
Source: "MyProg.chm"; DestDir: "{app}/customer2/"
Source: "Readme.txt"; DestDir: "{app}/customer2/"; Flags: isreadme
[Icons]
Name: "{group}\My Program"; Filename: "{app}\MyProg.exe"
请注意之所以结构如此,是因为它们是服务。每项服务最终都会包含更多与客户相关的内容。整个安装过程必须使用 .exe 完成,删除过程必须使用不同但也是单一的 .exe。
你的问题很不清楚。但我会尽量给你一些答案。
如果我没理解错的话,您想在 INI 文件中为每个客户部署相同的文件集。如果您需要在 run/install 时间读取 INI 文件(如果您在编译时读取 INI 文件,这是可能的)。
如果您需要在 run/install 时间克隆文件,您所能做的就是将它们安装到一个临时文件夹,然后使用 Pascal 脚本将它们复制过来。
[Files]
Source: "MyProg.exe"; DestDir: "{tmp}"
[Code]
procedure CurStepChanged(CurStep: TSetupStep);
var
I: Integer;
SourcePath: string;
TargetPath: string;
begin
if CurStep = ssPostInstall then
begin
for I := 0 to NumberOfCustomers - 1 then
begin
SourcePath := ExpandConstant('{tmp}\MyProg.exe');
TargetPath := GetPathForCustomer(I) + '\MyProg.exe';
if FileCopy(SourcePath, TargetPath, False) then
begin
Log(Format('Installed for customer %d', [I]));
end
else
begin
Log(Format('Failed to install for customer %d', [I]));
end;
end;
end;
end;
(您必须用您的实现替换 NumberOfCustomers
和 GetPathForCustomer
)
虽然这样,您将不会看到任何进度条,并且您会失去 Inno Setup 的所有内置错误处理。您还必须在 Pascal 脚本中实施卸载。
编译时读取INI文件肯定更好。这意味着每次更改 INI 文件时都必须重新生成安装程序。但这可以通过单击一次命令行编译器来完成。
尽管使用预处理器解析 INI 文件并不容易。
另一个 hackish 解决方案是在 [Files]
部分生成大量相同的条目,然后可以在 run/install 时间与客户动态关联。它不是通用的,因为总会有一个上限。但是,如果您知道您永远不会拥有比例如更多的东西。 100 个客户,这是一个可行的选择。并且进度条,错误处理和卸载都可以。
我不明白,[Languages]
部分与INI文件有什么关系,所以我跳过了。
旁注:您的 GetCustomersFromFile
和 GetCustomersFromFile
可以使用 GetIniString
和 TStringList.CommaText
替换为几行代码。但这是一个单独的问题。