commit 51943ee788348553665b6131104c0da8f16ca518
parent 82f994d0692d4b9fbbcbc1913d83971f70d46414
Author: Luis Chacón <lechacon@users.noreply.github.com>
Date: Wed, 7 Apr 2021 11:24:27 -0700
Add Group Policy E2E tests (#854)
Diffstat:
7 files changed, 494 insertions(+), 20 deletions(-)
diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt
@@ -161,6 +161,7 @@ IBuffer
icu
IDisposable
IDX
+IEnumerable
ifdef
ifndef
ifstream
@@ -511,6 +512,8 @@ xamarin
xlang
xml
xmlns
+XElement
+XName
XResource
xsd
XTOKEN
diff --git a/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj b/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj
@@ -9,8 +9,8 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.Msix.Utils" Version="1.0.200812001" />
- <PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" />
+ <PackageReference Include="Microsoft.Msix.Utils" Version="2.0.3" />
+ <PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="nunit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
@@ -31,6 +31,12 @@
</ItemGroup>
<ItemGroup>
+ <Content Include="..\..\doc\admx\DesktopAppInstaller.admx" Link="TestData\DesktopAppInstaller.admx">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ </ItemGroup>
+
+ <ItemGroup>
<ProjectReference Include="..\IndexCreationTool\IndexCreationTool.csproj" />
</ItemGroup>
diff --git a/src/AppInstallerCLIE2ETests/Constants.cs b/src/AppInstallerCLIE2ETests/Constants.cs
@@ -108,19 +108,23 @@ namespace AppInstallerCLIE2ETests
public const int ERROR_LIBYAML_ERROR = unchecked((int)0x8A150027);
public const int ERROR_MANIFEST_VALIDATION_WARNING = unchecked((int)0x8A150028);
public const int ERROR_MANIFEST_VALIDATION_FAILURE = unchecked((int)0x8A150029);
- public const int APPINSTALLER_CLI_ERROR_INVALID_MANIFEST = unchecked((int)0x8A15002A);
- public const int APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE = unchecked((int)0x8A15002B);
- public const int APPINSTALLER_CLI_ERROR_UPDATE_ALL_HAS_FAILURE = unchecked((int)0x8A15002C);
- public const int APPINSTALLER_CLI_ERROR_INSTALLER_SECURITY_CHECK_FAILED = unchecked((int)0x8A15002D);
- public const int APPINSTALLER_CLI_ERROR_DOWNLOAD_SIZE_MISMATCH = unchecked((int)0x8A15002E);
- public const int APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND = unchecked((int)0x8a15002F);
- public const int APPINSTALLER_CLI_ERROR_EXEC_UNINSTALL_COMMAND_FAILED = unchecked((int)0x8a150030);
- public const int APPINSTALLER_CLI_ERROR_ICU_BREAK_ITERATOR_ERROR = unchecked((int)0x8A150031);
- public const int APPINSTALLER_CLI_ERROR_ICU_CASEMAP_ERROR = unchecked((int)0x8A150032);
- public const int APPINSTALLER_CLI_ERROR_ICU_REGEX_ERROR = unchecked((int)0x8A150033);
- public const int APPINSTALLER_CLI_ERROR_IMPORT_INSTALL_FAILED = unchecked((int)0x8a150034);
- public const int APPINSTALLER_CLI_ERROR_NOT_ALL_PACKAGES_FOUND = unchecked((int)0x8a150035);
- public const int APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE = unchecked((int)0x8a150036);
+ public const int ERROR_INVALID_MANIFEST = unchecked((int)0x8A15002A);
+ public const int ERROR_UPDATE_NOT_APPLICABLE = unchecked((int)0x8A15002B);
+ public const int ERROR_UPDATE_ALL_HAS_FAILURE = unchecked((int)0x8A15002C);
+ public const int ERROR_INSTALLER_SECURITY_CHECK_FAILED = unchecked((int)0x8A15002D);
+ public const int ERROR_DOWNLOAD_SIZE_MISMATCH = unchecked((int)0x8A15002E);
+ public const int ERROR_NO_UNINSTALL_INFO_FOUND = unchecked((int)0x8a15002F);
+ public const int ERROR_EXEC_UNINSTALL_COMMAND_FAILED = unchecked((int)0x8a150030);
+ public const int ERROR_ICU_BREAK_ITERATOR_ERROR = unchecked((int)0x8A150031);
+ public const int ERROR_ICU_CASEMAP_ERROR = unchecked((int)0x8A150032);
+ public const int ERROR_ICU_REGEX_ERROR = unchecked((int)0x8A150033);
+ public const int ERROR_IMPORT_INSTALL_FAILED = unchecked((int)0x8a150034);
+ public const int ERROR_NOT_ALL_PACKAGES_FOUND = unchecked((int)0x8a150035);
+ public const int ERROR_JSON_INVALID_FILE = unchecked((int)0x8a150036);
+ public const int ERROR_SOURCE_NOT_REMOTE = unchecked((int)0x8A150037);
+ public const int ERROR_UNSUPPORTED_RESTSOURCE = unchecked((int)0x8A150038);
+ public const int ERROR_RESTSOURCE_INVALID_DATA = unchecked((int)0x8A150039);
+ public const int ERROR_BLOCKED_BY_POLICY = unchecked((int)0x8a15003A);
}
}
}
diff --git a/src/AppInstallerCLIE2ETests/GroupPolicy.cs b/src/AppInstallerCLIE2ETests/GroupPolicy.cs
@@ -0,0 +1,153 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using NUnit.Framework;
+
+namespace AppInstallerCLIE2ETests
+{
+ /// <summary>
+ /// Tests for enforcement of Group Policy.
+ /// Behavior is better tested in the unit tests; these tests mostly ensure match between the code and the definition.
+ /// </summary>
+ public class GroupPolicy : BaseCommand
+ {
+ [SetUp]
+ public void Setup()
+ {
+ InitializeAllFeatures(false);
+ GroupPolicyHelper.DeleteExistingPolicies();
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ InitializeAllFeatures(false);
+ GroupPolicyHelper.DeleteExistingPolicies();
+ }
+
+ [Test]
+ public void PolicyEnableWinget()
+ {
+ GroupPolicyHelper.EnableWinget.Disable();
+ var result = TestCommon.RunAICLICommand("search", string.Empty);
+ Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode);
+ }
+
+ [Test]
+ public void EnableSettings()
+ {
+ GroupPolicyHelper.EnableSettings.Disable();
+ var result = TestCommon.RunAICLICommand("settings", string.Empty);
+ Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode);
+ }
+
+ [Test]
+ public void EnableExperimentalFeatures()
+ {
+ ConfigureFeature("experimentalCmd", true);
+ var result = TestCommon.RunAICLICommand("experimental", string.Empty);
+ Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode);
+
+ // An experimental feature disabled by Group Policy behaves the same as one that is not enabled.
+ // The expected result is a command line error as the argument validation rejects this.
+ GroupPolicyHelper.EnableExperimentalFeatures.Disable();
+ result = TestCommon.RunAICLICommand("experimental", string.Empty);
+ Assert.AreEqual(Constants.ErrorCode.ERROR_INVALID_CL_ARGUMENTS, result.ExitCode);
+ }
+
+ [Test]
+ public void EnableLocalManifests()
+ {
+ GroupPolicyHelper.EnableLocalManifests.Disable();
+ var result = TestCommon.RunAICLICommand("install", $"-m {TestCommon.GetTestDataFile(@"Manifests\TestExeInstaller.yaml")}");
+ Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode);
+ }
+
+ [Test]
+ public void EnableHashOverride()
+ {
+ GroupPolicyHelper.EnableHashOverride.Disable();
+ var result = TestCommon.RunAICLICommand("install", "AnyPackage --force");
+ Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode);
+ }
+
+ [Test]
+ public void EnableDefaultSource()
+ {
+ // Default sources are disabled during setup so they are missing.
+ var result = TestCommon.RunAICLICommand("source list", "winget");
+ Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode);
+
+ GroupPolicyHelper.EnableDefaultSource.Enable();
+ result = TestCommon.RunAICLICommand("source list", "winget");
+ Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode);
+ }
+
+ [Test]
+ public void EnableMicrosoftStoreSource()
+ {
+ // Default sources are disabled during setup so they are missing.
+ var result = TestCommon.RunAICLICommand("source list", "msstore");
+ Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode);
+
+ GroupPolicyHelper.EnableMicrosoftStoreSource.Enable();
+ result = TestCommon.RunAICLICommand("source list", "msstore");
+ Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode);
+ }
+
+ [Test]
+ public void EnableAdditionalSources()
+ {
+ // Remove the test source, then add it with policy.
+ TestCommon.RunAICLICommand("source remove", "TestSource");
+ var result = TestCommon.RunAICLICommand("source list", "TestSource");
+ Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode);
+
+ GroupPolicyHelper.EnableAdditionalSources.SetEnabledList(new string[]
+ {
+ "{\"Arg\":\"https://localhost:5001/TestKit\",\"Data\":\"WingetE2E.Tests_8wekyb3d8bbwe\",\"Identifier\":\"WingetE2E.Tests_8wekyb3d8bbwe\",\"Name\":\"TestSource\",\"Type\":\"Microsoft.PreIndexed.Package\"}",
+ });
+
+ result = TestCommon.RunAICLICommand("source list", "TestSource");
+ Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode);
+ }
+
+ [Test]
+ public void EnableAllowedSources()
+ {
+ // Try listing the test source. We should only see it if it is allowed.
+ // With allowed sources disabled:
+ GroupPolicyHelper.EnableAllowedSources.Disable();
+ var result = TestCommon.RunAICLICommand("source list", "TestSource");
+ Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode);
+
+ // With allowed sources enabled, but not listing the test source:
+ GroupPolicyHelper.EnableAdditionalSources.SetEnabledList(new string[]
+ {
+ "{\"Arg\":\"An argument\",\"Data\":\"Some data\",\"Identifier\":\"Test id\",\"Name\":\"NotTestSource\",\"Type\":\"Microsoft.PreIndexed.Package\"}",
+ });
+
+ result = TestCommon.RunAICLICommand("source list", "TestSource");
+ Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode);
+
+ // With the test source allowed:
+ GroupPolicyHelper.EnableAdditionalSources.SetEnabledList(new string[]
+ {
+ "{\"Arg\":\"https://localhost:5001/TestKit\",\"Data\":\"WingetE2E.Tests_8wekyb3d8bbwe\",\"Identifier\":\"WingetE2E.Tests_8wekyb3d8bbwe\",\"Name\":\"TestSource\",\"Type\":\"Microsoft.PreIndexed.Package\"}",
+ });
+
+ result = TestCommon.RunAICLICommand("source list", "TestSource");
+ Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode);
+ }
+
+ [Test]
+ public void SourceAutoUpdateInterval()
+ {
+ // Test this policy by inspecting the result of --info
+ GroupPolicyHelper.SourceAutoUpdateInterval.SetEnabledValue(123);
+ var result = TestCommon.RunAICLICommand(string.Empty, "--info");
+ Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode);
+ Assert.IsTrue(result.StdOut.Contains("Source Auto Update Interval In Minutes 123"));
+ }
+ }
+}
diff --git a/src/AppInstallerCLIE2ETests/GroupPolicyHelper.cs b/src/AppInstallerCLIE2ETests/GroupPolicyHelper.cs
@@ -0,0 +1,302 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Xml.Linq;
+using Microsoft.Win32;
+
+namespace AppInstallerCLIE2ETests
+{
+ /// <summary>
+ /// Helper for setting Group Policy settings.
+ /// This helper reads the keys and values to use directly from the ADMX file to ensure that the names
+ /// used by the source code are correct.
+ /// </summary>
+ /// <remarks>
+ /// This helper modifies the policies for winget configured in the machine.
+ /// </remarks>
+ public class GroupPolicyHelper
+ {
+ private const string PoliciesDefinitionFileName = "DesktopAppInstaller.admx";
+ private static Lazy<XElement> policyDefinitions = new Lazy<XElement>(() =>
+ {
+ string filePath = TestCommon.GetTestDataFile(PoliciesDefinitionFileName);
+ string fileText = File.ReadAllText(filePath);
+ return XElement.Parse(fileText);
+ });
+
+ /// <summary>
+ /// Names of the XML elements and attributes that make up the definition file.
+ /// </summary>
+ private static class XmlNames
+ {
+ private const string Namespace = "http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions";
+
+ // Root element
+ public static readonly XName PolicyDefinitions = XName.Get("policyDefinitions", Namespace);
+
+ public static readonly XName Policies = XName.Get("policies", Namespace);
+ public static readonly XName Policy = XName.Get("policy", Namespace);
+
+ public static readonly XName EnabledValue = XName.Get("enabledValue", Namespace);
+ public static readonly XName DisabledValue = XName.Get("disabledValue", Namespace);
+ public static readonly XName Elements = XName.Get("elements", Namespace);
+
+ public static readonly XName Decimal = XName.Get("decimal", Namespace);
+ public static readonly XName List = XName.Get("list", Namespace);
+
+ public static class Attributes
+ {
+ public const string Name = "name";
+ public const string Value = "value";
+ public const string Id = "id";
+ public const string Key = "key";
+ public const string ValueName = "valueName";
+ }
+ }
+
+ /// <summary>
+ /// Name of the policy. Used to identify it in the file.
+ /// </summary>
+ private string name;
+
+ /// <summary>
+ /// ID of the value element of this policy (if it has one).
+ /// This assumes that each policy has a single value element.
+ /// </summary>
+ private string elementId;
+
+ // Policies available.
+ public static GroupPolicyHelper EnableWinget = new GroupPolicyHelper("EnableAppInstaller");
+ public static GroupPolicyHelper EnableSettings = new GroupPolicyHelper("EnableSettings");
+ public static GroupPolicyHelper EnableExperimentalFeatures = new GroupPolicyHelper("EnableExperimentalFeatures");
+ public static GroupPolicyHelper EnableLocalManifests = new GroupPolicyHelper("EnableLocalManifestFiles");
+ public static GroupPolicyHelper EnableHashOverride = new GroupPolicyHelper("EnableHashOverride");
+ public static GroupPolicyHelper EnableDefaultSource = new GroupPolicyHelper("EnableDefaultSource");
+ public static GroupPolicyHelper EnableMicrosoftStoreSource = new GroupPolicyHelper("EnableMicrosoftStoreSource");
+ public static GroupPolicyHelper EnableAdditionalSources = new GroupPolicyHelper("EnableAdditionalSources", "AdditionalSources");
+ public static GroupPolicyHelper EnableAllowedSources = new GroupPolicyHelper("EnableAllowedSources", "AllowedSources");
+ public static GroupPolicyHelper SourceAutoUpdateInterval = new GroupPolicyHelper("SourceAutoUpdateIntervalInMinutes", "SourceAutoUpdateIntervalInMinutes");
+
+ private static GroupPolicyHelper[] AllPolicies = new GroupPolicyHelper[]
+ {
+ EnableWinget,
+ EnableSettings,
+ EnableExperimentalFeatures,
+ EnableLocalManifests,
+ EnableHashOverride,
+ EnableDefaultSource,
+ EnableMicrosoftStoreSource,
+ EnableAdditionalSources,
+ EnableAllowedSources,
+ SourceAutoUpdateInterval,
+ };
+
+ private GroupPolicyHelper(string name)
+ {
+ this.name = name;
+ }
+
+ private GroupPolicyHelper(string name, string elementId)
+ {
+ this.name = name;
+ this.elementId = elementId;
+ }
+
+ /// <summary>
+ /// Gets the content of the ADMX file as an XML.
+ /// </summary>
+ private static XElement PolicyDefinitions => policyDefinitions.Value;
+
+ /// <summary>
+ /// Gets the XML element that defines this policy.
+ /// </summary>
+ // The XML structure is like this:
+ // <policyDefinitions ...>
+ // ...
+ // <policies>
+ // <policy name="..." ... />
+ // </policies>
+ // </policyDefinitions>
+ private XElement PolicyElement => PolicyDefinitions
+ .Element(XmlNames.Policies)
+ .Elements(XmlNames.Policy)
+ .First(policy => policy.Attribute(XmlNames.Attributes.Name).Value == this.name);
+
+ /// <summary>
+ /// Gets the path to the registry key that backs this policy.
+ /// </summary>
+ private string KeyPath => this.PolicyElement.Attribute(XmlNames.Attributes.Key).Value;
+
+ /// <summary>
+ /// Gets the name of the registry value that backs this policy.
+ /// </summary>
+ private string ValueName => this.PolicyElement.Attribute(XmlNames.Attributes.ValueName)?.Value;
+
+ /// <summary>
+ /// Gets the XElement that defines the single value element of this policy.
+ /// This only works if the policy has a single element for its value.
+ /// </summary>
+ // Looks for something like this:
+ // <policy>
+ // <elements>
+ // <something id="..." />
+ // </elements>
+ // </policy>
+ // We use only list and decimal elements.
+ private XElement ValueElement => this.PolicyElement
+ .Element(XmlNames.Elements)
+ .Elements()
+ .First(element => element.Attribute(XmlNames.Attributes.Id).Value == this.elementId);
+
+ /// <summary>
+ /// Deletes all of the existing policies from the registry.
+ /// </summary>
+ public static void DeleteExistingPolicies()
+ {
+ foreach (var policy in AllPolicies)
+ {
+ policy.SetNotConfigured();
+ }
+ }
+
+ /// <summary>
+ /// Sets the policy to the Enabled state.
+ /// This will fail if the policy's EnabledValue does not exist or is not exactly as expected.
+ /// </summary>
+ public void Enable()
+ {
+ // The expected format is like this:
+ // <enabledValue>
+ // <decimal value="1" />
+ // </enabledValue>
+ // We expect the value to always be 1, but still parse it to catch errors in the ADMX.
+ int enabledValue = GetDecimalValue(this.PolicyElement.Element(XmlNames.EnabledValue));
+ using (RegistryKey key = this.GetKey())
+ {
+ key.SetValue(this.ValueName, enabledValue);
+ }
+ }
+
+ /// <summary>
+ /// Sets the policy to the Disabled state.
+ /// This will fail if the policy's DisabledValue does not exist or is not exactly as expected.
+ /// </summary>
+ public void Disable()
+ {
+ // The expected format is like this:
+ // <enabledValue>
+ // <decimal value="0" />
+ // </enabledValue>
+ // We expect the value to always be 0, but still parse it to catch errors in the ADMX.
+ int disabledValue = GetDecimalValue(this.PolicyElement.Element(XmlNames.DisabledValue));
+ using (RegistryKey key = this.GetKey())
+ {
+ key.SetValue(this.ValueName, disabledValue);
+ }
+ }
+
+ /// <summary>
+ /// Sets the policy to the Not Configured state.
+ /// This deletes the value associated with the policy, including its list if it has one.
+ /// </summary>
+ public void SetNotConfigured()
+ {
+ // Delete the enabled/disabled value
+ if (this.ValueName != null)
+ {
+ using (RegistryKey key = this.GetKey())
+ {
+ key.DeleteValue(this.ValueName, throwOnMissingValue: false);
+ }
+ }
+
+ // Delete the value element
+ if (this.elementId != null)
+ {
+ if (this.ValueElement.Name == XmlNames.List)
+ {
+ // Lists are stored in separate keys.
+ Registry.LocalMachine.DeleteSubKeyTree(this.ValueElement.Attribute(XmlNames.Attributes.Key).Value, throwOnMissingSubKey: false);
+ }
+ else if (this.ValueElement.Name == XmlNames.Decimal)
+ {
+ // Decimals are stored in single values
+ using (RegistryKey key = this.GetKey())
+ {
+ key.DeleteValue(this.ValueElement.Attribute(XmlNames.Attributes.ValueName).Value, throwOnMissingValue: false);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Sets the value of the policy when enabled.
+ /// This uses only the "elements" of the policy, not the "enabledValue".
+ /// The type used in the registry is chosen automatically.
+ /// </summary>
+ /// <param name="value">Value of the policy.</param>
+ public void SetEnabledValue(object value)
+ {
+ using (RegistryKey key = this.GetKey())
+ {
+ key.SetValue(
+ this.ValueElement.Attribute(XmlNames.Attributes.ValueName).Value,
+ value);
+ }
+ }
+
+ /// <summary>
+ /// Sets the list value of the policy when enabled.
+ /// This sets from the "elements" and also sets the enabled value as lists are also gated by a toggle.
+ /// This will fail if the value of the policy is not a list.
+ /// </summary>
+ /// <param name="values">Values to set in the list.</param>
+ public void SetEnabledList(IEnumerable<string> values)
+ {
+ this.Enable();
+
+ // Delete the existing list
+ string listKeyPath = this.ValueElement.Attribute(XmlNames.Attributes.Key).Value;
+ Registry.LocalMachine.DeleteSubKeyTree(listKeyPath, throwOnMissingSubKey: false);
+
+ // Create and fill the key.
+ // This assumes that the values don't need to have special names or prefixes.
+ var listKey = Registry.LocalMachine.CreateSubKey(listKeyPath);
+ int index = 0;
+ foreach (string value in values)
+ {
+ listKey.SetValue(index++.ToString(), value);
+ }
+
+ listKey.Close();
+ }
+
+ /// <summary>
+ /// Gets the value from a "decimal" child element.
+ /// </summary>
+ /// <param name="element">Element containing the decimal.</param>
+ /// <returns>Value in the element.</returns>
+ private static int GetDecimalValue(XElement element)
+ {
+ // Reads a child that looks like this:
+ // <decimal value="X" />
+ return int.Parse(element.Element(XmlNames.Decimal).Attribute(XmlNames.Attributes.Value).Value);
+ }
+
+ /// <summary>
+ /// Gets the registry key backing this policy.
+ /// </summary>
+ /// <remarks>
+ /// This assumes that all the policies are machine-wide.
+ /// If this changes, we will need to parse the class="machine|user" attribute.
+ /// </remarks>
+ private RegistryKey GetKey()
+ {
+ return Registry.LocalMachine.CreateSubKey(this.KeyPath);
+ }
+ }
+}
diff --git a/src/AppInstallerCLIE2ETests/ImportCommand.cs b/src/AppInstallerCLIE2ETests/ImportCommand.cs
@@ -38,7 +38,7 @@ namespace AppInstallerCLIE2ETests
{
// Verify failure when trying to import with an invalid file
var result = TestCommon.RunAICLICommand("import", GetTestImportFile("ImportFile-Bad-Invalid.json"));
- Assert.AreEqual(Constants.ErrorCode.APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, result.ExitCode);
+ Assert.AreEqual(Constants.ErrorCode.ERROR_JSON_INVALID_FILE, result.ExitCode);
Assert.True(result.StdOut.Contains("JSON file is not valid"));
}
@@ -56,7 +56,7 @@ namespace AppInstallerCLIE2ETests
{
// Verify failure when trying to import an unavailable package
var result = TestCommon.RunAICLICommand("import", GetTestImportFile("ImportFile-Bad-UnknownPackage.json"));
- Assert.AreEqual(Constants.ErrorCode.APPINSTALLER_CLI_ERROR_NOT_ALL_PACKAGES_FOUND, result.ExitCode);
+ Assert.AreEqual(Constants.ErrorCode.ERROR_NOT_ALL_PACKAGES_FOUND, result.ExitCode);
Assert.True(result.StdOut.Contains("Package not found for import"));
}
@@ -65,7 +65,7 @@ namespace AppInstallerCLIE2ETests
{
// Verify failure when trying to import an unavailable package
var result = TestCommon.RunAICLICommand("import", GetTestImportFile("ImportFile-Bad-UnknownPackageVersion.json"));
- Assert.AreEqual(Constants.ErrorCode.APPINSTALLER_CLI_ERROR_NOT_ALL_PACKAGES_FOUND, result.ExitCode);
+ Assert.AreEqual(Constants.ErrorCode.ERROR_NOT_ALL_PACKAGES_FOUND, result.ExitCode);
Assert.True(result.StdOut.Contains("Package not found for import"));
}
diff --git a/src/AppInstallerCommonCore/ExperimentalFeature.cpp b/src/AppInstallerCommonCore/ExperimentalFeature.cpp
@@ -21,8 +21,8 @@ namespace AppInstaller::Settings
if (!GroupPolicies().IsEnabled(TogglePolicy::Policy::ExperimentalFeatures))
{
AICLI_LOG(Core, Info, <<
- "Experimental feature " << ExperimentalFeature::GetFeature(feature).Name() <<
- " is disabled due to group policy" << TogglePolicy::GetPolicy(TogglePolicy::Policy::ExperimentalFeatures).RegValueName());
+ "Experimental feature '" << ExperimentalFeature::GetFeature(feature).Name() <<
+ "' is disabled due to group policy: " << TogglePolicy::GetPolicy(TogglePolicy::Policy::ExperimentalFeatures).RegValueName());
return false;
}
@@ -35,6 +35,12 @@ namespace AppInstaller::Settings
case ExperimentalFeature::Feature::ExperimentalArg:
return userSettings.Get<Setting::EFExperimentalArg>();
case ExperimentalFeature::Feature::ExperimentalMSStore:
+ if (GroupPolicies().GetState(TogglePolicy::Policy::MSStoreSource) == PolicyState::Enabled)
+ {
+ // Force enable the feature
+ return true;
+ }
+
return userSettings.Get<Setting::EFExperimentalMSStore>();
case ExperimentalFeature::Feature::ExperimentalList:
return userSettings.Get<Setting::EFList>();