Skip to content

Commit

Permalink
Implementation for Portable install flow (#2078)
Browse files Browse the repository at this point in the history
  • Loading branch information
ryfu-msft authored Apr 28, 2022
1 parent 312ad6d commit 5d63c38
Show file tree
Hide file tree
Showing 49 changed files with 1,365 additions and 148 deletions.
1 change: 1 addition & 0 deletions .github/actions/spelling/allow.txt
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ EXTRADEBUG
EXTRAFLAGS
FAILIFTHERE
fakeswitch
fallthrough
FATALEXIT
FEBAB
FIELDTAG
Expand Down
8 changes: 8 additions & 0 deletions .github/actions/spelling/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ llvm
localhost
localizationpriority
LPBYTE
LPDWORD
LPWSTR
LSTATUS
LTDA
Expand Down Expand Up @@ -259,6 +260,7 @@ netlify
Newtonsoft
NOEXPAND
nonetwork
nonterminated
normer
NOSEPARATOR
NOTAPROPERTY
Expand All @@ -272,6 +274,7 @@ objbase
objidl
ofile
Outptr
OSVERSION
Packagedx
packageinuse
parametermap
Expand Down Expand Up @@ -316,6 +319,7 @@ Redist
REFIID
regexes
REGSAM
reparse
restsource
rgex
rhs
Expand Down Expand Up @@ -359,6 +363,7 @@ SUSE
swervy
SYD
SYG
symlink
sysrefcomp
Tagit
TCpp
Expand Down Expand Up @@ -393,6 +398,7 @@ uninstalls
unknwn
unparsable
UNSCOPED
unvirtualized
UParse
UPSERT
uris
Expand All @@ -404,8 +410,10 @@ USHORT
utils
uuid
UWP
VALUENAMECASE
VERSI
VERSIE
virtualization
vns
vscode
vstest
Expand Down
4 changes: 2 additions & 2 deletions schemas/JSON/settings/settings.schema.0.2.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,12 @@
"type": "boolean",
"default": false
},
"PortableAppUserRoot": {
"PortablePackageUserRoot": {
"description": "The default root directory where packages are installed to under User scope. Applies to the portable installer type.",
"type": "string",
"default": "%LOCALAPPDATA%/Microsoft/WinGet/Packages/"
},
"PortableAppMachineRoot": {
"PortablePackageMachineRoot": {
"description": "The default root directory where packages are installed to under Machine scope. Applies to the portable installer type.",
"type": "string",
"default": "%PROGRAMFILES%/WinGet/Packages/"
Expand Down
2 changes: 2 additions & 0 deletions src/AppInstallerCLICore/AppInstallerCLICore.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@
<ClInclude Include="Workflows\ImportExportFlow.h" />
<ClInclude Include="Workflows\MsiInstallFlow.h" />
<ClInclude Include="Workflows\MSStoreInstallerHandler.h" />
<ClInclude Include="Workflows\PortableInstallFlow.h" />
<ClInclude Include="Workflows\SettingsFlow.h" />
<ClInclude Include="Workflows\ShellExecuteInstallerHandler.h" />
<ClInclude Include="Workflows\InstallFlow.h" />
Expand Down Expand Up @@ -332,6 +333,7 @@
<ClCompile Include="Workflows\ImportExportFlow.cpp" />
<ClCompile Include="Workflows\MsiInstallFlow.cpp" />
<ClCompile Include="Workflows\MSStoreInstallerHandler.cpp" />
<ClCompile Include="Workflows\PortableInstallFlow.cpp" />
<ClCompile Include="Workflows\SettingsFlow.cpp" />
<ClCompile Include="Workflows\ShellExecuteInstallerHandler.cpp" />
<ClCompile Include="Workflows\InstallFlow.cpp" />
Expand Down
6 changes: 6 additions & 0 deletions src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@
<ClInclude Include="Public\COMContext.h">
<Filter>Public</Filter>
</ClInclude>
<ClInclude Include="Workflows\PortableInstallFlow.h">
<Filter>Workflows</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
Expand Down Expand Up @@ -319,6 +322,9 @@
<ClCompile Include="Workflows\DownloadFlow.cpp">
<Filter>Workflows</Filter>
</ClCompile>
<ClCompile Include="Workflows\PortableInstallFlow.cpp">
<Filter>Workflows</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="PropertySheet.props" />
Expand Down
4 changes: 3 additions & 1 deletion src/AppInstallerCLICore/Argument.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,15 @@ namespace AppInstaller::CLI
case Args::Type::ExperimentalArg:
return Argument{ "arg", NoAlias, Args::Type::ExperimentalArg, Resource::String::ExperimentalArgumentDescription, ArgumentType::Flag, ExperimentalFeature::Feature::ExperimentalArg };
case Args::Type::Rename:
return Argument{ "rename", NoAlias, Args::Type::Rename, Resource::String::RenameArgumentDescription, ArgumentType::Positional, false };
return Argument{ "rename", 'r', Args::Type::Rename, Resource::String::RenameArgumentDescription, ArgumentType::Standard, false };
case Args::Type::Purge:
return Argument{ "purge", NoAlias, Args::Type::Purge, Resource::String::PurgeArgumentDescription, ArgumentType::Flag, false };
case Args::Type::Preserve:
return Argument{ "preserve", NoAlias, Args::Type::Preserve, Resource::String::PreserveArgumentDescription, ArgumentType::Flag, false };
case Args::Type::Wait:
return Argument{ "wait", NoAlias, Args::Type::Wait, Resource::String::WaitArgumentDescription, ArgumentType::Flag, false };
case Args::Type::ProductCode:
return Argument{ "product-code", NoAlias, Args::Type::ProductCode, Resource::String::ProductCodeArgumentDescription, ArgumentType::Standard, false };
default:
THROW_HR(E_UNEXPECTED);
}
Expand Down
1 change: 1 addition & 0 deletions src/AppInstallerCLICore/Commands/InstallCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ namespace AppInstaller::CLI
Argument::ForType(Args::Type::AcceptPackageAgreements),
Argument::ForType(Args::Type::CustomHeader),
Argument::ForType(Args::Type::AcceptSourceAgreements),
Argument::ForType(Args::Type::Rename),
};
}

