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
2 changes: 1 addition & 1 deletion src/Interfaces/IRegistryKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ public interface IRegistryKey : IDisposable
object? GetValue(string name);
void DeleteValue(string name);
IRegistryKey? OpenSubKey(string name, bool writable);
IRegistryKey CreateSubKey(string name, bool writable);
IRegistryKey? CreateSubKey(string name, bool writable);
}
}
4 changes: 2 additions & 2 deletions src/Interfaces/ISystemSettingsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ namespace WinHome.Interfaces
{
public interface ISystemSettingsService
{
Task<IEnumerable<RegistryTweak>> GetTweaksAsync(Dictionary<string, object> settings);
Task ApplyNonRegistrySettingsAsync(Dictionary<string, object> settings, bool dryRun);
Task<IEnumerable<RegistryTweak>> GetTweaksAsync(Dictionary<string, object>? settings);
Task ApplyNonRegistrySettingsAsync(Dictionary<string, object>? settings, bool dryRun);
Task<Dictionary<string, object>> GetCapturedSettingsAsync();
string? GetFriendlyName(string registryPath, string registryName);
}
Expand Down
4 changes: 2 additions & 2 deletions src/Services/System/RegistryKeyWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ public void DeleteValue(string name)
return subKey == null ? null : new RegistryKeyWrapper(subKey);
}

public IRegistryKey CreateSubKey(string name, bool writable)
public IRegistryKey? CreateSubKey(string name, bool writable)
{
var subKey = _registryKey.CreateSubKey(name, writable);
return new RegistryKeyWrapper(subKey);
return subKey == null ? null : new RegistryKeyWrapper(subKey);
}

public void Dispose()
Expand Down
27 changes: 21 additions & 6 deletions src/Services/System/RegistryService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public void Apply(RegistryTweak tweak, bool dryRun)
{
object? currentValue = key?.GetValue(tweak.Name);

if (currentValue != null && currentValue.ToString() == tweak.Value.ToString())
if (currentValue != null && currentValue.ToString() == tweak.Value?.ToString())
{
Console.WriteLine($"[Registry] Skipped: {tweak.Name} (Already set)");
return;
Expand All @@ -44,8 +44,14 @@ public void Apply(RegistryTweak tweak, bool dryRun)
}
}

using (IRegistryKey key = root.CreateSubKey(subKeyPath, writable: true))
using (IRegistryKey? key = root.CreateSubKey(subKeyPath, writable: true))
{
if (key == null)
{
Console.WriteLine($"[Error] Could not create registry subkey: {tweak.Path}");
return;
}

RegistryValueKind kind = tweak.Type.ToLower() switch
{
"dword" => RegistryValueKind.DWord,
Expand All @@ -54,11 +60,20 @@ public void Apply(RegistryTweak tweak, bool dryRun)
_ => RegistryValueKind.String
};

object valueToWrite = tweak.Value;
if (kind == RegistryValueKind.DWord) valueToWrite = Convert.ToInt32(tweak.Value);
if (kind == RegistryValueKind.QWord) valueToWrite = Convert.ToInt64(tweak.Value);
object? valueToWrite = tweak.Value;
if (valueToWrite is global::System.Text.Json.JsonElement jsonElement)
{
if (kind == RegistryValueKind.DWord) valueToWrite = jsonElement.GetInt32();
else if (kind == RegistryValueKind.QWord) valueToWrite = jsonElement.GetInt64();
else valueToWrite = jsonElement.ToString() ?? string.Empty;
}
else
{
if (kind == RegistryValueKind.DWord) valueToWrite = Convert.ToInt32(tweak.Value);
else if (kind == RegistryValueKind.QWord) valueToWrite = Convert.ToInt64(tweak.Value);
}

key.SetValue(tweak.Name, valueToWrite, kind);
key.SetValue(tweak.Name, valueToWrite ?? string.Empty, kind);
Console.WriteLine($"[Registry] Set {tweak.Name} = {tweak.Value}");
}
}
Expand Down
23 changes: 21 additions & 2 deletions src/Services/System/SystemSettingsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,25 @@ public class SystemSettingsService : ISystemSettingsService
new RegistryTweak { Path = @"HKLM\System\CurrentControlSet\Control\Remote Assistance", Name = "fAllowToGetHelp", Value = 0, Type = "dword" },
// Disable NetBIOS over TCP/IP (prevent LLMNR/NBT-NS poisoning)
new RegistryTweak { Path = @"HKLM\SYSTEM\CurrentControlSet\Services\NetBT\Parameters\Interfaces", Name = "NetbiosOptions", Value = 2, Type = "dword" }
},
["privacy"] = new()
{
// Disable Windows Telemetry data collection
new RegistryTweak { Path = @"HKLM\SOFTWARE\Policies\Microsoft\Windows\DataCollection", Name = "AllowTelemetry", Value = 0, Type = "dword" },
// Disable Advertising ID for personalized ads
new RegistryTweak { Path = @"HKCU\Software\Microsoft\Windows\CurrentVersion\AdvertisingInfo", Name = "Enabled", Value = 0, Type = "dword" },
// Disable Activity History feed
new RegistryTweak { Path = @"HKLM\SOFTWARE\Policies\Microsoft\Windows\System", Name = "EnableActivityFeed", Value = 0, Type = "dword" },
// Disable Activity History cloud upload
new RegistryTweak { Path = @"HKLM\SOFTWARE\Policies\Microsoft\Windows\System", Name = "UploadUserActivities", Value = 0, Type = "dword" },
// Disable Tailored Experiences based on diagnostic data
new RegistryTweak { Path = @"HKCU\Software\Microsoft\Windows\CurrentVersion\Privacy", Name = "TailoredExperiencesWithDiagnosticDataEnabled", Value = 0, Type = "dword" },
// Disable Feedback Notifications
new RegistryTweak { Path = @"HKCU\Software\Microsoft\Siuf\Rules", Name = "NumberOfSIUFInPeriod", Value = 0, Type = "dword" },
// Disable implicit text/ink collection for input personalization
new RegistryTweak { Path = @"HKCU\Software\Microsoft\InputPersonalization", Name = "RestrictImplicitTextCollection", Value = 1, Type = "dword" },
// Disable contact harvesting for handwriting recognition
new RegistryTweak { Path = @"HKCU\Software\Microsoft\InputPersonalization\TrainedDataStore", Name = "HarvestContacts", Value = 0, Type = "dword" }
}
};

