521 lines
20 KiB
Plaintext
521 lines
20 KiB
Plaintext
// Included by the product installer and the isolated migration fixture.
|
|
const
|
|
#ifdef AcceptanceFixture
|
|
ProductUninstallKey = 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{76B778B5-76C6-4F60-9431-9E67C2A351AF}_is1';
|
|
ProductSettingsKey = 'Software\PebrelUpdateAcceptance';
|
|
LegacySettingsKey = 'Software\PebrelUpdateAcceptanceLegacy';
|
|
#else
|
|
ProductUninstallKey = 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{61022144-7D0A-4E54-94F2-C329A8F58656}_is1';
|
|
ProductSettingsKey = 'Software\Pebrel';
|
|
LegacySettingsKey = 'Software\Nebula Terminal';
|
|
#endif
|
|
|
|
var
|
|
PreviousInstallDir: string;
|
|
LegacyInstallDir: string;
|
|
LegacyUninstaller: string;
|
|
MigrationFailed: Boolean;
|
|
|
|
function MigrationFileAttributes(FileName: string): LongWord;
|
|
external 'GetFileAttributesW@kernel32.dll stdcall';
|
|
|
|
function NormalizedDirectory(Value: string): string;
|
|
begin
|
|
Result := RemoveBackslashUnlessRoot(ExpandFileName(RemoveQuotes(Trim(Value))));
|
|
end;
|
|
|
|
function SameDirectory(Left, Right: string): Boolean;
|
|
begin
|
|
Result := CompareText(NormalizedDirectory(Left), NormalizedDirectory(Right)) = 0;
|
|
end;
|
|
|
|
function SuggestedInstallDir(Previous, Fallback: string): string;
|
|
begin
|
|
Result := Fallback;
|
|
if Previous = '' then
|
|
Exit;
|
|
Result := NormalizedDirectory(Previous);
|
|
if CompareText(ExtractFileName(Result), 'Nebula Terminal') = 0 then
|
|
Result := AddBackslash(ExtractFileDir(Result)) + 'Pebrel';
|
|
end;
|
|
|
|
function PathContainsDirectory(Value, Directory: string): Boolean;
|
|
var
|
|
Entry: string;
|
|
Separator: Integer;
|
|
begin
|
|
Result := False;
|
|
repeat
|
|
Separator := Pos(';', Value);
|
|
if Separator = 0 then
|
|
Separator := Length(Value) + 1;
|
|
Entry := Copy(Value, 1, Separator - 1);
|
|
if (Trim(Entry) <> '') and SameDirectory(Entry, Directory) then begin
|
|
Result := True;
|
|
Exit;
|
|
end;
|
|
Delete(Value, 1, Separator);
|
|
until Value = '';
|
|
end;
|
|
|
|
function RemovePathDirectory(Value, Directory: string): string;
|
|
var
|
|
Entry: string;
|
|
Separator: Integer;
|
|
FirstEntry: Boolean;
|
|
begin
|
|
Result := '';
|
|
FirstEntry := True;
|
|
repeat
|
|
Separator := Pos(';', Value);
|
|
if Separator = 0 then
|
|
Separator := Length(Value) + 1;
|
|
Entry := Copy(Value, 1, Separator - 1);
|
|
if (Trim(Entry) = '') or not SameDirectory(Entry, Directory) then begin
|
|
if not FirstEntry then
|
|
Result := Result + ';';
|
|
Result := Result + Entry;
|
|
FirstEntry := False;
|
|
end;
|
|
Delete(Value, 1, Separator);
|
|
until Value = '';
|
|
end;
|
|
|
|
function IsLegacyUninstaller(Directory, FileName: string): Boolean;
|
|
var
|
|
Name: string;
|
|
Index: Integer;
|
|
begin
|
|
Name := Lowercase(ExtractFileName(FileName));
|
|
Result := SameDirectory(ExtractFileDir(FileName), Directory) and
|
|
(Length(Name) = 12) and (Copy(Name, 1, 5) = 'unins') and
|
|
(Copy(Name, 9, 4) = '.exe');
|
|
if Result then
|
|
for Index := 6 to 8 do
|
|
if (Name[Index] < '0') or (Name[Index] > '9') then
|
|
Result := False;
|
|
end;
|
|
|
|
function DefaultInstallDir(Param: string): string;
|
|
begin
|
|
Result := SuggestedInstallDir(PreviousInstallDir,
|
|
ExpandConstant('{localappdata}\Programs\Pebrel'));
|
|
end;
|
|
|
|
procedure InitializeWizard;
|
|
begin
|
|
{ Reuse the registered directory for normal upgrades, so Inno recognizes it
|
|
as an existing installation. Keep the old-brand relocation suggestion and
|
|
an explicit /DIR selection intact. }
|
|
if (PreviousInstallDir <> '') and
|
|
(CompareText(ExtractFileName(NormalizedDirectory(PreviousInstallDir)), 'Nebula Terminal') = 0) and
|
|
(ExpandConstant('{param:DIR|}') = '') then
|
|
WizardForm.DirEdit.Text := SuggestedInstallDir(PreviousInstallDir,
|
|
ExpandConstant('{localappdata}\Programs\Pebrel'));
|
|
end;
|
|
|
|
procedure DiscoverLegacyInstallation;
|
|
var
|
|
Pending, UninstallCommand: string;
|
|
begin
|
|
RegQueryStringValue(HKCU, ProductUninstallKey, 'Inno Setup: App Path', PreviousInstallDir);
|
|
if (PreviousInstallDir <> '') and
|
|
FileExists(AddBackslash(PreviousInstallDir) + 'nebula.exe') then begin
|
|
LegacyInstallDir := NormalizedDirectory(PreviousInstallDir);
|
|
if RegQueryStringValue(HKCU, ProductUninstallKey, 'UninstallString', UninstallCommand) then begin
|
|
UninstallCommand := RemoveQuotes(Trim(UninstallCommand));
|
|
if IsLegacyUninstaller(LegacyInstallDir, UninstallCommand) then
|
|
LegacyUninstaller := UninstallCommand;
|
|
end;
|
|
end;
|
|
if RegQueryStringValue(HKCU, ProductSettingsKey, 'PendingLegacyInstallDir', Pending) then begin
|
|
if Trim(Pending) = '' then
|
|
RaiseException('The pending legacy installation directory is empty.');
|
|
if (LegacyInstallDir <> '') and not SameDirectory(LegacyInstallDir, Pending) then
|
|
RaiseException('Another legacy installation is awaiting migration: ' + Pending);
|
|
LegacyInstallDir := NormalizedDirectory(Pending);
|
|
RegQueryStringValue(HKCU, ProductSettingsKey, 'PendingLegacyUninstaller', LegacyUninstaller);
|
|
if not IsLegacyUninstaller(LegacyInstallDir, LegacyUninstaller) then
|
|
LegacyUninstaller := '';
|
|
end;
|
|
end;
|
|
|
|
function IsExecutableRunning(FileName: string): Boolean;
|
|
var
|
|
Locator, Service, Processes, Process: Variant;
|
|
Index: Integer;
|
|
Name: string;
|
|
begin
|
|
Result := False;
|
|
if not FileExists(FileName) then
|
|
Exit;
|
|
Name := ExtractFileName(FileName);
|
|
StringChangeEx(Name, '''', '''''', True);
|
|
Locator := CreateOleObject('WbemScripting.SWbemLocator');
|
|
Service := Locator.ConnectServer('', 'root\CIMV2');
|
|
Processes := Service.ExecQuery('SELECT ExecutablePath FROM Win32_Process WHERE Name = ''' + Name + '''');
|
|
for Index := 0 to Processes.Count - 1 do begin
|
|
Process := Processes.ItemIndex(Index);
|
|
if not VarIsNull(Process.ExecutablePath) then
|
|
if SameDirectory(Process.ExecutablePath, FileName) then begin
|
|
Result := True;
|
|
Exit;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function SafePayloadPath(Root, Relative: string): string;
|
|
var
|
|
Part, Parent: string;
|
|
Attributes: LongWord;
|
|
begin
|
|
Root := NormalizedDirectory(Root);
|
|
Result := ExpandFileName(AddBackslash(Root) + Relative);
|
|
if Pos(Lowercase(AddBackslash(Root)), Lowercase(Result)) <> 1 then
|
|
RaiseException('Invalid legacy payload path: ' + Result);
|
|
Part := Result;
|
|
repeat
|
|
Attributes := MigrationFileAttributes(Part);
|
|
if (Attributes <> $FFFFFFFF) and ((Attributes and $400) <> 0) then
|
|
RaiseException('Linked legacy installation path must be migrated manually: ' + Part);
|
|
Parent := ExtractFileDir(Part);
|
|
if SameDirectory(Parent, Part) then
|
|
Break;
|
|
Part := Parent;
|
|
until Part = '';
|
|
end;
|
|
|
|
procedure RemoveLegacyFile(Root, Relative: string);
|
|
var
|
|
FileName: string;
|
|
begin
|
|
FileName := SafePayloadPath(Root, Relative);
|
|
if FileExists(FileName) and not DeleteFile(FileName) then
|
|
RaiseException('Unable to remove old installation file: ' + FileName);
|
|
end;
|
|
|
|
procedure RemoveEmptyLegacyDir(Root, Relative: string);
|
|
var
|
|
Directory: string;
|
|
begin
|
|
Directory := SafePayloadPath(Root, Relative);
|
|
if DirExists(Directory) then
|
|
RemoveDir(Directory);
|
|
end;
|
|
|
|
procedure RemoveLegacyPayload(OldDir, NewDir, Uninstaller: string);
|
|
begin
|
|
if OldDir = '' then
|
|
Exit;
|
|
RemoveLegacyFile(OldDir, 'runtime\nebula-hook.exe');
|
|
RemoveLegacyFile(OldDir, 'nebula-hook.exe');
|
|
RemoveLegacyFile(OldDir, 'skills\nebula-runtime\agents\openai.yaml');
|
|
RemoveLegacyFile(OldDir, 'skills\nebula-runtime\SKILL.md');
|
|
RemoveEmptyLegacyDir(OldDir, 'skills\nebula-runtime\agents');
|
|
RemoveEmptyLegacyDir(OldDir, 'skills\nebula-runtime');
|
|
if not SameDirectory(OldDir, NewDir) then begin
|
|
RemoveLegacyFile(OldDir, 'runtime\conpty.dll');
|
|
RemoveLegacyFile(OldDir, 'runtime\OpenConsole.exe');
|
|
RemoveLegacyFile(OldDir, 'conpty.dll');
|
|
RemoveLegacyFile(OldDir, 'OpenConsole.exe');
|
|
RemoveLegacyFile(OldDir, 'README.md');
|
|
RemoveLegacyFile(OldDir, 'docs\CHANGELOG.md');
|
|
RemoveLegacyFile(OldDir, 'docs\INSTALL.md');
|
|
RemoveLegacyFile(OldDir, 'docs\lua-configuration.md');
|
|
RemoveLegacyFile(OldDir, 'docs\runtime-control-api.md');
|
|
RemoveLegacyFile(OldDir, 'docs\runtime-api-v1.schema.json');
|
|
RemoveLegacyFile(OldDir, 'fonts\MapleMonoNormal-NF-CN-Regular.ttf');
|
|
RemoveLegacyFile(OldDir, 'licenses\LICENSE');
|
|
RemoveLegacyFile(OldDir, 'licenses\LICENSE-LUA');
|
|
RemoveLegacyFile(OldDir, 'licenses\LICENSE-MLUA');
|
|
RemoveLegacyFile(OldDir, 'licenses\THIRD-PARTY-NOTICES');
|
|
if IsLegacyUninstaller(OldDir, Uninstaller) then begin
|
|
RemoveLegacyFile(OldDir, ExtractFileName(Uninstaller));
|
|
RemoveLegacyFile(OldDir, ChangeFileExt(ExtractFileName(Uninstaller), '.dat'));
|
|
RemoveLegacyFile(OldDir, ChangeFileExt(ExtractFileName(Uninstaller), '.msg'));
|
|
end;
|
|
RemoveEmptyLegacyDir(OldDir, 'runtime');
|
|
RemoveEmptyLegacyDir(OldDir, 'docs');
|
|
RemoveEmptyLegacyDir(OldDir, 'fonts');
|
|
RemoveEmptyLegacyDir(OldDir, 'licenses');
|
|
RemoveEmptyLegacyDir(OldDir, 'skills');
|
|
end;
|
|
RemoveLegacyFile(OldDir, 'nebula.exe');
|
|
if not SameDirectory(OldDir, NewDir) then
|
|
RemoveDir(OldDir);
|
|
end;
|
|
|
|
procedure RemoveOwnedShortcut(FileName, Target: string);
|
|
var
|
|
Shell, Shortcut: Variant;
|
|
begin
|
|
if not FileExists(FileName) then
|
|
Exit;
|
|
Shell := CreateOleObject('WScript.Shell');
|
|
Shortcut := Shell.CreateShortcut(FileName);
|
|
if SameDirectory(Shortcut.TargetPath, Target) and not DeleteFile(FileName) then
|
|
RaiseException('Unable to remove old shortcut: ' + FileName);
|
|
end;
|
|
|
|
procedure RemoveOwnedRegistryKey(Key, ValueName, Expected: string);
|
|
var
|
|
Value: string;
|
|
begin
|
|
if RegQueryStringValue(HKCU, Key, ValueName, Value) and
|
|
(CompareText(Value, Expected) = 0) and not RegDeleteKeyIncludingSubkeys(HKCU, Key) then
|
|
RaiseException('Unable to remove old registration: ' + Key);
|
|
end;
|
|
|
|
procedure RemoveOwnedContextMenu(Key, ExpectedCommand: string);
|
|
var
|
|
Command: string;
|
|
begin
|
|
if RegQueryStringValue(HKCU, Key + '\command', '', Command) and
|
|
(CompareText(Command, ExpectedCommand) = 0) and not RegDeleteKeyIncludingSubkeys(HKCU, Key) then
|
|
RaiseException('Unable to remove old context menu: ' + Key);
|
|
end;
|
|
|
|
{ —— 按 WSL 发行版注册的右键项 ——
|
|
|
|
WSL 发行版子菜单按**安装那一刻**机器上的发行版动态生成:
|
|
`Lxss` 里的 `DistributionName` 是唯一真相,枚举口径与 Rust 侧
|
|
`shell_detect::find_wsl_distros` 一致(跳过 `docker-desktop*` 这类 plumbing 发行版)。
|
|
|
|
走 `[Code]` 而不是 `[Registry]` 的原因只有一个:键的条数取决于机器上有几个发行版。
|
|
代价是这些键没有 `uninsdeletekey`,卸载由 `RemoveOwnedWslContextMenus` 按名字前缀
|
|
加精确命令与级联菜单归属标记认领后删除。 }
|
|
|
|
const
|
|
LxssRoot = 'Software\Microsoft\Windows\CurrentVersion\Lxss';
|
|
|
|
function WslDistroNames: TArrayOfString;
|
|
var
|
|
Names: TArrayOfString;
|
|
Index, Count: Integer;
|
|
Name: string;
|
|
begin
|
|
SetArrayLength(Result, 0);
|
|
if not RegGetSubkeyNames(HKCU, LxssRoot, Names) then
|
|
Exit;
|
|
SetArrayLength(Result, GetArrayLength(Names));
|
|
Count := 0;
|
|
for Index := 0 to GetArrayLength(Names) - 1 do
|
|
if RegQueryStringValue(HKCU, LxssRoot + '\' + Names[Index], 'DistributionName', Name) then
|
|
if (Name <> '') and (Pos('docker-desktop', Lowercase(Name)) <> 1) then begin
|
|
Result[Count] := Name;
|
|
Count := Count + 1;
|
|
end;
|
|
SetArrayLength(Result, Count);
|
|
end;
|
|
|
|
{ WSL 右键项按精确可执行文件前缀归属;级联菜单还校验所有子项,保留用户编辑。
|
|
发行版移除后,下次安装重建菜单时不留下旧项。 }
|
|
function IsOwnedWslCommand(Command, Executable: string): Boolean;
|
|
var
|
|
Prefix: string;
|
|
begin
|
|
Prefix := '"' + Executable + '" --gpui --shell "wsl:';
|
|
Result := CompareText(Copy(Command, 1, Length(Prefix)), Prefix) = 0;
|
|
end;
|
|
|
|
function IsOwnedWslMenu(Key, Executable: string): Boolean;
|
|
var
|
|
Owner, Command: string;
|
|
Children, Subkeys: TArrayOfString;
|
|
Index: Integer;
|
|
begin
|
|
Result := False;
|
|
if not RegQueryStringValue(HKCU, Key, 'PebrelOwner', Owner) or
|
|
(CompareText(Owner, Executable) <> 0) then
|
|
Exit;
|
|
if not RegGetSubkeyNames(HKCU, Key, Subkeys) then
|
|
Exit;
|
|
for Index := 0 to GetArrayLength(Subkeys) - 1 do
|
|
if CompareText(Subkeys[Index], 'shell') <> 0 then
|
|
Exit;
|
|
if GetArrayLength(Subkeys) > 0 then begin
|
|
if not RegGetSubkeyNames(HKCU, Key + '\shell', Children) then
|
|
Exit;
|
|
for Index := 0 to GetArrayLength(Children) - 1 do begin
|
|
if not RegQueryStringValue(HKCU, Key + '\shell\' + Children[Index] + '\command', '', Command) or
|
|
not IsOwnedWslCommand(Command, Executable) then
|
|
Exit;
|
|
end;
|
|
end;
|
|
Result := True;
|
|
end;
|
|
|
|
procedure RemoveOwnedWslContextMenusAt(Root, Executable: string);
|
|
var
|
|
Key, Command: string;
|
|
Names: TArrayOfString;
|
|
NameIndex: Integer;
|
|
begin
|
|
if RegGetSubkeyNames(HKCU, Root, Names) then
|
|
for NameIndex := 0 to GetArrayLength(Names) - 1 do
|
|
if Pos('PebrelWsl', Names[NameIndex]) = 1 then begin
|
|
Key := Root + '\' + Names[NameIndex];
|
|
Command := '';
|
|
if IsOwnedWslMenu(Key, Executable) or
|
|
(RegQueryStringValue(HKCU, Key + '\command', '', Command) and
|
|
IsOwnedWslCommand(Command, Executable)) then
|
|
if not RegDeleteKeyIncludingSubkeys(HKCU, Key) then
|
|
RaiseException('Unable to remove an owned WSL context menu: ' + Key);
|
|
end;
|
|
end;
|
|
|
|
procedure RemoveOwnedWslContextMenus;
|
|
var
|
|
Roots: array[0..1] of string;
|
|
Index: Integer;
|
|
begin
|
|
Roots[0] := 'Software\Classes\Directory\shell';
|
|
Roots[1] := 'Software\Classes\Directory\Background\shell';
|
|
for Index := 0 to 1 do
|
|
RemoveOwnedWslContextMenusAt(Roots[Index], ExpandConstant('{app}\pebrel.exe'));
|
|
end;
|
|
|
|
procedure RegisterWslContextMenuAt(Root, Executable, DirectoryArgument: string;
|
|
Distros: TArrayOfString);
|
|
var
|
|
Index: Integer;
|
|
Distro, Verb, Command, Key, Menu: string;
|
|
begin
|
|
Menu := Root + '\PebrelWslMenu';
|
|
{ Never overwrite an unknown installation or an edited submenu. }
|
|
if RegKeyExists(HKCU, Menu) and not IsOwnedWslMenu(Menu, Executable) then
|
|
RaiseException(FmtMessage(CustomMessage('WslMenuConflict'), [Menu]));
|
|
RemoveOwnedWslContextMenusAt(Root, Executable);
|
|
if GetArrayLength(Distros) = 0 then
|
|
Exit;
|
|
if not RegWriteStringValue(HKCU, Menu, 'MUIVerb', CustomMessage('OpenInPebrelWsl')) or
|
|
not RegWriteStringValue(HKCU, Menu, 'Icon', Executable + ',0') or
|
|
not RegWriteStringValue(HKCU, Menu, 'SubCommands', '') or
|
|
not RegWriteStringValue(HKCU, Menu, 'PebrelOwner', Executable) then
|
|
RaiseException(CustomMessage('WslMenuRegistrationFailed'));
|
|
for Index := 0 to GetArrayLength(Distros) - 1 do begin
|
|
Distro := Distros[Index];
|
|
{ 键名用序号而不是发行版名:注册表键名里带空格与非 ASCII 只会给自己找麻烦,
|
|
何况我们靠前缀认领。 }
|
|
Verb := 'PebrelWsl' + IntToStr(Index);
|
|
{ `--shell` 必须排在 `--working-directory` 之前:盘根(`D:\`)时后者的收尾
|
|
反斜杠会吃掉它的收尾引号,并把后面整段并进同一个参数(issue #36 的另一面),
|
|
顺序写反会静默开出一个既没有 cwd、也没用上指定发行版的标签。 }
|
|
Command := '"' + Executable + '" --gpui --shell "wsl:' + Distro + '" --working-directory ';
|
|
Key := Menu + '\shell\' + Verb;
|
|
if not RegWriteStringValue(HKCU, Key, 'MUIVerb', Distro) or
|
|
not RegWriteStringValue(HKCU, Key, 'Icon', Executable + ',0') or
|
|
not RegWriteStringValue(HKCU, Key + '\command', '', Command + '"' + DirectoryArgument + '"') then
|
|
RaiseException('Unable to register the WSL context menu for ' + Distro + '.');
|
|
end;
|
|
end;
|
|
|
|
procedure RegisterWslContextMenus;
|
|
var
|
|
Distros: TArrayOfString;
|
|
Executable: string;
|
|
begin
|
|
Distros := WslDistroNames;
|
|
Executable := ExpandConstant('{app}\pebrel.exe');
|
|
RegisterWslContextMenuAt('Software\Classes\Directory\shell', Executable, '%1', Distros);
|
|
RegisterWslContextMenuAt('Software\Classes\Directory\Background\shell', Executable, '%V', Distros);
|
|
end;
|
|
|
|
procedure MigrateLegacyIntegrations;
|
|
var
|
|
Executable, ExistingPath, Key: string;
|
|
begin
|
|
Executable := AddBackslash(LegacyInstallDir) + 'nebula.exe';
|
|
RemoveOwnedShortcut(ExpandConstant('{autodesktop}\Nebula Terminal.lnk'), Executable);
|
|
RemoveOwnedShortcut(ExpandConstant('{autodesktop}\Pebrel.lnk'), Executable);
|
|
RemoveOwnedShortcut(ExpandConstant('{userstartup}\Nebula Terminal.lnk'), Executable);
|
|
RemoveOwnedShortcut(ExpandConstant('{userstartup}\Pebrel.lnk'), Executable);
|
|
RemoveOwnedShortcut(ExpandConstant('{userprograms}\Nebula Terminal\Nebula Terminal.lnk'), Executable);
|
|
RemoveOwnedShortcut(ExpandConstant('{userprograms}\Pebrel\Pebrel.lnk'), Executable);
|
|
if LegacyUninstaller <> '' then begin
|
|
RemoveOwnedShortcut(ExpandConstant('{userprograms}\Nebula Terminal\Uninstall Nebula Terminal.lnk'), LegacyUninstaller);
|
|
RemoveOwnedShortcut(ExpandConstant('{userprograms}\Nebula Terminal\卸载 Nebula Terminal.lnk'), LegacyUninstaller);
|
|
end;
|
|
RemoveDir(ExpandConstant('{userprograms}\Nebula Terminal'));
|
|
RemoveOwnedRegistryKey('Software\Microsoft\Windows\CurrentVersion\App Paths\nebula.exe', '', Executable);
|
|
Key := 'Software\Classes\Directory\Background\shell\NebulaTerminal';
|
|
RemoveOwnedContextMenu(Key, '"' + Executable + '" --gpui --working-directory "%V"');
|
|
Key := 'Software\Classes\Directory\shell\NebulaTerminal';
|
|
RemoveOwnedContextMenu(Key, '"' + Executable + '" --gpui --working-directory "%1"');
|
|
if RegValueExists(HKCU, LegacySettingsKey, 'InstallerAddedToPath') then begin
|
|
if SameDirectory(LegacyInstallDir, ExpandConstant('{app}')) then begin
|
|
if not RegWriteDWordValue(HKCU, ProductSettingsKey, 'InstallerAddedToPath', 1) then
|
|
RaiseException('Unable to transfer PATH ownership.');
|
|
end else if RegQueryStringValue(HKCU, 'Environment', 'Path', ExistingPath) then begin
|
|
if not RegWriteExpandStringValue(HKCU, 'Environment', 'Path',
|
|
RemovePathDirectory(ExistingPath, LegacyInstallDir)) then
|
|
RaiseException('Unable to remove old installation directory from PATH.');
|
|
end;
|
|
if not RegDeleteValue(HKCU, LegacySettingsKey, 'InstallerAddedToPath') then
|
|
RaiseException('Unable to clear previous PATH ownership.');
|
|
RegDeleteKeyIfEmpty(HKCU, LegacySettingsKey);
|
|
end;
|
|
end;
|
|
|
|
#ifndef MigrationFixture
|
|
function InitializeSetup: Boolean;
|
|
begin
|
|
Result := True;
|
|
try
|
|
DiscoverLegacyInstallation;
|
|
except
|
|
Result := False;
|
|
SuppressibleMsgBox(FmtMessage(CustomMessage('MigrationPreflightFailed'), [GetExceptionMessage]),
|
|
mbError, MB_OK, IDOK);
|
|
end;
|
|
end;
|
|
|
|
function PrepareToInstall(var NeedsRestart: Boolean): string;
|
|
var
|
|
Executable: string;
|
|
begin
|
|
Result := '';
|
|
if LegacyInstallDir = '' then
|
|
Exit;
|
|
try
|
|
Executable := SafePayloadPath(LegacyInstallDir, 'nebula.exe');
|
|
if IsExecutableRunning(Executable) then
|
|
Result := FmtMessage(CustomMessage('CloseLegacyProgram'), [Executable]);
|
|
except
|
|
Result := FmtMessage(CustomMessage('MigrationPreflightFailed'), [GetExceptionMessage]);
|
|
end;
|
|
end;
|
|
|
|
procedure CurStepChanged(CurStep: TSetupStep);
|
|
begin
|
|
if CurStep <> ssPostInstall then
|
|
Exit;
|
|
{ 与旧版迁移无关,装完就要做:菜单项取决于本机装了哪些 WSL 发行版。 }
|
|
RegisterWslContextMenus;
|
|
if LegacyInstallDir = '' then
|
|
Exit;
|
|
try
|
|
if not RegWriteStringValue(HKCU, ProductSettingsKey, 'PendingLegacyInstallDir', LegacyInstallDir) or
|
|
not RegWriteStringValue(HKCU, ProductSettingsKey, 'PendingLegacyUninstaller', LegacyUninstaller) then
|
|
RaiseException('Unable to record the previous installation for migration.');
|
|
MigrateLegacyIntegrations;
|
|
RemoveLegacyPayload(LegacyInstallDir, ExpandConstant('{app}'), LegacyUninstaller);
|
|
if not RegDeleteValue(HKCU, ProductSettingsKey, 'PendingLegacyInstallDir') or
|
|
not RegDeleteValue(HKCU, ProductSettingsKey, 'PendingLegacyUninstaller') then
|
|
RaiseException('Unable to clear the completed migration record.');
|
|
except
|
|
MigrationFailed := True;
|
|
Log('Legacy migration failed: ' + GetExceptionMessage);
|
|
SuppressibleMsgBox(FmtMessage(CustomMessage('MigrationFailed'), [GetExceptionMessage]),
|
|
mbError, MB_OK, IDOK);
|
|
end;
|
|
end;
|
|
|
|
function GetCustomSetupExitCode: Integer;
|
|
begin
|
|
Result := 0;
|
|
if MigrationFailed then
|
|
Result := 1;
|
|
end;
|
|
#endif
|