Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add experimental feature support for enabling Windows Feature dependencies #3005

Merged
merged 31 commits into from
Mar 24, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 10 additions & 12 deletions src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,36 +158,34 @@ namespace AppInstaller::CLI::Workflow
}

HRESULT hr = S_OK;
DismHelper dismHelper = DismHelper();
std::shared_ptr<DismHelper> dismHelper = std::make_shared<DismHelper>();

bool force = context.Args.Contains(Execution::Args::Type::Force);
bool rebootRequired = false;

auto info = context.Reporter.Info();
auto warn = context.Reporter.Warn();

rootDependencies.ApplyToType(DependencyType::WindowsFeature, [&hr, &dismHelper, &info, &warn, &force, &rebootRequired](Dependency dependency)
rootDependencies.ApplyToType(DependencyType::WindowsFeature, [&context, &hr, &dismHelper, &force, &rebootRequired](Dependency dependency)
{
if (SUCCEEDED(hr) || force)
{
auto featureName = dependency.Id();
DismHelper::WindowsFeature windowsFeature = dismHelper.CreateWindowsFeature(featureName);
WindowsFeature::WindowsFeature windowsFeature{ dismHelper, featureName };

if (windowsFeature.DoesExist())
{
if (!windowsFeature.IsEnabled())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's a missing check for a pending reboot if the feature is already enabled?

Thinking of the case where a user attempts an install and gets a failure that a reboot is required. If they attempt to run the install command again without rebooting, the Windows Feature will be shown as enabled(?) and therefore won't trigger the rebootRequired, effectively negating the need to use --force by running the command twice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the condition so that a reboot required is no longer considered as enabled. That means if they run the install command again, the feature would show up as needing a reboot and wouldn't show up as being enabled and a reboot required message would be the shown requiring --force to proceed.

{
std::string featureDisplayName = AppInstaller::Utility::ConvertToUTF8(windowsFeature.GetDisplayName());
Utility::LocIndString featureDisplayName = windowsFeature.GetDisplayName();
Utility::LocIndView locIndFeatureName{ featureName };

info << Resource::String::EnablingWindowsFeature(Utility::LocIndView{ featureDisplayName }, locIndFeatureName) << std::endl;
context.Reporter.Info() << Resource::String::EnablingWindowsFeature(featureDisplayName, locIndFeatureName) << std::endl;

hr = windowsFeature.Enable();
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))
{
warn << Resource::String::FailedToEnableWindowsFeature(locIndFeatureName) << std::endl;
context.Reporter.Warn() << Resource::String::FailedToEnableWindowsFeature(locIndFeatureName, HRESULT_FROM_WIN32(hr)) << std::endl;
}

if (hr == ERROR_SUCCESS_REBOOT_REQUIRED || windowsFeature.GetRestartRequiredStatus() == DismRestartType::DismRestartRequired)
Expand All @@ -201,12 +199,12 @@ namespace AppInstaller::CLI::Workflow
// 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;
warn << Resource::String::WindowsFeatureNotFound(Utility::LocIndView{ featureName }) << std::endl;
context.Reporter.Warn() << Resource::String::WindowsFeatureNotFound(Utility::LocIndView{ featureName }) << std::endl;
}
}
});

