Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,22 @@ dotnet test PriorityGear.slnx --configuration Release --no-build
dotnet run --project src/PriorityGear.App/PriorityGear.App.csproj --configuration Release
```

## v0.3.2 System Mode Installer Release
## v0.3.3 System Mode Installer Release

`v0.3.2` は formal System Mode installer 用の現在の GitHub release です。winget submission に必要な silent installer switches を追加しています。この installer が PriorityGear の通常の GitHub 配布インストール経路になります
`v0.3.3` は formal System Mode installer 用の次の GitHub release です。`v0.3.1` / `v0.3.2` の installer window が blank progress に見える問題を修正し、起動直後に log path と進行状況を表示します

公開 artifact は次です。

```text
PriorityGear-v0.3.2-win-x64-installer.zip
PriorityGear-v0.3.3-win-x64-installer.zip
```

zip には `PriorityGear.Setup.exe` が含まれます。ダブルクリックして UAC を承認すると PriorityGear を install / update します。この installer は AS IS であり、署名を明示的に追加するまでは unsigned です。

同じ installer artifact をローカルで作成する場合:

```powershell
.\scripts\package-release.ps1 -TagName "v0.3.2" -OutputDirectory ".\artifacts\release-test-v0.3.2"
.\scripts\package-release.ps1 -TagName "v0.3.3" -OutputDirectory ".\artifacts\release-test-v0.3.3"
```

installer は GUI app を配置し、`PriorityGear.Service` を `%ProgramFiles%\PriorityGear\versions` 以下の versioned directory から起動する LocalSystem Windows Service として構成します。`%ProgramData%\PriorityGear\rules.machine.json` と `%ProgramData%\PriorityGear\Logs` は保持します。
Expand All @@ -79,7 +79,7 @@ v0.2 の範囲は LocalSystem service の検証用 install/update、status/admin

`v0.2.0-preview.1` は System Mode foundation の過去の public prerelease として残ります。

Store 配布、署名、本番 MSI/MSIX packaging、GUI machine-rule editing、System Mode の active-window priority switching、任意の shared-host mutation、CPU affinity、I/O priority、EcoQoS、Realtime priority UI、driver、telemetry、network、updater は範囲外です。winget package[microsoft/winget-pkgs#375643](https://github.com/microsoft/winget-pkgs/pull/375643) に submitted ですが、Microsoft validation / merge が完了して `winget search` で見つかるまでは利用可能とは扱いません
Store 配布、winget 登録、署名、本番 MSI/MSIX packaging、GUI machine-rule editing、System Mode の active-window priority switching、任意の shared-host mutation、CPU affinity、I/O priority、EcoQoS、Realtime priority UI、driver、telemetry、network、updater は範囲外です。先行 winget submissioninstaller progress fix のため closed しています

検証後の状態として、`PriorityGear.Service` は install/running のまま残る場合があります。一時的な `PriorityGear.TestTarget.Service` と temporary machine rules は削除され、`%ProgramData%\PriorityGear\Logs` は残ります。`%ProgramData%\PriorityGear\rules.machine.json` は保持または復元されます。古い version directory cleanup は best-effort です。

Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,22 @@ dotnet test PriorityGear.slnx --configuration Release --no-build
dotnet run --project src/PriorityGear.App/PriorityGear.App.csproj --configuration Release
```

## v0.3.2 System Mode Installer Release
## v0.3.3 System Mode Installer Release

`v0.3.2` is the current GitHub release for the formal System Mode installer. It adds silent installer switches needed for winget submission. The installer is the normal GitHub-distributed install path for PriorityGear.
`v0.3.3` is the next GitHub release for the formal System Mode installer. It fixes the `v0.3.1` / `v0.3.2` blank-progress installer window by showing the log path immediately and streaming progress while setup runs.

The release artifact is:

```text
PriorityGear-v0.3.2-win-x64-installer.zip
PriorityGear-v0.3.3-win-x64-installer.zip
```

The zip contains `PriorityGear.Setup.exe`. Double-click it and approve UAC to install or update PriorityGear. The installer is AS IS and unsigned unless signing is explicitly added in a later release.

To build the same installer artifact locally:

```powershell
.\scripts\package-release.ps1 -TagName "v0.3.2" -OutputDirectory ".\artifacts\release-test-v0.3.2"
.\scripts\package-release.ps1 -TagName "v0.3.3" -OutputDirectory ".\artifacts\release-test-v0.3.3"
```

The installer installs the GUI app and configures `PriorityGear.Service` as a LocalSystem Windows Service under a versioned directory below `%ProgramFiles%\PriorityGear\versions`. It preserves `%ProgramData%\PriorityGear\rules.machine.json` and logs under `%ProgramData%\PriorityGear\Logs`.
Expand Down Expand Up @@ -77,17 +77,17 @@ Post-verification state: `PriorityGear.Service` may remain installed/running, te

## Artifacts

### v0.3.2 GitHub Installer
### v0.3.3 GitHub Installer

The current GitHub release artifact is:

```text
PriorityGear-v0.3.2-win-x64-installer.zip
PriorityGear-v0.3.3-win-x64-installer.zip
```

