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 src/BootstrapMate.App/ViewModels/PrefsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace BootstrapMate.App.ViewModels;

/// <summary>
/// ViewModel for the Prefs tab. Mirrors macOS SettingsViewModel.
/// Loads from ConfigManager, saves non-policy settings via elevated CLI, shows policy lock state.
/// Loads from ConfigManager, saves non-managed settings to the user registry (HKCU), shows management lock state.
/// </summary>
Comment on lines 7 to 10
public partial class PrefsViewModel : ObservableObject
{
Expand Down Expand Up @@ -64,11 +64,11 @@ partial void OnSaveStatusChanged(SaveState value)
OnPropertyChanged(nameof(IsSaveEnabled));
}

// ── Policy State ─────────────────────────────────────────────
// ── Management State ─────────────────────────────────────────────

private HashSet<string> _managedKeys = [];

public bool IsPolicyManaged(string key) => _managedKeys.Contains(key);
public bool IsManaged(string key) => _managedKeys.Contains(key);

// ── Binding-Friendly Display Properties ──────────────────────

Expand Down Expand Up @@ -137,7 +137,7 @@ public void Load()
configMgr.ReloadSettings();
var config = configMgr.Config;

_managedKeys = PolicyDetector.Instance.AllManagedKeys();
_managedKeys = ManagementDetector.Instance.AllManagedKeys();

ManifestUrl = config.ManifestUrl ?? "";
HasExistingAuth = !string.IsNullOrEmpty(config.AuthorizationHeader);
Expand All @@ -155,7 +155,7 @@ public void Load()
CustomInstallPath = config.CustomInstallPath ?? "";
NetworkTimeout = config.NetworkTimeout;

// Notify all bindings (policy locks, computed display properties)
// Notify all bindings (management locks, computed display properties)
OnPropertyChanged(string.Empty);

_isLoading = false;
Expand Down
68 changes: 34 additions & 34 deletions src/BootstrapMate.Core/ConfigManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ public sealed class ConfigManager

private ConfigManager()
{
LoadPolicyAndUserSettings();
LoadManagementAndUserSettings();
}

public enum ConfigSource
{
Default,
UserSettings,
Policy,
Management,
CliArgument
}

Expand Down Expand Up @@ -98,42 +98,42 @@ public string GetInstallPath()
public bool IsValid() => !string.IsNullOrWhiteSpace(Config.ManifestUrl);

/// <summary>
/// Reload settings from policy + user registry. Call when waiting for
/// Reload settings from management + user registry. Call when waiting for
/// Intune policy to land post-enrollment.
/// Returns true if a valid manifest URL was found.
/// </summary>
public bool ReloadSettings()
{
Config.ManifestUrl = null;
ManifestUrlSource = ConfigSource.Default;
LoadPolicyAndUserSettings();
LoadManagementAndUserSettings();
return IsValid();
}

