Skip to content

Commit aa83abb

Browse files
committed
加快安装完成后的安装包清理
- 将安装器完成页清理调度改为把当前安装包路径和已安装 CodexCliPlus.exe 路径传给清理器,避免按钮路径复制整个安装包。 - 精简 MicaSetup 清理模板,移除自复制和自清理入口,改为隐藏启动已安装应用的清理模式并继续校验目标为当前安装器。 - 在桌面应用启动入口新增安装包清理模式,先于常规桌面启动运行,等待安装器退出后删除安装包,失败时使用 MoveFileEx 延迟删除。 - 新增安装包清理模式测试,并更新安装器包装测试断言和伪模板以覆盖新调度路径。
1 parent 9673f8d commit aa83abb

7 files changed

Lines changed: 443 additions & 291 deletions

File tree

Lines changed: 17 additions & 228 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,18 @@
1-
using MicaSetup.Natives;
21
using System;
32
using System.Diagnostics;
43
using System.Globalization;
54
using System.IO;
6-
using System.Security.Cryptography;
75
using System.Text;
8-
using System.Threading;
96

107
namespace MicaSetup.Helper;
118

129
public static class InstallerSelfCleanupHelper
1310
{
14-
private const string SelfCleanupArgument = "codexcliplus-self-cleanup";
11+
private const string InstallerCleanupArgument = "codexcliplus-installer-cleanup";
1512
private const string TargetArgument = "codexcliplus-cleanup-target";
1613
private const string ParentProcessArgument = "codexcliplus-cleanup-parent";
17-
private const int DeleteRetryCount = 60;
18-
private const int DeleteRetryDelayMilliseconds = 1000;
19-
private const int ParentProcessWaitMilliseconds = 30000;
2014

21-
public static bool TryRunSelfCleanupMode()
22-
{
23-
string[] args = Environment.GetCommandLineArgs();
24-
if (!HasArgument(args, SelfCleanupArgument))
25-
{
26-
return false;
27-
}
28-
29-
try
30-
{
31-
RunSelfCleanup(args);
32-
}
33-
catch (Exception e)
34-
{
35-
Logger.Error("[InstallerCleanup] self cleanup failed:", e);
36-
}
37-
38-
return true;
39-
}
40-
41-
public static void ScheduleDelete(string installerPath)
15+
public static void ScheduleDelete(string installerPath, string cleanupHelperPath)
4216
{
4317
string targetPath = NormalizeInstallerPath(installerPath);
4418
string currentExecutablePath = GetCurrentExecutablePath();
@@ -47,13 +21,13 @@ public static class InstallerSelfCleanupHelper
4721
throw new InvalidOperationException("[InstallerCleanup] delete target must be the running installer.");
4822
}
4923

50-
string helperPath = CreateHelperCopy(currentExecutablePath);
24+
string helperPath = NormalizeCleanupHelperPath(cleanupHelperPath, targetPath);
5125
int parentProcessId = Process.GetCurrentProcess().Id;
5226
string arguments = string.Join(
5327
" ",
54-
"/" + SelfCleanupArgument,
55-
"/" + TargetArgument + "=" + EncodeArgument(targetPath),
56-
"/" + ParentProcessArgument + "=" + parentProcessId.ToString(CultureInfo.InvariantCulture)
28+
"--" + InstallerCleanupArgument,
29+
"--" + TargetArgument + "=" + EncodeArgument(targetPath),
30+
"--" + ParentProcessArgument + "=" + parentProcessId.ToString(CultureInfo.InvariantCulture)
5731
);
5832

5933
using Process process = new()
@@ -71,36 +45,6 @@ public static class InstallerSelfCleanupHelper
7145
process.Start();
7246
}
7347

74-
private static void RunSelfCleanup(string[] args)
75-
{
76-
string? encodedTarget = GetArgumentValue(args, TargetArgument);
77-
if (string.IsNullOrWhiteSpace(encodedTarget))
78-
{
79-
Logger.Warning("[InstallerCleanup] cleanup target argument is empty.");
80-
return;
81-
}
82-
83-
string targetPath = NormalizeInstallerPath(DecodeArgument(encodedTarget!));
84-
string helperPath = GetCurrentExecutablePath();
85-
if (string.Equals(targetPath, helperPath, StringComparison.OrdinalIgnoreCase))
86-
{
87-
Logger.Warning("[InstallerCleanup] refusing to delete cleanup helper itself as target.");
88-
return;
89-
}
90-
91-
int parentProcessId = ParseParentProcessId(GetArgumentValue(args, ParentProcessArgument));
92-
WaitForParentProcess(parentProcessId);
93-
94-
if (!IsSameFileContent(helperPath, targetPath))
95-
{
96-
Logger.Warning($"[InstallerCleanup] target does not match cleanup helper copy: {targetPath}");
97-
return;
98-
}
99-
100-
TryDeleteInstaller(targetPath);
101-
ScheduleHelperCleanupOnReboot(helperPath);
102-
}
103-
10448
private static string NormalizeInstallerPath(string installerPath)
10549
{
10650
if (string.IsNullOrWhiteSpace(installerPath))
@@ -117,135 +61,30 @@ public static class InstallerSelfCleanupHelper
11761
return fullPath;
11862
}
11963