if (FAILED(hr) || hr == APPINSTALLER_CLI_ERROR_INSTALL_MISSING_DEPENDENCY)
if (FAILED(hr))
{
if (force)
{
Expand Down
4 changes: 4 additions & 0 deletions src/AppInstallerCLICore/Workflows/DependenciesFlow.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,9 @@ namespace AppInstaller::CLI::Workflow
// Outputs: DependencySource
void OpenDependencySource(Execution::Context& context);

// Enables the Windows Feature dependencies.
// Required Args: None
// Inputs: Manifest, Installer
// Outputs: None
void EnableWindowsFeaturesDependencies(Execution::Context& context);
ryfu-msft marked this conversation as resolved.
Show resolved Hide resolved
}
3 changes: 2 additions & 1 deletion src/AppInstallerCLICore/Workflows/InstallFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -489,11 +489,13 @@ namespace AppInstaller::CLI::Workflow

void DownloadSinglePackage(Execution::Context& context)
{
// TODO: Split dependencies from download flow to prevent multiple installations.
context <<
Workflow::ReportIdentityAndInstallationDisclaimer <<
Workflow::ShowPromptsForSinglePackage(/* ensureAcceptance */ true) <<
Workflow::GetDependenciesFromInstaller <<
Workflow::ReportDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies) <<
Workflow::EnableWindowsFeaturesDependencies <<
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole thing doesn't seem like the right way to handle dependencies. At least, "download" is not consistent with "and also, handle getting all dependencies squared away". So at least maybe change the name of this task?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this out of "download" to the step right before InstallPackageInstaller.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In reality, this is a larger issue than your feature work, and I think it really needs to be addressed before we can enable dependencies on the whole. Neither of us considered that in retrospect.

You should move the workflow task back here (for now); and probably put it before package dependencies. I say that because one of the packages may actually be dependent on the Windows feature (although it really should have declared it). But maybe it is an optional dependency that affects the install time behavior. It is not likely the case that the Windows feature is dependent on any package though.

The issue is that multiple "downloads" are currently allowed via COM, but if dependencies are actually installed as part of "download", then multiple installs are allowed concurrently. So either we need to change the download/install phase split to download everything, then install everything, or we need to support going back and forth between download and install phases as we handle each dependency.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation, I didn't know that COM allowed for multiple downloads. I moved the task back to the suggested location and added a TODO comment explaining the issue that needs to be addressed.

Workflow::ManagePackageDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies) <<
Workflow::DownloadInstaller;
}
Expand All @@ -503,7 +505,6 @@ namespace AppInstaller::CLI::Workflow
context <<
Workflow::CheckForUnsupportedArgs <<
Workflow::DownloadSinglePackage <<
Workflow::EnableWindowsFeaturesDependencies <<
Workflow::InstallPackageInstaller;
}

Expand Down
9 changes: 5 additions & 4 deletions src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw
Original file line number Diff line number Diff line change
Expand Up @@ -1787,11 +1787,11 @@ Please specify one of them using the --source option to proceed.</value>
<comment>{Locked="{0}"} Error message displayed when a Windows feature was not found on the system.</comment>
</data>
<data name="FailedToEnableWindowsFeatureOverrideRequired" xml:space="preserve">
<value>Failed to enable Windows Feature dependencies. To proceed with installation, use --force</value>
<value>Failed to enable Windows Feature dependencies. To proceed with installation, use '--force'.</value>
<comment>{Locked="--force"}</comment>
</data>
<data name="RebootRequiredToEnableWindowsFeatureOverrideRequired" xml:space="preserve">
<value>Reboot required to fully enable the Windows Feature(s); to override this check use --force</value>
<value>Reboot required to fully enable the Windows Feature(s); to override this check use '--force'.</value>
<comment>"Windows Feature(s)" is the name of the options Windows features setting.</comment>
</data>
<data name="FailedToEnableWindowsFeatureOverridden" xml:space="preserve">
Expand All @@ -1804,8 +1804,9 @@ Please specify one of them using the --source option to proceed.</value>
<comment>{Locked="{0}","{1}"} 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}] feature.</value>
<comment>{Locked="{0}"} Error message displayed when a failure was encountered when enabling a Windows Feature.</comment>
<value>Failed to enable [{0}] feature with exit code: {1}</value>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<value>Failed to enable [{0}] feature with exit code: {1}</value>
<value>Failed to enable {0} [{1}] feature.
{2}: {3}</value>

I was looking for something with more information directly, showing the message as well. Example:

Failed to enable Windows Sandbox [windows-sandbox] feature.
0x80001234: The system error message for the HRESULT.

0 = Display Name
1 = Feature Name
2 = HRESULT
3 = HRESULT description

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to suggested, I used GetPresentableUserMessage to handle {2}: {3}

