commit 1ac30f8509d01671aa4f69f559d3fbcd1da2f142
parent ebca6192efcb117aee92ab1ab21dd76db1076ce5
Author: yao-msft <50888816+yao-msft@users.noreply.github.com>
Date: Tue, 10 Mar 2020 10:50:59 -0700
Add mark of the web for downloaded installer. (#49)
* Add mark of the web for downloaded installer.
* PR comments
Diffstat:
5 files changed, 81 insertions(+), 13 deletions(-)
diff --git a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp
@@ -28,7 +28,6 @@ namespace AppInstaller::Workflow
std::bind(ExecuteInstaller,
m_downloadedInstaller,
installerArgs,
- m_argsRef.Contains(ExecutionArgs::Type::Interactive),
std::placeholders::_1));
if (!installResult)
@@ -48,7 +47,7 @@ namespace AppInstaller::Workflow
}
}
- std::optional<DWORD> ShellExecuteInstallerHandler::ExecuteInstaller(const std::filesystem::path& filePath, const std::string& args, bool interactive, IProgressCallback& progress)
+ std::optional<DWORD> ShellExecuteInstallerHandler::ExecuteInstaller(const std::filesystem::path& filePath, const std::string& args, IProgressCallback& progress)
{
AICLI_LOG(CLI, Info, << "Staring installer. Path: " << filePath);
@@ -58,7 +57,9 @@ namespace AppInstaller::Workflow
std::string filePathUTF8Str = Utility::ConvertToUTF8(filePath.c_str());
execInfo.lpFile = filePathUTF8Str.c_str();
execInfo.lpParameters = args.c_str();
- execInfo.nShow = interactive ? SW_SHOW : SW_HIDE;
+ // Some installer forces UI. Setting to SW_HIDE will hide installer UI and installation will hang forever.
+ // Verified setting to SW_SHOW does not hurt silent mode since no UI will be shown.
+ execInfo.nShow = SW_SHOW;
if (!ShellExecuteExA(&execInfo) || !execInfo.hProcess)
{
return GetLastError();
@@ -194,6 +195,7 @@ namespace AppInstaller::Workflow
break;
}
+ // std::filesystem::rename() handles motw correctly if applicable.
std::filesystem::rename(m_downloadedInstaller, renamedDownloadedInstaller);
m_downloadedInstaller.assign(renamedDownloadedInstaller);
diff --git a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.h b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.h
@@ -22,7 +22,7 @@ namespace AppInstaller::Workflow
void Install() override;
protected:
- static std::optional<DWORD> ExecuteInstaller(const std::filesystem::path& filePath, const std::string& args, bool interactive, IProgressCallback& progress);
+ static std::optional<DWORD> ExecuteInstaller(const std::filesystem::path& filePath, const std::string& args, IProgressCallback& progress);
// Construct the installer arg string from appropriate source(known args, manifest) according to command line args.
// Token is not replaced with actual values yet.
diff --git a/src/AppInstallerCLITests/Downloader.cpp b/src/AppInstallerCLITests/Downloader.cpp
@@ -28,6 +28,15 @@ TEST_CASE("DownloadValidFileAndVerifyHash", "[Downloader]")
resultHash.begin()));
REQUIRE(std::filesystem::file_size(tempFile.GetPath()) > 0);
+
+ // Verify motw content
+ std::filesystem::path motwFile(tempFile);
+ motwFile += ":Zone.Identifier:$data";
+ std::ifstream motwStream(motwFile);
+ std::stringstream motwContent;
+ motwContent << motwStream.rdbuf();
+ std::string motwContentStr = motwContent.str();
+ REQUIRE(motwContentStr.find("ZoneId=3") != std::string::npos);
}
TEST_CASE("DownloadValidFileAndCancel", "[Downloader]")
diff --git a/src/AppInstallerCommonCore/Downloader.cpp b/src/AppInstallerCommonCore/Downloader.cpp
@@ -19,7 +19,7 @@ namespace AppInstaller::Utility
{
THROW_HR_IF(E_INVALIDARG, url.empty());
- AICLI_LOG(CLI, Info, << "Downloading from url: " << url);
+ AICLI_LOG(Core, Info, << "Downloading from url: " << url);
wil::unique_hinternet session(InternetOpenA(
"appinstaller-cli",
@@ -50,11 +50,11 @@ namespace AppInstaller::Utility
if (requestStatus != HTTP_STATUS_OK)
{
- AICLI_LOG(CLI, Error, << "Download request failed. Returned status: " << requestStatus);
+ AICLI_LOG(Core, Error, << "Download request failed. Returned status: " << requestStatus);
THROW_HR_MSG(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_HTTP, requestStatus), "Download request status is not success.");
}
- AICLI_LOG(CLI, Verbose, << "Download request status success.");
+ AICLI_LOG(Core, Verbose, << "Download request status success.");
// Get content length. Don't fail the download if failed.
LONGLONG contentLength = 0;
@@ -66,7 +66,7 @@ namespace AppInstaller::Utility
&contentLength,
&cbContentLength,
nullptr);
- AICLI_LOG(CLI, Verbose, << "Download size: " << contentLength);
+ AICLI_LOG(Core, Verbose, << "Download size: " << contentLength);
// Setup hash engine
SHA256 hashEngine;
@@ -83,7 +83,7 @@ namespace AppInstaller::Utility
{
if (progress.IsCancelled())
{
- AICLI_LOG(CLI, Info, << "Download cancelled.");
+ AICLI_LOG(Core, Info, << "Download cancelled.");
return {};
}
@@ -113,10 +113,10 @@ namespace AppInstaller::Utility
if (computeHash)
{
result = hashEngine.Get();
- AICLI_LOG(CLI, Info, << "Download hash: " << SHA256::ConvertToString(result));
+ AICLI_LOG(Core, Info, << "Download hash: " << SHA256::ConvertToString(result));
}
- AICLI_LOG(CLI, Info, << "Download completed.");
+ AICLI_LOG(Core, Info, << "Download completed.");
return result;
}
@@ -130,11 +130,17 @@ namespace AppInstaller::Utility
THROW_HR_IF(E_INVALIDARG, url.empty());
THROW_HR_IF(E_INVALIDARG, dest.empty());
- AICLI_LOG(CLI, Info, << "Downloading to path: " << dest);
+ AICLI_LOG(Core, Info, << "Downloading to path: " << dest);
std::filesystem::create_directories(dest.parent_path());
- std::ofstream outfile(dest, std::ofstream::binary);
+ std::ofstream emptyDestFile(dest);
+ emptyDestFile.close();
+ ApplyMotwIfApplicable(dest);
+
+ // Use std::ofstream::app to append to previous empty file so that it will not
+ // create a new file and clear motw.
+ std::ofstream outfile(dest, std::ofstream::binary | std::ofstream::app);
return DownloadToStream(url, outfile, progress, computeHash);
}
@@ -153,4 +159,52 @@ namespace AppInstaller::Utility
return false;
}
+
+ void ApplyMotwIfApplicable(const std::filesystem::path& filePath)
+ {
+ AICLI_LOG(Core, Info, << "Started applying motw to " << filePath);
+
+ {
+ // Check the file system the input file is on.
+ wil::unique_hfile fileHandle{ CreateFileW(
+ filePath.c_str(), /*lpFileName*/
+ GENERIC_READ, /*dwDesiredAccess*/
+ 0, /*dwShareMode*/
+ NULL, /*lpSecurityAttributes*/
+ OPEN_EXISTING, /*dwCreationDisposition*/
+ FILE_ATTRIBUTE_NORMAL, /*dwFlagsAndAttributes*/
+ NULL /*hTemplateFile*/) };
+
+ THROW_LAST_ERROR_IF(fileHandle.get() == INVALID_HANDLE_VALUE);
+
+ wchar_t fileSystemName[MAX_PATH];
+ THROW_LAST_ERROR_IF(!GetVolumeInformationByHandleW(
+ fileHandle.get(), /*hFile*/
+ NULL, /*lpVolumeNameBuffer*/
+ 0, /*nVolumeNameSize*/
+ NULL, /*lpVolumeSerialNumber*/
+ NULL, /*lpMaximumComponentLength*/
+ NULL, /*lpFileSystemFlags*/
+ fileSystemName, /*lpFileSystemNameBuffer*/
+ MAX_PATH /*nFileSystemNameSize*/));
+
+ if (_wcsicmp(fileSystemName, L"NTFS") != 0)
+ {
+ AICLI_LOG(Core, Info, << "File system is not NTFS. Skipped applying motw");
+ return;
+ }
+ }
+
+ // Zone Indentifier stream name
+ // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/6e3f7352-d11c-4d76-8c39-2516a9df36e8
+ std::filesystem::path motwPath(filePath);
+ motwPath += L":Zone.Identifier:$DATA";
+
+ // Apply mark of the web. ZoneId 3 means downloaded from internet.
+ std::ofstream motwStream(motwPath);
+ motwStream << "[ZoneTransfer]" << std::endl;
+ motwStream << "ZoneId=3" << std::endl;
+
+ AICLI_LOG(Core, Info, << "Finished applying motw");
+ }
}
diff --git a/src/AppInstallerCommonCore/Public/AppInstallerDownloader.h b/src/AppInstallerCommonCore/Public/AppInstallerDownloader.h
@@ -34,4 +34,7 @@ namespace AppInstaller::Utility
// Determines if the given url is a remote location.
bool IsUrlRemote(std::string_view url);
+
+ // Apply Mark of the web if the target file is on NTFS, otherwise does nothing.
+ void ApplyMotwIfApplicable(const std::filesystem::path& filePath);
}