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

Use MSI API to allow UAC prompts on MSI silent installs #1398

Merged
merged 17 commits into from Aug 27, 2021
23 changes: 23 additions & 0 deletions .github/actions/spelling/allow.txt
@@ -1,5 +1,7 @@
abi
ACCESSDENIED
ACTIONDATA
ACTIONSTART
addmanifest
addstore
admins
Expand Down Expand Up @@ -53,6 +55,7 @@ cnt
codepage
COMMANDBARFLYOUT
Commandline
COMMONDATA
comparand
conemu
config
Expand Down Expand Up @@ -108,6 +111,7 @@ dword
DWORDLONG
elseif
emoji
ENDDIALOG
endif
endl
ensureandinsert
Expand All @@ -131,19 +135,24 @@ exesilent
exeswp
exitcode
expr
EXTRADEBUG
EXTRAFLAGS
FAILIFTHERE
fakeswitch
FATALEXIT
FIELDTAG
FILEFLAGS
FILEFLAGSMASK
FILELOGGER
FILEOS
filepath
FILESINUSE
FILESUBTYPE
filesystem
FILETYPE
FILEVERSION
FLUSHEACHLINE
forcerestart
foreach
fstream
func
Expand All @@ -154,6 +163,7 @@ github
githubusercontent
hfile
HGLOBAL
HIDECANCEL
hinternet
HKEY
hmac
Expand Down Expand Up @@ -194,7 +204,10 @@ Inq
installertype
Installeruniqueness
installlocation
INSTALLLOGATTRIBUTES
INSTALLLOGMODE
INSTALLPATH
INSTALLUILEVEL
interop
INVALIDARG
iomanip
Expand All @@ -221,7 +234,9 @@ LLVM
llvmorg
LOCALAPPDATA
localtime
LOGONLYONERROR
LOGPATH
LOGPERFORMANCE
logsql
logto
LONGLONG
Expand Down Expand Up @@ -275,6 +290,7 @@ NOMINMAX
NONAME
nonexistentsetting
NONINFRINGEMENT
norestart
NOTHROW
NOTIMPL
NOTNULL
Expand All @@ -295,6 +311,7 @@ ostream
ostringstream
OSVERSIONINFOEXW
outfile
OUTOFDISKSPACE
OUTOFMEMORY
OWC
PACKAGESSCHEMA
Expand Down Expand Up @@ -326,6 +343,9 @@ PRIMARYKEY
prioritization
PRODUCTNAME
PRODUCTVERSION
PROGRESSONLY
promptrestart
PROPERTYDUMP
psz
ptr
publiccontainer
Expand All @@ -341,6 +361,7 @@ READONLY
READWRITE
realloc
REALTIME
REBOOTPROMPT
Redistributable
REFCLSID
regex
Expand All @@ -350,6 +371,7 @@ repolibtest
rescap
resheader
resmimetype
RESOLVESOURCE
RESTSOURCE
resw
resx
Expand Down Expand Up @@ -471,6 +493,7 @@ ttl
typedef
typename
UAC
UACONLY
uap
UBool
UBreak
Expand Down
5 changes: 5 additions & 0 deletions .github/actions/spelling/expect.txt
Expand Up @@ -180,13 +180,16 @@ libsolv
libyaml
Linq
liv
liwpx
llvm
localhost
localizationpriority
LPBYTE
LPWSTR
LSTATUS
LTDA
lw
lz
malware
MBH
megamorf
Expand Down Expand Up @@ -218,6 +221,8 @@ netlify
Newtonsoft
NOEXPAND
normer
NOSEPARATOR
NOTAPROPERTY
npp
nsis
nuffing
Expand Down
4 changes: 2 additions & 2 deletions src/AppInstallerCLI/AppInstallerCLI.vcxproj
Expand Up @@ -184,7 +184,7 @@
<Link>
<SubSystem>Console</SubSystem>
<GenerateWindowsMetadata>false</GenerateWindowsMetadata>
<AdditionalDependencies Condition="'$(Configuration)'=='Debug'">wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies Condition="'$(Configuration)'=='Debug'">wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<Manifest>
<AdditionalManifestFiles Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">$(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles)</AdditionalManifestFiles>
Expand Down Expand Up @@ -246,7 +246,7 @@
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateWindowsMetadata>false</GenerateWindowsMetadata>
<AdditionalDependencies Condition="'$(Configuration)'=='Release'">wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies Condition="'$(Configuration)'=='Release'">wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<Manifest>
<AdditionalManifestFiles Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">$(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles)</AdditionalManifestFiles>
Expand Down
2 changes: 2 additions & 0 deletions src/AppInstallerCLICore/AppInstallerCLICore.vcxproj
Expand Up @@ -266,6 +266,7 @@
<ClInclude Include="PackageCollection.h" />
<ClInclude Include="Workflows\CompletionFlow.h" />
<ClInclude Include="Workflows\ImportExportFlow.h" />
<ClInclude Include="Workflows\MsiInstallFlow.h" />
<ClInclude Include="Workflows\MSStoreInstallerHandler.h" />
<ClInclude Include="Workflows\ShellExecuteInstallerHandler.h" />
<ClInclude Include="Workflows\InstallFlow.h" />
Expand Down Expand Up @@ -313,6 +314,7 @@
<ClCompile Include="VTSupport.cpp" />
<ClCompile Include="Workflows\CompletionFlow.cpp" />
<ClCompile Include="Workflows\ImportExportFlow.cpp" />
<ClCompile Include="Workflows\MsiInstallFlow.cpp" />
<ClCompile Include="Workflows\MSStoreInstallerHandler.cpp" />
<ClCompile Include="Workflows\ShellExecuteInstallerHandler.cpp" />
<ClCompile Include="Workflows\InstallFlow.cpp" />
Expand Down
6 changes: 6 additions & 0 deletions src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters
Expand Up @@ -164,6 +164,9 @@
<ClInclude Include="Commands\COMInstallCommand.h">
<Filter>Commands</Filter>
</ClInclude>
<ClInclude Include="Workflows\MsiInstallFlow.h">
<Filter>Workflows</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
Expand Down Expand Up @@ -295,6 +298,9 @@
<ClCompile Include="Commands\COMInstallCommand.cpp">
<Filter>Commands</Filter>
</ClCompile>
<ClCompile Include="Workflows\MsiInstallFlow.cpp">
<Filter>Workflows</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="PropertySheet.props" />
Expand Down
31 changes: 30 additions & 1 deletion src/AppInstallerCLICore/Workflows/InstallFlow.cpp
Expand Up @@ -7,6 +7,7 @@
#include "Resources.h"
#include "ShellExecuteInstallerHandler.h"
#include "MSStoreInstallerHandler.h"
#include "MsiInstallFlow.h"
#include "WorkflowBase.h"
#include "Workflows/DependenciesFlow.h"

