Skip to content

Commit efb757d

Browse files
committed
Add ElevatedTaskService and remove MSIX packaging
Introduce IElevatedTaskService and ElevatedTaskService to manage scheduled-task based elevated launch and autostart. Update App.xaml.cs to bootstrap elevation, attempt managed elevated launch, and handle task registration markers. Refactor AutostartService to delegate autostart operations to the elevated task service, remove legacy registry usage where appropriate, add logging, and update DI registration and unit tests. Remove MSIX packaging support and related tooling: delete build/build-msix.ps1, remove MSIX publish/validation/upload steps from GitHub Actions, drop MSIX-related csproj targets and EnableMsixTooling, and update docs/README/.gitignore to no longer reference MSIX artifacts.
1 parent 2806662 commit efb757d

18 files changed

Lines changed: 721 additions & 365 deletions

.github/workflows/release.yml

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -84,21 +84,6 @@ jobs:
8484
8585
& iscc.exe "/DMyAppVersion=$version" "/DMyAppSourceDir=$sourceDir" "Installer/setup.iss"
8686
87-
- name: Publish MSIX
88-
run: dotnet publish "ThreadPilot.csproj" --configuration Release -p:PublishProfile=WinX64-MSIX
89-
90-
- name: Validate MSIX output
91-
shell: pwsh
92-
run: |
93-
$ErrorActionPreference = "Stop"
94-
$msixFiles = Get-ChildItem "artifacts/release/msix" -Recurse -File -Include *.msix,*.appx,*.msixbundle,*.appxbundle -ErrorAction SilentlyContinue
95-
if (-not $msixFiles)
96-
{
97-
throw "MSIX package generation failed: no package artifacts found in artifacts/release/msix."
98-
}
99-
100-
Write-Host "MSIX artifacts found: $($msixFiles.Count)"
101-
10287
- name: Prepare signing certificate (optional)
10388
shell: pwsh
10489
run: |
@@ -134,7 +119,6 @@ jobs:
134119
$targets += Get-ChildItem "artifacts/release/singlefile" -Recurse -File -Include *.exe -ErrorAction SilentlyContinue
135120
$targets += Get-ChildItem "artifacts/release/readytorun" -Recurse -File -Include *.exe -ErrorAction SilentlyContinue
136121
$targets += Get-ChildItem "artifacts/release/installer" -Recurse -File -Include *.exe -ErrorAction SilentlyContinue
137-
$targets += Get-ChildItem "artifacts/release/msix" -Recurse -File -Include *.msix,*.appx,*.msixbundle,*.appxbundle -ErrorAction SilentlyContinue
138122
139123
foreach ($target in $targets)
140124
{
@@ -226,7 +210,6 @@ jobs:
226210
$releaseFiles = @()
227211
$releaseFiles += Get-ChildItem "artifacts/release/packages" -File -ErrorAction SilentlyContinue
228212
$releaseFiles += Get-ChildItem "artifacts/release/installer/*.exe" -File -ErrorAction SilentlyContinue
229-
$releaseFiles += Get-ChildItem "artifacts/release/msix" -Recurse -File -Include *.msix,*.appx,*.msixbundle,*.appxbundle -ErrorAction SilentlyContinue
230213
231214
$releaseFiles | ForEach-Object {
232215
$hash = Get-FileHash $_.FullName -Algorithm SHA256
@@ -284,12 +267,6 @@ jobs:
284267
name: release-packages
285268
path: artifacts/release/packages
286269

287-
- name: Upload msix artifact
288-
uses: actions/upload-artifact@v4
289-
with:
290-
name: release-msix
291-
path: artifacts/release/msix
292-
293270
- name: Upload checksums artifact
294271
uses: actions/upload-artifact@v4
295272
with:
@@ -358,12 +335,6 @@ jobs:
358335
name: release-packages
359336
path: release-assets/packages
360337

361-
- name: Download msix artifact
362-
uses: actions/download-artifact@v4
363-
with:
364-
name: release-msix
365-
path: release-assets/msix
366-
367338
- name: Download checksums artifact
368339
uses: actions/download-artifact@v4
369340
with:
@@ -407,10 +378,6 @@ jobs:
407378
files: |
408379
release-assets/installer/*.exe
409380
release-assets/packages/*.zip
410-
release-assets/msix/**/*.msix
411-
release-assets/msix/**/*.appx
412-
release-assets/msix/**/*.msixbundle
413-
release-assets/msix/**/*.appxbundle
414381
release-assets/SHA256SUMS.txt
415382
winget-manifests/**/*.yaml
416383
sbom/manifest.spdx.json

.gitignore

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,6 @@ Logs/
9393
Installer/Output/
9494
*.cab
9595
*.msi
96-
*.msix
97-
*.msixbundle
9896
*.msm
9997
*.msp
10098
*.lnk

App.xaml.cs

Lines changed: 67 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ namespace ThreadPilot
2525
using System;
2626
using System.Collections.Generic;
2727
using System.Linq;
28-
using System.Security.Principal;
2928
using System.Threading;
3029
using System.Threading.Tasks;
3130
using System.Windows;
@@ -36,6 +35,9 @@ namespace ThreadPilot
3635

3736
public partial class App : System.Windows.Application
3837
{
38+
private const string RegisterLaunchTaskArgument = "--register-launch-task";
39+
private const string LaunchedViaTaskArgument = "--launched-via-task";
40+
3941
private Mutex? singleInstanceMutex;
4042
private int uiExceptionDialogOpen;
4143
private DateTime lastUiExceptionDialogUtc = DateTime.MinValue;
@@ -63,6 +65,8 @@ protected override void OnStartup(StartupEventArgs e)
6365
bool startMinimized = false;
6466
bool isAutostart = false;
6567
bool isSmokeTest = false;
68+
bool registerLaunchTask = false;
69+
bool launchedViaTask = false;
6670
#if DEBUG
6771
bool isTestMode = false;
6872
#endif
@@ -89,10 +93,71 @@ protected override void OnStartup(StartupEventArgs e)
8993
isAutostart = true;
9094
startMinimized = true;
9195
break;
96+
case RegisterLaunchTaskArgument:
97+
registerLaunchTask = true;
98+
break;
99+
case LaunchedViaTaskArgument:
100+
launchedViaTask = true;
101+
break;
102+
}
103+
}
104+
105+
// Set up global exception handlers first
106+
AppDomain.CurrentDomain.UnhandledException += this.OnUnhandledException;
107+
this.DispatcherUnhandledException += this.OnDispatcherUnhandledException;
108+
TaskScheduler.UnobservedTaskException += this.OnUnobservedTaskException;
109+
110+
// Check elevation status first
111+
var elevationService = this.ServiceProvider.GetRequiredService<IElevationService>();
112+
var elevatedTaskService = this.ServiceProvider.GetRequiredService<IElevatedTaskService>();
113+
var logger = this.ServiceProvider.GetRequiredService<ILogger<App>>();
114+
var isRunningAsAdministrator = elevationService.IsRunningAsAdministrator();
115+
116+
if (isRunningAsAdministrator)
117+
{
118+
logger.LogInformation("Application is running with administrator privileges");
119+
120+
var launchTaskEnsured = Task.Run(async () => await elevatedTaskService.EnsureLaunchTaskAsync()).GetAwaiter().GetResult();
121+
if (!launchTaskEnsured)
122+
{
123+
logger.LogWarning("Failed to ensure managed elevated launch task during startup. Future launches may require one-time elevation again.");
124+
}
125+
}
126+
else
127+
{
128+
if (launchedViaTask)
129+
{
130+
logger.LogWarning("Application was launched via managed task marker but is still not elevated. Continuing in limited mode.");
131+
}
132+
#if DEBUG
133+
else if (!isSmokeTest && !isTestMode)
134+
#else
135+
else if (!isSmokeTest)
136+
#endif
137+
{
138+
var launchedElevatedInstance = Task.Run(async () => await elevatedTaskService.TryRunLaunchTaskAsync()).GetAwaiter().GetResult();
139+
if (launchedElevatedInstance)
140+
{
141+
logger.LogInformation("Managed elevated launch task started successfully. Exiting current non-elevated instance.");
142+
this.Shutdown();
143+
return;
144+
}
145+
146+
if (!registerLaunchTask)
147+
{
148+
logger.LogInformation("Managed elevated launch task is unavailable. Requesting one-time elevation to bootstrap persistent launch.");
149+
var restartInitiated = Task.Run(async () => await elevationService.RestartWithElevation(new[] { RegisterLaunchTaskArgument })).GetAwaiter().GetResult();
150+
if (restartInitiated)
151+
{
152+
return;
153+
}
154+
}
92155
}
156+
157+
logger.LogWarning("Application is running without administrator privileges. Elevated operations will require explicit elevation.");
93158
}
94159

95-
// Enforce single-instance: bail out if another instance is already running
160+
// Enforce single-instance after elevation bootstrap logic to avoid mutex races during handoff.
96161
if (!isSmokeTest)
97162
{
98163
bool createdNew;
@@ -110,24 +175,6 @@ protected override void OnStartup(StartupEventArgs e)
110175
}
111176
}
112177