It contains `PriorityGear.Setup.exe` and the service/app/CLI payload needed for install or update after UAC approval. It is not Store, MSI, MSIX, or signed packaging.

For winget submission, the installer supports `--install --silent` and `--uninstall --silent`. The winget package is submitted in [microsoft/winget-pkgs#375643](https://github.com/microsoft/winget-pkgs/pull/375643), but it is not available through `winget search` until Microsoft validation is complete and the PR is merged.
The installer supports `--install --silent` and `--uninstall --silent`. winget registration is not done in this release; the earlier winget submission was closed while this installer progress fix is prepared.

### v0.1 User Mode Portable Publish

Expand Down
12 changes: 4 additions & 8 deletions docs/installer.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# PriorityGear Installer

PriorityGear `v0.3.2` is the current formal GitHub release installer path. It adds the silent installer mode needed for Windows Package Manager / winget submission.
PriorityGear `v0.3.3` is the next formal GitHub release installer path. It fixes the blank-progress setup window by showing startup diagnostics immediately and streaming logs while setup runs.

## Artifact

The primary release artifact is:

```text
PriorityGear-v0.3.2-win-x64-installer.zip
PriorityGear-v0.3.3-win-x64-installer.zip
```

The zip contains `PriorityGear.Setup.exe` and a `payload` directory with the GUI app, CLI, and System Mode service binaries.
Expand All @@ -17,7 +17,7 @@ The zip contains `PriorityGear.Setup.exe` and a `payload` directory with the GUI
Double-click `PriorityGear.Setup.exe` and approve UAC. The installer:

- requires elevation;
- installs files under `%ProgramFiles%\PriorityGear\versions\v0.3.2`;
- installs files under `%ProgramFiles%\PriorityGear\versions\v0.3.3`;
- configures `PriorityGear.Service` as LocalSystem;
- starts or restarts the service;
- confirms the status pipe responds;
Expand Down Expand Up @@ -52,11 +52,7 @@ For silent uninstall:

## winget

The installer is submitted for winget distribution as a zip package with nested `PriorityGear.Setup.exe`:

https://github.com/microsoft/winget-pkgs/pull/375643

The winget package is not available until Microsoft validation is complete, the PR is merged, and `winget search` can find it.
winget registration is not done in this release. A previous submission was closed while the installer progress fix is prepared. The package must not be treated as available until a future winget PR is validated, merged, and `winget search` can find it.

## Boundaries

Expand Down
44 changes: 0 additions & 44 deletions docs/release-drafts/v0.3.2.md

This file was deleted.

45 changes: 45 additions & 0 deletions docs/release-drafts/v0.3.3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# PriorityGear v0.3.3 - installer progress visibility fix

`v0.3.3` is a patch release for the formal GitHub installer. It fixes the blank installer progress window reported in the `v0.3.1` / `v0.3.2` installer line.

## Summary

- Fixes the blank setup window by writing startup diagnostics immediately.
- Streams setup log lines into the installer window as work happens.
- Runs long install/update work on a background task so the form can repaint and stay responsive.
- Flushes setup logs incrementally so failures leave durable diagnostics.
- Keeps silent installer entrypoints:
- `PriorityGear.Setup.exe --install --silent`
- `PriorityGear.Setup.exe --uninstall --silent`
- Publishes `PriorityGear-v0.3.3-win-x64-installer.zip`.

## Installer

`PriorityGear.Setup.exe` remains the normal install/update entrypoint. Double-click installation keeps the setup UI and UAC flow. The window now shows the setup start, log path, and mode before long-running installation work begins.

Silent mode runs the same production installer state machine without setup UI or message boxes. It still requires elevation for install/update and uninstall. Exit code `0` means success; required install/update or uninstall failures return non-zero.

The installer installs the GUI app, CLI, and System Mode service payload, configures `PriorityGear.Service` as LocalSystem, uses versioned install directories, and preserves ProgramData rules/logs by default.

## Safety boundary

PriorityGear System Mode is privileged software and installs a LocalSystem Windows Service. It changes process priority and may affect system responsiveness or stability. It is provided AS IS.

The installer is unsigned. Microsoft Store, winget registration, signing, MSI, and MSIX packaging are not included in this release.

This release does not expand unsafe service mutation support. Arbitrary shared-host `svchost.exe` priority mutation remains out of scope.

GitHub Actions builds and packages the installer. GitHub Actions does not run elevated setup.

## Validation

Expected validation:

```powershell
dotnet restore PriorityGear.slnx
dotnet build PriorityGear.slnx --configuration Release --no-restore
dotnet test PriorityGear.slnx --configuration Release --no-build
.\scripts\build-verification-installer.ps1
.\scripts\package-release.ps1 -TagName "v0.3.3" -OutputDirectory ".\artifacts\release-test-v0.3.3"
.\scripts\inspect-release-artifacts.ps1 -ArtifactDirectory ".\artifacts\release-test-v0.3.3" -TagName "v0.3.3"
```
65 changes: 58 additions & 7 deletions src/PriorityGear.Setup/InstallerStateMachine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ public sealed record InstallerRunResult(
IReadOnlyList<InstallerStep> CompletedSteps,
IReadOnlyList<string> Warnings);

public enum InstallerProgressKind
{
Starting,
Completed,
Failed,
Warning
}

public sealed record InstallerProgress(InstallerProgressKind Kind, InstallerStep Step, string Message);

public interface IInstallerExecutor
{
void ValidatePayload();
Expand All @@ -46,6 +56,8 @@ public sealed class InstallerStateMachine(SetupInstallPlan plan, IInstallerExecu
private readonly List<InstallerStep> _completedSteps = [];
private readonly List<string> _warnings = [];

public event Action<InstallerProgress>? Progress;

public InstallerRunResult InstallOrUpdate()
{
_completedSteps.Clear();
Expand All @@ -59,41 +71,53 @@ public InstallerRunResult InstallOrUpdate()
RunRequired(InstallerStep.ConfigureService, executor.ConfigureService);
RunRequired(InstallerStep.StartService, executor.StartService);

Emit(InstallerProgressKind.Starting, InstallerStep.VerifyStatusPipe, "Verifying service status pipe.");
InstallerStatus status = QueryStatus();
if (!status.ServiceRunning)
{
return Fail("Service status reports that the service is not running.");
return Fail(InstallerStep.VerifyStatusPipe, "Service status reports that the service is not running.");
}
_completedSteps.Add(InstallerStep.VerifyStatusPipe);
Emit(InstallerProgressKind.Completed, InstallerStep.VerifyStatusPipe, "Service status pipe responded.");

Emit(InstallerProgressKind.Starting, InstallerStep.VerifyServiceConfiguration, "Validating service configuration.");
if (!string.Equals(status.ConfiguredServiceAccount, "LocalSystem", StringComparison.OrdinalIgnoreCase))
{
return Fail($"Service account is not LocalSystem: {status.ConfiguredServiceAccount}");
return Fail(InstallerStep.VerifyServiceConfiguration, $"Service account is not LocalSystem: {status.ConfiguredServiceAccount}");
}

if (string.IsNullOrWhiteSpace(status.ProcessIdentity))
{
return Fail("Service status did not report process identity.");
return Fail(InstallerStep.VerifyServiceConfiguration, "Service status did not report process identity.");
}

if (!ServiceBinaryPathMatches(status.ServiceBinaryPath, plan.ServiceExePath))
{
return Fail($"Service binary path does not point at the new version directory: {status.ServiceBinaryPath}");
return Fail(InstallerStep.VerifyServiceConfiguration, $"Service binary path does not point at the new version directory: {status.ServiceBinaryPath}");
}
_completedSteps.Add(InstallerStep.VerifyServiceConfiguration);
Emit(InstallerProgressKind.Completed, InstallerStep.VerifyServiceConfiguration, "Service configuration is valid.");

try
{
Emit(InstallerProgressKind.Starting, InstallerStep.CleanupOldVersions, "Cleaning old version directories.");
executor.CleanupOldVersions();
_completedSteps.Add(InstallerStep.CleanupOldVersions);
Emit(InstallerProgressKind.Completed, InstallerStep.CleanupOldVersions, "Old version cleanup completed.");
}
catch (Exception ex)
{
_warnings.Add($"Old version cleanup failed: {ex.Message}");
string warning = $"Old version cleanup failed: {ex.Message}";
_warnings.Add(warning);
Emit(InstallerProgressKind.Warning, InstallerStep.CleanupOldVersions, warning);
}

return Success("Install/update completed.");
}
catch (InstallerStepException ex)
{
return Fail($"{ex.Step} failed: {ex.InnerException?.Message ?? ex.Message}");
}
catch (Exception ex)
{
return Fail(ex.Message);
Expand All @@ -107,20 +131,47 @@ private InstallerStatus QueryStatus()

private void RunRequired(InstallerStep step, Action action)
{
action();
_completedSteps.Add(step);
Emit(InstallerProgressKind.Starting, step, "Starting.");
try
{
action();
_completedSteps.Add(step);
Emit(InstallerProgressKind.Completed, step, "Completed.");
}
catch (Exception ex)
{
Emit(InstallerProgressKind.Failed, step, ex.Message);
throw new InstallerStepException(step, ex);
}
}

private InstallerRunResult Fail(string message)
{
return new InstallerRunResult(false, message, _completedSteps.ToArray(), _warnings.ToArray());
}

private InstallerRunResult Fail(InstallerStep step, string message)
{
Emit(InstallerProgressKind.Failed, step, message);
return Fail(message);
}

private InstallerRunResult Success(string message)
{
return new InstallerRunResult(true, message, _completedSteps.ToArray(), _warnings.ToArray());
}

private void Emit(InstallerProgressKind kind, InstallerStep step, string message)
{
Progress?.Invoke(new InstallerProgress(kind, step, message));
}

private sealed class InstallerStepException(InstallerStep step, Exception innerException)
: Exception(innerException.Message, innerException)
{
public InstallerStep Step { get; } = step;
}

public static bool ServiceBinaryPathMatches(string configuredPath, string expectedPath)
{
string? configuredExe = ExtractExecutablePath(configuredPath);
Expand Down
Loading
Loading