wix: install dotnet 3 after installation if needed (#2775)

This commit is contained in:
Andrey Nekrasov 2020-05-07 17:39:32 +03:00 committed by GitHub
parent 9f724221fa
commit 73c6cbb562
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 200 additions and 10 deletions

View file

@ -47,8 +47,8 @@
<UIRef Id="WixUI_PTInstallDir"/>
<Publish Dialog="ExitDialog"
Control="Finish"
Event="DoAction"
Value="LaunchApplication">WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed</Publish>
Event="EndDialog"
Value="Return">NOT Installed</Publish>
<Publish Dialog="MaintenanceTypeDlg" Control="RemoveButton" Property="_REMOVE_ALL" Value="Yes">1</Publish>
<Publish Dialog="UserExit" Control="Finish" Event="DoAction" Value="TelemetryLogInstallCancel">NOT Installed</Publish>
<Publish Dialog="FatalError" Control="Finish" Event="DoAction" Value="TelemetryLogInstallFail">NOT Installed</Publish>
@ -62,11 +62,8 @@
<WixVariable Id="WixUILicenseRtf" Value="$(var.RepoDir)\installer\License.rtf" />
<Property Id="INSTALLSTARTMENUSHORTCUT" Value="1"/>
<Property Id="CREATESCHEDULEDTASK" Value="1"/>
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1"/>
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="Launch PowerToys" />
<Property Id="WixShellExecTarget" Value="[#PowerToys.exe]" />
<CustomAction Id="LaunchApplication" BinaryKey="WixCA" DllEntry="WixShellExec" Impersonate="yes" />
<Property Id="WixShellExecTarget" Value="[#action_runner.exe]" />
<Property Id ="EXISTINGPOWERRENAMEEXTPATH">
<RegistrySearch Id="ExistingExtPath" Root="HKCR" Key="CLSID\{0440049F-D1DC-4E46-B27B-98393D79486B}\InprocServer32" Type="raw"/>
</Property>
@ -90,8 +87,21 @@
<Custom Action="TelemetryLogUninstallSuccess" After="InstallFinalize">
Installed and (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")
</Custom>
<Custom Action="InstallDotNet" After="InstallFinalize">
NOT Installed
</Custom>
</InstallExecuteSequence>
<CustomAction
Id="InstallDotNet"
FileKey="action_runner.exe"
ExeCommand="-install_dotnet"
Return="asyncNoWait"
Execute="immediate"
Impersonate="no" />
<CustomAction Id="SetRegisterPowerToysSchTaskParam"
Property="RegisterPowerToysSchTask"
Value="[#PowerToys.exe]" />

View file

@ -14,6 +14,10 @@
#include "../runner/tray_icon.h"
#include "../runner/action_runner_utils.h"
#include "resource.h"
extern "C" IMAGE_DOS_HEADER __ImageBase;
int uninstall_msi_action()
{
const auto package_path = updating::get_msi_package_path();
@ -122,6 +126,56 @@ bool install_new_version_stage_2(std::wstring_view installer_path, std::wstring_
return true;
}
bool dotnet_is_installed()
{
auto runtimes = exec_and_read_output(LR"(dotnet --list-runtimes)");
if (!runtimes)
{
return false;
}
const char DESKTOP_DOTNET_RUNTIME_STRING[] = "Microsoft.WindowsDesktop.App 3.1.";
return runtimes->find(DESKTOP_DOTNET_RUNTIME_STRING) != std::string::npos;
}
bool install_dotnet()
{
const wchar_t DOTNET_DESKTOP_DOWNLOAD_LINK[] = L"https://download.visualstudio.microsoft.com/download/pr/a1510e74-b31a-4434-b8a0-8074ff31fb3f/b7de8ecba4a14d8312551cfdc745dea1/windowsdesktop-runtime-3.1.0-win-x64.exe";
const wchar_t DOTNET_DESKTOP_FILENAME[] = L"windowsdesktop-runtime-3.1.0-win-x64.exe";
auto dotnet_download_path = fs::temp_directory_path() / DOTNET_DESKTOP_FILENAME;
winrt::Windows::Foundation::Uri download_link{ DOTNET_DESKTOP_DOWNLOAD_LINK };
const size_t max_attempts = 3;
bool download_success = false;
for (size_t i = 0; i < max_attempts; ++i)
{
try
{
updating::try_download_file(dotnet_download_path, download_link).wait();
download_success = true;
break;
}
catch (...)
{
// couldn't download
}
}
if (!download_success)
{
MessageBoxW(nullptr,
GET_RESOURCE_STRING(IDS_DOTNET_CORE_DOWNLOAD_FAILURE).c_str(),
GET_RESOURCE_STRING(IDS_DOTNET_CORE_DOWNLOAD_FAILURE_TITLE).c_str(),
MB_OK | MB_ICONERROR);
return false;
}
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_NOASYNC };
sei.lpFile = dotnet_download_path.c_str();
sei.nShow = SW_SHOWNORMAL;
sei.lpParameters = L"/install /passive";
return ShellExecuteExW(&sei) == TRUE;
}
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
int nArgs = 0;
@ -132,7 +186,15 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
}
std::wstring_view action{ args[1] };
if (action == L"-uninstall_msi")
if (action == L"-install_dotnet")
{
if (dotnet_is_installed())
{
return 0;
}
return !install_dotnet();
}
else if (action == L"-uninstall_msi")
{
return uninstall_msi_action();
}

View file

@ -0,0 +1,43 @@
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#include "../common/version.h"
STRINGTABLE
BEGIN
IDS_DOTNET_CORE_DOWNLOAD_FAILURE "Couldn't download .NET Core Desktop Runtime 3.1.3, please install it manually."
IDS_DOTNET_CORE_DOWNLOAD_FAILURE_TITLE "PowerToys installation error"
END
1 VERSIONINFO
FILEVERSION FILE_VERSION
PRODUCTVERSION PRODUCT_VERSION
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", COMPANY_NAME
VALUE "FileDescription", "PowerToys Action Runner"
VALUE "FileVersion", FILE_VERSION_STRING
VALUE "InternalName", "PowerToys Action Runner"
VALUE "LegalCopyright", COPYRIGHT_NOTE
VALUE "OriginalFilename", "action_runner.exe"
VALUE "ProductName", "PowerToys"
VALUE "ProductVersion", PRODUCT_VERSION_STRING
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END

View file

@ -160,10 +160,14 @@
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\runner\updating.h" />
<ClInclude Include="resource.h" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="action_runner.rc" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.targets')" />

View file

@ -0,0 +1,3 @@
#define IDS_DOTNET_CORE_DOWNLOAD_FAILURE 101
#define IDS_DOTNET_CORE_DOWNLOAD_FAILURE_TITLE 102

View file

@ -6,6 +6,8 @@
#include <sddl.h>
#include "version.h"
#include <wil/resource.h>
#pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "shlwapi.lib")
@ -735,3 +737,64 @@ bool find_app_name_in_path(const std::wstring& where, const std::vector<std::wst
}
return false;
}
std::optional<std::string> exec_and_read_output(const std::wstring_view command, const DWORD timeout)
{
SECURITY_ATTRIBUTES saAttr{ sizeof(saAttr) };
saAttr.bInheritHandle = true;
wil::unique_handle childStdoutRead;
wil::unique_handle childStdoutWrite;
if (!CreatePipe(&childStdoutRead, &childStdoutWrite, &saAttr, 0))
{
return std::nullopt;
}
if (!SetHandleInformation(childStdoutRead.get(), HANDLE_FLAG_INHERIT, 0))
{
return std::nullopt;
}
PROCESS_INFORMATION piProcInfo{};
STARTUPINFOW siStartInfo{ sizeof(siStartInfo) };
siStartInfo.hStdError = childStdoutWrite.get();
siStartInfo.hStdOutput = childStdoutWrite.get();
siStartInfo.dwFlags |= STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
siStartInfo.wShowWindow = SW_HIDE;
std::wstring cmdLine{ command };
if (!CreateProcessW(nullptr,
cmdLine.data(),
nullptr,
nullptr,
true,
NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE,
nullptr,
nullptr,
&siStartInfo,
&piProcInfo))
{
return std::nullopt;
}
WaitForSingleObject(piProcInfo.hProcess, timeout);
childStdoutWrite.reset();
CloseHandle(piProcInfo.hThread);
std::string childOutput;
for (;;)
{
char buffer[4096];
DWORD gotBytes = 0;
if (!ReadFile(childStdoutRead.get(), buffer, sizeof(buffer), &gotBytes, nullptr) || !gotBytes)
{
break;
}
childOutput += std::string_view{ buffer, gotBytes };
}
CloseHandle(piProcInfo.hProcess);
return childOutput;
}