113-
// Set up global exception handlers first
114-
AppDomain.CurrentDomain.UnhandledException += this.OnUnhandledException;
115-
this.DispatcherUnhandledException += this.OnDispatcherUnhandledException;
116-
TaskScheduler.UnobservedTaskException += this.OnUnobservedTaskException;
117-
118-
// Check elevation status first
119-
var elevationService = this.ServiceProvider.GetRequiredService<IElevationService>();
120-
var logger = this.ServiceProvider.GetRequiredService<ILogger<App>>();
121-
122-
if (!elevationService.IsRunningAsAdministrator())
123-
{
124-
logger.LogWarning("Application is running without administrator privileges. Elevated operations will require explicit elevation.");
125-
}
126-
else
127-
{
128-
logger.LogInformation("Application is running with administrator privileges");
129-
}
130-
131178
base.OnStartup(e);
132179

133180
// Check for test mode

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ Latest artifacts are published on each tagged release in [GitHub Releases](https
4141
|---|---|---|
4242
| Installer (Recommended) | `ThreadPilot_v1.1.1_Setup.exe` | Standard Windows installer (Inno Setup) for most users |
4343
| Portable | `ThreadPilot_v1.1.1_singlefile_win-x64.zip` | No-install deployment for power users |
44-
| MSIX (Secondary) | `ThreadPilot_1.1.1.0_win-x64.msix` | Advanced/enterprise sideload scenarios |
4544

4645
Verification example:
4746

@@ -54,7 +53,6 @@ Install flow summary:
5453
1. Download the package matching your deployment model.
5554
2. Installer package (recommended): run `ThreadPilot_vX.Y.Z_Setup.exe` and complete the wizard.
5655
3. Portable package: extract ZIP and launch `ThreadPilot.exe`.
57-
4. MSIX package (secondary): install only if your environment supports sideload trust requirements.
5856

5957
Notes:
6058

0 commit comments

Comments
 (0)