<comment>{Locked="{0}"} Error message displayed when a failure was encountered when enabling a Windows Feature.
{Locked="{1}} Failed exit code</comment>
</data>
<data name="RebootRequiredToEnableWindowsFeatureOverridden" xml:space="preserve">
<value>Reboot required to fully enable the Windows Feature(s); proceeding due to --force</value>
Expand Down
6 changes: 3 additions & 3 deletions src/AppInstallerCLITests/TestHooks.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ namespace AppInstaller
void TestHook_SetEnableWindowsFeatureResult_Override(HRESULT* result);
void TestHook_SetIsWindowsFeatureEnabledResult_Override(bool* status);
void TestHook_SetDoesWindowsFeatureExistResult_Override(bool* status);
void TestHook_SetWindowsFeatureGetDisplayNameResult_Override(std::wstring* displayName);
void TestHook_SetWindowsFeatureGetDisplayNameResult_Override(Utility::LocIndString* displayName);
void TestHook_SetWindowsFeatureGetRestartStatusResult_Override(AppInstaller::WindowsFeature::DismRestartType* restartType);
}
}
Expand Down Expand Up @@ -181,7 +181,7 @@ namespace TestHook

struct SetWindowsFeatureGetDisplayNameResult_Override
{
SetWindowsFeatureGetDisplayNameResult_Override(std::wstring displayName) : m_displayName(displayName)
SetWindowsFeatureGetDisplayNameResult_Override(AppInstaller::Utility::LocIndString displayName) : m_displayName(displayName)
{
AppInstaller::WindowsFeature::TestHook_SetWindowsFeatureGetDisplayNameResult_Override(&m_displayName);
}
Expand All @@ -192,7 +192,7 @@ namespace TestHook
}

private:
std::wstring m_displayName;
AppInstaller::Utility::LocIndString m_displayName;
};

struct SetWindowsFeatureGetRestartStatusResult_Override
Expand Down
17 changes: 7 additions & 10 deletions src/AppInstallerCLITests/WindowsFeature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ TEST_CASE("InstallFlow_WindowsFeatureDoesNotExist", "[windowsFeature]")
std::ostringstream installOutput;
TestContext context{ installOutput, std::cin };
auto previousThreadGlobals = context.SetForCurrentThread();
OverrideForShellExecute(context);
context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_WindowsFeatures.yaml").GetPath().u8string());

auto mockDismHelperOverride = TestHook::MockDismHelper_Override();
Expand Down Expand Up @@ -65,16 +64,15 @@ TEST_CASE("InstallFlow_FailedToEnableWindowsFeature", "[windowsFeature]")
std::ostringstream installOutput;
TestContext context{ installOutput, std::cin };
auto previousThreadGlobals = context.SetForCurrentThread();
OverrideForShellExecute(context);
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 setEnableFeatureOverride = TestHook::SetEnableWindowsFeatureResult_Override(dismErrorResult);
auto doesFeatureExistOverride = TestHook::SetDoesWindowsFeatureExistResult_Override(true);
auto setIsFeatureEnabledOverride = TestHook::SetIsWindowsFeatureEnabledResult_Override(false);
auto getDisplayNameOverride = TestHook::SetWindowsFeatureGetDisplayNameResult_Override(L"Test Windows Feature");
auto setEnableFeatureOverride = TestHook::SetEnableWindowsFeatureResult_Override(dismErrorResult);
auto getDisplayNameOverride = TestHook::SetWindowsFeatureGetDisplayNameResult_Override(LocIndString{ "Test Windows Feature"_liv });
auto getRestartStatusOverride = TestHook::SetWindowsFeatureGetRestartStatusResult_Override(DismRestartNo);

