Skip to content

Commit

Permalink
Use MSI API to allow UAC prompts on MSI silent installs (#1398)
Browse files Browse the repository at this point in the history
  • Loading branch information
Chacón committed Aug 27, 2021
1 parent e50438e commit 9a15df4
Show file tree
Hide file tree
Showing 45 changed files with 1,090 additions and 30 deletions.
24 changes: 24 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 @@ -459,6 +481,7 @@ TEXTINCLUDE
there're
Timeline
todo
tokenizer
tolower
toupper
TOutput
Expand All @@ -472,6 +495,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
1 change: 0 additions & 1 deletion src/AppInstallerCLICore/Argument.cpp
@@ -1,6 +1,5 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma once
#include "pch.h"
#include "Argument.h"
#include "Resources.h"
Expand Down
1 change: 0 additions & 1 deletion src/AppInstallerCLICore/Resources.cpp
@@ -1,6 +1,5 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma once
#include "pch.h"
#include "Resources.h"

Expand Down
1 change: 0 additions & 1 deletion src/AppInstallerCLICore/Workflows/CompletionFlow.cpp
@@ -1,6 +1,5 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma once
#include "pch.h"
#include "CompletionFlow.h"

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, bool isSilentInstall)
{
switch (type)
{
case InstallerTypeEnum::Msi:
case InstallerTypeEnum::Wix:
return isSilentInstall || ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::DirectMSI);
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.Args.Contains(Execution::Args::Type::Silent)))
{
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;
}
}
}

0 comments on commit 9a15df4

Please sign in to comment.