| 1 | /*++ |
| 2 | |
| 3 | Copyright (c) Microsoft. All rights reserved. |
| 4 | |
| 5 | Module Name: |
| 6 | |
| 7 | install.cpp |
| 8 | |
| 9 | Abstract: |
| 10 | |
| 11 | This file contains MSI/Wintrust install helper functions. |
| 12 | Split from wslutil.cpp to avoid pulling msi.dll/wintrust.dll |
| 13 | into targets that don't need them. |
| 14 | |
| 15 | --*/ |
| 16 | |
| 17 | #include "precomp.h" |
| 18 | #include "install.h" |
| 19 | #include "wslutil.h" |
| 20 | #include "WslPluginApi.h" |
| 21 | #include "wslinstallerservice.h" |
| 22 | |
| 23 | #include "ConsoleProgressBar.h" |
| 24 | #include "ExecutionContext.h" |
| 25 | #include "MsiQuery.h" |
| 26 | |
| 27 | using winrt::Windows::Foundation::Uri; |
| 28 | using winrt::Windows::Management::Deployment::DeploymentOptions; |
| 29 | using wsl::shared::Localization; |
| 30 | using wsl::windows::common::Context; |
| 31 | using namespace wsl::windows::common::registry; |
| 32 | using namespace wsl::windows::common::wslutil; |
| 33 | using namespace wsl::windows::common::install; |
| 34 | |
| 35 | namespace { |
| 36 | |
| 37 | bool PromptForKeyPress() |
| 38 | { |
| 39 | THROW_IF_WIN32_BOOL_FALSE(FlushConsoleInputBuffer(GetStdHandle(STD_INPUT_HANDLE))); |
| 40 | |
| 41 | // Note: Ctrl-c causes _getch to return 0x3. |
| 42 | return _getch() != 0x3; |
| 43 | } |
| 44 | |
| 45 | bool PromptForKeyPressWithTimeout() |
| 46 | { |
| 47 | // Run PromptForKeyPress on a separate thread so we can apply a timeout. |
| 48 | // If PromptForKeyPress fails, fulfill the promise with false so the caller doesn't hang. |
| 49 | std::promise<bool> pressedKey; |
| 50 | auto thread = std::thread([&pressedKey]() { |
| 51 | try |
| 52 | { |
| 53 | pressedKey.set_value(PromptForKeyPress()); |
| 54 | } |
| 55 | catch (...) |
| 56 | { |
| 57 | LOG_CAUGHT_EXCEPTION(); |
| 58 | try |
| 59 | { |
| 60 | pressedKey.set_value(false); |
| 61 | } |
| 62 | CATCH_LOG() |
| 63 | } |
| 64 | }); |
| 65 | |
| 66 | auto cancelRead = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&thread]() { |
| 67 | if (thread.joinable()) |
| 68 | { |
| 69 | LOG_IF_WIN32_BOOL_FALSE(CancelSynchronousIo(thread.native_handle())); |
| 70 | thread.join(); |
| 71 | } |
| 72 | }); |
| 73 | |
| 74 | auto future = pressedKey.get_future(); |
| 75 | const auto waitResult = future.wait_for(std::chrono::minutes(1)); |
| 76 | |
| 77 | return waitResult == std::future_status::ready && future.get(); |
| 78 | } |
| 79 | |
| 80 | int UpdatePackageImpl(bool preRelease, bool repair, bool callerOwnsProcess) |
| 81 | { |
| 82 | if (!repair && callerOwnsProcess) |
| 83 | { |
| 84 | PrintMessage(Localization::MessageCheckingForUpdates()); |
| 85 | } |
| 86 | |
| 87 | auto [version, release] = GetLatestGitHubRelease(preRelease); |
| 88 | |
| 89 | if (!repair && ParseWslPackageVersion(version) <= wsl::shared::PackageVersion) |
| 90 | { |
| 91 | if (callerOwnsProcess) |
| 92 | { |
| 93 | PrintMessage(Localization::MessageUpdateNotNeeded()); |
| 94 | } |
| 95 | return 0; |
| 96 | } |
| 97 | |
| 98 | if (callerOwnsProcess) |
| 99 | { |
| 100 | PrintMessage(Localization::MessageUpdatingToVersion(version.c_str())); |
| 101 | } |
| 102 | |
| 103 | const bool msiInstall = wsl::shared::string::EndsWith<wchar_t>(release.name, L".msi"); |
| 104 | const auto downloadPath = DownloadFile(release.url, release.name, callerOwnsProcess); |
| 105 | if (msiInstall) |
| 106 | { |
| 107 | auto logFile = std::filesystem::temp_directory_path() / L"wsl-install-logs.txt"; |
| 108 | auto clearLogs = |
| 109 | wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&logFile]() { LOG_IF_WIN32_BOOL_FALSE(DeleteFile(logFile.c_str())); }); |
| 110 | |
| 111 | const auto exitCode = UpgradeViaMsi(downloadPath.c_str(), L"", logFile.c_str(), callerOwnsProcess ? &MsiMessageCallback : nullptr); |
| 112 | |
| 113 | if (exitCode == ERROR_SUCCESS_REBOOT_REQUIRED) |
| 114 | { |
| 115 | if (callerOwnsProcess) |
| 116 | { |
| 117 | PrintSystemError(ERROR_SUCCESS_REBOOT_REQUIRED); |
| 118 | } |
| 119 | } |
| 120 | else if (exitCode != 0) |
| 121 | { |
| 122 | clearLogs.release(); |
| 123 | THROW_HR_WITH_USER_ERROR( |
| 124 | HRESULT_FROM_WIN32(exitCode), |
| 125 | wsl::shared::Localization::MessageUpdateFailed(exitCode) + L"\r\n" + |
| 126 | wsl::shared::Localization::MessageSeeLogFile(logFile.c_str())); |
| 127 | } |
| 128 | } |
| 129 | else |
| 130 | { |
| 131 | // Set FILE_FLAG_DELETE_ON_CLOSE on the file to make sure it's deleted when the installation completes. |
| 132 | const wil::unique_hfile package{CreateFileW( |
| 133 | downloadPath.c_str(), DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, nullptr)}; |
| 134 | |
| 135 | THROW_LAST_ERROR_IF(!package); |
| 136 | |
| 137 | const winrt::Windows::Management::Deployment::PackageManager packageManager; |
| 138 | const auto result = packageManager.AddPackageAsync( |
| 139 | Uri{downloadPath.c_str()}, nullptr, DeploymentOptions::ForceApplicationShutdown | DeploymentOptions::ForceTargetApplicationShutdown); |
| 140 | |
| 141 | THROW_IF_FAILED(result.get().ExtendedErrorCode()); |
| 142 | |
| 143 | // Note: If the installation is successful, this process is expected to receive a Ctrl-C and exit |
| 144 | } |
| 145 | |
| 146 | return 0; |
| 147 | } |
| 148 | |
| 149 | void WaitForMsiInstall() |
| 150 | { |
| 151 | wil::com_ptr_t<IWslInstaller> installer; |
| 152 | |
| 153 | auto retry_pred = []() { |
| 154 | const auto errorCode = wil::ResultFromCaughtException(); |
| 155 | return errorCode == REGDB_E_CLASSNOTREG; |
| 156 | }; |
| 157 | |
| 158 | wsl::shared::retry::RetryWithTimeout<void>( |
| 159 | [&installer]() { installer = wil::CoCreateInstance<IWslInstaller>(__uuidof(WslInstaller), CLSCTX_LOCAL_SERVER); }, |
| 160 | std::chrono::seconds(1), |
| 161 | std::chrono::minutes(1), |
| 162 | retry_pred); |
| 163 | |
| 164 | fputws(wsl::shared::Localization::MessageFinishMsiInstallation().c_str(), stderr); |
| 165 | |
| 166 | auto finishLine = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, []() { fputws(L"\n", stderr); }); |
| 167 | |
| 168 | UINT exitCode = -1; |
| 169 | wil::unique_cotaskmem_string message{}; |
| 170 | THROW_IF_FAILED(installer->Install(&exitCode, &message)); |
| 171 | |
| 172 | if (message && *message.get() != UNICODE_NULL) |
| 173 | { |
| 174 | finishLine.release(); |
| 175 | wprintf(L"\n%ls\n", message.get()); |
| 176 | } |
| 177 | |
| 178 | if (exitCode != 0) |
| 179 | { |
| 180 | THROW_HR_WITH_USER_ERROR(HRESULT_FROM_WIN32(exitCode), wsl::shared::Localization::MessageUpdateFailed(exitCode)); |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | wil::unique_handle CreateJob() |
| 185 | { |
| 186 | // Create a job object that will terminate all processes in the job on |
| 187 | // close but will not terminate the children of the processes in the job. |
| 188 | // This is used to ensure that when forwarding from an inbox binary (I) |
| 189 | // to a lifted binary (L), if I is terminated L is terminated as well but |
| 190 | // any children of L (e.g. wslhost.exe) continue to run. |
| 191 | wil::unique_handle job{CreateJobObject(nullptr, nullptr)}; |
| 192 | THROW_LAST_ERROR_IF_NULL(job.get()); |
| 193 | |
| 194 | JOBOBJECT_EXTENDED_LIMIT_INFORMATION info{}; |
| 195 | info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK; |
| 196 | THROW_IF_WIN32_BOOL_FALSE(SetInformationJobObject(job.get(), JobObjectExtendedLimitInformation, &info, sizeof(info))); |
| 197 | |
| 198 | return job; |
| 199 | } |
| 200 | |
| 201 | int WINAPI InstallRecordHandler(void* context, UINT messageType, LPCWSTR message) |
| 202 | { |
| 203 | try |
| 204 | { |
| 205 | WSL_LOG("MSIMessage", TraceLoggingValue(messageType, "type"), TraceLoggingValue(message, "message")); |
| 206 | auto type = (INSTALLMESSAGE)(0xFF000000 & (UINT)messageType); |
| 207 | |
| 208 | if (type == INSTALLMESSAGE_ERROR || type == INSTALLMESSAGE_FATALEXIT || type == INSTALLMESSAGE_WARNING) |
| 209 | { |
| 210 | WriteInstallLog(std::format("MSI message: {}", message)); |
| 211 | } |
| 212 | |
| 213 | auto* callback = reinterpret_cast<const std::function<void(INSTALLMESSAGE, LPCWSTR)>*>(context); |
| 214 | if (callback != nullptr) |
| 215 | { |
| 216 | (*callback)(type, message); |
| 217 | } |
| 218 | } |
| 219 | CATCH_LOG(); |
| 220 | |
| 221 | return IDOK; |
| 222 | } |
| 223 | |
| 224 | void ConfigureMsiLogging(_In_opt_ LPCWSTR LogFile, _In_ const std::function<void(INSTALLMESSAGE, LPCWSTR)>& Callback) |
| 225 | { |
| 226 | if (LogFile != nullptr) |
| 227 | { |
| 228 | LOG_IF_WIN32_ERROR(MsiEnableLog(INSTALLLOGMODE_VERBOSE | INSTALLLOGMODE_EXTRADEBUG | INSTALLLOGMODE_PROGRESS, LogFile, 0)); |
| 229 | } |
| 230 | |
| 231 | MsiSetExternalUI( |
| 232 | &InstallRecordHandler, |
| 233 | INSTALLLOGMODE_FATALEXIT | INSTALLLOGMODE_ERROR | INSTALLLOGMODE_WARNING | INSTALLLOGMODE_USER | INSTALLLOGMODE_INFO | |
| 234 | INSTALLLOGMODE_RESOLVESOURCE | INSTALLLOGMODE_OUTOFDISKSPACE | INSTALLLOGMODE_ACTIONSTART | INSTALLLOGMODE_ACTIONDATA | |
| 235 | INSTALLLOGMODE_COMMONDATA | INSTALLLOGMODE_INITIALIZE | INSTALLLOGMODE_TERMINATE | INSTALLLOGMODE_SHOWDIALOG, |
| 236 | (void*)&Callback); |
| 237 | |
| 238 | MsiSetInternalUI(INSTALLUILEVEL(INSTALLUILEVEL_NONE | INSTALLUILEVEL_UACONLY | INSTALLUILEVEL_SOURCERESONLY), nullptr); |
| 239 | } |
| 240 | |
| 241 | } // namespace |
| 242 | |
| 243 | int wsl::windows::common::install::CallMsiPackage() |
| 244 | { |
| 245 | wsl::windows::common::ExecutionContext context(wsl::windows::common::CallMsi); |
| 246 | |
| 247 | auto msiPath = GetMsiPackagePath(); |
| 248 | if (!msiPath.has_value()) |
| 249 | { |
| 250 | wsl::windows::common::ExecutionContext context(wsl::windows::common::Install); |
| 251 | |
| 252 | try |
| 253 | { |
| 254 | WaitForMsiInstall(); |
| 255 | msiPath = GetMsiPackagePath(); |
| 256 | } |
| 257 | catch (...) |
| 258 | { |
| 259 | LOG_CAUGHT_EXCEPTION(); |
| 260 | |
| 261 | // GetMsiPackagePath() will generate a user error if the registry access fails. |
| 262 | // Save the error from GetMsiPackagePath() to return a proper 'install failed' message. |
| 263 | auto savedError = context.ReportedError(); |
| 264 | |
| 265 | // There is a race where the service might stop before returning the install result. |
| 266 | // if this happens, only fail if the MSI still isn't installed. |
| 267 | msiPath = GetMsiPackagePath(); |
| 268 | if (!msiPath.has_value()) |
| 269 | { |
| 270 | // Offer to directly install the MSI package if the MsixInstaller logic fails |
| 271 | // This can trigger a UAC so only do it |
| 272 | if (IsInteractiveConsole()) |
| 273 | { |
| 274 | auto errorCode = savedError.has_value() ? ErrorToString(savedError.value()).Code |
| 275 | : ErrorCodeToString(wil::ResultFromCaughtException()); |
| 276 | |
| 277 | EMIT_USER_WARNING(wsl::shared::Localization::MessageInstallationCorrupted(errorCode)); |
| 278 | |
| 279 | if (PromptForKeyPressWithTimeout()) |
| 280 | { |
| 281 | return UpdatePackage(false, true); |
| 282 | } |
| 283 | } |
| 284 | |
| 285 | if (savedError.has_value()) |
| 286 | { |
| 287 | THROW_HR_WITH_USER_ERROR(savedError->Code, savedError->Message.value_or(L"")); |
| 288 | } |
| 289 | |
| 290 | throw; |
| 291 | } |
| 292 | } |
| 293 | |
| 294 | THROW_HR_IF(E_UNEXPECTED, !msiPath.has_value()); |
| 295 | } |
| 296 | |
| 297 | auto target = msiPath.value() + L"\\" WSL_BINARY_NAME; |
| 298 | |
| 299 | SubProcess process(target.c_str(), GetCommandLine()); |
| 300 | process.SetDesktopAppPolicy(PROCESS_CREATION_DESKTOP_APP_BREAKAWAY_ENABLE_PROCESS_TREE); |
| 301 | auto runningProcess = process.Start(); |
| 302 | |
| 303 | // N.B. The job cannot be assigned at process creation time as the packaged process |
| 304 | // creation path will assign the new process to a per package job object. |
| 305 | // In the case of multiple processes running in a single package, assigning |
| 306 | // the new process to the per package job object will fail for the second request |
| 307 | // since both jobs already have processes which prevents a job hierarchy from |
| 308 | // being established. |
| 309 | auto job = CreateJob(); |
| 310 | |
| 311 | // Assign the process to the job, ignoring failures when the process has |
| 312 | // terminated. |
| 313 | // |
| 314 | // N.B. Assigning the job after process creation without CREATE_SUSPENDED is |
| 315 | // safe to do here since only the new child process will be in the job |
| 316 | // object. None of the grandchildren processes are included since the |
| 317 | // job is created with JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK. |
| 318 | if (!AssignProcessToJobObject(job.get(), runningProcess.get())) |
| 319 | { |
| 320 | auto lastError = GetLastError(); |
| 321 | if (lastError != ERROR_ACCESS_DENIED) |
| 322 | { |
| 323 | THROW_WIN32(lastError); |
| 324 | } |
| 325 | } |
| 326 | |
| 327 | return static_cast<int>(SubProcess::GetExitCode(runningProcess.get())); |
| 328 | } |
| 329 | |
| 330 | void wsl::windows::common::install::MsiMessageCallback(INSTALLMESSAGE type, LPCWSTR message) |
| 331 | { |
| 332 | switch (type) |
| 333 | { |
| 334 | case INSTALLMESSAGE_ERROR: |
| 335 | case INSTALLMESSAGE_FATALEXIT: |
| 336 | case INSTALLMESSAGE_WARNING: |
| 337 | wprintf(L"%ls\n", message); |
| 338 | break; |
| 339 | |
| 340 | default: |
| 341 | break; |
| 342 | } |
| 343 | } |
| 344 | |
| 345 | int wsl::windows::common::install::UpdatePackage(bool PreRelease, bool Repair, bool CallerOwnsProcess) |
| 346 | { |
| 347 | bool clearHandler = false; |
| 348 | auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] { |
| 349 | if (clearHandler) |
| 350 | { |
| 351 | SetConsoleCtrlHandler(nullptr, FALSE); |
| 352 | } |
| 353 | }); |
| 354 | |
| 355 | if (CallerOwnsProcess) |
| 356 | { |
| 357 | // Register a console control handler so "^C" is not printed when the app platform terminates the process. |
| 358 | THROW_IF_WIN32_BOOL_FALSE(SetConsoleCtrlHandler( |
| 359 | [](DWORD ctrlType) { |
| 360 | if (ctrlType == CTRL_C_EVENT) |
| 361 | { |
| 362 | ExitProcess(0); |
| 363 | } |
| 364 | return FALSE; |
| 365 | }, |
| 366 | TRUE)); |
| 367 | clearHandler = true; |
| 368 | } |
| 369 | |
| 370 | try |
| 371 | { |
| 372 | return UpdatePackageImpl(PreRelease, Repair, CallerOwnsProcess); |
| 373 | } |
| 374 | catch (...) |
| 375 | { |
| 376 | // Rethrowing via WIL is required for the error context to be properly set in case a winrt exception was thrown. |
| 377 | THROW_HR(wil::ResultFromCaughtException()); |
| 378 | } |
| 379 | } |
| 380 | |
| 381 | UINT wsl::windows::common::install::UpgradeViaMsi( |
| 382 | _In_ LPCWSTR PackageLocation, _In_opt_ LPCWSTR ExtraArgs, _In_opt_ LPCWSTR LogFile, _In_ const std::function<void(INSTALLMESSAGE, LPCWSTR)>& Callback) |
| 383 | { |
| 384 | // Always suppress MSI-initiated reboots. With INSTALLUILEVEL_NONE, Windows Installer |
| 385 | // will silently reboot the machine if files are in use and REBOOT is not suppressed. |
| 386 | std::wstring args = L"REBOOT=ReallySuppress"; |
| 387 | if (ExtraArgs != nullptr && *ExtraArgs != L'\0') |
| 388 | { |
| 389 | args = std::wstring(ExtraArgs) + L" " + args; |
| 390 | } |
| 391 | |
| 392 | WriteInstallLog(std::format("Upgrading via MSI package: {}. Args: {}", PackageLocation, args)); |
| 393 | |
| 394 | ConfigureMsiLogging(LogFile, Callback); |
| 395 | |
| 396 | auto result = MsiInstallProduct(PackageLocation, args.c_str()); |
| 397 | WSL_LOG("MsiInstallResult", TraceLoggingValue(result, "result"), TraceLoggingValue(args.c_str(), "ExtraArgs")); |
| 398 | |
| 399 | WriteInstallLog(std::format("MSI upgrade result: {}", result)); |
| 400 | |
| 401 | return result; |
| 402 | } |
| 403 | |
| 404 | UINT wsl::windows::common::install::UninstallViaMsi(_In_opt_ LPCWSTR LogFile, _In_ const std::function<void(INSTALLMESSAGE, LPCWSTR)>& Callback) |
| 405 | { |
| 406 | const auto key = OpenLxssMachineKey(KEY_READ); |
| 407 | const auto productCode = ReadString(key.get(), L"Msi", L"ProductCode", nullptr); |
| 408 | |
| 409 | WriteInstallLog(std::format("Uninstalling MSI package: {}", productCode)); |
| 410 | |
| 411 | ConfigureMsiLogging(LogFile, Callback); |
| 412 | |
| 413 | auto result = MsiConfigureProductEx(productCode.c_str(), 0, INSTALLSTATE_ABSENT, L"REBOOT=ReallySuppress"); |
| 414 | WSL_LOG("MsiUninstallResult", TraceLoggingValue(result, "result")); |
| 415 | |
| 416 | WriteInstallLog(std::format("MSI package uninstall result: {}", result)); |
| 417 | |
| 418 | return result; |
| 419 | } |
| 420 | |
| 421 | wil::unique_hfile wsl::windows::common::install::ValidateFileSignature(LPCWSTR Path) |
| 422 | { |
| 423 | wil::unique_hfile fileHandle{CreateFileW(Path, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr)}; |
| 424 | THROW_LAST_ERROR_IF(!fileHandle); |
| 425 | |
| 426 | GUID action = WINTRUST_ACTION_GENERIC_VERIFY_V2; |
| 427 | WINTRUST_DATA trust{}; |
| 428 | trust.cbStruct = sizeof(trust); |
| 429 | trust.dwUIChoice = WTD_UI_NONE; |
| 430 | trust.dwUnionChoice = WTD_CHOICE_FILE; |
| 431 | trust.dwStateAction = WTD_STATEACTION_VERIFY; |
| 432 | |
| 433 | WINTRUST_FILE_INFO file = {0}; |
| 434 | file.cbStruct = sizeof(file); |
| 435 | file.hFile = fileHandle.get(); |
| 436 | trust.pFile = &file; |
| 437 | |
| 438 | auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { |
| 439 | trust.dwStateAction = WTD_STATEACTION_CLOSE; |
| 440 | WinVerifyTrust(nullptr, &action, &trust); |
| 441 | }); |
| 442 | |
| 443 | THROW_IF_WIN32_ERROR(WinVerifyTrust(nullptr, &action, &trust)); |
| 444 | |
| 445 | return fileHandle; |
| 446 | } |
| 447 | |
| 448 | void wsl::windows::common::install::WriteInstallLog(const std::string& Content) |
| 449 | try |
| 450 | { |
| 451 | static std::wstring path = wil::GetWindowsDirectoryW<std::wstring>() + L"\\temp\\wsl-install-log.txt"; |
| 452 | |
| 453 | // Wait up to 10 seconds for the log file mutex |
| 454 | wil::unique_handle mutex{CreateMutex(nullptr, true, L"Global\\WslInstallLog")}; |
| 455 | THROW_LAST_ERROR_IF(!mutex); |
| 456 | |
| 457 | THROW_LAST_ERROR_IF(WaitForSingleObject(mutex.get(), 10 * 1000) != WAIT_OBJECT_0); |
| 458 | |
| 459 | wil::unique_handle file{CreateFile( |
| 460 | path.c_str(), GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_ALWAYS, 0, nullptr)}; |
| 461 | |
| 462 | THROW_LAST_ERROR_IF(!file); |
| 463 | |
| 464 | LARGE_INTEGER size{}; |
| 465 | THROW_IF_WIN32_BOOL_FALSE(GetFileSizeEx(file.get(), &size)); |
| 466 | |
| 467 | // Append to the file if its size is below 10MB, otherwise truncate. |
| 468 | if (size.QuadPart < 10 * _1MB) |
| 469 | { |
| 470 | THROW_LAST_ERROR_IF(SetFilePointer(file.get(), 0, nullptr, FILE_END) == INVALID_SET_FILE_POINTER); |
| 471 | } |
| 472 | else |
| 473 | { |
| 474 | THROW_IF_WIN32_BOOL_FALSE(SetEndOfFile(file.get())); |
| 475 | } |
| 476 | |
| 477 | static auto processName = wil::GetModuleFileNameW<std::wstring>(); |
| 478 | auto logLine = std::format("{:%FT%TZ} {}[{}]: {}\n", std::chrono::system_clock::now(), processName, WSL_PACKAGE_VERSION, Content); |
| 479 | |
| 480 | DWORD bytesWritten{}; |
| 481 | THROW_IF_WIN32_BOOL_FALSE(WriteFile(file.get(), logLine.c_str(), static_cast<DWORD>(logLine.size()), &bytesWritten, nullptr)); |
| 482 | } |
| 483 | CATCH_LOG(); |