InstallCommand install({});
Expand Down Expand Up @@ -105,7 +103,7 @@ TEST_CASE("InstallFlow_FailedToEnableWindowsFeature_Force", "[windowsFeature]")
auto setEnableFeatureOverride = TestHook::SetEnableWindowsFeatureResult_Override(dismErrorResult);
auto doesFeatureExistOverride = TestHook::SetDoesWindowsFeatureExistResult_Override(true);
auto setIsFeatureEnabledOverride = TestHook::SetIsWindowsFeatureEnabledResult_Override(false);
auto getDisplayNameOverride = TestHook::SetWindowsFeatureGetDisplayNameResult_Override(L"Test Windows Feature");
auto getDisplayNameOverride = TestHook::SetWindowsFeatureGetDisplayNameResult_Override(LocIndString{ "Test Windows Feature"_liv });
auto getRestartStatusOverride = TestHook::SetWindowsFeatureGetRestartStatusResult_Override(DismRestartNo);

std::ostringstream installOutput;
Expand All @@ -121,8 +119,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(LocIndView{ "testFeature1" })).get()) != std::string::npos);
REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::FailedToEnableWindowsFeature(LocIndView{ "testFeature2" })).get()) != std::string::npos);
REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::FailedToEnableWindowsFeature(LocIndView{ "testFeature1" }, dismErrorResult)).get()) != std::string::npos);
REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::FailedToEnableWindowsFeature(LocIndView{ "testFeature2" }, dismErrorResult)).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());
Expand Down Expand Up @@ -151,13 +149,12 @@ TEST_CASE("InstallFlow_RebootRequired", "[windowsFeature]")
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(L"Test Windows Feature");
auto getDisplayNameOverride = TestHook::SetWindowsFeatureGetDisplayNameResult_Override(LocIndString{ "Test Windows Feature"_liv });
auto getRestartStatusOverride = TestHook::SetWindowsFeatureGetRestartStatusResult_Override(DismRestartRequired);

std::ostringstream installOutput;
TestContext context{ installOutput, std::cin };
auto previousThreadGlobals = context.SetForCurrentThread();
OverrideForShellExecute(context);
context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_WindowsFeatures.yaml").GetPath().u8string());

InstallCommand install({});
Expand Down Expand Up @@ -187,7 +184,7 @@ TEST_CASE("InstallFlow_RebootRequired_Force", "[windowsFeature]")
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(L"Test Windows Feature");
auto getDisplayNameOverride = TestHook::SetWindowsFeatureGetDisplayNameResult_Override(LocIndString{ "Test Windows Feature"_liv });
auto getRestartStatusOverride = TestHook::SetWindowsFeatureGetRestartStatusResult_Override(DismRestartRequired);

std::ostringstream installOutput;
Expand Down
1 change: 1 addition & 0 deletions src/AppInstallerCommonCore/FileLogger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ namespace AppInstaller::Logging
}
else
{
AICLI_LOG(Core, Error, << "Failed to open log file");
ryfu-msft marked this conversation as resolved.
Show resolved Hide resolved
throw std::system_error(fopenError, std::generic_category());
ryfu-msft marked this conversation as resolved.
Show resolved Hide resolved
}
}
Expand Down
105 changes: 48 additions & 57 deletions src/AppInstallerCommonCore/Public/winget/WindowsFeature.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma once
#include <AppInstallerProgress.h>
#include <winget/LocIndependent.h>