View file

@ -99,6 +99,8 @@ std::wstring get_resource_string(UINT resource_id, HINSTANCE instance, const wch
// is added to the .cpp file.
#define GET_RESOURCE_STRING(resource_id) get_resource_string(resource_id, reinterpret_cast<HINSTANCE>(&__ImageBase), L#resource_id)
std::optional<std::string> exec_and_read_output(const std::wstring_view command, const DWORD timeout = INFINITE);
// Helper class for various COM-related APIs, e.g working with security descriptors
template<typename T>
struct typed_storage

View file

@ -209,7 +209,7 @@ namespace updating
return { std::move(path_str) };
}
std::future<void> attempt_to_download_installer(const std::filesystem::path& destination, const winrt::Windows::Foundation::Uri& url)
std::future<void> try_download_file(const std::filesystem::path& destination, const winrt::Windows::Foundation::Uri& url)
{
namespace storage = winrt::Windows::Storage;
@ -218,6 +218,7 @@ namespace updating
(void)response.EnsureSuccessStatusCode();
auto msi_installer_file_stream = co_await storage::Streams::FileRandomAccessStream::OpenAsync(destination.c_str(), storage::FileAccessMode::ReadWrite, storage::StorageOpenOptions::AllowReadersAndWriters, storage::Streams::FileOpenDisposition::CreateAlways);
co_await response.Content().WriteToStreamAsync(msi_installer_file_stream);
msi_installer_file_stream.Close();
}
std::future<void> try_autoupdate(const bool download_updates_automatically)
@ -244,7 +245,7 @@ namespace updating
{
try
{
co_await attempt_to_download_installer(installer_download_dst, new_version->msi_download_url);
co_await try_download_file(installer_download_dst, new_version->msi_download_url);
download_success = true;
break;
}

View file

@ -9,6 +9,8 @@
namespace updating
{
std::future<void> try_download_file(const std::filesystem::path& destination, const winrt::Windows::Foundation::Uri& url);
std::wstring get_msi_package_path();
bool uninstall_msi_version(const std::wstring& package_path);
bool offer_msi_uninstallation();