diff --git a/src/Common/Helpers/GPOHelper.cs b/src/Common/Helpers/GPOHelper.cs new file mode 100644 index 0000000..fec0fc6 --- /dev/null +++ b/src/Common/Helpers/GPOHelper.cs @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Win32; +using Serilog; + +namespace WindowsAdvancedSettings.Common.Helpers; + +public class GPOHelper +{ + private static readonly ILogger _log = Log.ForContext("SourceContext", nameof(GPOHelper)); + + private enum GpoRuleConfigured + { + WrongValue = -3, // The policy is set to an unrecognized value + Unavailable = -2, // Couldn't access registry + NotConfigured = -1, // Policy is not configured + Disabled = 0, // Policy is disabled + Enabled = 1, // Policy is enabled + } + + // Registry path where gpo policy values are stored + private const string PoliciesScopeMachine = "HKEY_LOCAL_MACHINE"; + private const string PoliciesPath = @"\SOFTWARE\Policies\Microsoft\Windows\WindowsAdvancedSettings"; + + // Registry value names + private const string PolicyConfigureEnabledWindowsAdvancedSettings = "ConfigureEnabledWindowsAdvancedSettings"; + + private static GpoRuleConfigured GetConfiguredValue(string registryValueName) + { + try + { + var rawValue = Registry.GetValue( + keyName: PoliciesScopeMachine + PoliciesPath, + valueName: registryValueName, + defaultValue: GpoRuleConfigured.NotConfigured); + + _log.Debug($"Registry value {registryValueName} set to {rawValue}"); + + // Value will be null if the subkey specified by keyName does not exist. + if (rawValue == null) + { + return GpoRuleConfigured.NotConfigured; + } + else if (rawValue is not int && rawValue is not GpoRuleConfigured) + { + return GpoRuleConfigured.WrongValue; + } + else + { + return (GpoRuleConfigured)rawValue; + } + } + catch (System.Security.SecurityException) + { + // The user does not have the permissions required to read from the registry key. + return GpoRuleConfigured.Unavailable; + } + catch (System.IO.IOException) + { + // The RegistryKey that contains the specified value has been marked for deletion. + return GpoRuleConfigured.Unavailable; + } + catch (System.ArgumentException) + { + // keyName does not begin with a valid registry root. + return GpoRuleConfigured.NotConfigured; + } + } + + private static bool EvaluateConfiguredValue(string registryValueName, GpoRuleConfigured defaultValue) + { + var configuredValue = GetConfiguredValue(registryValueName); + if (configuredValue < 0) + { + if (configuredValue != GpoRuleConfigured.NotConfigured) + { + // Only log an error if a configuration was attempted but was incorrect. NotConfigured is expected state. + _log.Error($"Registry value {registryValueName} set to {configuredValue}, using default {defaultValue} instead."); + } + + configuredValue = defaultValue; + } + + return configuredValue == GpoRuleConfigured.Enabled; + } + + public static bool GetConfiguredEnabledWindowsAdvancedSettingsValue() + { + var defaultValue = GpoRuleConfigured.Enabled; + return EvaluateConfiguredValue(PolicyConfigureEnabledWindowsAdvancedSettings, defaultValue); + } +} diff --git a/src/Common/WindowsAdvancedSettings.admx b/src/Common/WindowsAdvancedSettings.admx new file mode 100644 index 0000000..e540dc2 --- /dev/null +++ b/src/Common/WindowsAdvancedSettings.admx @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Common/gpo/assets/WindowsAdvancedSettings.admx b/src/Common/gpo/assets/WindowsAdvancedSettings.admx new file mode 100644 index 0000000..f50c07e --- /dev/null +++ b/src/Common/gpo/assets/WindowsAdvancedSettings.admx @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Common/gpo/assets/en-US/WindowsAdvancedSettings.adml b/src/Common/gpo/assets/en-US/WindowsAdvancedSettings.adml new file mode 100644 index 0000000..13435e7 --- /dev/null +++ b/src/Common/gpo/assets/en-US/WindowsAdvancedSettings.adml @@ -0,0 +1,30 @@ + + + + Windows Advanced Settings + Windows Advanced Settings + + + Microsoft Windows Advanced Settings + + Windows Advanced Settings version 0.1900.* or later + + + This policy configures the enabled state for Windows Advanced Settings. + + If you enable this setting, Windows Advanced Settings will be always enabled and the user won't be able to disable it. + + If you disable this setting, Windows Advanced Settings will be always disabled and the user won't be able to enable it. + + If you don't configure this setting, users are able to enable or disable Windows Advanced Settings. + + This policy will override any enabled state policies for individual Windows Advanced Settings features. + + + Configure Windows Advanced Settings enabled state + + + + + diff --git a/src/FileExplorerGitIntegration/Program.cs b/src/FileExplorerGitIntegration/Program.cs index e63ca02..6a03df2 100644 --- a/src/FileExplorerGitIntegration/Program.cs +++ b/src/FileExplorerGitIntegration/Program.cs @@ -76,6 +76,13 @@ private static void AppActivationRedirected(object? sender, Microsoft.Windows.Ap private static void HandleCOMServerActivation() { + var gpoPolicyEnabled = GPOHelper.GetConfiguredEnabledWindowsAdvancedSettingsValue(); + if (!gpoPolicyEnabled) + { + Log.Information($"Windows Advanced Settings is disabled by policy, exiting."); + return; + } + Log.Information($"Activating COM Server"); RepositoryCache cache = new RepositoryCache(); diff --git a/test/AdvancedSettings.Tester/ConfigureFolderPath.cs b/src/FileExplorerSourceControlIntegration/ConfigureFolderPath.cs similarity index 52% rename from test/AdvancedSettings.Tester/ConfigureFolderPath.cs rename to src/FileExplorerSourceControlIntegration/ConfigureFolderPath.cs index 2de9356..24dbabb 100644 --- a/test/AdvancedSettings.Tester/ConfigureFolderPath.cs +++ b/src/FileExplorerSourceControlIntegration/ConfigureFolderPath.cs @@ -1,12 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using FileExplorerSourceControlIntegration; using Microsoft.Internal.Windows.DevHome.Helpers.FileExplorer; using Serilog; -using Windows.Storage; -namespace AdvancedSettings.Tester; +namespace FileExplorerSourceControlIntegration; public enum ProviderType { @@ -14,28 +12,21 @@ public enum ProviderType Release, } -internal class ConfigureFolderPath +public class ConfigureFolderPath { private static readonly string _releaseProviderGuidString = "1212F95B-257E-414e-B44F-F26634BD2627"; private static readonly string _devProviderGuidString = "40FE4D6E-C9A0-48B4-A83E-AAA1D002C0D5"; - private static readonly Guid _releaseProvider = new(_releaseProviderGuidString); - private static readonly Guid _devProvider = new(_devProviderGuidString); - - public static void DisplayStatus() - { - foreach (var folderInfo in ExtraFolderPropertiesWrapper.GetRegisteredFolderInfos()) - { - var providerName = GetProviderName(folderInfo.HandlerClsid); - Console.WriteLine($"{providerName}: {folderInfo.RootFolderPath} {{{folderInfo.HandlerClsid}}} {folderInfo.AppId}"); - } - } + public static readonly Guid ReleaseProvider = new(_releaseProviderGuidString); + public static readonly Guid DevProvider = new(_devProviderGuidString); + public static readonly Guid CurrentProvider = typeof(SourceControlProvider).GUID; public static void AddPath(string provider, string path) { try { var providerType = (ProviderType)Enum.Parse(typeof(ProviderType), provider, true); - AddPath(providerType, path); + var providerGuid = GetProvider(providerType); + AddPath(providerGuid, path); } catch (Exception ex) { @@ -43,10 +34,8 @@ public static void AddPath(string provider, string path) } } - public static void AddPath(ProviderType providerType, string path) + public static void AddPath(Guid provider, string path) { - var provider = GetProvider(providerType); - Console.WriteLine($"Registering source folder: {path} for provider {providerType}"); try { if (!Directory.Exists(path)) @@ -79,28 +68,50 @@ public static void RemovePath(string path) } } - private static Guid GetProvider(ProviderType providerType) + public static Guid GetProvider(ProviderType providerType) { return providerType switch { - ProviderType.Release => _releaseProvider, - _ => _devProvider, + ProviderType.Release => ReleaseProvider, + _ => DevProvider, }; } - private static string GetProviderName(Guid guid) + public static void RemoveAllForProvider(string provider) { - if (guid.Equals(_devProvider)) + try { - return "DEV"; + var providerType = (ProviderType)Enum.Parse(typeof(ProviderType), provider, true); + var providerGuid = GetProvider(providerType); + RemoveAllForProvider(providerGuid); } - else if (guid.Equals(_releaseProvider)) + catch (Exception ex) { - return "REL"; + Log.Error(ex, $"Invalid provider: {provider}"); } - else + } + + public static void RemoveAllForProvider(Guid provider) + { + Log.Information($"Removing all registered folders for provider: {provider}"); + try + { + foreach (var folderInfo in ExtraFolderPropertiesWrapper.GetRegisteredFolderInfos()) + { + if (folderInfo.HandlerClsid.Equals(provider)) + { + RemovePath(folderInfo.RootFolderPath); + } + } + } + catch (Exception ex) { - return "UNK"; + Log.Error(ex, $"An exception occurred while enumerating the folder properties."); } } + + public static void RemoveAllForCurrentProvider() + { + RemoveAllForProvider(typeof(SourceControlProvider).GUID); + } } diff --git a/src/FileExplorerSourceControlIntegration/Program.cs b/src/FileExplorerSourceControlIntegration/Program.cs index cd8980e..0010bea 100644 --- a/src/FileExplorerSourceControlIntegration/Program.cs +++ b/src/FileExplorerSourceControlIntegration/Program.cs @@ -52,6 +52,14 @@ public static async Task Main([System.Runtime.InteropServices.WindowsRuntime.Rea private static void HandleCOMServerActivation() { + var gpoPolicyEnabled = GPOHelper.GetConfiguredEnabledWindowsAdvancedSettingsValue(); + if (!gpoPolicyEnabled) + { + Log.Information($"Windows Advanced Settings is disabled by policy, removing all registered entries for this provider and exiting."); + ConfigureFolderPath.RemoveAllForCurrentProvider(); + return; + } + Log.Information($"Activating COM Server"); using var sourceControlProviderServer = new SourceControlProviderServer(); var sourceControlProviderInstance = new SourceControlProvider(); diff --git a/test/AdvancedSettings.Tester/Program.cs b/test/AdvancedSettings.Tester/Program.cs index f194b7b..f36256d 100644 --- a/test/AdvancedSettings.Tester/Program.cs +++ b/test/AdvancedSettings.Tester/Program.cs @@ -1,8 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using AdvancedSettings.Tester; +using FileExplorerSourceControlIntegration; using Microsoft.Extensions.Configuration; +using Microsoft.Internal.Windows.DevHome.Helpers.FileExplorer; +using Microsoft.Windows.DevHome.SDK; using Serilog; using Windows.Storage; @@ -21,17 +23,24 @@ private static void Main([System.Runtime.InteropServices.WindowsRuntime.ReadOnly if (args.Length == 0) { - ConfigureFolderPath.DisplayStatus(); + DisplayStatus(); } else if (args.Length > 2 && args[0].Equals("add", StringComparison.OrdinalIgnoreCase)) { + Console.WriteLine($"Registering source folder: {args[1]} for provider {args[2]}"); ConfigureFolderPath.AddPath(args[1], args[2]); - ConfigureFolderPath.DisplayStatus(); + DisplayStatus(); } else if (args.Length > 1 && args[0].Equals("remove", StringComparison.OrdinalIgnoreCase)) { + Console.WriteLine($"Removing path {args[1]}"); ConfigureFolderPath.RemovePath(args[1]); - ConfigureFolderPath.DisplayStatus(); + DisplayStatus(); + } + else if (args.Length > 1 && args[0].Equals("removeall", StringComparison.OrdinalIgnoreCase)) + { + Console.WriteLine($"Removing all folders for provider: {args[1]}"); + ConfigureFolderPath.RemoveAllForProvider(args[1]); } else { @@ -41,6 +50,31 @@ private static void Main([System.Runtime.InteropServices.WindowsRuntime.ReadOnly Log.CloseAndFlush(); } + private static void DisplayStatus() + { + foreach (var folderInfo in ExtraFolderPropertiesWrapper.GetRegisteredFolderInfos()) + { + var providerName = GetProviderName(folderInfo.HandlerClsid); + Console.WriteLine($"{providerName}: {folderInfo.RootFolderPath} {{{folderInfo.HandlerClsid}}} {folderInfo.AppId}"); + } + } + + private static string GetProviderName(Guid guid) + { + if (guid.Equals(ConfigureFolderPath.DevProvider)) + { + return "DEV"; + } + else if (guid.Equals(ConfigureFolderPath.ReleaseProvider)) + { + return "REL"; + } + else + { + return "UNK"; + } + } + private static void DisplayHelp() { var help = "WASTester Usage:\n" +