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
5 changes: 4 additions & 1 deletion SysManager/SysManager/ServiceRegistration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ public static IServiceCollection ConfigureServices(this IServiceCollection servi
{
// ── Core services ──────────────────────────────────────────────
// PowerShellRunner is Transient — each consumer gets its own instance
// to avoid LineReceived event cross-talk between tabs.
// to avoid LineReceived event cross-talk between tabs. Registered under
// both the concrete type (legacy consumers) and the IPowerShellRunner
// seam (DNS / network repair / winget install — substitutable in tests).
services.AddTransient<PowerShellRunner>();
services.AddTransient<IPowerShellRunner, PowerShellRunner>();
services.AddSingleton<SystemInfoService>();
services.AddSingleton<WingetService>();
services.AddSingleton<TrayIconService>();
Expand Down
4 changes: 2 additions & 2 deletions SysManager/SysManager/Services/BulkInstallerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ namespace SysManager.Services;
/// </summary>
public sealed partial class BulkInstallerService
{
private readonly PowerShellRunner _runner;
private readonly IPowerShellRunner _runner;

public BulkInstallerService(PowerShellRunner runner) => _runner = runner;
public BulkInstallerService(IPowerShellRunner runner) => _runner = runner;

public event Action<PowerShellLine>? LineReceived
{
Expand Down
4 changes: 2 additions & 2 deletions SysManager/SysManager/Services/DnsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ namespace SysManager.Services;
/// </summary>
public sealed class DnsService : IDisposable
{
private readonly PowerShellRunner _ps;
private readonly IPowerShellRunner _ps;
private readonly SemaphoreSlim _gate = new(1, 1);

public DnsService(PowerShellRunner ps) => _ps = ps;
public DnsService(IPowerShellRunner ps) => _ps = ps;

public void Dispose() => _gate.Dispose();

Expand Down
61 changes: 61 additions & 0 deletions SysManager/SysManager/Services/IPowerShellRunner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SysManager · IPowerShellRunner
// Author: laurentiu021 · https://github.com/laurentiu021/SystemManager
// License: MIT

using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Text;
using SysManager.Models;

namespace SysManager.Services;

/// <summary>
/// Abstraction over <see cref="PowerShellRunner"/> — the single seam through which
/// services run PowerShell scripts and external processes. Extracting this interface
/// lets system-mutating services (DNS, network repair, winget install) be unit-tested
/// with a substituted runner instead of touching the live OS (Gate-ARCH: "external
/// process/PowerShell calls route through the single runner seam").
///
/// <para>The same <b>SECURITY CONTRACT</b> as <see cref="PowerShellRunner"/> applies:
/// callers MUST only pass hard-coded script strings to <see cref="RunAsync"/> and
/// <see cref="RunScriptViaPwshAsync"/>. User input MUST NEVER be interpolated into
/// scripts.</para>
/// </summary>
public interface IPowerShellRunner
{
/// <summary>
/// Raised for each line of output from any stream. Fires on a thread-pool thread —
/// subscribers that update UI elements must marshal to the dispatcher.
/// </summary>
event Action<PowerShellLine>? LineReceived;

/// <summary>Raised with a 0-100 percentage as PowerShell progress records arrive.</summary>
event Action<int>? ProgressChanged;

/// <summary>
/// Execute a script in-process and return the collected PSObject results.
/// All streams are forwarded via <see cref="LineReceived"/> for live UI display.
/// </summary>
Task<Collection<PSObject>> RunAsync(
string script,
IDictionary<string, object?>? parameters = null,
CancellationToken cancellationToken = default);

/// <summary>
/// Run a PowerShell script via an external powershell.exe (Windows PS 5.1),
/// returning the process exit code. Output is streamed via <see cref="LineReceived"/>.
/// </summary>
Task<int> RunScriptViaPwshAsync(
string script,
CancellationToken cancellationToken = default);

/// <summary>
/// Run an external process (winget, netsh, ipconfig, …) with live line streaming,
/// returning the process exit code.
/// </summary>
Task<int> RunProcessAsync(
string fileName,
string arguments,
CancellationToken cancellationToken = default,
Encoding? outputEncoding = null);
}
4 changes: 2 additions & 2 deletions SysManager/SysManager/Services/NetworkRepairService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ namespace SysManager.Services;
/// </summary>
public sealed class NetworkRepairService : IDisposable
{
private readonly PowerShellRunner _ps;
private readonly IPowerShellRunner _ps;
private readonly SemaphoreSlim _gate = new(1, 1);

public NetworkRepairService(PowerShellRunner ps) => _ps = ps;
public NetworkRepairService(IPowerShellRunner ps) => _ps = ps;

/// <inheritdoc />
public void Dispose() => _gate.Dispose();
Expand Down
2 changes: 1 addition & 1 deletion SysManager/SysManager/Services/PowerShellRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ namespace SysManager.Services;
/// Violation of this contract creates a code injection vulnerability. The Bypass policy
/// is safe ONLY because the script content is fully controlled by SysManager's source code.</para>
/// </summary>
public sealed class PowerShellRunner
public sealed class PowerShellRunner : IPowerShellRunner
{
/// <summary>
/// Raised for each line of output from any stream (stdout, stderr, information,
Expand Down
Loading