winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

GroupPolicyHelper.cs (18930B)


      1 // -----------------------------------------------------------------------------
      2 // <copyright file="GroupPolicyHelper.cs" company="Microsoft Corporation">
      3 //     Copyright (c) Microsoft Corporation. Licensed under the MIT License.
      4 // </copyright>
      5 // -----------------------------------------------------------------------------
      6 
      7 namespace AppInstallerCLIE2ETests
      8 {
      9     using System;
     10     using System.Collections.Generic;
     11     using System.IO;
     12     using System.Linq;
     13     using System.Xml.Linq;
     14     using AppInstallerCLIE2ETests.Helpers;
     15     using Microsoft.Win32;
     16     using Newtonsoft.Json;
     17     using NUnit.Framework;
     18 
     19     /// <summary>
     20     /// Helper for setting Group Policy settings.
     21     /// This helper reads the keys and values to use directly from the ADMX file to ensure that the names
     22     /// used by the source code are correct.
     23     /// </summary>
     24     /// <remarks>
     25     /// This helper modifies the policies for winget configured in the machine.
     26     /// </remarks>
     27     public class GroupPolicyHelper
     28     {
     29         private const string PoliciesDefinitionFileName = "DesktopAppInstaller.admx";
     30         private static Lazy<XElement> policyDefinitions = new Lazy<XElement>(() =>
     31         {
     32             string filePath = TestCommon.GetTestDataFile(PoliciesDefinitionFileName);
     33             string fileText = File.ReadAllText(filePath);
     34             return XElement.Parse(fileText);
     35         });
     36 
     37         /// <summary>
     38         /// Name of the policy. Used to identify it in the file.
     39         /// </summary>
     40         private string name;
     41 
     42         /// <summary>
     43         /// ID of the value element of this policy (if it has one).
     44         /// This assumes that each policy has a single value element.
     45         /// </summary>
     46         private string elementId;
     47 
     48         private GroupPolicyHelper(string name)
     49         {
     50             this.name = name;
     51         }
     52 
     53         private GroupPolicyHelper(string name, string elementId)
     54         {
     55             this.name = name;
     56             this.elementId = elementId;
     57         }
     58 
     59         // Policies available.
     60 
     61         /// <summary>
     62         /// Gets the Enable winget policy.
     63         /// </summary>
     64         public static GroupPolicyHelper EnableWinget { get; private set; } = new GroupPolicyHelper("EnableAppInstaller");
     65 
     66         /// <summary>
     67         /// Gets the Enable Windows Package Manager CommandLine Interfaces policy.
     68         /// </summary>
     69         public static GroupPolicyHelper EnableWinGetCommandLineInterfaces { get; private set; } = new GroupPolicyHelper("EnableWindowsPackageManagerCommandLineInterfaces");
     70 
     71         /// <summary>
     72         /// Gets the Enable settings policy.
     73         /// </summary>
     74         public static GroupPolicyHelper EnableSettings { get; private set; } = new GroupPolicyHelper("EnableSettings");
     75 
     76         /// <summary>
     77         /// Gets the Enable experimental features policy.
     78         /// </summary>
     79         public static GroupPolicyHelper EnableExperimentalFeatures { get; private set; } = new GroupPolicyHelper("EnableExperimentalFeatures");
     80 
     81         /// <summary>
     82         /// Gets the Enable local manifest policy.
     83         /// </summary>
     84         public static GroupPolicyHelper EnableLocalManifests { get; private set; } = new GroupPolicyHelper("EnableLocalManifestFiles");
     85 
     86         /// <summary>
     87         /// Gets the Enable hash override policy.
     88         /// </summary>
     89         public static GroupPolicyHelper EnableHashOverride { get; private set; } = new GroupPolicyHelper("EnableHashOverride");
     90 
     91         /// <summary>
     92         /// Gets the Enable ignore malware scan policy.
     93         /// </summary>
     94         public static GroupPolicyHelper EnableLocalArchiveMalwareScanOverride { get; private set; } = new GroupPolicyHelper("EnableLocalArchiveMalwareScanOverride");
     95 
     96         /// <summary>
     97         /// Gets the Enable default source policy.
     98         /// </summary>
     99         public static GroupPolicyHelper EnableDefaultSource { get; private set; } = new GroupPolicyHelper("EnableDefaultSource");
    100 
    101         /// <summary>
    102         /// Gets the Enable store source policy.
    103         /// </summary>
    104         public static GroupPolicyHelper EnableMicrosoftStoreSource { get; private set; } = new GroupPolicyHelper("EnableMicrosoftStoreSource");
    105 
    106         /// <summary>
    107         /// Gets the Enable additional sources policy.
    108         /// </summary>
    109         public static GroupPolicyHelper EnableAdditionalSources { get; private set; } = new GroupPolicyHelper("EnableAdditionalSources", "AdditionalSources");
    110 
    111         /// <summary>
    112         /// Gets the Enable allowed sources policy.
    113         /// </summary>
    114         public static GroupPolicyHelper EnableAllowedSources { get; private set; } = new GroupPolicyHelper("EnableAllowedSources", "AllowedSources");
    115 
    116         /// <summary>
    117         /// Gets the Enable Windows Package Manager Configuration Interfaces policy.
    118         /// </summary>
    119         public static GroupPolicyHelper EnableConfiguration { get; private set; } = new GroupPolicyHelper("EnableWindowsPackageManagerConfiguration");
    120 
    121         /// <summary>
    122         /// Gets the Enable Windows Package Manager proxy command line options policy.
    123         /// </summary>
    124         public static GroupPolicyHelper EnableProxyCommandLineOptions { get; private set; } = new GroupPolicyHelper("EnableWindowsPackageManagerProxyCommandLineOptions");
    125 
    126         /// <summary>
    127         /// Gets the Enable auto update interval policy.
    128         /// </summary>
    129         public static GroupPolicyHelper SourceAutoUpdateInterval { get; private set; } = new GroupPolicyHelper("SourceAutoUpdateInterval", "SourceAutoUpdateInterval");
    130 
    131         private static GroupPolicyHelper[] AllPolicies { get; set; } = new GroupPolicyHelper[]
    132         {
    133             EnableWinget,
    134             EnableSettings,
    135             EnableExperimentalFeatures,
    136             EnableLocalManifests,
    137             EnableHashOverride,
    138             EnableLocalArchiveMalwareScanOverride,
    139             EnableDefaultSource,
    140             EnableMicrosoftStoreSource,
    141             EnableAdditionalSources,
    142             EnableAllowedSources,
    143             SourceAutoUpdateInterval,
    144             EnableWinGetCommandLineInterfaces,
    145             EnableConfiguration,
    146             EnableProxyCommandLineOptions,
    147         };
    148 
    149         /// <summary>
    150         /// Gets the content of the ADMX file as an XML.
    151         /// </summary>
    152         private static XElement PolicyDefinitions => policyDefinitions.Value;
    153 
    154         /// <summary>
    155         /// Gets the XML element that defines this policy.
    156         /// </summary>
    157         // The XML structure is like this:
    158         // <policyDefinitions ...>
    159         //   ...
    160         //   <policies>
    161         //     <policy name="..." ... />
    162         //   </policies>
    163         // </policyDefinitions>
    164         private XElement PolicyElement => PolicyDefinitions
    165             .Element(XmlNames.Policies)
    166             .Elements(XmlNames.Policy)
    167             .First(policy => policy.Attribute(XmlNames.Attributes.Name).Value == this.name);
    168 
    169         /// <summary>
    170         /// Gets the path to the registry key that backs this policy.
    171         /// </summary>
    172         private string KeyPath => this.PolicyElement.Attribute(XmlNames.Attributes.Key).Value;
    173 
    174         /// <summary>
    175         /// Gets the name of the registry value that backs this policy.
    176         /// </summary>
    177         private string ValueName => this.PolicyElement.Attribute(XmlNames.Attributes.ValueName)?.Value;
    178 
    179         /// <summary>
    180         /// Gets the XElement that defines the single value element of this policy.
    181         /// This only works if the policy has a single element for its value.
    182         /// </summary>
    183         // Looks for something like this:
    184         // <policy>
    185         //   <elements>
    186         //     <something id="..." />
    187         //   </elements>
    188         // </policy>
    189         // We use only list and decimal elements.
    190         private XElement ValueElement => this.PolicyElement
    191             .Element(XmlNames.Elements)
    192             .Elements()
    193             .First(element => element.Attribute(XmlNames.Attributes.Id).Value == this.elementId);
    194 
    195         /// <summary>
    196         /// Deletes all of the existing policies from the registry.
    197         /// </summary>
    198         public static void DeleteExistingPolicies()
    199         {
    200             foreach (var policy in AllPolicies)
    201             {
    202                 policy.SetNotConfigured();
    203             }
    204         }
    205 
    206         /// <summary>
    207         /// Sets the policy to the Enabled state.
    208         /// This will fail if the policy's EnabledValue does not exist or is not exactly as expected.
    209         /// </summary>
    210         public void Enable()
    211         {
    212             // The expected format is like this:
    213             // <enabledValue>
    214             //   <decimal value="1" />
    215             // </enabledValue>
    216             // We expect the value to always be 1, but still parse it to catch errors in the ADMX.
    217             int enabledValue = GetDecimalValue(this.PolicyElement.Element(XmlNames.EnabledValue));
    218             using (RegistryKey key = this.GetKey())
    219             {
    220                 key.SetValue(this.ValueName, enabledValue);
    221             }
    222         }
    223 
    224         /// <summary>
    225         /// Sets the policy to the Disabled state.
    226         /// This will fail if the policy's DisabledValue does not exist or is not exactly as expected.
    227         /// </summary>
    228         public void Disable()
    229         {
    230             // The expected format is like this:
    231             // <enabledValue>
    232             //   <decimal value="0" />
    233             // </enabledValue>
    234             // We expect the value to always be 0, but still parse it to catch errors in the ADMX.
    235             int disabledValue = GetDecimalValue(this.PolicyElement.Element(XmlNames.DisabledValue));
    236             using (RegistryKey key = this.GetKey())
    237             {
    238                 key.SetValue(this.ValueName, disabledValue);
    239             }
    240         }
    241 
    242         /// <summary>
    243         /// Sets the policy to the Not Configured state.
    244         /// This deletes the value associated with the policy, including its list if it has one.
    245         /// </summary>
    246         public void SetNotConfigured()
    247         {
    248             // Delete the enabled/disabled value
    249             if (this.ValueName != null)
    250             {
    251                 using (RegistryKey key = this.GetKey())
    252                 {
    253                     key.DeleteValue(this.ValueName, throwOnMissingValue: false);
    254                 }
    255             }
    256 
    257             // Delete the value element
    258             if (this.elementId != null)
    259             {
    260                 if (this.ValueElement.Name == XmlNames.List)
    261                 {
    262                     // Lists are stored in separate keys.
    263                     Registry.LocalMachine.DeleteSubKeyTree(this.ValueElement.Attribute(XmlNames.Attributes.Key).Value, throwOnMissingSubKey: false);
    264                 }
    265                 else if (this.ValueElement.Name == XmlNames.Decimal)
    266                 {
    267                     // Decimals are stored in single values
    268                     using (RegistryKey key = this.GetKey())
    269                     {
    270                         key.DeleteValue(this.ValueElement.Attribute(XmlNames.Attributes.ValueName).Value, throwOnMissingValue: false);
    271                     }
    272                 }
    273             }
    274         }
    275 
    276         /// <summary>
    277         /// Sets the value of the policy when enabled.
    278         /// This uses only the "elements" of the policy, not the "enabledValue".
    279         /// The type used in the registry is chosen automatically.
    280         /// </summary>
    281         /// <param name="value">Value of the policy.</param>
    282         public void SetEnabledValue(object value)
    283         {
    284             using (RegistryKey key = this.GetKey())
    285             {
    286                 key.SetValue(
    287                     this.ValueElement.Attribute(XmlNames.Attributes.ValueName).Value,
    288                     value);
    289             }
    290         }
    291 
    292         /// <summary>
    293         /// Sets the list value of the policy when enabled.
    294         /// This sets from the "elements" and also sets the enabled value as lists are also gated by a toggle.
    295         /// This will fail if the value of the policy is not a list.
    296         /// </summary>
    297         /// <param name="values">Values to set in the list.</param>
    298         public void SetEnabledList(IEnumerable<string> values)
    299         {
    300             this.Enable();
    301 
    302             // Delete the existing list
    303             string listKeyPath = this.ValueElement.Attribute(XmlNames.Attributes.Key).Value;
    304             Registry.LocalMachine.DeleteSubKeyTree(listKeyPath, throwOnMissingSubKey: false);
    305 
    306             // Create and fill the key.
    307             // This assumes that the values don't need to have special names or prefixes.
    308             var listKey = Registry.LocalMachine.CreateSubKey(listKeyPath);
    309             int index = 0;
    310             foreach (string value in values)
    311             {
    312                 TestContext.Out.WriteLine($"Setting {this.name} list value: {value}");
    313                 listKey.SetValue(index++.ToString(), value);
    314             }
    315 
    316             listKey.Close();
    317         }
    318 
    319         /// <summary>
    320         /// Sets the list value of the policy when enabled.
    321         /// This sets from the "elements" and also sets the enabled value as lists are also gated by a toggle.
    322         /// This will fail if the value of the policy is not a list.
    323         /// </summary>
    324         /// <param name="values">Values to set in the list.</param>
    325         public void SetEnabledList(IEnumerable<GroupPolicySource> values)
    326         {
    327             this.SetEnabledList(values.Select(source => JsonConvert.SerializeObject(source)));
    328         }
    329 
    330         /// <summary>
    331         /// Gets the value from a "decimal" child element.
    332         /// </summary>
    333         /// <param name="element">Element containing the decimal.</param>
    334         /// <returns>Value in the element.</returns>
    335         private static int GetDecimalValue(XElement element)
    336         {
    337             // Reads a child that looks like this:
    338             // <decimal value="X" />
    339             return int.Parse(element.Element(XmlNames.Decimal).Attribute(XmlNames.Attributes.Value).Value);
    340         }
    341 
    342         /// <summary>
    343         /// Gets the registry key backing this policy.
    344         /// </summary>
    345         /// <remarks>
    346         /// This assumes that all the policies are machine-wide.
    347         /// If this changes, we will need to parse the class="machine|user" attribute.
    348         /// </remarks>
    349         private RegistryKey GetKey()
    350         {
    351             return Registry.LocalMachine.CreateSubKey(this.KeyPath);
    352         }
    353 
    354         /// <summary>
    355         /// A group policy source object as used by AdditionalSources and AllowedSources.
    356         /// </summary>
    357         public class GroupPolicySource
    358         {
    359             /// <summary>
    360             /// Gets or sets the source name.
    361             /// </summary>
    362             public string Name { get; set; }
    363 
    364             /// <summary>
    365             /// Gets or sets the source arg.
    366             /// </summary>
    367             public string Arg { get; set; }
    368 
    369             /// <summary>
    370             /// Gets or sets the source type.
    371             /// </summary>
    372             public string Type { get; set; }
    373 
    374             /// <summary>
    375             /// Gets or sets the source data.
    376             /// </summary>
    377             public string Data { get; set; }
    378 
    379             /// <summary>
    380             /// Gets or sets the source identifier.
    381             /// </summary>
    382             public string Identifier { get; set; }
    383 
    384             /// <summary>
    385             /// Gets or sets certificate pinning.
    386             /// </summary>
    387             public GroupPolicyCertificatePinning CertificatePinning { get; set; }
    388 
    389             /// <summary>
    390             /// Gets or sets the source trust levels.
    391             /// </summary>
    392             public string[] TrustLevel { get; set; }
    393 
    394             /// <summary>
    395             /// Gets or sets a value indicating whether the source is explicit.
    396             /// </summary>
    397             public bool Explicit { get; set; }
    398         }
    399 
    400         /// <summary>
    401         /// Group policy certificate pinning.
    402         /// </summary>
    403         public class GroupPolicyCertificatePinning
    404         {
    405             /// <summary>
    406             /// Gets or sets the cert pinning chains.
    407             /// </summary>
    408             public GroupPolicyCertificatePinningChain[] Chains { get; set; }
    409         }
    410 
    411         /// <summary>
    412         /// Group policy certificate pinning chain.
    413         /// </summary>
    414         public class GroupPolicyCertificatePinningChain
    415         {
    416             /// <summary>
    417             /// Gets or sets the cert pinning details.
    418             /// </summary>
    419             public GroupPolicyCertificatePinningDetails[] Chain { get; set; }
    420         }
    421 
    422         /// <summary>
    423         /// Group policy certificate pinning details.
    424         /// </summary>
    425         public class GroupPolicyCertificatePinningDetails
    426         {
    427             /// <summary>
    428             /// Gets or sets the validation.
    429             /// </summary>
    430             public string[] Validation { get; set; }
    431 
    432             /// <summary>
    433             /// Gets or sets the embedded cert.
    434             /// </summary>
    435             public string EmbeddedCertificate { get; set; }
    436         }
    437 
    438         /// <summary>
    439         /// Names of the XML elements and attributes that make up the definition file.
    440         /// </summary>
    441         private static class XmlNames
    442         {
    443             // Root element
    444             public static readonly XName PolicyDefinitions = XName.Get("policyDefinitions", Namespace);
    445 
    446             public static readonly XName Policies = XName.Get("policies", Namespace);
    447             public static readonly XName Policy = XName.Get("policy", Namespace);
    448 
    449             public static readonly XName EnabledValue = XName.Get("enabledValue", Namespace);
    450             public static readonly XName DisabledValue = XName.Get("disabledValue", Namespace);
    451             public static readonly XName Elements = XName.Get("elements", Namespace);
    452 
    453             public static readonly XName Decimal = XName.Get("decimal", Namespace);
    454             public static readonly XName List = XName.Get("list", Namespace);
    455 
    456             private const string Namespace = "http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions";
    457 
    458             public static class Attributes
    459             {
    460                 public const string Name = "name";
    461                 public const string Value = "value";
    462                 public const string Id = "id";
    463                 public const string Key = "key";
    464                 public const string ValueName = "valueName";
    465             }
    466         }
    467     }
    468 }