Expand Down
1 change: 1 addition & 0 deletions src/AppInstallerCLICore/ExecutionArgs.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ namespace AppInstaller::CLI::Execution
// Uninstall behavior
Purge, // Removes all files and directories related to a package during an uninstall. Only applies to the portable installerType.
Preserve, // Retains any files and directories created by the portable exe.
ProductCode, // Uninstalls using the product code as the identifier.

//Source Command
SourceName,
Expand Down
3 changes: 3 additions & 0 deletions src/AppInstallerCLICore/ExecutionContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ namespace AppInstaller::CLI::Execution
// Returns a value indicating whether the context is terminated.
bool IsTerminated() const { return m_isTerminated; }

// Resets the context to a nonterminated state.
void ResetTermination() { m_terminationHR = S_OK; m_isTerminated = false; }

// Gets the HRESULT reason for the termination.
HRESULT GetTerminationHR() const { return m_terminationHR; }

Expand Down
6 changes: 6 additions & 0 deletions src/AppInstallerCLICore/Resources.h
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(ManifestValidationSuccess);
WINGET_DEFINE_RESOURCE_STRINGID(ManifestValidationWarning);
WINGET_DEFINE_RESOURCE_STRINGID(MissingArgumentError);
WINGET_DEFINE_RESOURCE_STRINGID(ModifiedPathRequiresShellRestart);
WINGET_DEFINE_RESOURCE_STRINGID(MonikerArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(MsixArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(MsixSignatureHashFailed);
Expand Down Expand Up @@ -197,6 +198,7 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(Options);
WINGET_DEFINE_RESOURCE_STRINGID(OutputFileArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(OverrideArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(OverwritingExistingFileAtMessage);
WINGET_DEFINE_RESOURCE_STRINGID(Package);
WINGET_DEFINE_RESOURCE_STRINGID(PackageAgreementsNotAgreedTo);
WINGET_DEFINE_RESOURCE_STRINGID(PackageAgreementsPrompt);
Expand All @@ -206,17 +208,21 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(PoliciesEnabled);
WINGET_DEFINE_RESOURCE_STRINGID(PoliciesPolicy);
WINGET_DEFINE_RESOURCE_STRINGID(PoliciesState);
WINGET_DEFINE_RESOURCE_STRINGID(PortableRegistryCollisionOverridden);
WINGET_DEFINE_RESOURCE_STRINGID(PositionArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(PreserveArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(PrivacyStatement);
WINGET_DEFINE_RESOURCE_STRINGID(ProductCodeArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(PromptOptionNo);
WINGET_DEFINE_RESOURCE_STRINGID(PromptOptionYes);
WINGET_DEFINE_RESOURCE_STRINGID(PurgeArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(QueryArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(RainbowArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(RenameArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(ReparsePointsNotSupportedError);
WINGET_DEFINE_RESOURCE_STRINGID(ReportIdentityFound);
WINGET_DEFINE_RESOURCE_STRINGID(RequiredArgError);
WINGET_DEFINE_RESOURCE_STRINGID(ReservedFilenameError);
WINGET_DEFINE_RESOURCE_STRINGID(RetroArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(SearchCommandLongDescription);
WINGET_DEFINE_RESOURCE_STRINGID(SearchCommandShortDescription);
Expand Down
67 changes: 4 additions & 63 deletions src/AppInstallerCLICore/Workflows/DownloadFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.
#include "pch.h"
#include "DownloadFlow.h"
#include "winget/Filesystem.h"

#include <AppInstallerMsixInfo.h>

Expand Down Expand Up @@ -38,6 +39,7 @@ namespace AppInstaller::CLI::Workflow
case InstallerTypeEnum::Exe:
case InstallerTypeEnum::Inno:
case InstallerTypeEnum::Nullsoft:
case InstallerTypeEnum::Portable:
return L".exe"sv;
case InstallerTypeEnum::Msi:
case InstallerTypeEnum::Wix:
Expand Down Expand Up @@ -122,68 +124,6 @@ namespace AppInstaller::CLI::Workflow

return false;
}

// Complicated rename algorithm due to somewhat arbitrary failures.
// 1. First, try to rename.
// 2. Then, create an empty file for the target, and attempt to rename.
// 3. Then, try repeatedly for 500ms in case it is a timing thing.
// 4. Attempt to use a hard link if available.
// 5. Copy the file if nothing else has worked so far.
void RenameFile(const std::filesystem::path& from, const std::filesystem::path& to)
{
// 1. First, try to rename.
try
{
// std::filesystem::rename() handles motw correctly if applicable.
std::filesystem::rename(from, to);
return;
}
CATCH_LOG();

// 2. Then, create an empty file for the target, and attempt to rename.
// This seems to fix things in certain cases, so we do it.
try
{
{
std::ofstream targetFile{ to };
}
std::filesystem::rename(from, to);
return;
}
CATCH_LOG();

// 3. Then, try repeatedly for 500ms in case it is a timing thing.
for (int i = 0; i < 5; ++i)
{
try
{
std::this_thread::sleep_for(100ms);
std::filesystem::rename(from, to);
return;
}
CATCH_LOG();
}

// 4. Attempt to use a hard link if available.
if (Runtime::SupportsHardLinks(from))
{
try
{
// Create a hard link to the file; the installer will be left in the temp directory afterward
// but it is better to succeed the operation and leave a file around than to fail.
// First we have to remove the target file as the function will not overwrite.
std::filesystem::remove(to);
std::filesystem::create_hard_link(from, to);
return;
}
CATCH_LOG();
}

// 5. Copy the file if nothing else has worked so far.
// Create a copy of the file; the installer will be left in the temp directory afterward
// but it is better to succeed the operation and leave a file around than to fail.
std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing);
}
}

void DownloadInstaller(Execution::Context& context)
Expand All @@ -210,6 +150,7 @@ namespace AppInstaller::CLI::Workflow
case InstallerTypeEnum::Inno:
case InstallerTypeEnum::Msi:
case InstallerTypeEnum::Nullsoft:
case InstallerTypeEnum::Portable:
case InstallerTypeEnum::Wix:
context << DownloadInstallerFile;
break;
Expand Down Expand Up @@ -513,7 +454,7 @@ namespace AppInstaller::CLI::Workflow
return;
}

RenameFile(installerPath, renamedDownloadedInstaller);
Filesystem::RenameFile(installerPath, renamedDownloadedInstaller);

installerPath.assign(renamedDownloadedInstaller);
AICLI_LOG(CLI, Info, << "Successfully renamed downloaded installer. Path: " << installerPath);
Expand Down
19 changes: 19 additions & 0 deletions src/AppInstallerCLICore/Workflows/InstallFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "ShellExecuteInstallerHandler.h"
#include "MSStoreInstallerHandler.h"
#include "MsiInstallFlow.h"
#include "PortableInstallFlow.h"
#include "WorkflowBase.h"
#include "Workflows/DependenciesFlow.h"
#include <AppInstallerDeployment.h>
Expand Down Expand Up @@ -116,6 +117,8 @@ namespace AppInstaller::CLI::Workflow
context.Reporter.Error() << Resource::String::NoApplicableInstallers << std::endl;
AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER);
}

context << EnsureSupportForInstall;
}

void ShowInstallationDisclaimer(Execution::Context& context)
Expand Down Expand Up @@ -257,6 +260,9 @@ namespace AppInstaller::CLI::Workflow
EnsureStorePolicySatisfied <<
(isUpdate ? MSStoreUpdate : MSStoreInstall);
break;
case InstallerTypeEnum::Portable:
context << PortableInstall;
break;
default:
THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED));
}
Expand All @@ -278,6 +284,13 @@ namespace AppInstaller::CLI::Workflow
ReportInstallerResult("MsiInstallProduct"sv, APPINSTALLER_CLI_ERROR_MSI_INSTALL_FAILED);
}

void PortableInstall(Execution::Context& context)
{
context <<
PortableInstallImpl <<
ReportInstallerResult("Portable"sv, APPINSTALLER_CLI_ERROR_PORTABLE_INSTALL_FAILED, true);
}

void MsixInstall(Execution::Context& context)
{
std::string uri;
Expand Down Expand Up @@ -398,6 +411,12 @@ namespace AppInstaller::CLI::Workflow
Workflow::InstallPackageInstaller;
}

void EnsureSupportForInstall(Execution::Context& context)
{
context <<
Workflow::EnsureSupportForPortableInstall;
}

void InstallMultiplePackages::operator()(Execution::Context& context) const
{
if (m_ensurePackageAgreements)
Expand Down
12 changes: 12 additions & 0 deletions src/AppInstallerCLICore/Workflows/InstallFlow.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,18 @@ namespace AppInstaller::CLI::Workflow
// Outputs: None
void MsixInstall(Execution::Context& context);

// Runs the flow for installing a Portable package.
// Required Args: None
// Inputs: Installer, InstallerPath
// Outputs: None
void PortableInstall(Execution::Context& context);

// Verifies parameters for install to ensure success.
// Required Args: None
// Inputs:
// Outputs: None
void EnsureSupportForInstall(Execution::Context& context);

// Reports the return code returned by the installer.
// Required Args: None
// Inputs: Manifest, Installer, InstallerResult
Expand Down
Loading

0 comments on commit 5d63c38

Please sign in to comment.