namespace AppInstaller::WindowsFeature
{
Expand Down Expand Up @@ -113,69 +115,17 @@ namespace AppInstaller::WindowsFeature
using DismDisableFeaturePtr = HRESULT(WINAPI*)(UINT, PCWSTR, PCWSTR, BOOL, HANDLE, DISM_PROGRESS_CALLBACK, PVOID);
using DismDeletePtr = HRESULT(WINAPI*)(VOID*);

// forward declaration
struct WindowsFeature;

struct DismHelper
{
/// <summary>
/// Struct representation of a single Windows Feature.
/// </summary>
struct WindowsFeature
{
HRESULT Enable();
HRESULT Disable();
bool DoesExist();
bool IsEnabled();
std::wstring GetDisplayName();
DismRestartType GetRestartRequiredStatus();

DismPackageFeatureState GetState()
{
return m_featureInfo->FeatureState;
}

~WindowsFeature()
{
if (m_featureInfo)
{
m_deletePtr(m_featureInfo);
}
}

private:
friend DismHelper;

// This default constructor is only used for mocking unit tests.
WindowsFeature(){};

WindowsFeature(
const std::string& name,
DismGetFeatureInfoPtr getFeatureInfoPtr,
DismEnableFeaturePtr enableFeaturePtr,
DismDisableFeaturePtr disableFeaturePtr,
DismDeletePtr deletePtr,
DismSession session);

void GetFeatureInfo();

std::string m_featureName;
DismFeatureInfo* m_featureInfo = nullptr;
DismGetFeatureInfoPtr m_getFeatureInfoPtr = nullptr;
DismEnableFeaturePtr m_enableFeature = nullptr;
DismDisableFeaturePtr m_disableFeaturePtr = nullptr;
DismDeletePtr m_deletePtr = nullptr;
DismSession m_session = DISM_SESSION_DEFAULT;
};

DismHelper();

~DismHelper()
{
CloseSession();
Shutdown();
};

WindowsFeature CreateWindowsFeature(const std::string& name);
~DismHelper();

private:
friend WindowsFeature;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look like it needs to be a friend if this object simply exposed public methods for calling into the function pointers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed and exposed public functions

typedef UINT DismSession;

wil::unique_hmodule m_module;
Expand All @@ -194,5 +144,46 @@ namespace AppInstaller::WindowsFeature
void OpenSession();
void CloseSession();
void Shutdown();

template<typename FuncType>
FuncType GetProcAddressHelper(HMODULE module, LPCSTR functionName);
};

/// <summary>
/// Struct representation of a single Windows Feature.
/// </summary>
struct WindowsFeature
{
// This default constructor is only used for mocking unit tests.
WindowsFeature() = default;
WindowsFeature(std::shared_ptr<DismHelper> dismHelper, const std::string& name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I find this awkward and would have a helper on DismHelper (that also requires that DismHelper derive from std::enable_shared_from_this).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left the constructor as is to be able to get a shared_ptr to DismHelper. I changed DismHelper to derive from enable_shared_from_this so that we can safely get another shared_ptr as suggested.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The awkward part is the call site; the need to pass the shared_ptr yourself. (I should have put the comment there I suppose...) I wasn't suggesting that you should take the constructor away (although you could make it protected and friend the other way). I was suggesting that you add a function like:

WindowsFeature DismHelper::GetFeature(std::string featureName);

So that the call site was less awkward, like:

WindowsFeature::WindowsFeature windowsFeature = dismHelper->GetFeature(featureName};

If you did make the constructor protected, you then don't have to worry about the shared_ptr being null.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree the call site is weird passing shared ptrs around. I feel that this is important if dismhelper were to be extended to other scenarios so I changed it based on your suggestions. I created a helper function within dismhelper that creates the windows object for you, calling the constructor with the shared ptr. I made the WindowsFeature constructor protected and made dismHelper a friend so that only the dismhelper can create the WindowsFeature object.


HRESULT Enable(IProgressCallback& progress);
ryfu-msft marked this conversation as resolved.
Show resolved Hide resolved
HRESULT Disable();
bool DoesExist();
bool IsEnabled();
Utility::LocIndString GetDisplayName();
DismRestartType GetRestartRequiredStatus();

DismPackageFeatureState GetState()
{
return m_featureInfo->FeatureState;
}

~WindowsFeature()
{
if (m_featureInfo)
{
m_dismHelper->m_dismDelete(m_featureInfo);
}
}

private:
void GetFeatureInfo();

std::string m_featureName;
std::shared_ptr<DismHelper> m_dismHelper;
DismFeatureInfo* m_featureInfo = nullptr;
DismSession m_session = DISM_SESSION_DEFAULT;
};
}
Loading