commit 3d60cece22103f3e9bfff1ca67afd5d4a7009421 parent 18df795e134356b75a004e8f86b3d6ab432c2a94 Author: Ryan <69221034+ryfu-msft@users.noreply.github.com> Date: Wed, 11 Oct 2023 16:51:52 -0400 Invoke ShellExecute on dism.exe for enabling Windows Features (#3659) Diffstat:
14 files changed, 236 insertions(+), 678 deletions(-)
diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt @@ -129,6 +129,7 @@ deserializing dest devblogs differentpath +DISMAPI DIRECTONLY distro dll diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h @@ -157,6 +157,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(DownloadDirectoryArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(Downloading); WINGET_DEFINE_RESOURCE_STRINGID(EnableAdminSettingFailed); + WINGET_DEFINE_RESOURCE_STRINGID(EnableWindowsFeaturesSuccess); WINGET_DEFINE_RESOURCE_STRINGID(EnablingWindowsFeature); WINGET_DEFINE_RESOURCE_STRINGID(ErrorCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(ErrorCommandShortDescription); @@ -226,7 +227,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(IncludeUnknownArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(IncludeUnknownInListArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(IncompatibleArgumentsProvided); - WINGET_DEFINE_RESOURCE_STRINGID(InstallationAbandoned); + WINGET_DEFINE_RESOURCE_STRINGID(InstallAbandoned); WINGET_DEFINE_RESOURCE_STRINGID(InstallationDisclaimer1); WINGET_DEFINE_RESOURCE_STRINGID(InstallationDisclaimer2); WINGET_DEFINE_RESOURCE_STRINGID(InstallationDisclaimerMSStore); diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -6,12 +6,11 @@ #include "ManifestComparator.h" #include "InstallFlow.h" #include "winget\DependenciesGraph.h" -#include "winget\WindowsFeature.h" #include "DependencyNodeProcessor.h" +#include "ShellExecuteInstallerHandler.h" using namespace AppInstaller::Repository; using namespace AppInstaller::Manifest; -using namespace AppInstaller::WindowsFeature; namespace AppInstaller::CLI::Workflow { @@ -136,82 +135,91 @@ namespace AppInstaller::CLI::Workflow const auto& rootDependencies = context.Get<Execution::Data::Installer>()->Dependencies; - if (rootDependencies.Empty()) + if (rootDependencies.Empty() || !rootDependencies.HasAnyOf(DependencyType::WindowsFeature)) { return; } - if (rootDependencies.HasAnyOf(DependencyType::WindowsFeature)) + context << Workflow::EnsureRunningAsAdmin; + + if (context.IsTerminated()) { - context << Workflow::EnsureRunningAsAdmin; - if (context.IsTerminated()) + return; + } + + bool isCancelled = false; + bool enableFeaturesFailed = false; + bool rebootRequired = false; + bool force = context.Args.Contains(Execution::Args::Type::Force); + + rootDependencies.ApplyToType(DependencyType::WindowsFeature, [&context, &isCancelled, &enableFeaturesFailed, &force, &rebootRequired](Dependency dependency) { - return; - } + if (enableFeaturesFailed && !force || isCancelled) + { + return; + } - HRESULT hr = S_OK; - std::shared_ptr<DismHelper> dismHelper = std::make_shared<DismHelper>(); + auto featureName = dependency.Id(); - bool force = context.Args.Contains(Execution::Args::Type::Force); - bool rebootRequired = false; + auto featureContextPtr = context.CreateSubContext(); + Execution::Context& featureContext = *featureContextPtr; + auto previousThreadGlobals = featureContext.SetForCurrentThread(); - rootDependencies.ApplyToType(DependencyType::WindowsFeature, [&context, &hr, &dismHelper, &force, &rebootRequired](Dependency dependency) + featureContext << Workflow::ShellExecuteEnableWindowsFeature(featureName); + + if (featureContext.IsTerminated()) { - if (SUCCEEDED(hr) || force) - { - auto featureName = dependency.Id(); - AICLI_LOG(Core, Verbose, << "Processing Windows Feature dependency [" << featureName << "]"); - WindowsFeature::WindowsFeature windowsFeature = dismHelper->GetWindowsFeature(featureName); + isCancelled = true; + return; + } - if (windowsFeature.DoesExist()) - { - if (!windowsFeature.IsEnabled()) - { - Utility::LocIndString featureDisplayName = windowsFeature.GetDisplayName(); - Utility::LocIndView locIndFeatureName{ featureName }; - - context.Reporter.Info() << Resource::String::EnablingWindowsFeature(featureDisplayName, locIndFeatureName) << std::endl; - - AICLI_LOG(Core, Info, << "Enabling Windows Feature [" << featureName << "] returned with HRESULT: " << hr); - auto enableFeatureFunction = [&](IProgressCallback& progress)->HRESULT { return windowsFeature.Enable(progress); }; - hr = context.Reporter.ExecuteWithProgress(enableFeatureFunction, true); - - if (FAILED(hr)) - { - AICLI_LOG(Core, Error, << "Failed to enable Windows Feature " << featureDisplayName << " [" << locIndFeatureName << "] with exit code: " << hr); - context.Reporter.Warn() << Resource::String::FailedToEnableWindowsFeature(featureDisplayName, locIndFeatureName) << std::endl - << GetUserPresentableMessage(hr) << std::endl; - } - - if (hr == ERROR_SUCCESS_REBOOT_REQUIRED || windowsFeature.GetRestartRequiredStatus() == DismRestartType::DismRestartRequired) - { - rebootRequired = true; - } - } - } - else - { - // Note: If a feature is not found, continue enabling the rest of the dependencies but block immediately after unless force arg is present. - AICLI_LOG(Core, Info, << "Windows Feature [" << featureName << "] does not exist"); - hr = APPINSTALLER_CLI_ERROR_INSTALL_MISSING_DEPENDENCY; - context.Reporter.Warn() << Resource::String::WindowsFeatureNotFound(Utility::LocIndView{ featureName }) << std::endl; - } - } - }); + Utility::LocIndView locIndFeatureName{ featureName }; + DWORD result = featureContext.Get<Execution::Data::OperationReturnCode>(); - if (FAILED(hr)) - { - if (force) + if (result == ERROR_SUCCESS) + { + AICLI_LOG(Core, Info, << "Successfully enabled [" << featureName << "]"); + } + else if (result == 0x800f080c) // DISMAPI_E_UNKNOWN_FEATURE + { + AICLI_LOG(Core, Warning, << "Windows Feature [" << featureName << "] does not exist"); + enableFeaturesFailed = true; + featureContext.Reporter.Warn() << Resource::String::WindowsFeatureNotFound(locIndFeatureName) << std::endl; + } + else if (result == ERROR_SUCCESS_REBOOT_REQUIRED) { - context.Reporter.Warn() << Resource::String::FailedToEnableWindowsFeatureOverridden << std::endl; + AICLI_LOG(Core, Info, << "Reboot required for [" << featureName << "]"); + rebootRequired = true; } else { - context.Reporter.Error() << Resource::String::FailedToEnableWindowsFeatureOverrideRequired << std::endl; - AICLI_TERMINATE_CONTEXT(hr); + AICLI_LOG(Core, Error, << "Failed to enable Windows Feature [" << featureName << "] with exit code: " << result); + enableFeaturesFailed = true; + featureContext.Reporter.Warn() << Resource::String::FailedToEnableWindowsFeature(locIndFeatureName, result) << std::endl; } + }); + + if (isCancelled) + { + context.Reporter.Warn() << Resource::String::InstallAbandoned << std::endl; + AICLI_TERMINATE_CONTEXT(E_ABORT); + } + + if (enableFeaturesFailed) + { + if (force) + { + context.Reporter.Warn() << Resource::String::FailedToEnableWindowsFeatureOverridden << std::endl; } - else if (rebootRequired) + else + { + context.Reporter.Error() << Resource::String::FailedToEnableWindowsFeatureOverrideRequired << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES); + } + } + else + { + if (rebootRequired) { if (force) { @@ -223,6 +231,10 @@ namespace AppInstaller::CLI::Workflow AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_TO_INSTALL); } } + else + { + context.Reporter.Info() << Resource::String::EnableWindowsFeaturesSuccess << std::endl; + } } } diff --git a/src/AppInstallerCLICore/Workflows/MsiInstallFlow.cpp b/src/AppInstallerCLICore/Workflows/MsiInstallFlow.cpp @@ -52,7 +52,7 @@ namespace AppInstaller::CLI::Workflow if (!installResult) { - context.Reporter.Warn() << Resource::String::InstallationAbandoned << std::endl; + context.Reporter.Warn() << Resource::String::InstallAbandoned << std::endl; AICLI_TERMINATE_CONTEXT(E_ABORT); } else diff --git a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp @@ -4,6 +4,7 @@ #include "ShellExecuteInstallerHandler.h" #include <AppInstallerFileLogger.h> #include <AppInstallerRuntime.h> +#include <winget/Filesystem.h> using namespace AppInstaller::CLI; using namespace AppInstaller::Utility; @@ -15,7 +16,7 @@ namespace AppInstaller::CLI::Workflow namespace { // ShellExecutes the given path. - std::optional<DWORD> InvokeShellExecuteEx(const std::filesystem::path& filePath, const std::string& args, bool useRunAs, IProgressCallback& progress) + std::optional<DWORD> InvokeShellExecuteEx(const std::filesystem::path& filePath, const std::string& args, bool useRunAs, int show, IProgressCallback& progress) { AICLI_LOG(CLI, Info, << "Starting: '" << filePath.u8string() << "' with arguments '" << args << '\''); @@ -25,9 +26,7 @@ namespace AppInstaller::CLI::Workflow execInfo.lpFile = filePath.c_str(); std::wstring argsUtf16 = Utility::ConvertToUTF16(args); execInfo.lpParameters = argsUtf16.c_str(); - // Some installers force UI. Setting to SW_HIDE will hide installer UI and installation will never complete. - // Verified setting to SW_SHOW does not hurt silent mode since no UI will be shown. - execInfo.nShow = SW_SHOW; + execInfo.nShow = show; // This installer must be run elevated, but we are not currently. // Have ShellExecute elevate the installer since it won't do so itself. @@ -68,7 +67,9 @@ namespace AppInstaller::CLI::Workflow std::optional<DWORD> InvokeShellExecute(const std::filesystem::path& filePath, const std::string& args, IProgressCallback& progress) { - return InvokeShellExecuteEx(filePath, args, false, progress); + // Some installers force UI. Setting to SW_HIDE will hide installer UI and installation will never complete. + // Verified setting to SW_SHOW does not hurt silent mode since no UI will be shown. + return InvokeShellExecuteEx(filePath, args, false, SW_SHOW, progress); } // Gets the escaped installer args. @@ -201,6 +202,45 @@ namespace AppInstaller::CLI::Workflow return args; } + + std::filesystem::path GetDismExecutablePath() + { + return AppInstaller::Filesystem::GetExpandedPath("%windir%\\system32\\dism.exe"); + } + + std::optional<DWORD> DoesWindowsFeatureExist(Execution::Context& context, std::string_view featureName) + { + std::string args = "/Online /Get-FeatureInfo /FeatureName:" + std::string{ featureName }; + auto dismExecPath = GetDismExecutablePath(); + + auto getFeatureInfoResult = context.Reporter.ExecuteWithProgress( + std::bind(InvokeShellExecuteEx, + dismExecPath, + args, + false, + SW_HIDE, + std::placeholders::_1)); + + return getFeatureInfoResult; + } + + std::optional<DWORD> EnableWindowsFeature(Execution::Context& context, std::string_view featureName) + { + std::string args = "/Online /Enable-Feature /NoRestart /FeatureName:" + std::string{ featureName }; + auto dismExecPath = GetDismExecutablePath(); + + AICLI_LOG(Core, Info, << "Enabling Windows Feature [" << featureName << "]"); + + auto enableFeatureResult = context.Reporter.ExecuteWithProgress( + std::bind(InvokeShellExecuteEx, + dismExecPath, + args, + false, + SW_HIDE, + std::placeholders::_1)); + + return enableFeatureResult; + } } void ShellExecuteInstallImpl(Execution::Context& context) @@ -221,16 +261,19 @@ namespace AppInstaller::CLI::Workflow context.Reporter.Warn() << Resource::String::InstallerElevationExpected << std::endl; } + // Some installers force UI. Setting to SW_HIDE will hide installer UI and installation will never complete. + // Verified setting to SW_SHOW does not hurt silent mode since no UI will be shown. auto installResult = context.Reporter.ExecuteWithProgress( std::bind(InvokeShellExecuteEx, context.Get<Execution::Data::InstallerPath>(), installerArgs, installer->ElevationRequirement == ElevationRequirementEnum::ElevationRequired && !isElevated, + SW_SHOW, std::placeholders::_1)); if (!installResult) { - context.Reporter.Warn() << Resource::String::InstallationAbandoned << std::endl; + context.Reporter.Warn() << Resource::String::InstallAbandoned << std::endl; AICLI_TERMINATE_CONTEXT(E_ABORT); } else @@ -307,8 +350,65 @@ namespace AppInstaller::CLI::Workflow else { context.Add<Execution::Data::OperationReturnCode>(uninstallResult.value()); - } } } + +#ifndef AICLI_DISABLE_TEST_HOOKS + std::optional<DWORD> s_EnableWindowsFeatureResult_Override{}; + + void TestHook_SetEnableWindowsFeatureResult_Override(std::optional<DWORD>&& result) + { + s_EnableWindowsFeatureResult_Override = std::move(result); + } + + std::optional<DWORD> s_DoesWindowsFeatureExistResult_Override{}; + + void TestHook_SetDoesWindowsFeatureExistResult_Override(std::optional<DWORD>&& result) + { + s_DoesWindowsFeatureExistResult_Override = std::move(result); + } +#endif + + void ShellExecuteEnableWindowsFeature::operator()(Execution::Context& context) const + { + Utility::LocIndView locIndFeatureName{ m_featureName }; + +#ifndef AICLI_DISABLE_TEST_HOOKS + auto doesFeatureExistResult = s_DoesWindowsFeatureExistResult_Override.has_value() ? + s_DoesWindowsFeatureExistResult_Override.value() : + DoesWindowsFeatureExist(context, m_featureName); +#else + auto doesFeatureExistResult = DoesWindowsFeatureExist(context, featureName); +#endif + + if (!doesFeatureExistResult) + { + AICLI_TERMINATE_CONTEXT(E_ABORT); + } + else if (doesFeatureExistResult.value() != ERROR_SUCCESS) + { + context.Add<Execution::Data::OperationReturnCode>(doesFeatureExistResult.value()); + return; + } + + context.Reporter.Info() << Resource::String::EnablingWindowsFeature(locIndFeatureName) << std::endl; + +#ifndef AICLI_DISABLE_TEST_HOOKS + auto enableFeatureResult = s_EnableWindowsFeatureResult_Override.has_value() ? + s_EnableWindowsFeatureResult_Override.value() : + EnableWindowsFeature(context, m_featureName); +#else + auto enableFeatureResult = EnableWindowsFeature(context, featureName); +#endif + + if (!enableFeatureResult) + { + AICLI_TERMINATE_CONTEXT(E_ABORT); + } + else + { + context.Add<Execution::Data::OperationReturnCode>(enableFeatureResult.value()); + } + } } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.h b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.h @@ -34,4 +34,18 @@ namespace AppInstaller::CLI::Workflow // Inputs: Manifest?, Installer, InstallerPath // Outputs: InstallerArgs void GetInstallerArgs(Execution::Context& context); + + // Enables the Windows Feature dependency by invoking ShellExecute on the DISM executable. + // Required Args: None + // Inputs: Windows Feature dependency + // Outputs: None + struct ShellExecuteEnableWindowsFeature : public WorkflowTask + { + ShellExecuteEnableWindowsFeature(std::string_view featureName) : WorkflowTask("ShellExecuteEnableWindowsFeature"), m_featureName(featureName) {} + + void operator()(Execution::Context& context) const override; + + private: + std::string_view m_featureName; + }; } \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/InstallCommand.cs b/src/AppInstallerCLIE2ETests/InstallCommand.cs @@ -629,25 +629,25 @@ namespace AppInstallerCLIE2ETests /// Test install a package with an invalid Windows Feature dependency. /// </summary> [Test] - [Ignore("Need change to implementation of Windows Feature dependencies.")] public void InstallWithWindowsFeatureDependency_FeatureNotFound() { var testDir = TestCommon.GetRandomTestDir(); var installResult = TestCommon.RunAICLICommand("install", $"AppInstallerTest.WindowsFeature -l {testDir}"); - Assert.AreEqual(Constants.ErrorCode.ERROR_INSTALL_MISSING_DEPENDENCY, installResult.ExitCode); + Assert.AreEqual(Constants.ErrorCode.ERROR_INSTALL_DEPENDENCIES, installResult.ExitCode); Assert.True(installResult.StdOut.Contains("The feature [invalidFeature] was not found.")); + Assert.True(installResult.StdOut.Contains("Failed to enable Windows Feature dependencies. To proceed with installation, use '--force'.")); } /// <summary> /// Test install a package with a Windows Feature dependency using the force argument. /// </summary> [Test] - [Ignore("Need change to implementation of Windows Feature dependencies.")] public void InstallWithWindowsFeatureDependency_Force() { var testDir = TestCommon.GetRandomTestDir(); var installResult = TestCommon.RunAICLICommand("install", $"AppInstallerTest.WindowsFeature --silent --force -l {testDir}"); Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode); + Assert.True(installResult.StdOut.Contains("Failed to enable Windows Feature dependencies; proceeding due to --force")); Assert.True(installResult.StdOut.Contains("Successfully installed")); Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(testDir)); } diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1838,13 +1838,13 @@ Please specify one of them using the --source option to proceed.</value> {Locked="--force"}</comment> </data> <data name="EnablingWindowsFeature" xml:space="preserve"> - <value>Enabling {0} [{1}]...</value> - <comment>{Locked="{0}","{1}"} Message displayed to the user regarding which Windows Feature is being enabled.</comment> + <value>Enabling [{0}]...</value> + <comment>{Locked="{0}"} Message displayed to the user regarding which Windows Feature is being enabled.</comment> </data> <data name="FailedToEnableWindowsFeature" xml:space="preserve"> - <value>Failed to enable {0} [{1}] feature.</value> - <comment>{Locked="{0}"} Windows Feature display name -{Locked="{1}"} Windows Feature name</comment> + <value>Failed to enable [{0}] feature: {1}</value> + <comment>{Locked="{0}","{1}"} An error when enabling a Windows Feature. {0} is a placeholder for the name of the Windows Feature. +{1} is a placeholder for the unrecognized error code.</comment> </data> <data name="RebootRequiredToEnableWindowsFeatureOverridden" xml:space="preserve"> <value>Reboot required to fully enable the Windows Feature(s); proceeding due to --force</value> @@ -2584,4 +2584,7 @@ Please specify one of them using the --source option to proceed.</value> <data name="Unavailable" xml:space="preserve"> <value>Unavailable</value> </data> + <data name="EnableWindowsFeaturesSuccess" xml:space="preserve"> + <value>Successfully enabled Windows Features dependencies</value> + </data> </root> \ No newline at end of file diff --git a/src/AppInstallerCLITests/TestHooks.h b/src/AppInstallerCLITests/TestHooks.h @@ -11,7 +11,6 @@ #include <AppInstallerRuntime.h> #include <winget/UserSettings.h> #include <winget/Filesystem.h> -#include <winget/WindowsFeature.h> #include <winget/IconExtraction.h> #ifdef AICLI_DISABLE_TEST_HOOKS @@ -62,14 +61,10 @@ namespace AppInstaller void TestHook_SetScanArchiveResult_Override(bool* status); } - namespace WindowsFeature + namespace CLI::Workflow { - void TestHook_MockDismHelper_Override(bool status); - void TestHook_SetEnableWindowsFeatureResult_Override(HRESULT* result); - void TestHook_SetIsWindowsFeatureEnabledResult_Override(bool* status); - void TestHook_SetDoesWindowsFeatureExistResult_Override(bool* status); - void TestHook_SetWindowsFeatureGetDisplayNameResult_Override(Utility::LocIndString* displayName); - void TestHook_SetWindowsFeatureGetRestartStatusResult_Override(AppInstaller::WindowsFeature::DismRestartType* restartType); + void TestHook_SetEnableWindowsFeatureResult_Override(std::optional<DWORD>&& result); + void TestHook_SetDoesWindowsFeatureExistResult_Override(std::optional<DWORD>&& result); } } @@ -120,112 +115,45 @@ namespace TestHook } }; - struct MockDismHelper_Override - { - MockDismHelper_Override() - { - AppInstaller::WindowsFeature::TestHook_MockDismHelper_Override(true); - } - - ~MockDismHelper_Override() - { - AppInstaller::WindowsFeature::TestHook_MockDismHelper_Override(false); - } - }; - - struct SetEnableWindowsFeatureResult_Override + struct SetExtractIconFromArpEntryResult_Override { - SetEnableWindowsFeatureResult_Override(HRESULT result) : m_result(result) + SetExtractIconFromArpEntryResult_Override(std::vector<AppInstaller::Repository::ExtractedIconInfo> extractedIcons) : m_extractedIcons(std::move(extractedIcons)) { - AppInstaller::WindowsFeature::TestHook_SetEnableWindowsFeatureResult_Override(&m_result); + AppInstaller::Repository::TestHook_SetExtractIconFromArpEntryResult_Override(&m_extractedIcons); } - ~SetEnableWindowsFeatureResult_Override() + ~SetExtractIconFromArpEntryResult_Override() { - AppInstaller::WindowsFeature::TestHook_SetEnableWindowsFeatureResult_Override(nullptr); + AppInstaller::Repository::TestHook_SetExtractIconFromArpEntryResult_Override(nullptr); } private: - HRESULT m_result; + std::vector<AppInstaller::Repository::ExtractedIconInfo> m_extractedIcons; }; - struct SetIsWindowsFeatureEnabledResult_Override + struct SetEnableWindowsFeatureResult_Override { - SetIsWindowsFeatureEnabledResult_Override(bool status) : m_status(status) + SetEnableWindowsFeatureResult_Override(DWORD result) { - AppInstaller::WindowsFeature::TestHook_SetIsWindowsFeatureEnabledResult_Override(&m_status); + AppInstaller::CLI::Workflow::TestHook_SetEnableWindowsFeatureResult_Override(result); } - ~SetIsWindowsFeatureEnabledResult_Override() + ~SetEnableWindowsFeatureResult_Override() { - AppInstaller::WindowsFeature::TestHook_SetIsWindowsFeatureEnabledResult_Override(nullptr); + AppInstaller::CLI::Workflow::TestHook_SetEnableWindowsFeatureResult_Override({}); } - - private: - bool m_status; }; struct SetDoesWindowsFeatureExistResult_Override { - SetDoesWindowsFeatureExistResult_Override(bool status) : m_status(status) + SetDoesWindowsFeatureExistResult_Override(DWORD result) { - AppInstaller::WindowsFeature::TestHook_SetDoesWindowsFeatureExistResult_Override(&m_status); + AppInstaller::CLI::Workflow::TestHook_SetDoesWindowsFeatureExistResult_Override(result); } ~SetDoesWindowsFeatureExistResult_Override() { - AppInstaller::WindowsFeature::TestHook_SetDoesWindowsFeatureExistResult_Override(nullptr); - } - - private: - bool m_status; - }; - - struct SetWindowsFeatureGetDisplayNameResult_Override - { - SetWindowsFeatureGetDisplayNameResult_Override(AppInstaller::Utility::LocIndString displayName) : m_displayName(displayName) - { - AppInstaller::WindowsFeature::TestHook_SetWindowsFeatureGetDisplayNameResult_Override(&m_displayName); - } - - ~SetWindowsFeatureGetDisplayNameResult_Override() - { - AppInstaller::WindowsFeature::TestHook_SetWindowsFeatureGetDisplayNameResult_Override(nullptr); - } - - private: - AppInstaller::Utility::LocIndString m_displayName; - }; - - struct SetWindowsFeatureGetRestartStatusResult_Override - { - SetWindowsFeatureGetRestartStatusResult_Override(AppInstaller::WindowsFeature::DismRestartType restartType) : m_restartType(restartType) - { - AppInstaller::WindowsFeature::TestHook_SetWindowsFeatureGetRestartStatusResult_Override(&m_restartType); - } - - ~SetWindowsFeatureGetRestartStatusResult_Override() - { - AppInstaller::WindowsFeature::TestHook_SetWindowsFeatureGetRestartStatusResult_Override(nullptr); - } - - private: - AppInstaller::WindowsFeature::DismRestartType m_restartType; - }; - - struct SetExtractIconFromArpEntryResult_Override - { - SetExtractIconFromArpEntryResult_Override(std::vector<AppInstaller::Repository::ExtractedIconInfo> extractedIcons) : m_extractedIcons(std::move(extractedIcons)) - { - AppInstaller::Repository::TestHook_SetExtractIconFromArpEntryResult_Override(&m_extractedIcons); + AppInstaller::CLI::Workflow::TestHook_SetDoesWindowsFeatureExistResult_Override({}); } - - ~SetExtractIconFromArpEntryResult_Override() - { - AppInstaller::Repository::TestHook_SetExtractIconFromArpEntryResult_Override(nullptr); - } - - private: - std::vector<AppInstaller::Repository::ExtractedIconInfo> m_extractedIcons; }; } \ No newline at end of file diff --git a/src/AppInstallerCLITests/WindowsFeature.cpp b/src/AppInstallerCLITests/WindowsFeature.cpp @@ -5,13 +5,11 @@ #include "WorkflowCommon.h" #include <AppInstallerRuntime.h> #include <Commands/InstallCommand.h> -#include <winget/WindowsFeature.h> #include "TestHooks.h" using namespace AppInstaller::CLI; using namespace AppInstaller::Settings; using namespace AppInstaller::Utility; -using namespace AppInstaller::WindowsFeature; using namespace TestCommon; TEST_CASE("InstallFlow_WindowsFeatureDoesNotExist", "[windowsFeature]") @@ -34,14 +32,11 @@ TEST_CASE("InstallFlow_WindowsFeatureDoesNotExist", "[windowsFeature]") context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_WindowsFeatures.yaml").GetPath().u8string()); - auto mockDismHelperOverride = TestHook::MockDismHelper_Override(); - auto doesFeatureExistOverride = TestHook::SetDoesWindowsFeatureExistResult_Override(false); - InstallCommand install({}); install.Execute(context); INFO(installOutput.str()); - REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_INSTALL_MISSING_DEPENDENCY); + REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES); REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::WindowsFeatureNotFound(LocIndView{ "testFeature1" })).get()) != std::string::npos); @@ -70,20 +65,14 @@ TEST_CASE("InstallFlow_FailedToEnableWindowsFeature", "[windowsFeature]") context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_WindowsFeatures.yaml").GetPath().u8string()); - // Override with arbitrary DISM api error (DISMAPI_E_DISMAPI_NOT_INITIALIZED) and make windows feature discoverable. - HRESULT dismErrorResult = 0xc0040001; - auto mockDismHelperOverride = TestHook::MockDismHelper_Override(); - auto doesFeatureExistOverride = TestHook::SetDoesWindowsFeatureExistResult_Override(true); - auto setIsFeatureEnabledOverride = TestHook::SetIsWindowsFeatureEnabledResult_Override(false); - auto setEnableFeatureOverride = TestHook::SetEnableWindowsFeatureResult_Override(dismErrorResult); - auto getDisplayNameOverride = TestHook::SetWindowsFeatureGetDisplayNameResult_Override(LocIndString{ "Test Windows Feature"_liv }); - auto getRestartStatusOverride = TestHook::SetWindowsFeatureGetRestartStatusResult_Override(DismRestartNo); + auto setDoesFeatureExistOverride = TestHook::SetDoesWindowsFeatureExistResult_Override(ERROR_SUCCESS); + auto setEnableFeatureOverride = TestHook::SetEnableWindowsFeatureResult_Override(0xc0040001); // DISMAPI_E_DISMAPI_NOT_INITIALIZED InstallCommand install({}); install.Execute(context); INFO(installOutput.str()); - REQUIRE(context.GetTerminationHR() == dismErrorResult); + REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES); REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::FailedToEnableWindowsFeatureOverrideRequired).get()) != std::string::npos); } @@ -101,15 +90,9 @@ TEST_CASE("InstallFlow_FailedToEnableWindowsFeature_Force", "[windowsFeature]") TestCommon::TestUserSettings testSettings; testSettings.Set<Setting::EFWindowsFeature>(true); - // Override with arbitrary DISM api error (DISMAPI_E_DISMAPI_NOT_INITIALIZED) and make windows feature discoverable. - HRESULT dismErrorResult = 0xc0040001; - LocIndString testFeatureDisplayName = LocIndString{ "Test Windows Feature"_liv }; - auto mockDismHelperOverride = TestHook::MockDismHelper_Override(); - auto setEnableFeatureOverride = TestHook::SetEnableWindowsFeatureResult_Override(dismErrorResult); - auto doesFeatureExistOverride = TestHook::SetDoesWindowsFeatureExistResult_Override(true); - auto setIsFeatureEnabledOverride = TestHook::SetIsWindowsFeatureEnabledResult_Override(false); - auto getDisplayNameOverride = TestHook::SetWindowsFeatureGetDisplayNameResult_Override(testFeatureDisplayName); - auto getRestartStatusOverride = TestHook::SetWindowsFeatureGetRestartStatusResult_Override(DismRestartNo); + auto doesFeatureExistOverride = TestHook::SetDoesWindowsFeatureExistResult_Override(ERROR_SUCCESS); + auto expectedErrorCode = 0xc0040001; // DISMAPI_E_DISMAPI_NOT_INITIALIZED + auto setEnableFeatureOverride = TestHook::SetEnableWindowsFeatureResult_Override(expectedErrorCode); std::ostringstream installOutput; TestContext context{ installOutput, std::cin }; @@ -126,8 +109,8 @@ TEST_CASE("InstallFlow_FailedToEnableWindowsFeature_Force", "[windowsFeature]") // Verify Installer is called and parameters are passed in. REQUIRE(context.GetTerminationHR() == ERROR_SUCCESS); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::FailedToEnableWindowsFeature(testFeatureDisplayName, LocIndView{ "testFeature1" })).get()) != std::string::npos); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::FailedToEnableWindowsFeature(testFeatureDisplayName, LocIndView{ "testFeature2" })).get()) != std::string::npos); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::FailedToEnableWindowsFeature(LocIndView{ "testFeature1" }, expectedErrorCode)).get()) != std::string::npos); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::FailedToEnableWindowsFeature(LocIndView{ "testFeature2" }, expectedErrorCode)).get()) != std::string::npos); REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::FailedToEnableWindowsFeatureOverridden).get()) != std::string::npos); REQUIRE(std::filesystem::exists(installResultPath.GetPath())); std::ifstream installResultFile(installResultPath.GetPath()); @@ -152,12 +135,8 @@ TEST_CASE("InstallFlow_RebootRequired", "[windowsFeature]") testSettings.Set<Setting::EFWindowsFeature>(true); // Override with reboot required HRESULT. - auto mockDismHelperOverride = TestHook::MockDismHelper_Override(); + auto doesFeatureExistOverride = TestHook::SetDoesWindowsFeatureExistResult_Override(ERROR_SUCCESS); auto setEnableFeatureOverride = TestHook::SetEnableWindowsFeatureResult_Override(ERROR_SUCCESS_REBOOT_REQUIRED); - auto setIsFeatureEnabledOverride = TestHook::SetIsWindowsFeatureEnabledResult_Override (false); - auto doesFeatureExistOverride = TestHook::SetDoesWindowsFeatureExistResult_Override(true); - auto getDisplayNameOverride = TestHook::SetWindowsFeatureGetDisplayNameResult_Override(LocIndString{ "Test Windows Feature"_liv }); - auto getRestartStatusOverride = TestHook::SetWindowsFeatureGetRestartStatusResult_Override(DismRestartRequired); std::ostringstream installOutput; TestContext context{ installOutput, std::cin }; @@ -189,12 +168,8 @@ TEST_CASE("InstallFlow_RebootRequired_Force", "[windowsFeature]") testSettings.Set<Setting::EFWindowsFeature>(true); // Override with reboot required HRESULT. - auto mockDismHelperOverride = TestHook::MockDismHelper_Override(); + auto doesFeatureExistOverride = TestHook::SetDoesWindowsFeatureExistResult_Override(ERROR_SUCCESS); auto setEnableFeatureOverride = TestHook::SetEnableWindowsFeatureResult_Override(ERROR_SUCCESS_REBOOT_REQUIRED); - auto setIsFeatureEnabledOverride = TestHook::SetIsWindowsFeatureEnabledResult_Override(false); - auto doesFeatureExistOverride = TestHook::SetDoesWindowsFeatureExistResult_Override(true); - auto getDisplayNameOverride = TestHook::SetWindowsFeatureGetDisplayNameResult_Override(LocIndString{ "Test Windows Feature"_liv }); - auto getRestartStatusOverride = TestHook::SetWindowsFeatureGetRestartStatusResult_Override(DismRestartRequired); std::ostringstream installOutput; TestContext context{ installOutput, std::cin }; diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -438,7 +438,6 @@ <ClInclude Include="Public\winget\ThreadGlobals.h" /> <ClInclude Include="Public\winget\TraceLogger.h" /> <ClInclude Include="Public\winget\UserSettings.h" /> - <ClInclude Include="Public\winget\WindowsFeature.h" /> <ClInclude Include="Telemetry\TraceLogging.h" /> </ItemGroup> <ItemGroup> @@ -503,7 +502,6 @@ <ClCompile Include="ThreadGlobals.cpp" /> <ClCompile Include="TraceLogger.cpp" /> <ClCompile Include="UserSettings.cpp" /> - <ClCompile Include="WindowsFeature.cpp" /> </ItemGroup> <ItemGroup> <None Include="packages.config" /> diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -174,9 +174,6 @@ <ClInclude Include="Public\winget\Pin.h"> <Filter>Public\winget</Filter> </ClInclude> - <ClInclude Include="Public\winget\WindowsFeature.h"> - <Filter>Public\winget</Filter> - </ClInclude> <ClInclude Include="Public\winget\SelfManagement.h"> <Filter>Public\winget</Filter> </ClInclude> @@ -320,9 +317,6 @@ <ClCompile Include="Pin.cpp"> <Filter>Source Files</Filter> </ClCompile> - <ClCompile Include="WindowsFeature.cpp"> - <Filter>Source Files</Filter> - </ClCompile> <ClCompile Include="SelfManagement.cpp"> <Filter>Source Files</Filter> </ClCompile> diff --git a/src/AppInstallerCommonCore/Public/winget/WindowsFeature.h b/src/AppInstallerCommonCore/Public/winget/WindowsFeature.h @@ -1,210 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include <AppInstallerProgress.h> -#include <winget/LocIndependent.h> - -namespace AppInstaller::WindowsFeature -{ - /****************************************************************************\ - - Declaration copied from DismApi.h to support enabling Windows Features. - - Copyright (c) Microsoft Corporation. - All rights reserved. - - \****************************************************************************/ - -#ifndef _DISMAPI_H_ -#define _DISMAPI_H_ - -#include <winapifamily.h> - -#pragma region Desktop Family or DISM Package -#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_PKG_DISM) - -#ifdef __cplusplus - extern "C" - { -#endif - - typedef UINT DismSession; - typedef void(CALLBACK* DISM_PROGRESS_CALLBACK)(_In_ UINT Current, _In_ UINT Total, _In_opt_ PVOID UserData); - -#define DISM_ONLINE_IMAGE L"DISM_{53BFAE52-B167-4E2F-A258-0A37B57FF845}" -#define DISM_SESSION_DEFAULT 0 - - typedef enum _DismLogLevel - { - DismLogErrors = 0, - DismLogErrorsWarnings, - DismLogErrorsWarningsInfo, - DismLogErrorsWarningsInfoDebug - } DismLogLevel; - - typedef enum _DismPackageIdentifier - { - DismPackageNone = 0, - DismPackageName, - DismPackagePath - } DismPackageIdentifier; - - typedef enum _DismPackageFeatureState - { - DismStateNotPresent = 0, - DismStateUninstallPending, - DismStateStaged, - DismStateResolved, // For internal use only - DismStateRemoved = DismStateResolved, - DismStateInstalled, - DismStateInstallPending, - DismStateSuperseded, - DismStatePartiallyInstalled - } DismPackageFeatureState; - - typedef enum _DismRestartType - { - DismRestartNo = 0, - DismRestartPossible, - DismRestartRequired - } DismRestartType; - -#pragma pack(push, 1) - - typedef struct _DismCustomProperty - { - PCWSTR Name; - PCWSTR Value; - PCWSTR Path; - } DismCustomProperty; - - typedef struct _DismFeature - { - PCWSTR FeatureName; - DismPackageFeatureState State; - } DismFeature; - - typedef struct _DismFeatureInfo - { - PCWSTR FeatureName; - DismPackageFeatureState FeatureState; - PCWSTR DisplayName; - PCWSTR Description; - DismRestartType RestartRequired; - DismCustomProperty* CustomProperty; - UINT CustomPropertyCount; - } DismFeatureInfo; - -#pragma pack(pop) - -#ifdef __cplusplus - } -#endif - -#endif /* WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_PKG_DISM) */ -#pragma endregion - -#endif // _DISMAPI_H_ - - using DismInitializePtr = HRESULT(WINAPI*)(int, PCWSTR, PCWSTR); - using DismOpenSessionPtr = HRESULT(WINAPI*)(PCWSTR, PCWSTR, PCWSTR, DismSession*); - using DismCloseSessionPtr = HRESULT(WINAPI*)(DismSession); - using DismShutdownPtr = HRESULT(WINAPI*)(); - using DismGetFeatureInfoPtr = HRESULT(WINAPI*)(UINT, PCWSTR, PCWSTR, DismPackageIdentifier, DismFeatureInfo**); - using DismEnableFeaturePtr = HRESULT(WINAPI*)(UINT, PCWSTR, PCWSTR, DismPackageIdentifier, BOOL, PCWSTR*, UINT, BOOL, HANDLE, DISM_PROGRESS_CALLBACK, PVOID); - using DismDisableFeaturePtr = HRESULT(WINAPI*)(UINT, PCWSTR, PCWSTR, BOOL, HANDLE, DISM_PROGRESS_CALLBACK, PVOID); - using DismDeletePtr = HRESULT(WINAPI*)(VOID*); - - // Forward declaration - struct DismHelper; - - /// <summary> - /// Struct representation of a single Windows Feature. - /// </summary> - struct WindowsFeature - { - friend DismHelper; - - ~WindowsFeature(); - - // TODO: Implement progress via DismProgressFunction - HRESULT Enable(IProgressCallback& progress); - HRESULT Disable(); - bool DoesExist(); - bool IsEnabled(); - Utility::LocIndString GetDisplayName(); - DismRestartType GetRestartRequiredStatus(); - - DismPackageFeatureState GetState() - { - return m_featureInfo->FeatureState; - } - - protected: - WindowsFeature(std::shared_ptr<DismHelper> dismHelper, const std::string& name); - - private: - void GetFeatureInfo(); - - std::string m_featureName; - std::shared_ptr<DismHelper> m_dismHelper; - DismFeatureInfo* m_featureInfo = nullptr; - }; - - struct DismHelper : public std::enable_shared_from_this<DismHelper> - { - DismHelper(); - ~DismHelper(); - - WindowsFeature GetWindowsFeature(const std::string& featureName) - { - return WindowsFeature(std::move(GetPtr()), featureName); - } - - HRESULT EnableFeature( - PCWSTR featureName, - PCWSTR identifier, - DismPackageIdentifier packageIdentifier, - BOOL limitAccess, - PCWSTR* sourcePaths, - UINT sourcePathCount, - BOOL enableAll, - HANDLE cancelEvent, - DISM_PROGRESS_CALLBACK progress, - PVOID userData); - - HRESULT DisableFeature(PCWSTR featureName, PCWSTR packageName, BOOL removePayload, HANDLE cancelEvent, DISM_PROGRESS_CALLBACK progress, PVOID userData); - - HRESULT GetFeatureInfo(PCWSTR featureName, PCWSTR identifier, DismPackageIdentifier packageIdentifier, DismFeatureInfo** featureInfo); - - HRESULT Delete(VOID* dismStructure); - - private: - typedef UINT DismSession; - - wil::unique_hmodule m_module; - DismSession m_dismSession = DISM_SESSION_DEFAULT; - - DismInitializePtr m_dismInitialize = nullptr; - DismOpenSessionPtr m_dismOpenSession = nullptr; - DismGetFeatureInfoPtr m_dismGetFeatureInfo = nullptr; - DismEnableFeaturePtr m_dismEnableFeature = nullptr; - DismDisableFeaturePtr m_dismDisableFeature = nullptr; - DismCloseSessionPtr m_dismCloseSession = nullptr; - DismShutdownPtr m_dismShutdown = nullptr; - DismDeletePtr m_dismDelete = nullptr; - - std::shared_ptr<DismHelper> GetPtr() - { - return shared_from_this(); - } - - void Initialize(); - void OpenSession(); - void CloseSession(); - void Shutdown(); - - template<typename FuncType> - FuncType GetProcAddressHelper(HMODULE module, LPCSTR functionName); - }; -}- \ No newline at end of file diff --git a/src/AppInstallerCommonCore/WindowsFeature.cpp b/src/AppInstallerCommonCore/WindowsFeature.cpp @@ -1,256 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "winget/WindowsFeature.h" -#include "Public/AppInstallerLogging.h" -#include "Public/AppInstallerStrings.h" - -namespace AppInstaller::WindowsFeature -{ -#ifndef AICLI_DISABLE_TEST_HOOKS - static bool s_MockDismHelper_Override = false; - - void TestHook_MockDismHelper_Override(bool status) - { - s_MockDismHelper_Override = status; - } -#endif - - DismHelper::DismHelper() - { -#ifndef AICLI_DISABLE_TEST_HOOKS - // The entire DismHelper class and its functions needs to be mocked since DismHost.exe inherits log file handles. - // Without this, the unit tests will fail to complete waiting for DismHost.exe to release the log file handles. - if (s_MockDismHelper_Override) - { - return; - } -#endif - - m_module.reset(LoadLibraryEx(L"dismapi.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32)); - - if (!m_module) - { - AICLI_LOG(Core, Error, << "Could not load dismapi.dll"); - THROW_LAST_ERROR(); - } - - m_dismInitialize = GetProcAddressHelper<DismInitializePtr>(m_module.get(), "DismInitialize"); - m_dismOpenSession = GetProcAddressHelper<DismOpenSessionPtr>(m_module.get(), "DismOpenSession"); - m_dismGetFeatureInfo = GetProcAddressHelper<DismGetFeatureInfoPtr>(m_module.get(), "DismGetFeatureInfo"); - m_dismEnableFeature = GetProcAddressHelper<DismEnableFeaturePtr>(m_module.get(), "DismEnableFeature"); - m_dismDisableFeature = GetProcAddressHelper<DismDisableFeaturePtr>(m_module.get(), "DismDisableFeature"); - m_dismDelete = GetProcAddressHelper<DismDeletePtr>(m_module.get(), "DismDelete"); - m_dismCloseSession = GetProcAddressHelper<DismCloseSessionPtr>(m_module.get(), "DismCloseSession"); - m_dismShutdown = GetProcAddressHelper<DismShutdownPtr>(m_module.get(), "DismShutdown"); - - Initialize(); - OpenSession(); - } - - DismHelper::~DismHelper() - { -#ifndef AICLI_DISABLE_TEST_HOOKS - if (s_MockDismHelper_Override) - { - return; - } -#endif - CloseSession(); - Shutdown(); - }; - - template<typename FuncType> - FuncType DismHelper::GetProcAddressHelper(HMODULE module, LPCSTR functionName) - { - FuncType result = reinterpret_cast<FuncType>(GetProcAddress(module, functionName)); - if (!result) - { - AICLI_LOG(Core, Error, << "Could not get proc address of " << functionName); - THROW_LAST_ERROR(); - } - return result; - } - - void DismHelper::Initialize() - { - LOG_IF_FAILED(m_dismInitialize(DismLogErrorsWarningsInfo, NULL, NULL)); - } - - void DismHelper::OpenSession() - { - LOG_IF_FAILED(m_dismOpenSession(DISM_ONLINE_IMAGE, NULL, NULL, &m_dismSession)); - } - - void DismHelper::CloseSession() - { - LOG_IF_FAILED(m_dismCloseSession(m_dismSession)); - } - - HRESULT DismHelper::EnableFeature( - PCWSTR featureName, - PCWSTR identifier, - DismPackageIdentifier packageIdentifier, - BOOL limitAccess, - PCWSTR* sourcePaths, - UINT sourcePathCount, - BOOL enableAll, - HANDLE cancelEvent, - DISM_PROGRESS_CALLBACK progress, - PVOID userData) - { - return m_dismEnableFeature(m_dismSession, featureName, identifier, packageIdentifier, limitAccess, sourcePaths, sourcePathCount, enableAll, cancelEvent, progress, userData); - } - - HRESULT DismHelper::DisableFeature(PCWSTR featureName, PCWSTR packageName, BOOL removePayload, HANDLE cancelEvent, DISM_PROGRESS_CALLBACK progress, PVOID userData) - { - return m_dismDisableFeature(m_dismSession, featureName, packageName, removePayload, cancelEvent, progress, userData); - } - - HRESULT DismHelper::GetFeatureInfo(PCWSTR featureName, PCWSTR identifier, DismPackageIdentifier packageIdentifier, DismFeatureInfo** featureInfo) - { - return m_dismGetFeatureInfo(m_dismSession, featureName, identifier, packageIdentifier, featureInfo); - } - - HRESULT DismHelper::Delete(VOID* dismStructure) - { - return m_dismDelete(dismStructure); - } - - void DismHelper::Shutdown() - { - LOG_IF_FAILED(m_dismShutdown()); - } - - WindowsFeature::WindowsFeature(std::shared_ptr<DismHelper> dismHelper, const std::string& name) - : m_dismHelper(dismHelper), m_featureName(name) - { -#ifndef AICLI_DISABLE_TEST_HOOKS - if (s_MockDismHelper_Override) - { - return; - } -#endif - GetFeatureInfo(); - } - - WindowsFeature::~WindowsFeature() - { - if (m_featureInfo) - { - LOG_IF_FAILED(m_dismHelper->Delete(m_featureInfo)); - } - } - -#ifndef AICLI_DISABLE_TEST_HOOKS - static HRESULT* s_EnableWindowsFeatureResult_TestHook_Override = nullptr; - - void TestHook_SetEnableWindowsFeatureResult_Override(HRESULT* result) - { - s_EnableWindowsFeatureResult_TestHook_Override = result; - } - - static bool* s_DoesWindowsFeatureExistResult_TestHook_Override = nullptr; - - void TestHook_SetDoesWindowsFeatureExistResult_Override(bool* result) - { - s_DoesWindowsFeatureExistResult_TestHook_Override = result; - } - - static bool* s_IsWindowsFeatureEnabledResult_TestHook_Override = nullptr; - - void TestHook_SetIsWindowsFeatureEnabledResult_Override(bool* status) - { - s_IsWindowsFeatureEnabledResult_TestHook_Override = status; - } - - static Utility::LocIndString* s_WindowsFeatureGetDisplayNameResult_TestHook_Override = nullptr; - - void TestHook_SetWindowsFeatureGetDisplayNameResult_Override(Utility::LocIndString* displayName) - { - s_WindowsFeatureGetDisplayNameResult_TestHook_Override = displayName; - } - - static DismRestartType* s_WindowsFeatureGetRestartRequiredStatusResult_TestHook_Override = nullptr; - - void TestHook_SetWindowsFeatureGetRestartStatusResult_Override(DismRestartType* restartType) - { - s_WindowsFeatureGetRestartRequiredStatusResult_TestHook_Override = restartType; - } -#endif - - HRESULT WindowsFeature::Enable(AppInstaller::IProgressCallback& progress) - { - UNREFERENCED_PARAMETER(progress); - -#ifndef AICLI_DISABLE_TEST_HOOKS - if (s_EnableWindowsFeatureResult_TestHook_Override) - { - return *s_EnableWindowsFeatureResult_TestHook_Override; - } -#endif - HRESULT hr = m_dismHelper->EnableFeature(Utility::ConvertToUTF16(m_featureName).c_str(), NULL, DismPackageNone, FALSE, NULL, NULL, FALSE, NULL, NULL, NULL); - LOG_IF_FAILED(hr); - return hr; - } - - HRESULT WindowsFeature::Disable() - { - HRESULT hr = m_dismHelper->DisableFeature(Utility::ConvertToUTF16(m_featureName).c_str(), NULL, FALSE, NULL, NULL, NULL); - LOG_IF_FAILED(hr); - return hr; - } - - bool WindowsFeature::DoesExist() - { -#ifndef AICLI_DISABLE_TEST_HOOKS - if (s_DoesWindowsFeatureExistResult_TestHook_Override) - { - return *s_DoesWindowsFeatureExistResult_TestHook_Override; - } -#endif - return m_featureInfo; - } - - bool WindowsFeature::IsEnabled() - { -#ifndef AICLI_DISABLE_TEST_HOOKS - if (s_IsWindowsFeatureEnabledResult_TestHook_Override) - { - return *s_IsWindowsFeatureEnabledResult_TestHook_Override; - } -#endif - // Refresh feature info state prior to retrieving state info. - GetFeatureInfo(); - DismPackageFeatureState featureState = GetState(); - AICLI_LOG(Core, Info, << "Feature state of " << m_featureName << " is " << featureState); - return featureState == DismStateInstalled; - } - - Utility::LocIndString WindowsFeature::GetDisplayName() - { -#ifndef AICLI_DISABLE_TEST_HOOKS - if (s_WindowsFeatureGetDisplayNameResult_TestHook_Override) - { - return *s_WindowsFeatureGetDisplayNameResult_TestHook_Override; - } -#endif - return Utility::LocIndString{ Utility::ConvertToUTF8(std::wstring{ m_featureInfo->DisplayName }) }; - } - - DismRestartType WindowsFeature::GetRestartRequiredStatus() - { -#ifndef AICLI_DISABLE_TEST_HOOKS - if (s_WindowsFeatureGetRestartRequiredStatusResult_TestHook_Override) - { - return *s_WindowsFeatureGetRestartRequiredStatusResult_TestHook_Override; - } -#endif - return m_featureInfo->RestartRequired; - } - - void WindowsFeature::GetFeatureInfo() - { - LOG_IF_FAILED(m_dismHelper->GetFeatureInfo(Utility::ConvertToUTF16(m_featureName).c_str(), NULL, DismPackageNone, &m_featureInfo)); - } -}- \ No newline at end of file