120-
private static string CreateHelperCopy(string currentExecutablePath)
64+
private static string NormalizeCleanupHelperPath(string cleanupHelperPath, string targetPath)
12165
{
122-
string helperRoot = Path.Combine(
123-
Path.GetTempPath(),
124-
"CodexCliPlus",
125-
"InstallerCleanup",
126-
Guid.NewGuid().ToString("N")
127-
);
128-
Directory.CreateDirectory(helperRoot);
129-
130-
string helperPath = Path.Combine(helperRoot, "CodexCliPlus.InstallerCleanup.exe");
131-
File.Copy(currentExecutablePath, helperPath, overwrite: false);
132-
File.SetAttributes(helperPath, FileAttributes.Normal);
133-
return helperPath;
134-
}
135-
136-
private static void WaitForParentProcess(int parentProcessId)
137-
{
138-
if (parentProcessId <= 0)
66+
if (string.IsNullOrWhiteSpace(cleanupHelperPath))
13967
{
140-
return;
141-
}
142-
143-
try
144-
{
145-
using Process parentProcess = Process.GetProcessById(parentProcessId);
146-
parentProcess.WaitForExit(ParentProcessWaitMilliseconds);
147-
}
148-
catch (ArgumentException)
149-
{
150-
}
151-
catch (Exception e)
152-
{
153-
Logger.Warning($"[InstallerCleanup] cannot wait for parent process: {e.Message}");
154-
}
155-
156-
Thread.Sleep(DeleteRetryDelayMilliseconds);
157-
}
158-
159-
private static void TryDeleteInstaller(string targetPath)
160-
{
161-
Exception? lastError = null;
162-
for (int attempt = 1; attempt <= DeleteRetryCount; attempt++)
163-
{
164-
try
165-
{
166-
if (!File.Exists(targetPath))
167-
{
168-
Logger.Information($"[InstallerCleanup] installer already deleted: {targetPath}");
169-
return;
170-
}
171-
172-
File.SetAttributes(targetPath, FileAttributes.Normal);
173-
File.Delete(targetPath);
174-
if (!File.Exists(targetPath))
175-
{
176-
Logger.Information($"[InstallerCleanup] deleted installer: {targetPath}");
177-
return;
178-
}
179-
}
180-
catch (Exception e)
181-
{
182-
lastError = e;
183-
}
184-
185-
Thread.Sleep(DeleteRetryDelayMilliseconds);
68+
throw new ArgumentException("Cleanup helper path is empty.", nameof(cleanupHelperPath));
18669
}
18770

188-
Logger.Error($"[InstallerCleanup] installer delete failed: {targetPath}", lastError?.Message ?? string.Empty);
189-
TryScheduleDeleteOnReboot(targetPath);
190-
}
191-
192-
private static bool IsSameFileContent(string leftPath, string rightPath)
193-
{
194-
if (!File.Exists(leftPath) || !File.Exists(rightPath))
71+
string fullPath = Path.GetFullPath(cleanupHelperPath);
72+
if (!File.Exists(fullPath))
19573
{
196-
return false;
74+
throw new FileNotFoundException("[InstallerCleanup] cleanup helper is missing.", fullPath);
19775
}
19876

199-
FileInfo left = new(leftPath);
200-
FileInfo right = new(rightPath);
201-
if (left.Length != right.Length)
77+
if (!string.Equals(Path.GetExtension(fullPath), ".exe", StringComparison.OrdinalIgnoreCase))
20278
{
203-
return false;
79+
throw new InvalidOperationException("[InstallerCleanup] cleanup helper must be an executable.");
20480
}
20581

206-
string leftHash = ComputeSha256(leftPath);
207-
string rightHash = ComputeSha256(rightPath);
208-
return string.Equals(leftHash, rightHash, StringComparison.Ordinal);
209-
}
210-
211-
private static string ComputeSha256(string path)
212-
{
213-
using FileStream stream = new(
214-
path,
215-
FileMode.Open,
216-
FileAccess.Read,
217-
FileShare.ReadWrite | FileShare.Delete,
218-
bufferSize: 1024 * 128,
219-
FileOptions.SequentialScan
220-
);
221-
using SHA256 sha256 = SHA256.Create();
222-
return Convert.ToBase64String(sha256.ComputeHash(stream));
223-
}
224-
225-
private static void ScheduleHelperCleanupOnReboot(string helperPath)
226-
{
227-
TryScheduleDeleteOnReboot(helperPath);
228-
229-
string? helperDirectory = Path.GetDirectoryName(helperPath);
230-
if (!string.IsNullOrWhiteSpace(helperDirectory))
82+
if (string.Equals(fullPath, targetPath, StringComparison.OrdinalIgnoreCase))
23183
{
232-
TryScheduleDeleteOnReboot(helperDirectory);
84+
throw new InvalidOperationException("[InstallerCleanup] cleanup helper cannot be the installer target.");
23385
}
234-
}
23586

