diff --git a/src/Infrastructure/AppRunner.cs b/src/Infrastructure/AppRunner.cs
index 8cdd2415..74d5b0ca 100644
--- a/src/Infrastructure/AppRunner.cs
+++ b/src/Infrastructure/AppRunner.cs
@@ -4,6 +4,9 @@
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
+using WinHome.Infrastructure.Helpers; // <-- add this line
+
+
namespace WinHome.Infrastructure;
/// Orchestrates configuration loading, validation, secret resolution, and engine execution.
@@ -36,7 +39,9 @@ public AppRunner(IEngine engine, IConfigValidator validator, ISecretResolver sec
public async Task RunAsync(FileInfo configFile, bool dryRun, string? profile, bool debug, bool diff, bool json, bool force = false, bool continueOnError = false)
{
try
+
{
+ AdminGuard.EnsureAdministrator();
if (!configFile.Exists)
{
_logger.LogError($"[Error] Configuration file not found: {configFile.FullName}");
diff --git a/src/Infrastructure/Helpers/AdminGuard.cs b/src/Infrastructure/Helpers/AdminGuard.cs
new file mode 100644
index 00000000..0bb2a062
--- /dev/null
+++ b/src/Infrastructure/Helpers/AdminGuard.cs
@@ -0,0 +1,39 @@
+using System.Security.Principal;
+using System.Runtime.Versioning;
+
+namespace WinHome.Infrastructure.Helpers;
+
+/// Validates that the current process has administrative privileges.
+[SupportedOSPlatform("windows")]
+public static class AdminGuard
+{
+ internal static Func IsAdministrator = () =>
+ {
+ using var identity = WindowsIdentity.GetCurrent();
+ var principal = new WindowsPrincipal(identity);
+ return principal.IsInRole(WindowsBuiltInRole.Administrator);
+ };
+
+ /// Throws if the current process is not running as Administrator.
+ /// Thrown when not running with admin privileges.
+ public static void EnsureAdministrator()
+ {
+ if (!IsAdministrator())
+ {
+ throw new UnauthorizedAccessException(
+ "Error: WinHome requires Administrative Privileges to manage system configurations. " +
+ "Please re-run this command from an elevated (Administrator) terminal.");
+ }
+ }
+
+ /// Resets the administrator check delegate to its default implementation (used for testing).
+ internal static void ResetAdminCheck()
+ {
+ IsAdministrator = () =>
+ {
+ using var identity = WindowsIdentity.GetCurrent();
+ var principal = new WindowsPrincipal(identity);
+ return principal.IsInRole(WindowsBuiltInRole.Administrator);
+ };
+ }
+}
\ No newline at end of file
diff --git a/tests/WinHome.Tests/AdminGuardTests.cs b/tests/WinHome.Tests/AdminGuardTests.cs
new file mode 100644
index 00000000..16793c3f
--- /dev/null
+++ b/tests/WinHome.Tests/AdminGuardTests.cs
@@ -0,0 +1,32 @@
+using WinHome.Infrastructure.Helpers;
+using Xunit;
+
+namespace WinHome.Tests;
+
+[Collection("SequentialTests")]
+public class AdminGuardTests
+{
+ [Fact]
+ public void EnsureAdministrator_DoesNotThrow_WhenAdmin()
+ {
+ AdminGuard.IsAdministrator = () => true;
+ var ex = Record.Exception(() => AdminGuard.EnsureAdministrator());
+ Assert.Null(ex);
+ }
+
+ [Fact]
+ public void EnsureAdministrator_ThrowsUnauthorizedAccessException_WhenNotAdmin()
+ {
+ AdminGuard.IsAdministrator = () => false;
+ var ex = Assert.Throws(() => AdminGuard.EnsureAdministrator());
+ Assert.Contains("Administrative Privileges", ex.Message);
+ }
+
+ [Fact]
+ public void ResetAdminCheck_RestoresDefaultDelegate()
+ {
+ AdminGuard.IsAdministrator = () => false;
+ AdminGuard.ResetAdminCheck();
+ Assert.NotNull(AdminGuard.IsAdministrator);
+ }
+}
\ No newline at end of file