Expand All @@ -22,6 +23,7 @@ namespace AppInstaller::CLI::Workflow
using namespace AppInstaller::Utility;
using namespace AppInstaller::Manifest;
using namespace AppInstaller::Repository;
using namespace AppInstaller::Settings;

namespace
{
Expand All @@ -40,6 +42,18 @@ namespace AppInstaller::CLI::Workflow
return false;
}
}

bool ShouldUseDirectMSIInstall(InstallerTypeEnum type)
{
switch (type)
{
case InstallerTypeEnum::Msi:
case InstallerTypeEnum::Wix:
return ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::DirectMSI);
florelis marked this conversation as resolved.
Show resolved Hide resolved
default:
return false;
}
}
}

void EnsureApplicableInstaller(Execution::Context& context)
Expand Down Expand Up @@ -385,7 +399,14 @@ namespace AppInstaller::CLI::Workflow
ExecuteUninstaller;
context.ClearFlags(Execution::ContextFlag::InstallerExecutionUseUpdate);
}
context << ShellExecuteInstall;
if (ShouldUseDirectMSIInstall(installer.InstallerType))
{
context << DirectMSIInstall;
}
else
{
context << ShellExecuteInstall;
}
break;
case InstallerTypeEnum::Msix:
context << MsixInstall;
Expand All @@ -409,6 +430,14 @@ namespace AppInstaller::CLI::Workflow
ShellExecuteInstallImpl;
}

void DirectMSIInstall(Execution::Context& context)
{
context <<
GetInstallerArgs <<
RenameDownloadedInstaller <<
DirectMSIInstallImpl;
}

