1016 lines
34 KiB
Plaintext
1016 lines
34 KiB
Plaintext
; YMhut Box - WinUI 3 Windows installer (Stage B packaging)
|
||
; This installer ships the self-contained WinUI 3 publish output.
|
||
; Build the payload with:
|
||
; dotnet publish src\box-winUI\YMhut.Box.WinUI.csproj -c Release -r win-x64 --self-contained -o publish\winui
|
||
; then run ISCC against this .iss.
|
||
|
||
#define MyAppName "YMhut Box"
|
||
#define MyAppPublisher "YMhut"
|
||
#define MyAppURL "https://ymhut.cn"
|
||
#define MyAppExeName "YMhutBox.exe"
|
||
#define MyAppId "cn.ymhut.box.winui"
|
||
|
||
#ifndef MyAppVersion
|
||
#define MyAppVersion "2.0.2"
|
||
#endif
|
||
#ifndef MyAppBuild
|
||
#define MyAppBuild "1"
|
||
#endif
|
||
#ifndef MyAppChannel
|
||
#define MyAppChannel "stable"
|
||
#endif
|
||
#ifndef PayloadDir
|
||
#define PayloadDir "..\latest"
|
||
#endif
|
||
#ifndef BundleVCRedist
|
||
#define BundleVCRedist 0
|
||
#endif
|
||
#ifndef BundleWebView2
|
||
#define BundleWebView2 0
|
||
#endif
|
||
#ifndef ChineseMessagesFile
|
||
#define ChineseMessagesFile "compiler:Default.isl"
|
||
#endif
|
||
#ifndef WebView2BootstrapperUrl
|
||
#define WebView2BootstrapperUrl "https://go.microsoft.com/fwlink/?LinkId=2124703"
|
||
#endif
|
||
#ifndef VCRedistUrl
|
||
#define VCRedistUrl "https://aka.ms/vs/17/release/vc_redist.x64.exe"
|
||
#endif
|
||
|
||
[Setup]
|
||
AppId={#MyAppId}
|
||
AppName={#MyAppName}
|
||
AppVersion={#MyAppVersion}
|
||
AppVerName={#MyAppName} {#MyAppVersion}
|
||
AppPublisher={#MyAppPublisher}
|
||
AppPublisherURL={#MyAppURL}
|
||
DefaultDirName={code:GetDefaultInstallDir}
|
||
DefaultGroupName={#MyAppName}
|
||
OutputDir=..\installer_output
|
||
OutputBaseFilename=YMhut_Box_WinUI_Setup_{#MyAppVersion}
|
||
Compression=lzma2
|
||
CompressionThreads=auto
|
||
LZMADictionarySize=1048576
|
||
LZMAUseSeparateProcess=yes
|
||
SolidCompression=yes
|
||
WizardStyle=modern
|
||
ShowLanguageDialog=yes
|
||
LanguageDetectionMethod=uilanguage
|
||
PrivilegesRequired=lowest
|
||
PrivilegesRequiredOverridesAllowed=commandline
|
||
UsePreviousPrivileges=no
|
||
ArchitecturesAllowed=x64compatible
|
||
ArchitecturesInstallIn64BitMode=x64compatible
|
||
UsePreviousAppDir=no
|
||
AllowNoIcons=yes
|
||
SetupLogging=yes
|
||
CloseApplications=yes
|
||
CloseApplicationsFilter={#MyAppExeName}
|
||
RestartApplications=no
|
||
UninstallDisplayIcon={app}\{#MyAppExeName}
|
||
|
||
[Languages]
|
||
Name: "chinesesimp"; MessagesFile: "{#ChineseMessagesFile}"
|
||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||
|
||
[CustomMessages]
|
||
english.DesktopIcon=Create a desktop shortcut
|
||
english.StartMenuIcon=Create a Start menu shortcut
|
||
english.ShortcutGroup=Shortcuts
|
||
english.AutoStart=Start automatically after sign-in
|
||
english.LaunchApp=Launch {#MyAppName} after setup finishes
|
||
english.StartupGroup=Startup
|
||
english.MaintenanceGroup=Maintenance
|
||
english.InstallSummaryTitle=Install output summary
|
||
english.InstallSummaryDescription=Review the install directories and selected output before copying files.
|
||
english.InstallSummaryVersion=Version
|
||
english.InstallSummaryInstallDir=Install directory
|
||
english.InstallSummaryPayload=Payload source
|
||
english.InstallSummaryUserData=User data
|
||
english.InstallSummaryLogs=Logs
|
||
english.InstallSummaryTasks=Selected tasks
|
||
english.InstallOutputInitial=Ready to extract files.
|
||
english.InstallOutputClean=Cleaning old application files.
|
||
english.InstallOutputExtract=Extracting new application payload.
|
||
english.InstallOutputFile=Extracting:
|
||
english.InstallOutputPostInstall=Finalizing installation.
|
||
english.InstallOutputDone=Installation files are ready.
|
||
english.ProtectedInstallNeedsAdmin=The existing installation is in a protected system directory. Restart setup as administrator or choose a user-writable folder.
|
||
english.NonSystemInstallRequired=Please choose a writable folder on a non-system drive. YMhut Box no longer installs to the Windows system drive by default.
|
||
chinesesimp.NonSystemInstallRequired=请选择一个非系统盘上的可写目录。YMhut Box 不再默认安装到 Windows 系统盘。
|
||
english.RepairRuntimes=Check and install system prerequisites (VC++ / WebView2)
|
||
english.MigrateLegacyData=Import legacy data
|
||
english.CleanCache=Clean legacy cache folders
|
||
english.WebView2RuntimeName=WebView2 Runtime
|
||
english.VCRuntimeName=VC++ Runtime
|
||
english.DependencyDownloadCancelled=%1 download was canceled.
|
||
english.DependencyDownloadFailed=%1 download failed: %2
|
||
english.DependencyStartFailed=%1 installer could not be started.
|
||
english.DependencyInstallFailed=%1 installation failed with exit code %2.
|
||
english.WebView2StillMissing=WebView2 Runtime is still not detected after installation. Check the network connection or system policy, then retry.
|
||
english.VCRuntimeStillMissing=VC++ Runtime is still not detected after installation. Check the network connection or system policy, then retry.
|
||
chinesesimp.DesktopIcon=创建桌面快捷方式
|
||
chinesesimp.StartMenuIcon=创建开始菜单快捷方式
|
||
chinesesimp.ShortcutGroup=快捷方式
|
||
chinesesimp.AutoStart=开机后自动启动
|
||
chinesesimp.LaunchApp=安装完成后立即启动 {#MyAppName}
|
||
chinesesimp.StartupGroup=启动
|
||
chinesesimp.MaintenanceGroup=维护
|
||
chinesesimp.InstallSummaryTitle=安装输出摘要
|
||
chinesesimp.InstallSummaryDescription=复制文件前确认安装目录、输出位置和已选任务。
|
||
chinesesimp.InstallSummaryVersion=版本
|
||
chinesesimp.InstallSummaryInstallDir=安装目录
|
||
chinesesimp.InstallSummaryPayload=载荷来源
|
||
chinesesimp.InstallSummaryUserData=用户数据
|
||
chinesesimp.InstallSummaryLogs=日志
|
||
chinesesimp.InstallSummaryTasks=已选任务
|
||
chinesesimp.InstallOutputInitial=准备提取文件。
|
||
chinesesimp.InstallOutputClean=正在清理旧版本程序文件。
|
||
chinesesimp.InstallOutputExtract=正在提取新版本程序文件。
|
||
chinesesimp.InstallOutputFile=正在提取:
|
||
chinesesimp.InstallOutputPostInstall=正在完成安装收尾。
|
||
chinesesimp.InstallOutputDone=安装文件已准备完成。
|
||
chinesesimp.ProtectedInstallNeedsAdmin=检测到旧版本安装在受保护的系统目录。请以管理员身份重新运行安装器修复,或选择当前用户可写的安装目录。
|
||
chinesesimp.RepairRuntimes=检查并安装系统依赖(VC++ / WebView2)
|
||
chinesesimp.MigrateLegacyData=导入旧版数据
|
||
chinesesimp.CleanCache=清理旧版缓存目录
|
||
chinesesimp.WebView2RuntimeName=WebView2 Runtime
|
||
chinesesimp.VCRuntimeName=VC++ Runtime
|
||
chinesesimp.DependencyDownloadCancelled=%1 下载已取消。
|
||
chinesesimp.DependencyDownloadFailed=%1 下载失败:%2
|
||
chinesesimp.DependencyStartFailed=%1 安装器启动失败。
|
||
chinesesimp.DependencyInstallFailed=%1 安装失败,退出码:%2。
|
||
chinesesimp.WebView2StillMissing=WebView2 Runtime 安装后仍未检测到,请检查网络连接或系统策略后重试。
|
||
chinesesimp.VCRuntimeStillMissing=VC++ Runtime 安装后仍未检测到,请检查网络连接或系统策略后重试。
|
||
|
||
[Tasks]
|
||
Name: "desktopicon"; Description: "{cm:DesktopIcon}"; GroupDescription: "{cm:ShortcutGroup}"; Flags: checkedonce
|
||
Name: "startmenuicon"; Description: "{cm:StartMenuIcon}"; GroupDescription: "{cm:ShortcutGroup}"; Flags: unchecked
|
||
Name: "autostart"; Description: "{cm:AutoStart}"; GroupDescription: "{cm:StartupGroup}"; Flags: unchecked
|
||
Name: "launchapp"; Description: "{cm:LaunchApp}"; GroupDescription: "{cm:StartupGroup}"; Flags: checkedonce
|
||
|
||
[Files]
|
||
Source: "{#PayloadDir}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; BeforeInstall: LogCurrentExtractFile
|
||
#if Int(BundleVCRedist) == 1
|
||
Source: "{#PayloadDir}\prereqs\vc_redist.x64.exe"; Flags: dontcopy
|
||
#endif
|
||
#if Int(BundleWebView2) == 1
|
||
Source: "{#PayloadDir}\prereqs\MicrosoftEdgeWebView2RuntimeInstallerX64.exe"; Flags: dontcopy
|
||
#endif
|
||
|
||
[Dirs]
|
||
Name: "{app}\data"
|
||
Name: "{app}\FeedbackPackages"; Permissions: users-modify
|
||
Name: "{app}\data\legacy-imports"
|
||
|
||
[InstallDelete]
|
||
Type: filesandordirs; Name: "{app}\resources\lang"
|
||
Type: filesandordirs; Name: "{app}\lang"
|
||
Type: filesandordirs; Name: "{localappdata}\YMhut Box\WinUI\Runtime"
|
||
Type: filesandordirs; Name: "{localappdata}\YMhut Box\WinUI\runtime"
|
||
Type: filesandordirs; Name: "{localappdata}\YMhut Box\WinUI\Runtimes"
|
||
Type: filesandordirs; Name: "{localappdata}\YMhut Box\WinUI\runtimes"
|
||
Type: filesandordirs; Name: "{localappdata}\YMhut Box\WinUI\Tools"
|
||
Type: filesandordirs; Name: "{localappdata}\YMhut Box\WinUI\Metadata"
|
||
Type: filesandordirs; Name: "{localappdata}\YMhut Box\Runtime"
|
||
Type: filesandordirs; Name: "{localappdata}\YMhut Box\runtime"
|
||
Type: filesandordirs; Name: "{localappdata}\YMhut Box\Tools"
|
||
Type: filesandordirs; Name: "{localappdata}\YMhut Box\Metadata"
|
||
Type: filesandordirs; Name: "{localappdata}\ymhut_box\Runtime"
|
||
Type: filesandordirs; Name: "{localappdata}\ymhut_box\runtime"
|
||
Type: filesandordirs; Name: "{localappdata}\ymhut_box\Tools"
|
||
Type: filesandordirs; Name: "{localappdata}\ymhut_box\Metadata"
|
||
Type: filesandordirs; Name: "{localappdata}\YMhut Box\cache"; Check: ShouldCleanCache
|
||
Type: filesandordirs; Name: "{userappdata}\YMhut Box\cache"; Check: ShouldCleanCache
|
||
Type: filesandordirs; Name: "{localappdata}\ymhut_box\cache"; Check: ShouldCleanCache
|
||
Type: filesandordirs; Name: "{userappdata}\ymhut_box\cache"; Check: ShouldCleanCache
|
||
Type: files; Name: "{app}\*.winmd"
|
||
Type: files; Name: "{app}\*.pdb"
|
||
Type: files; Name: "{app}\workloads*.json"
|
||
Type: files; Name: "{app}\installer-layout.txt"
|
||
Type: files; Name: "{app}\config\installer-layout.dat"
|
||
Type: files; Name: "{app}\config\installer-required-files.dat"
|
||
Type: files; Name: "{app}\Assets\data\reference-data.dat"
|
||
Type: files; Name: "{app}\YMhut.Box.Worker.exe"
|
||
Type: files; Name: "{app}\YMhut.Box.Worker.dll"
|
||
Type: files; Name: "{app}\YMhut.Box.Worker.deps.json"
|
||
Type: files; Name: "{app}\YMhut.Box.Worker.runtimeconfig.json"
|
||
Type: files; Name: "{app}\YMhut.Box.PluginHost.exe"
|
||
Type: files; Name: "{app}\YMhut.Box.PluginHost.dll"
|
||
Type: files; Name: "{app}\YMhut.Box.PluginHost.deps.json"
|
||
Type: files; Name: "{app}\YMhut.Box.PluginHost.runtimeconfig.json"
|
||
Type: filesandordirs; Name: "{app}\updater"
|
||
Type: files; Name: "{app}\YMhut.Box.Updater.exe"
|
||
Type: files; Name: "{app}\YMhut.Box.Updater.dll"
|
||
Type: files; Name: "{app}\YMhut.Box.Updater.deps.json"
|
||
Type: files; Name: "{app}\YMhut.Box.Updater.runtimeconfig.json"
|
||
|
||
[Icons]
|
||
Name: "{userprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: startmenuicon
|
||
Name: "{userdesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
||
|
||
[Registry]
|
||
Root: HKCU; Subkey: "Software\Microsoft\Windows\CurrentVersion\Run"; ValueType: string; ValueName: "{#MyAppName}"; ValueData: """{app}\{#MyAppExeName}"""; Flags: uninsdeletevalue; Tasks: autostart
|
||
|
||
[Run]
|
||
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchApp}"; Flags: nowait postinstall skipifsilent; Tasks: launchapp
|
||
|
||
[UninstallDelete]
|
||
Type: files; Name: "{userdesktop}\{#MyAppName}.lnk"
|
||
Type: files; Name: "{userprograms}\{#MyAppName}.lnk"
|
||
|
||
[Code]
|
||
const
|
||
DirectoryAttribute = $10;
|
||
InstallOutputFlushLines = 16;
|
||
InstallOutputTrimCharacters = 65000;
|
||
InstallOutputKeepCharacters = 48000;
|
||
BundledVCRedistIncluded = {#BundleVCRedist};
|
||
BundledWebView2Included = {#BundleWebView2};
|
||
WebView2ClientKey = 'SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}';
|
||
WebView2ClientKeyWow64 = 'SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}';
|
||
WebView2ClientStateKey = 'SOFTWARE\Microsoft\EdgeUpdate\ClientState\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}';
|
||
WebView2ClientStateKeyWow64 = 'SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\ClientState\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}';
|
||
VCRedistKey = 'SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64';
|
||
|
||
var
|
||
DownloadPage: TDownloadWizardPage;
|
||
SummaryPage: TWizardPage;
|
||
SummaryMemo: TNewMemo;
|
||
InstallOutputMemo: TNewMemo;
|
||
ExistingInstallDir: string;
|
||
LastInstallProgressPercent: Integer;
|
||
InstallOutputLineCount: Integer;
|
||
InstallOutputPendingLineCount: Integer;
|
||
InstallOutputBuffer: string;
|
||
|
||
function NormalizePathPrefix(const Value: string): string;
|
||
begin
|
||
Result := Lowercase(AddBackslash(ExpandConstant(Value)));
|
||
end;
|
||
|
||
function PathStartsWith(const Path, Prefix: string): Boolean;
|
||
begin
|
||
Result := Pos(Lowercase(Prefix), Lowercase(AddBackslash(Path))) = 1;
|
||
end;
|
||
|
||
function IsProtectedInstallPath(const DirName: string): Boolean;
|
||
begin
|
||
Result :=
|
||
PathStartsWith(DirName, NormalizePathPrefix('{autopf}')) or
|
||
PathStartsWith(DirName, NormalizePathPrefix('{pf}')) or
|
||
PathStartsWith(DirName, NormalizePathPrefix('{pf32}')) or
|
||
PathStartsWith(DirName, NormalizePathPrefix('{commonpf}')) or
|
||
PathStartsWith(DirName, NormalizePathPrefix('{commonpf32}'));
|
||
end;
|
||
|
||
function GetSystemDrivePrefix(): string;
|
||
begin
|
||
Result := Uppercase(Copy(ExpandConstant('{win}'), 1, 2));
|
||
end;
|
||
|
||
function IsSystemDrivePath(const DirName: string): Boolean;
|
||
begin
|
||
Result := Uppercase(Copy(ExpandConstant(DirName), 1, 2)) = GetSystemDrivePrefix();
|
||
end;
|
||
|
||
function IsWritableDirectory(const DirName: string): Boolean;
|
||
var
|
||
ProbePath: string;
|
||
begin
|
||
Result := False;
|
||
try
|
||
if not ForceDirectories(DirName) then
|
||
Exit;
|
||
|
||
ProbePath := AddBackslash(DirName) + '.ymhut-write-test.tmp';
|
||
if SaveStringToFile(ProbePath, 'ok', False) then
|
||
begin
|
||
DeleteFile(ProbePath);
|
||
Result := True;
|
||
end;
|
||
except
|
||
Result := False;
|
||
end;
|
||
end;
|
||
|
||
function IsNonSystemWritableInstallDir(const DirName: string): Boolean;
|
||
begin
|
||
Result := (DirName <> '') and
|
||
(not IsSystemDrivePath(DirName)) and
|
||
(not IsProtectedInstallPath(DirName)) and
|
||
IsWritableDirectory(DirName);
|
||
end;
|
||
|
||
function FindDefaultNonSystemInstallDir(): string;
|
||
var
|
||
Index: Integer;
|
||
DriveLetter: string;
|
||
Candidate: string;
|
||
begin
|
||
Result := '';
|
||
for Index := 1 to Length('DEFGHIJKLMNOPQRSTUVWXYZ') do
|
||
begin
|
||
DriveLetter := Copy('DEFGHIJKLMNOPQRSTUVWXYZ', Index, 1);
|
||
if DriveLetter + ':' = GetSystemDrivePrefix() then
|
||
Continue;
|
||
|
||
Candidate := DriveLetter + ':\Programs\YMhut Box';
|
||
if DirExists(DriveLetter + ':\') and IsWritableDirectory(Candidate) then
|
||
begin
|
||
Result := Candidate;
|
||
Exit;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
function GetDefaultInstallDir(Param: string): string;
|
||
var
|
||
DefaultDir: string;
|
||
begin
|
||
if (ExistingInstallDir <> '') and ((not IsProtectedInstallPath(ExistingInstallDir)) or IsAdminInstallMode) then
|
||
Result := ExistingInstallDir
|
||
else
|
||
begin
|
||
DefaultDir := FindDefaultNonSystemInstallDir();
|
||
if DefaultDir <> '' then
|
||
Result := DefaultDir
|
||
else
|
||
Result := 'D:\Programs\YMhut Box';
|
||
end;
|
||
end;
|
||
|
||
function ShouldCleanCache(): Boolean;
|
||
begin
|
||
Result := True;
|
||
end;
|
||
|
||
function DetectExistingInstallDir(): string;
|
||
var
|
||
UninstallKey: string;
|
||
InstallLocation: string;
|
||
begin
|
||
Result := '';
|
||
UninstallKey := 'Software\Microsoft\Windows\CurrentVersion\Uninstall\' + '{#MyAppId}' + '_is1';
|
||
if RegQueryStringValue(HKLM64, UninstallKey, 'InstallLocation', InstallLocation) and
|
||
FileExists(AddBackslash(InstallLocation) + '{#MyAppExeName}') then
|
||
begin
|
||
Result := InstallLocation;
|
||
Exit;
|
||
end;
|
||
|
||
if RegQueryStringValue(HKLM, UninstallKey, 'InstallLocation', InstallLocation) and
|
||
FileExists(AddBackslash(InstallLocation) + '{#MyAppExeName}') then
|
||
begin
|
||
Result := InstallLocation;
|
||
Exit;
|
||
end;
|
||
|
||
if RegQueryStringValue(HKCU, UninstallKey, 'InstallLocation', InstallLocation) and
|
||
FileExists(AddBackslash(InstallLocation) + '{#MyAppExeName}') then
|
||
begin
|
||
Result := InstallLocation;
|
||
Exit;
|
||
end;
|
||
end;
|
||
|
||
function InitializeSetup(): Boolean;
|
||
begin
|
||
ExistingInstallDir := DetectExistingInstallDir();
|
||
if (ExistingInstallDir <> '') and IsProtectedInstallPath(ExistingInstallDir) and (not IsAdminInstallMode) then
|
||
begin
|
||
MsgBox(CustomMessage('ProtectedInstallNeedsAdmin'), mbError, MB_OK);
|
||
Result := False;
|
||
Exit;
|
||
end;
|
||
|
||
Result := True;
|
||
end;
|
||
|
||
procedure LayoutInstallOutputMemo(); forward;
|
||
procedure ScrollInstallOutputToBottom(); forward;
|
||
|
||
procedure InitializeWizard;
|
||
begin
|
||
DownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), nil);
|
||
DownloadPage.ShowBaseNameInsteadOfUrl := True;
|
||
ExistingInstallDir := DetectExistingInstallDir();
|
||
|
||
SummaryPage := CreateCustomPage(wpSelectTasks, CustomMessage('InstallSummaryTitle'), CustomMessage('InstallSummaryDescription'));
|
||
SummaryMemo := TNewMemo.Create(SummaryPage);
|
||
SummaryMemo.Parent := SummaryPage.Surface;
|
||
SummaryMemo.Left := 0;
|
||
SummaryMemo.Top := 0;
|
||
SummaryMemo.Width := SummaryPage.SurfaceWidth;
|
||
SummaryMemo.Height := SummaryPage.SurfaceHeight;
|
||
SummaryMemo.ReadOnly := True;
|
||
SummaryMemo.WordWrap := False;
|
||
SummaryMemo.ScrollBars := ssVertical;
|
||
|
||
InstallOutputMemo := TNewMemo.Create(WizardForm.InstallingPage);
|
||
InstallOutputMemo.Parent := WizardForm.InstallingPage;
|
||
InstallOutputMemo.ReadOnly := True;
|
||
InstallOutputMemo.WordWrap := False;
|
||
InstallOutputMemo.ScrollBars := ssVertical;
|
||
InstallOutputMemo.Text := CustomMessage('InstallOutputInitial') + #13#10;
|
||
InstallOutputLineCount := 1;
|
||
InstallOutputPendingLineCount := 0;
|
||
InstallOutputBuffer := '';
|
||
LayoutInstallOutputMemo();
|
||
ScrollInstallOutputToBottom();
|
||
end;
|
||
|
||
procedure LayoutInstallOutputMemo();
|
||
var
|
||
AvailableWidth: Integer;
|
||
AvailableHeight: Integer;
|
||
begin
|
||
if InstallOutputMemo = nil then
|
||
Exit;
|
||
|
||
InstallOutputMemo.Left := WizardForm.ProgressGauge.Left;
|
||
InstallOutputMemo.Top := WizardForm.ProgressGauge.Top + WizardForm.ProgressGauge.Height + ScaleY(10);
|
||
|
||
AvailableWidth := WizardForm.InstallingPage.Width - (InstallOutputMemo.Left * 2);
|
||
if AvailableWidth < WizardForm.ProgressGauge.Width then
|
||
AvailableWidth := WizardForm.ProgressGauge.Width;
|
||
InstallOutputMemo.Width := AvailableWidth;
|
||
|
||
AvailableHeight := WizardForm.InstallingPage.Height - InstallOutputMemo.Top - ScaleY(6);
|
||
if AvailableHeight < ScaleY(96) then
|
||
AvailableHeight := ScaleY(96);
|
||
InstallOutputMemo.Height := AvailableHeight;
|
||
end;
|
||
|
||
procedure ScrollInstallOutputToBottom();
|
||
begin
|
||
if InstallOutputMemo = nil then
|
||
Exit;
|
||
|
||
InstallOutputMemo.SelStart := Length(InstallOutputMemo.Text);
|
||
end;
|
||
|
||
procedure FlushInstallOutput();
|
||
var
|
||
TextValue: string;
|
||
begin
|
||
if InstallOutputMemo = nil then
|
||
begin
|
||
InstallOutputBuffer := '';
|
||
InstallOutputPendingLineCount := 0;
|
||
Exit;
|
||
end;
|
||
|
||
if InstallOutputBuffer = '' then
|
||
begin
|
||
ScrollInstallOutputToBottom();
|
||
Exit;
|
||
end;
|
||
|
||
TextValue := InstallOutputMemo.Text + InstallOutputBuffer;
|
||
InstallOutputLineCount := InstallOutputLineCount + InstallOutputPendingLineCount;
|
||
|
||
if Length(TextValue) > InstallOutputTrimCharacters then
|
||
begin
|
||
TextValue := Copy(TextValue, Length(TextValue) - InstallOutputKeepCharacters + 1, InstallOutputKeepCharacters);
|
||
InstallOutputLineCount := 500;
|
||
end;
|
||
|
||
InstallOutputMemo.Text := TextValue;
|
||
InstallOutputBuffer := '';
|
||
InstallOutputPendingLineCount := 0;
|
||
ScrollInstallOutputToBottom();
|
||
end;
|
||
|
||
procedure QueueInstallOutput(const Text: string; const ForceFlush, WriteSetupLog: Boolean);
|
||
begin
|
||
if WriteSetupLog then
|
||
Log(Text);
|
||
if InstallOutputMemo = nil then
|
||
Exit;
|
||
|
||
InstallOutputBuffer := InstallOutputBuffer + Text + #13#10;
|
||
InstallOutputPendingLineCount := InstallOutputPendingLineCount + 1;
|
||
|
||
if ForceFlush or (InstallOutputPendingLineCount >= InstallOutputFlushLines) then
|
||
FlushInstallOutput();
|
||
end;
|
||
|
||
procedure AppendInstallOutput(const Text: string);
|
||
begin
|
||
QueueInstallOutput(Text, True, True);
|
||
end;
|
||
|
||
procedure LogCurrentExtractFile();
|
||
var
|
||
FileName: string;
|
||
begin
|
||
FileName := ExpandConstant(CurrentFileName);
|
||
if FileName <> '' then
|
||
QueueInstallOutput(CustomMessage('InstallOutputFile') + ' ' + FileName, False, False);
|
||
end;
|
||
|
||
function SelectedTaskText(const TaskName, LabelText: string): string;
|
||
begin
|
||
if WizardIsTaskSelected(TaskName) then
|
||
Result := ' - ' + LabelText + #13#10
|
||
else
|
||
Result := '';
|
||
end;
|
||
|
||
function BuildInstallSummary(): string;
|
||
var
|
||
Tasks: string;
|
||
begin
|
||
Tasks :=
|
||
SelectedTaskText('desktopicon', CustomMessage('DesktopIcon')) +
|
||
SelectedTaskText('startmenuicon', CustomMessage('StartMenuIcon')) +
|
||
SelectedTaskText('autostart', CustomMessage('AutoStart')) +
|
||
SelectedTaskText('launchapp', CustomMessage('LaunchApp'));
|
||
if Tasks = '' then
|
||
Tasks := ' - (none)' + #13#10;
|
||
|
||
Result :=
|
||
CustomMessage('InstallSummaryVersion') + ': {#MyAppVersion}' + #13#10 +
|
||
CustomMessage('InstallSummaryInstallDir') + ': ' + WizardDirValue + #13#10 +
|
||
CustomMessage('InstallSummaryPayload') + ': {#PayloadDir}' + #13#10 +
|
||
'' + #13#10 +
|
||
CustomMessage('InstallSummaryTasks') + ':' + #13#10 +
|
||
Tasks;
|
||
end;
|
||
|
||
procedure UpdateInstallSummary();
|
||
begin
|
||
if SummaryMemo <> nil then
|
||
SummaryMemo.Text := BuildInstallSummary();
|
||
end;
|
||
|
||
procedure CurPageChanged(CurPageID: Integer);
|
||
begin
|
||
if (SummaryPage <> nil) and (CurPageID = SummaryPage.ID) then
|
||
UpdateInstallSummary();
|
||
if CurPageID = wpInstalling then
|
||
begin
|
||
LayoutInstallOutputMemo();
|
||
FlushInstallOutput();
|
||
end;
|
||
end;
|
||
|
||
function NextButtonClick(CurPageID: Integer): Boolean;
|
||
begin
|
||
Result := True;
|
||
if (CurPageID = wpSelectDir) and
|
||
((ExistingInstallDir = '') or (IsProtectedInstallPath(ExistingInstallDir) and (not IsAdminInstallMode))) then
|
||
begin
|
||
if not IsNonSystemWritableInstallDir(WizardDirValue) then
|
||
begin
|
||
MsgBox(CustomMessage('NonSystemInstallRequired'), mbError, MB_OK);
|
||
Result := False;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
function DirectoryHasEntries(const DirName: string): Boolean;
|
||
var
|
||
FindRec: TFindRec;
|
||
begin
|
||
Result := False;
|
||
if not DirExists(DirName) then
|
||
Exit;
|
||
|
||
if FindFirst(AddBackslash(DirName) + '*', FindRec) then
|
||
begin
|
||
try
|
||
repeat
|
||
if (FindRec.Name <> '.') and (FindRec.Name <> '..') then
|
||
begin
|
||
Result := True;
|
||
Exit;
|
||
end;
|
||
until not FindNext(FindRec);
|
||
finally
|
||
FindClose(FindRec);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
function PathLooksLikeUpgradeInstall(const DirName: string): Boolean;
|
||
begin
|
||
Result :=
|
||
FileExists(AddBackslash(DirName) + '{#MyAppExeName}') or
|
||
FileExists(AddBackslash(DirName) + 'YMhutBoxDesktop.exe') or
|
||
FileExists(AddBackslash(DirName) + 'installer-layout.txt') or
|
||
FileExists(AddBackslash(DirName) + 'config\installer-layout.dat') or
|
||
FileExists(AddBackslash(DirName) + 'config\installer-required-files.dat') or
|
||
FileExists(AddBackslash(DirName) + 'config\install-manifest.ini');
|
||
end;
|
||
|
||
function IsVCRedistInstalled(): Boolean;
|
||
var
|
||
Version: string;
|
||
Installed: Cardinal;
|
||
begin
|
||
Result :=
|
||
(RegQueryDWordValue(HKLM64, VCRedistKey, 'Installed', Installed) and (Installed = 1) and RegQueryStringValue(HKLM64, VCRedistKey, 'Version', Version) and (Version <> '') and (Version <> '0.0.0.0')) or
|
||
(RegQueryDWordValue(HKLM, VCRedistKey, 'Installed', Installed) and (Installed = 1) and RegQueryStringValue(HKLM, VCRedistKey, 'Version', Version) and (Version <> '') and (Version <> '0.0.0.0')) or
|
||
(RegQueryDWordValue(HKLM32, VCRedistKey, 'Installed', Installed) and (Installed = 1) and RegQueryStringValue(HKLM32, VCRedistKey, 'Version', Version) and (Version <> '') and (Version <> '0.0.0.0'));
|
||
end;
|
||
|
||
function HasWebView2RuntimeExecutable(const Root: string): Boolean;
|
||
var
|
||
FindRec: TFindRec;
|
||
begin
|
||
Result := False;
|
||
if not DirExists(Root) then
|
||
Exit;
|
||
|
||
if FileExists(AddBackslash(Root) + 'msedgewebview2.exe') then
|
||
begin
|
||
Result := True;
|
||
Exit;
|
||
end;
|
||
|
||
if FindFirst(AddBackslash(Root) + '*', FindRec) then
|
||
begin
|
||
try
|
||
repeat
|
||
if ((FindRec.Attributes and DirectoryAttribute) <> 0) and
|
||
FileExists(AddBackslash(Root) + FindRec.Name + '\msedgewebview2.exe') then
|
||
begin
|
||
Result := True;
|
||
Exit;
|
||
end;
|
||
until not FindNext(FindRec);
|
||
finally
|
||
FindClose(FindRec);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
function IsWebView2RuntimeVersionInstalled(const RootKey: Integer; const SubKey: string): Boolean;
|
||
var
|
||
Version: string;
|
||
begin
|
||
Result :=
|
||
(RegQueryStringValue(RootKey, SubKey, 'pv', Version) or
|
||
RegQueryStringValue(RootKey, SubKey, 'Version', Version)) and
|
||
(Version <> '') and
|
||
(Version <> '0.0.0.0');
|
||
end;
|
||
|
||
function IsWebView2Installed(): Boolean;
|
||
begin
|
||
Result :=
|
||
IsWebView2RuntimeVersionInstalled(HKLM64, WebView2ClientKey) or
|
||
IsWebView2RuntimeVersionInstalled(HKLM, WebView2ClientKey) or
|
||
IsWebView2RuntimeVersionInstalled(HKLM32, WebView2ClientKey) or
|
||
IsWebView2RuntimeVersionInstalled(HKCU, WebView2ClientKey) or
|
||
IsWebView2RuntimeVersionInstalled(HKLM64, WebView2ClientKeyWow64) or
|
||
IsWebView2RuntimeVersionInstalled(HKLM, WebView2ClientKeyWow64) or
|
||
IsWebView2RuntimeVersionInstalled(HKLM32, WebView2ClientKeyWow64) or
|
||
IsWebView2RuntimeVersionInstalled(HKCU, WebView2ClientKeyWow64) or
|
||
IsWebView2RuntimeVersionInstalled(HKLM64, WebView2ClientStateKey) or
|
||
IsWebView2RuntimeVersionInstalled(HKLM, WebView2ClientStateKey) or
|
||
IsWebView2RuntimeVersionInstalled(HKLM32, WebView2ClientStateKey) or
|
||
IsWebView2RuntimeVersionInstalled(HKCU, WebView2ClientStateKey) or
|
||
IsWebView2RuntimeVersionInstalled(HKLM64, WebView2ClientStateKeyWow64) or
|
||
IsWebView2RuntimeVersionInstalled(HKLM, WebView2ClientStateKeyWow64) or
|
||
IsWebView2RuntimeVersionInstalled(HKLM32, WebView2ClientStateKeyWow64) or
|
||
IsWebView2RuntimeVersionInstalled(HKCU, WebView2ClientStateKeyWow64) or
|
||
HasWebView2RuntimeExecutable(ExpandConstant('{localappdata}\Microsoft\EdgeWebView\Application')) or
|
||
HasWebView2RuntimeExecutable(ExpandConstant('{pf}\Microsoft\EdgeWebView\Application')) or
|
||
HasWebView2RuntimeExecutable(ExpandConstant('{pf32}\Microsoft\EdgeWebView\Application')) or
|
||
HasWebView2RuntimeExecutable(ExpandConstant('{localappdata}\Microsoft\EdgeCore')) or
|
||
HasWebView2RuntimeExecutable(ExpandConstant('{pf}\Microsoft\EdgeCore')) or
|
||
HasWebView2RuntimeExecutable(ExpandConstant('{pf32}\Microsoft\EdgeCore')) or
|
||
HasWebView2RuntimeExecutable(ExpandConstant('{localappdata}\Microsoft\Edge\Application')) or
|
||
HasWebView2RuntimeExecutable(ExpandConstant('{pf}\Microsoft\Edge\Application')) or
|
||
HasWebView2RuntimeExecutable(ExpandConstant('{pf32}\Microsoft\Edge\Application'));
|
||
end;
|
||
|
||
function WaitForWebView2Installed(const Seconds: Integer): Boolean;
|
||
var
|
||
Index: Integer;
|
||
begin
|
||
for Index := 1 to Seconds do
|
||
begin
|
||
if IsWebView2Installed() then
|
||
begin
|
||
Result := True;
|
||
Exit;
|
||
end;
|
||
Sleep(1000);
|
||
end;
|
||
|
||
Result := IsWebView2Installed();
|
||
end;
|
||
|
||
function WaitForVCRedistInstalled(const Seconds: Integer): Boolean;
|
||
var
|
||
Index: Integer;
|
||
begin
|
||
for Index := 1 to Seconds do
|
||
begin
|
||
if IsVCRedistInstalled() then
|
||
begin
|
||
Result := True;
|
||
Exit;
|
||
end;
|
||
Sleep(1000);
|
||
end;
|
||
|
||
Result := IsVCRedistInstalled();
|
||
end;
|
||
|
||
function GetCustomMessageValue(const Key: string): string;
|
||
begin
|
||
Result := CustomMessage(Key);
|
||
end;
|
||
|
||
function FormatCustomMessage(const Key, Value: string): string;
|
||
begin
|
||
Result := FmtMessage(CustomMessage(Key), [Value]);
|
||
end;
|
||
|
||
function FormatCustomMessage2(const Key, Value1, Value2: string): string;
|
||
begin
|
||
Result := FmtMessage(CustomMessage(Key), [Value1, Value2]);
|
||
end;
|
||
|
||
function DownloadAndInstallPrerequisite(const Url, FileName, Arguments, FriendlyName: string; var NeedsRestart: Boolean): Boolean;
|
||
var
|
||
ResultCode: Integer;
|
||
ExecutablePath: string;
|
||
begin
|
||
Result := False;
|
||
ExecutablePath := ExpandConstant('{tmp}\' + FileName);
|
||
DownloadPage.Clear;
|
||
DownloadPage.Add(Url, FileName, '');
|
||
if not WizardSilent then
|
||
DownloadPage.Show;
|
||
try
|
||
DownloadPage.Download;
|
||
except
|
||
if DownloadPage.AbortedByUser then
|
||
RaiseException(FormatCustomMessage('DependencyDownloadCancelled', FriendlyName))
|
||
else
|
||
RaiseException(FormatCustomMessage2('DependencyDownloadFailed', FriendlyName, GetExceptionMessage));
|
||
end;
|
||
if not WizardSilent then
|
||
DownloadPage.Hide;
|
||
|
||
if not Exec(ExecutablePath, Arguments, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
|
||
RaiseException(FormatCustomMessage('DependencyStartFailed', FriendlyName));
|
||
|
||
if (ResultCode <> 0) and (ResultCode <> 1638) and (ResultCode <> 3010) and (ResultCode <> 1641) then
|
||
RaiseException(FormatCustomMessage2('DependencyInstallFailed', FriendlyName, IntToStr(ResultCode)));
|
||
|
||
if (ResultCode = 3010) or (ResultCode = 1641) then
|
||
NeedsRestart := True;
|
||
|
||
Result := True;
|
||
end;
|
||
|
||
procedure CopyIfExists(const Source, TargetDir: string);
|
||
begin
|
||
if FileExists(Source) then
|
||
begin
|
||
ForceDirectories(TargetDir);
|
||
FileCopy(Source, AddBackslash(TargetDir) + ExtractFileName(Source), False);
|
||
end;
|
||
end;
|
||
|
||
procedure MigrateLegacyData();
|
||
var
|
||
TargetDir: string;
|
||
begin
|
||
TargetDir := ExpandConstant('{app}\data\legacy-imports');
|
||
CopyIfExists(ExpandConstant('{localappdata}\YMhut Box\settings.json'), TargetDir);
|
||
CopyIfExists(ExpandConstant('{localappdata}\YMhut Box\shared_preferences.json'), TargetDir);
|
||
CopyIfExists(ExpandConstant('{localappdata}\YMhut Box\app_state.db'), TargetDir);
|
||
CopyIfExists(ExpandConstant('{userappdata}\ymhut_box\settings.json'), TargetDir);
|
||
CopyIfExists(ExpandConstant('{userappdata}\ymhut_box\shared_preferences.json'), TargetDir);
|
||
CopyIfExists(ExpandConstant('{userappdata}\ymhut_box\app_state.db'), TargetDir);
|
||
end;
|
||
|
||
procedure DeleteFilesByPattern(const DirName, Pattern: string; const Recursive: Boolean);
|
||
var
|
||
FindRec: TFindRec;
|
||
Child: string;
|
||
begin
|
||
if not DirExists(DirName) then
|
||
Exit;
|
||
|
||
if FindFirst(AddBackslash(DirName) + Pattern, FindRec) then
|
||
begin
|
||
try
|
||
repeat
|
||
if ((FindRec.Attributes and DirectoryAttribute) = 0) then
|
||
DeleteFile(AddBackslash(DirName) + FindRec.Name);
|
||
until not FindNext(FindRec);
|
||
finally
|
||
FindClose(FindRec);
|
||
end;
|
||
end;
|
||
|
||
if not Recursive then
|
||
Exit;
|
||
|
||
if FindFirst(AddBackslash(DirName) + '*', FindRec) then
|
||
begin
|
||
try
|
||
repeat
|
||
if (FindRec.Name <> '.') and (FindRec.Name <> '..') and
|
||
((FindRec.Attributes and DirectoryAttribute) <> 0) then
|
||
begin
|
||
Child := AddBackslash(DirName) + FindRec.Name;
|
||
DeleteFilesByPattern(Child, Pattern, True);
|
||
end;
|
||
until not FindNext(FindRec);
|
||
finally
|
||
FindClose(FindRec);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
procedure CleanCultureFolders(const DirName: string);
|
||
var
|
||
FindRec: TFindRec;
|
||
CultureDir: string;
|
||
begin
|
||
if not DirExists(DirName) then
|
||
Exit;
|
||
|
||
if FindFirst(AddBackslash(DirName) + '*', FindRec) then
|
||
begin
|
||
try
|
||
repeat
|
||
if (FindRec.Name <> '.') and (FindRec.Name <> '..') and
|
||
((FindRec.Attributes and DirectoryAttribute) <> 0) then
|
||
begin
|
||
CultureDir := AddBackslash(DirName) + FindRec.Name;
|
||
if FileExists(AddBackslash(CultureDir) + 'Microsoft.ui.xaml.dll.mui') or
|
||
FileExists(AddBackslash(CultureDir) + 'Microsoft.UI.Xaml.Phone.dll.mui') then
|
||
DelTree(CultureDir, True, True, True);
|
||
end;
|
||
until not FindNext(FindRec);
|
||
finally
|
||
FindClose(FindRec);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
function ShouldPreserveAppEntry(const Name: string): Boolean;
|
||
var
|
||
LowerName: string;
|
||
begin
|
||
LowerName := Lowercase(Name);
|
||
Result :=
|
||
(LowerName = 'data') or
|
||
(LowerName = 'feedbackpackages') or
|
||
(Pos('unins', LowerName) = 1);
|
||
end;
|
||
|
||
procedure CleanOldApplicationPayload(const DirName: string);
|
||
var
|
||
FindRec: TFindRec;
|
||
EntryPath: string;
|
||
begin
|
||
if not DirExists(DirName) then
|
||
Exit;
|
||
|
||
if FindFirst(AddBackslash(DirName) + '*', FindRec) then
|
||
begin
|
||
try
|
||
repeat
|
||
if (FindRec.Name <> '.') and (FindRec.Name <> '..') and
|
||
(not ShouldPreserveAppEntry(FindRec.Name)) then
|
||
begin
|
||
EntryPath := AddBackslash(DirName) + FindRec.Name;
|
||
if ((FindRec.Attributes and DirectoryAttribute) <> 0) then
|
||
DelTree(EntryPath, True, True, True)
|
||
else
|
||
DeleteFile(EntryPath);
|
||
end;
|
||
until not FindNext(FindRec);
|
||
finally
|
||
FindClose(FindRec);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
procedure CleanLegacyInstallLayout();
|
||
var
|
||
AppDir: string;
|
||
begin
|
||
AppDir := ExpandConstant('{app}');
|
||
CleanOldApplicationPayload(AppDir);
|
||
DeleteFilesByPattern(AppDir, '*.winmd', True);
|
||
DeleteFilesByPattern(AppDir, '*.pdb', True);
|
||
DeleteFilesByPattern(AppDir, 'workloads*.json', True);
|
||
DeleteFilesByPattern(AddBackslash(AppDir) + 'Assets\data', '*.json', True);
|
||
DeleteFile(AddBackslash(AppDir) + 'installer-layout.txt');
|
||
DeleteFile(AddBackslash(AppDir) + 'config\installer-layout.dat');
|
||
DeleteFile(AddBackslash(AppDir) + 'config\installer-required-files.dat');
|
||
DeleteFile(AddBackslash(AppDir) + 'Assets\data\reference-data.dat');
|
||
CleanCultureFolders(AppDir);
|
||
end;
|
||
|
||
function PrepareToInstall(var NeedsRestart: Boolean): string;
|
||
var
|
||
ResultCode: Integer;
|
||
begin
|
||
AppendInstallOutput('Prepare system prerequisites.');
|
||
Result := '';
|
||
if (ExistingInstallDir <> '') and IsProtectedInstallPath(ExistingInstallDir) and (not IsAdminInstallMode) then
|
||
begin
|
||
Result := CustomMessage('ProtectedInstallNeedsAdmin');
|
||
Exit;
|
||
end;
|
||
|
||
if IsProtectedInstallPath(WizardDirValue) and (not IsAdminInstallMode) then
|
||
begin
|
||
Result := CustomMessage('ProtectedInstallNeedsAdmin');
|
||
Exit;
|
||
end;
|
||
|
||
try
|
||
if not IsWebView2Installed() then
|
||
begin
|
||
DownloadAndInstallPrerequisite('{#WebView2BootstrapperUrl}', 'MicrosoftEdgeWebView2Setup.exe', '/silent /install', GetCustomMessageValue('WebView2RuntimeName'), NeedsRestart);
|
||
|
||
if not WaitForWebView2Installed(60) then
|
||
begin
|
||
Result := GetCustomMessageValue('WebView2StillMissing');
|
||
Exit;
|
||
end;
|
||
end;
|
||
|
||
if (not IsVCRedistInstalled()) then
|
||
begin
|
||
DownloadAndInstallPrerequisite('{#VCRedistUrl}', 'vc_redist.x64.exe', '/install /quiet /norestart', GetCustomMessageValue('VCRuntimeName'), NeedsRestart);
|
||
|
||
if not WaitForVCRedistInstalled(10) then
|
||
begin
|
||
Result := GetCustomMessageValue('VCRuntimeStillMissing');
|
||
Exit;
|
||
end;
|
||
end;
|
||
except
|
||
Result := GetExceptionMessage;
|
||
end;
|
||
end;
|
||
|
||
function ShouldSkipPage(PageID: Integer): Boolean;
|
||
begin
|
||
Result := (PageID = wpSelectDir) and (ExistingInstallDir <> '') and
|
||
((not IsProtectedInstallPath(ExistingInstallDir)) or IsAdminInstallMode);
|
||
end;
|
||
|
||
procedure CurStepChanged(CurStep: TSetupStep);
|
||
begin
|
||
if CurStep = ssInstall then
|
||
begin
|
||
LastInstallProgressPercent := -1;
|
||
AppendInstallOutput(CustomMessage('InstallOutputClean'));
|
||
CleanLegacyInstallLayout();
|
||
AppendInstallOutput(CustomMessage('InstallOutputExtract'));
|
||
end
|
||
else if CurStep = ssPostInstall then
|
||
begin
|
||
FlushInstallOutput();
|
||
AppendInstallOutput(CustomMessage('InstallOutputPostInstall'));
|
||
MigrateLegacyData();
|
||
AppendInstallOutput(CustomMessage('InstallOutputDone'));
|
||
end;
|
||
end;
|
||
|
||
procedure CurInstallProgressChanged(CurProgress, MaxProgress: Integer);
|
||
var
|
||
ProgressPercent: Integer;
|
||
begin
|
||
if (MaxProgress > 0) then
|
||
begin
|
||
if (CurProgress >= MaxProgress) then
|
||
begin
|
||
if LastInstallProgressPercent <> 100 then
|
||
begin
|
||
LastInstallProgressPercent := 100;
|
||
FlushInstallOutput();
|
||
end;
|
||
Exit;
|
||
end;
|
||
|
||
if (CurProgress > 0) then
|
||
begin
|
||
ProgressPercent := CurProgress * 100 div MaxProgress;
|
||
if ProgressPercent <> LastInstallProgressPercent then
|
||
begin
|
||
LastInstallProgressPercent := ProgressPercent;
|
||
FlushInstallOutput();
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|