-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Changes from 3 commits
a6ec947
cea7e0d
8000470
f66dd54
ba74875
6d8ca3e
5d048d4
98b44d7
8ebe76b
d59eb0e
8c8a7cf
d4d9430
a4e0eee
b5b2ae4
61a4143
3ba134a
f23ccb7
6711502
427cc0a
c28f5e4
fa20945
fb1a034
0e027e8
09b1a0f
12c4cf5
e1dd564
fdc86bb
918875f
0f36ef9
13200c3
db3c8ac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 << | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved this out of "download" to the step right before There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
} | ||
|
@@ -503,7 +505,6 @@ namespace AppInstaller::CLI::Workflow | |
context << | ||
Workflow::CheckForUnsupportedArgs << | ||
Workflow::DownloadSinglePackage << | ||
Workflow::EnableWindowsFeaturesDependencies << | ||
Workflow::InstallPackageInstaller; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -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"> | ||||||||
|
@@ -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> | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I was looking for something with more information directly, showing the message as well. Example:
0 = Display Name There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed to suggested, I used GetPresentableUserMessage to handle |
||||||||
<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> | ||||||||
|
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 | ||
{ | ||
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I find this awkward and would have a helper on There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The awkward part is the call site; the need to pass the
So that the call site was less awkward, like:
If you did make the constructor protected, you then don't have to worry about the There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
}; | ||
} |
There was a problem hiding this comment.
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.There was a problem hiding this comment.
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.