Files
QWQLwToo f59190251d
build-winui / winui (push) Has been cancelled
Add project metadata and docs
2026-06-26 13:26:40 +08:00

1016 lines
34 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
; 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;