Expand Down Expand Up @@ -123,7 +142,7 @@ Dictionary<string, object> ValueMap
new() { { "true", 1 }, { "false", 0 } }),
};

public async Task<IEnumerable<RegistryTweak>> GetTweaksAsync(Dictionary<string, object> settings)
public async Task<IEnumerable<RegistryTweak>> GetTweaksAsync(Dictionary<string, object>? settings)
{
return await Task.Run(() =>
{
Expand Down Expand Up @@ -223,7 +242,7 @@ public async Task<Dictionary<string, object>> GetCapturedSettingsAsync()
return match?.SettingKey;
}

public Task ApplyNonRegistrySettingsAsync(Dictionary<string, object> settings, bool dryRun)
public Task ApplyNonRegistrySettingsAsync(Dictionary<string, object>? settings, bool dryRun)
{
if (settings == null) return Task.CompletedTask;

Expand Down
22 changes: 22 additions & 0 deletions tests/WinHome.Tests/RegistryServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,28 @@ public void Apply_Should_Set_Registry_Value()
_mockRegistryKey.Verify(x => x.SetValue(tweak.Name, tweak.Value, RegistryValueKind.String), Times.Once);
}

[Fact]
public void Apply_Should_Create_SubKey_When_Key_Is_Missing()
{
// Arrange
var tweak = new RegistryTweak { Path = "HKCU\\Software\\TestMissing", Name = "TestValue", Value = "Test", Type = "string" };
var subKeyPath = "Software\\TestMissing";
_mockRegistryWrapper.Setup(x => x.GetRootKey(tweak.Path, out subKeyPath))
.Callback(new GetRootKeyCallback((string fullPath, out string s) => s = subKeyPath))
.Returns(_mockRegistryKey.Object);

// Simulate missing key: OpenSubKey returns null
_mockRegistryKey.Setup(x => x.OpenSubKey(It.IsAny<string>(), false)).Returns((IRegistryKey?)null);
_mockRegistryKey.Setup(x => x.CreateSubKey(It.IsAny<string>(), true)).Returns(_mockRegistryKey.Object);

// Act
_registryService.Apply(tweak, false);

// Assert
_mockRegistryKey.Verify(x => x.CreateSubKey(subKeyPath, true), Times.Once);
_mockRegistryKey.Verify(x => x.SetValue(tweak.Name, tweak.Value, RegistryValueKind.String), Times.Once);
}

[Fact]
public void Revert_Should_Delete_Registry_Value()
{
Expand Down
Loading
Loading