void MsixInstall(Execution::Context& context)
{
std::string uri;
Expand Down
6 changes: 6 additions & 0 deletions src/AppInstallerCLICore/Workflows/InstallFlow.h
Expand Up @@ -102,6 +102,12 @@ namespace AppInstaller::CLI::Workflow
// Outputs: None
void ShellExecuteInstall(Execution::Context& context);

// Runs an MSI installer directly via MSI APIs.
// Required Args: None
// Inputs: Installer, InstallerPath
// Outputs: None
void DirectMSIInstall(Execution::Context& context);

// Deploys the MSIX.
// Required Args: None
// Inputs: Manifest?, Installer || InstallerPath
Expand Down
70 changes: 70 additions & 0 deletions src/AppInstallerCLICore/Workflows/MsiInstallFlow.cpp
@@ -0,0 +1,70 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#include "pch.h"
#include "MsiInstallFlow.h"
#include "winget/MsiExecArguments.h"

namespace AppInstaller::CLI::Workflow
{
namespace
{
std::optional<UINT> InvokeMsiInstallProduct(const std::filesystem::path& installerPath, const Msi::MsiParsedArguments& msiArgs, IProgressCallback&)
{
if (msiArgs.LogFile)
{
THROW_IF_WIN32_ERROR(MsiEnableLogW(msiArgs.LogMode, msiArgs.LogFile->c_str(), msiArgs.LogAttributes));
}
else
{
// Disable logging
THROW_IF_WIN32_ERROR(MsiEnableLogW(0, nullptr, 0));
}

// Returns old UI level. We don't need to reset it so we ignore it.
MsiSetInternalUI(msiArgs.UILevel, nullptr);

// TODO: Use progress callback
return MsiInstallProductW(installerPath.c_str(), msiArgs.Properties.c_str());
}
}

void DirectMSIInstallImpl(Execution::Context& context)
{
context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl;

const std::filesystem::path& installerPath = context.Get<Execution::Data::InstallerPath>();
const auto& additionalSuccessCodes = context.Get<Execution::Data::Installer>()->InstallerSuccessCodes;

Msi::MsiParsedArguments parsedArgs = Msi::ParseMSIArguments(context.Get<Execution::Data::InstallerArgs>());

auto installResult = context.Reporter.ExecuteWithProgress(
std::bind(InvokeMsiInstallProduct,
installerPath,
parsedArgs,
std::placeholders::_1));

if (!installResult)
{
context.Reporter.Warn() << "Installation abandoned" << std::endl;
AICLI_TERMINATE_CONTEXT(E_ABORT);
}
else if (installResult.value() != 0 && (std::find(additionalSuccessCodes.begin(), additionalSuccessCodes.end(), installResult.value()) == additionalSuccessCodes.end()))
{
const auto& manifest = context.Get<Execution::Data::Manifest>();
Logging::Telemetry().LogInstallerFailure(manifest.Id, manifest.Version, manifest.Channel, "ShellExecute", installResult.value());

context.Reporter.Error() << "Installer failed with exit code: " << installResult.value() << std::endl;
// Show installer log path if exists
if (context.Contains(Execution::Data::LogPath) && std::filesystem::exists(context.Get<Execution::Data::LogPath>()))
{
context.Reporter.Info() << "Installer log is available at: " << context.Get<Execution::Data::LogPath>().u8string() << std::endl;
}

AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED);
}
else
{
context.Reporter.Info() << Resource::String::InstallFlowInstallSuccess << std::endl;
}
}
}
13 changes: 13 additions & 0 deletions src/AppInstallerCLICore/Workflows/MsiInstallFlow.h
@@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma once
#include "ExecutionContext.h"

namespace AppInstaller::CLI::Workflow
{
// Ensures that there is an applicable installer.
// Required Args: None
// Inputs: InstallerArgs, Installer, InstallerPath, Manifest
// Outputs: None
void DirectMSIInstallImpl(Execution::Context& context);
}
1 change: 1 addition & 0 deletions src/AppInstallerCLICore/pch.h
Expand Up @@ -6,6 +6,7 @@
#include <windows.h>
#include <shellapi.h>
#include <WinInet.h>
#include <msi.h>

#pragma warning( push )
#pragma warning ( disable : 4458 4100 6031 4702 )
Expand Down