/// <summary>
/// Save user-configured settings to HKLM\SOFTWARE\BootstrapMate\Settings.
/// Skips any key that is already policy-managed.
/// Requires elevation.
/// Save user-configured settings to HKCU\SOFTWARE\BootstrapMate\Settings.
/// Skips any key that is already managed (set via Group Policy / Intune).
/// Writes to the current user's hive, so no elevation is required.
/// </summary>
public static void SaveUserSettings(BootstrapMateConfig settings)
{
var policy = PolicyDetector.Instance;
var management = ManagementDetector.Instance;

void WriteString(string key, string? value)
{
if (policy.IsManagedByPolicy(key)) return;
if (management.IsManaged(key)) return;
WriteRegistryValue(key, value ?? string.Empty, RegistryValueKind.String);
}

void WriteBool(string key, bool value)
{
if (policy.IsManagedByPolicy(key)) return;
if (management.IsManaged(key)) return;
WriteRegistryValue(key, value ? 1 : 0, RegistryValueKind.DWord);
}

void WriteInt(string key, int value)
{
if (policy.IsManagedByPolicy(key)) return;
if (management.IsManaged(key)) return;
WriteRegistryValue(key, value, RegistryValueKind.DWord);
}

Expand All @@ -160,9 +160,9 @@ void WriteInt(string key, int value)

// ── Private ──────────────────────────────────────────────────────

private void LoadPolicyAndUserSettings()
private void LoadManagementAndUserSettings()
{
var policy = PolicyDetector.Instance;
var management = ManagementDetector.Instance;

// Lowest priority first: baked-in default → HKCU user → HKLM machine → CSP policy.
// Higher-priority sources overwrite earlier ones; CLI runs last from caller.
Expand All @@ -175,66 +175,66 @@ private void LoadPolicyAndUserSettings()

LoadFromUserRegistry();
LoadFromMachineRegistry();
LoadFromPolicy(policy);
LoadFromManagement(management);
}

private void LoadFromPolicy(PolicyDetector policy)
private void LoadFromManagement(ManagementDetector management)
{
if (policy.GetManagedString("ManifestUrl") is { Length: > 0 } url)
if (management.GetManagedString("ManifestUrl") is { Length: > 0 } url)
{
Config.ManifestUrl = url;
ManifestUrlSource = ConfigSource.Policy;
ManifestUrlSource = ConfigSource.Management;
}

if (policy.GetManagedString("AuthorizationHeader") is { Length: > 0 } auth)
if (management.GetManagedString("AuthorizationHeader") is { Length: > 0 } auth)
Config.AuthorizationHeader = auth;

if (policy.GetManagedBool("FollowRedirects") is { } followRedirects)
if (management.GetManagedBool("FollowRedirects") is { } followRedirects)
Config.FollowRedirects = followRedirects;

if (policy.GetManagedBool("Reboot") is { } reboot)
if (management.GetManagedBool("Reboot") is { } reboot)
Config.Reboot = reboot;

if (policy.GetManagedBool("SilentMode") is { } silent)
if (management.GetManagedBool("SilentMode") is { } silent)
Config.SilentMode = silent;

if (policy.GetManagedBool("VerboseMode") is { } verbose)
if (management.GetManagedBool("VerboseMode") is { } verbose)
Config.VerboseMode = verbose;

if (policy.GetManagedBool("DryRun") is { } dryRun)
if (management.GetManagedBool("DryRun") is { } dryRun)
Config.DryRun = dryRun;

if (policy.GetManagedBool("EnableDialog") is { } enableDialog)
if (management.GetManagedBool("EnableDialog") is { } enableDialog)
Config.EnableDialog = enableDialog;

if (policy.GetManagedBool("NoDialog") is { } noDialog)
if (management.GetManagedBool("NoDialog") is { } noDialog)
Config.NoDialog = noDialog;

if (policy.GetManagedString("DialogTitle") is { Length: > 0 } title)
if (management.GetManagedString("DialogTitle") is { Length: > 0 } title)
Config.DialogTitle = title;

if (policy.GetManagedString("DialogMessage") is { Length: > 0 } msg)
if (management.GetManagedString("DialogMessage") is { Length: > 0 } msg)
Config.DialogMessage = msg;

if (policy.GetManagedString("DialogIcon") is { Length: > 0 } icon)
if (management.GetManagedString("DialogIcon") is { Length: > 0 } icon)
Config.DialogIcon = icon;

if (policy.GetManagedBool("BlurScreen") is { } blur)
if (management.GetManagedBool("BlurScreen") is { } blur)
Config.BlurScreen = blur;

if (policy.GetManagedString("CustomInstallPath") is { Length: > 0 } path)
if (management.GetManagedString("CustomInstallPath") is { Length: > 0 } path)
Config.CustomInstallPath = path;

if (policy.GetManagedInt("NetworkTimeout") is { } timeout)
if (management.GetManagedInt("NetworkTimeout") is { } timeout)
Config.NetworkTimeout = timeout;

if (policy.GetManagedBool("VerifyPackageSignatures") is { } verifySig)
if (management.GetManagedBool("VerifyPackageSignatures") is { } verifySig)
Config.VerifyPackageSignatures = verifySig;

if (policy.GetManagedString("ExpectedPublisher") is { Length: > 0 } publisher)
if (management.GetManagedString("ExpectedPublisher") is { Length: > 0 } publisher)
Config.ExpectedPublisher = publisher;

if (policy.GetManagedBool("AllowUnsigned") is { } allowUnsigned)
if (management.GetManagedBool("AllowUnsigned") is { } allowUnsigned)
Config.AllowUnsigned = allowUnsigned;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ namespace BootstrapMate.Core;

/// <summary>
/// Detects which settings are managed by Intune CSP / Group Policy.
/// Windows equivalent of macOS MDMDetector — checks HKLM\SOFTWARE\Policies\BootstrapMate.
/// Windows equivalent of macOS ManagementDetector — checks HKLM\SOFTWARE\Policies\BootstrapMate.
///
/// Intune writes here via OMA-URI:
/// ./Device/Vendor/MSFT/Policy/Config/BootstrapMate~Policy~BootstrapMate/{KeyName}
/// Group Policy writes here via ADMX-backed policies.
/// </summary>
public sealed class PolicyDetector
public sealed class ManagementDetector
{
public static PolicyDetector Instance { get; } = new();
public static ManagementDetector Instance { get; } = new();

/// <summary>
/// Known key aliases — maps canonical key to all variant names that may appear in policy.
/// Mirrors macOS MDMDetector.keyAliases for consistent admin experience.
/// Mirrors macOS ManagementDetector.keyAliases for consistent admin experience.
/// </summary>
private static readonly Dictionary<string, string[]> KeyAliases = new(StringComparer.OrdinalIgnoreCase)
{
Expand All @@ -40,27 +40,27 @@ public sealed class PolicyDetector
["AllowUnsigned"] = ["AllowUnsigned"],
};

private PolicyDetector() { }
private ManagementDetector() { }

/// <summary>Returns true if the canonical key is present in the Policies registry hive.</summary>
public bool IsManagedByPolicy(string canonicalKey)
public bool IsManaged(string canonicalKey)
{
var aliases = GetAliases(canonicalKey);
return FindRegistryValue(aliases) is not null;
}

/// <summary>Returns the policy-managed value for a canonical key, or null.</summary>
/// <summary>Returns the managed value for a canonical key, or null.</summary>
public object? GetManagedValue(string canonicalKey)
{
var aliases = GetAliases(canonicalKey);
return FindRegistryValue(aliases);
}

/// <summary>Returns a string policy value, or null.</summary>
/// <summary>Returns a string value, or null.</summary>
public string? GetManagedString(string canonicalKey)
=> GetManagedValue(canonicalKey)?.ToString();

/// <summary>Returns a bool policy value, or null. Reads DWORD 0/1.</summary>
/// <summary>Returns a bool value, or null. Reads DWORD 0/1.</summary>
public bool? GetManagedBool(string canonicalKey)
{
var value = GetManagedValue(canonicalKey);
Expand All @@ -73,7 +73,7 @@ string s when int.TryParse(s, out var i) => i != 0,
};
}

/// <summary>Returns an int policy value, or null.</summary>
/// <summary>Returns an int value, or null.</summary>
public int? GetManagedInt(string canonicalKey)
{
var value = GetManagedValue(canonicalKey);
Expand All @@ -85,7 +85,7 @@ string s when int.TryParse(s, out var i) => i,
};
}

/// <summary>Returns the set of canonical keys that are currently policy-managed.</summary>
/// <summary>Returns the set of canonical keys that are currently managed.</summary>
public HashSet<string> AllManagedKeys()
{
var result = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
Expand Down
Loading