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 }