236-
private static void TryScheduleDeleteOnReboot(string path)
237-
{
238-
try
239-
{
240-
if (File.Exists(path) || Directory.Exists(path))
241-
{
242-
_ = Kernel32.MoveFileEx(path, null!, MoveFileFlags.MOVEFILE_DELAY_UNTIL_REBOOT);
243-
}
244-
}
245-
catch (Exception e)
246-
{
247-
Logger.Warning($"[InstallerCleanup] cannot schedule cleanup on reboot: {e.Message}");
248-
}
87+
return fullPath;
24988
}
25089

25190
private static string GetCurrentExecutablePath()
@@ -267,58 +106,8 @@ public static class InstallerSelfCleanupHelper
267106
return Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.FriendlyName));
268107
}
269108

270-
private static bool HasArgument(string[] args, string name)
271-
{
272-
string slashName = "/" + name;
273-
string dashName = "-" + name;
274-
foreach (string arg in args)
275-
{
276-
if (
277-
string.Equals(arg, slashName, StringComparison.OrdinalIgnoreCase)
278-
|| string.Equals(arg, dashName, StringComparison.OrdinalIgnoreCase)
279-
)
280-
{
281-
return true;
282-
}
283-
}
284-
285-
return false;
286-
}
287-
288-
private static string? GetArgumentValue(string[] args, string name)
289-
{
290-
string slashPrefix = "/" + name + "=";
291-
string dashPrefix = "-" + name + "=";
292-
foreach (string arg in args)
293-
{
294-
if (arg.StartsWith(slashPrefix, StringComparison.OrdinalIgnoreCase))
295-
{
296-
return arg.Substring(slashPrefix.Length);
297-
}
298-
299-
if (arg.StartsWith(dashPrefix, StringComparison.OrdinalIgnoreCase))
300-
{
301-
return arg.Substring(dashPrefix.Length);
302-
}
303-
}
304-
305-
return null;
306-
}
307-
308-
private static int ParseParentProcessId(string? value)
309-
{
310-
return int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int processId)
311-
? processId
312-
: 0;
313-
}
314-
315109
private static string EncodeArgument(string value)
316110
{
317111
return Convert.ToBase64String(Encoding.UTF8.GetBytes(value));
318112
}
319-
320-
private static string DecodeArgument(string value)
321-
{
322-
return Encoding.UTF8.GetString(Convert.FromBase64String(value));
323-
}
324113
}

build/micasetup/overrides/MicaSetup/Program.cs.template

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,6 @@ internal static class Program
2525
[STAThread]
2626
internal static void Main()
2727
{
28-
if (InstallerSelfCleanupHelper.TryRunSelfCleanupMode())
29-
{
30-
return;
31-
}
32-
3328
Hosting.CreateBuilder()
3429
.UseLogger(false)
3530
.UseSingleInstance("BlackblockInc.CodexCliPlus.Setup")

build/micasetup/overrides/MicaSetup/ViewModels/Inst/FinishViewModel.cs.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public partial class FinishViewModel : ObservableObject
9797
return;
9898
}
9999

100-
InstallerSelfCleanupHelper.ScheduleDelete(currentFullPath);
100+
InstallerSelfCleanupHelper.ScheduleDelete(currentFullPath, installedExecutablePath);
101101
installerDeleteScheduled = true;
102102
Logger.Information($"[InstallerCleanup] scheduled installer delete: {currentFullPath}");
103103
}

src/CodexCliPlus.App/App.xaml.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ protected override void OnStartup(System.Windows.StartupEventArgs e)
2929
{
3030
base.OnStartup(e);
3131

32+
if (InstallerCleanupMode.TryRun(e.Args, out var installerCleanupExitCode))
33+
{
34+
Shutdown(installerCleanupExitCode);
35+
return;
36+
}
37+
3238
var services = new ServiceCollection();
3339
services.AddCpadInfrastructure();
3440
services.AddSingleton<IBuildInfo, BuildInfo>();

0 commit comments

Comments
 (0)