master
cpp 1,041 lines 36.6 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 DllMain.cpp
8
9 Abstract:
10
11 This file contains various methods used during MSI installation (see package.wix.in)
12
13 --*/
14
15 #include "precomp.h"
16 #include "install.h"
17 #include <msiquery.h>
18 #include <winrt/Windows.ApplicationModel.Core.h>
19 #include <winrt/Windows.Foundation.Collections.h>
20 #include <winrt/windows.management.deployment.h>
21 #include <Sfc.h>
22 #include "defs.h"
23
24 using unique_msi_handle = wil::unique_any<MSIHANDLE, decltype(MsiCloseHandle), &MsiCloseHandle>;
25
26 using namespace wsl::windows::common::registry;
27 using namespace wsl::windows::common::wslutil;
28 using namespace wsl::windows::common::install;
29
30 static constexpr auto c_progIdPrefix{L"App."};
31 static constexpr auto c_protocolProgIdSuffix{L".Protocol"};
32 static constexpr auto c_wslSettingsInstalledDirectoryPropertyName = L"WSLSETTINGS";
33 static constexpr auto c_wslSettingsAppIDPropertyName = L"WSLSETTINGSAPPID";
34 static constexpr auto c_wslSettingsProgIDPropertyName = L"WSLSETTINGSPROGID";
35
36 #define IGNORE_MSIX_ERROR_IF_DIRECT_MSI_EXECUTION_SUPPORTED() \
37 if (DoesBuildSupportDirectMsiExecution()) \
38 { \
39 WSL_LOG( \
40 "IgnoredMsixError", \
41 TraceLoggingValue(wil::ResultFromCaughtException(), "Error"), \
42 TraceLoggingValue(__FUNCTION__, "Stage")); \
43 \
44 return NOERROR; \
45 }
46
47 #define WSL_INSTALL_LOG(Name, ...) \
48 { \
49 WSL_LOG(Name, __VA_ARGS__); \
50 WriteInstallLog(std::format("MSI install: {}", Name)); \
51 }
52
53 #ifndef WSL_OFFICIAL_BUILD
54 void TrustPackageCertificate(LPCWSTR Path)
55 {
56 wil::unique_hcertstore store;
57 wil::unique_hcryptmsg msg;
58
59 WSL_LOG("TrustMSIXCertificate", TraceLoggingValue(Path, "Path"));
60
61 // Retrieve the certificate from the MSIX
62 THROW_IF_WIN32_BOOL_FALSE(CryptQueryObject(
63 CERT_QUERY_OBJECT_FILE, Path, CERT_QUERY_CONTENT_FLAG_ALL, CERT_QUERY_FORMAT_FLAG_ALL, 0, nullptr, nullptr, nullptr, &store, &msg, nullptr));
64
65 const wil::unique_cert_context cert{
66 CertFindCertificateInStore(store.get(), X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_ANY, nullptr, nullptr)};
67 THROW_LAST_ERROR_IF(!cert);
68
69 const wil::unique_hcertstore trustedRoot{
70 CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, CERT_STORE_OPEN_EXISTING_FLAG | CERT_SYSTEM_STORE_LOCAL_MACHINE, L"ROOT")};
71
72 THROW_LAST_ERROR_IF(!trustedRoot);
73
74 THROW_IF_WIN32_BOOL_FALSE(CertAddCertificateContextToStore(trustedRoot.get(), cert.get(), CERT_STORE_ADD_USE_EXISTING, nullptr));
75 }
76 #endif
77
78 winrt::Windows::Management::Deployment::DeploymentResult WaitForDeploymentOperation(
79 const winrt::Windows::Foundation::IAsyncOperationWithProgress<winrt::Windows::Management::Deployment::DeploymentResult, winrt::Windows::Management::Deployment::DeploymentProgress>& operation,
80 const std::source_location& source = std::source_location::current())
81 {
82 // IAsyncOperation::get() installs a completion delegate whose implementation resides in this DLL. The operation can retain
83 // that delegate after get() returns, allowing its final Release() to call into the DLL after MSI unloads it.
84 // To avoid this, poll until the operation is completed.
85 auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { operation.Close(); });
86
87 auto status = operation.Status();
88 while (status == winrt::Windows::Foundation::AsyncStatus::Started)
89 {
90 Sleep(10);
91 status = operation.Status();
92 }
93
94 if (status == winrt::Windows::Foundation::AsyncStatus::Error)
95 {
96 const auto error = operation.ErrorCode();
97 THROW_HR_MSG(error, "Source: %hs() - %hs:%lu", source.function_name(), source.file_name(), source.line());
98 }
99
100 if (status == winrt::Windows::Foundation::AsyncStatus::Canceled)
101 {
102 throw winrt::hresult_canceled();
103 }
104
105 return operation.GetResults();
106 }
107
108 void ThrowIfOperationError(
109 const winrt::Windows::Foundation::IAsyncOperationWithProgress<winrt::Windows::Management::Deployment::DeploymentResult, winrt::Windows::Management::Deployment::DeploymentProgress>& operation,
110 const std::source_location& source = std::source_location::current())
111 {
112 const auto result = WaitForDeploymentOperation(operation, source);
113 THROW_IF_FAILED_MSG(result.ExtendedErrorCode(), "%ls", result.ErrorText().c_str());
114 }
115
116 std::wstring GetMsiProperty(MSIHANDLE install, LPCWSTR name)
117 {
118 DWORD size{};
119 std::wstring output(1, '\0');
120 UINT result = MsiGetProperty(install, name, output.data(), &size);
121 THROW_HR_IF_MSG(E_UNEXPECTED, result != ERROR_SUCCESS && result != ERROR_MORE_DATA, "MsiGetProperty failed with %u", result);
122
123 output.resize(size);
124 size = static_cast<DWORD>(output.size() + 1);
125 result = MsiGetProperty(install, name, output.data(), &size);
126 THROW_HR_IF_MSG(E_UNEXPECTED, result != ERROR_SUCCESS, "MsiGetProperty for '%s' failed with %u", name, result);
127
128 WI_ASSERT(size == output.size());
129 return output;
130 }
131
132 std::wstring GetInstallTarget(MSIHANDLE install)
133 {
134 return GetMsiProperty(install, L"CustomActionData");
135 }
136
137 void DisplayError(MSIHANDLE install, LPCWSTR message)
138 {
139 const unique_msi_handle record{MsiCreateRecord(0)};
140 MsiRecordSetString(record.get(), 0, message);
141
142 MsiProcessMessage(install, INSTALLMESSAGE(INSTALLMESSAGE_ERROR + MB_OK), record.get());
143 }
144
145 void DeleteRegistryKeyIfVolatile(LPCWSTR Parent, LPCWSTR Key)
146 {
147
148 const std::wstring path = Parent + std::wstring(L"\\") + Key;
149 auto [key, error] = wsl::windows::common::registry::OpenKeyNoThrow(HKEY_LOCAL_MACHINE, path.c_str(), KEY_READ);
150
151 if (FAILED(error))
152 {
153 THROW_HR_IF(error, error != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) && error != HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND));
154
155 // The key doesn't exist, nothing to do.
156 return;
157 }
158
159 if (!wsl::windows::common::registry::IsKeyVolatile(key.get()))
160 {
161 // Registry key is not volatile, nothing to do.
162 return;
163 }
164
165 WSL_LOG("CleanMsixRegistryKeys", TraceLoggingValue(Parent, "Parent"), TraceLoggingValue(Key, "Key"));
166
167 const auto parent = wsl::windows::common::registry::OpenKey(HKEY_LOCAL_MACHINE, Parent, KEY_ALL_ACCESS);
168 wsl::windows::common::registry::DeleteKey(parent.get(), Key);
169 }
170
171 bool IsWindowsServerCore()
172 {
173 wil::unique_hkey key;
174 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Server\\ServerLevels", 0, KEY_READ, &key) == ERROR_SUCCESS)
175 {
176 // NanoServer must be 1, or ServerCore must be 1, Server-Gui-Mgmt must be zero or not present, and Server-Gui-Shell must be zero or not present
177 DWORD value = 0;
178 DWORD valueSize = sizeof(value);
179 if ((RegGetValue(key.get(), nullptr, L"NanoServer", RRF_RT_REG_DWORD, nullptr, &value, &valueSize) == ERROR_SUCCESS) && (value == 1))
180 {
181 return true;
182 }
183 else
184 {
185 value = 0;
186 valueSize = sizeof(value);
187 if ((RegGetValue(key.get(), nullptr, L"ServerCore", RRF_RT_REG_DWORD, nullptr, &value, &valueSize) == ERROR_SUCCESS) &&
188 (value == 1))
189 {
190 value = 0;
191 valueSize = sizeof(value);
192 RegGetValue(key.get(), nullptr, L"Server-Gui-Mgmt", (RRF_RT_REG_DWORD | RRF_ZEROONFAILURE), nullptr, &value, &valueSize);
193 if (value == 0)
194 {
195 value = 0;
196 valueSize = sizeof(value);
197 RegGetValue(key.get(), nullptr, L"Server-Gui-Shell", (RRF_RT_REG_DWORD | RRF_ZEROONFAILURE), nullptr, &value, &valueSize);
198 return value == 0;
199 }
200 }
201 }
202 }
203
204 return false;
205 }
206
207 bool IsWindowsServerCoreWithMsiSupport()
208 {
209 return IsWindowsServerCore() && wsl::windows::common::helpers::GetWindowsVersion().BuildNumber >=
210 wsl::windows::common::helpers::WindowsBuildNumbers::Germanium;
211 }
212
213 bool DoesBuildSupportDirectMsiExecution()
214 {
215 const auto buildInfo = wsl::windows::common::helpers::GetWindowsVersion();
216
217 switch (buildInfo.BuildNumber)
218 {
219
220 // For Windows 10, the fix was only serviced to 22h2 and 21h2.
221 case wsl::windows::common::helpers::WindowsBuildNumbers::Vibranium_21H2:
222 return buildInfo.UpdateBuildRevision >= 4529;
223
224 case wsl::windows::common::helpers::WindowsBuildNumbers::Vibranium_22H2:
225 return buildInfo.UpdateBuildRevision >= 4474;
226
227 case wsl::windows::common::helpers::WindowsBuildNumbers::Iron:
228 return buildInfo.UpdateBuildRevision >= 2582;
229
230 case wsl::windows::common::helpers::WindowsBuildNumbers::Cobalt:
231 return false; // cobalt builds aren't serviced anymore, so the fix wasn't backported there.
232
233 case wsl::windows::common::helpers::WindowsBuildNumbers::Nickel:
234 case wsl::windows::common::helpers::WindowsBuildNumbers::Nickel_23H2: // See: https://learn.microsoft.com/en-us/windows/release-health/windows11-release-information
235 return buildInfo.UpdateBuildRevision >= 3672;
236
237 case wsl::windows::common::helpers::WindowsBuildNumbers::Zinc:
238 return buildInfo.UpdateBuildRevision >= 1009;
239
240 default:
241 return buildInfo.BuildNumber >= wsl::windows::common::helpers::WindowsBuildNumbers::Germanium;
242 }
243 }
244
245 void GrantDeletePermissionToSystem(SC_HANDLE Service)
246 {
247 // Get the current security descriptor
248 DWORD bytesNeeded{};
249 THROW_LAST_ERROR_IF(!QueryServiceObjectSecurity(Service, DACL_SECURITY_INFORMATION, nullptr, 0, &bytesNeeded) && GetLastError() != ERROR_INSUFFICIENT_BUFFER);
250
251 std::vector<char> buffer(bytesNeeded);
252 THROW_IF_WIN32_BOOL_FALSE(
253 QueryServiceObjectSecurity(Service, DACL_SECURITY_INFORMATION, buffer.data(), static_cast<DWORD>(buffer.size()), &bytesNeeded));
254
255 // Get the DACL.
256 PACL previousAcl{};
257 BOOL present{};
258 BOOL defaulted{};
259 THROW_IF_WIN32_BOOL_FALSE(GetSecurityDescriptorDacl(buffer.data(), &present, &previousAcl, &defaulted));
260
261 // Build a new ACE for SYSTEM.
262 EXPLICIT_ACCESS access{};
263 std::wstring account = L"SYSTEM";
264 BuildExplicitAccessWithName(&access, account.data(), DELETE, SET_ACCESS, NO_INHERITANCE);
265
266 // Create a new ACL with the new ACE.
267 wsl::windows::common::security::unique_acl newAcl;
268
269 THROW_IF_WIN32_ERROR(SetEntriesInAcl(1, &access, previousAcl, &newAcl));
270
271 // Build a new security descriptor with that ACL.
272 SECURITY_DESCRIPTOR newDescriptor{};
273 THROW_IF_WIN32_BOOL_FALSE(InitializeSecurityDescriptor(&newDescriptor, SECURITY_DESCRIPTOR_REVISION));
274 THROW_IF_WIN32_BOOL_FALSE(SetSecurityDescriptorDacl(&newDescriptor, true, newAcl.get(), false));
275
276 // Update the service's ACL.
277 THROW_IF_WIN32_BOOL_FALSE(SetServiceObjectSecurity(Service, DACL_SECURITY_INFORMATION, &newDescriptor));
278 }
279
280 void RemoveMsixService()
281 try
282 {
283 const wil::unique_schandle manager{OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS)};
284 THROW_LAST_ERROR_IF(!manager);
285
286 wil::unique_schandle wslservice{OpenService(manager.get(), L"wslservice", READ_CONTROL | WRITE_DAC)};
287 if (!wslservice)
288 {
289 THROW_LAST_ERROR_IF(GetLastError() != ERROR_SERVICE_DOES_NOT_EXIST);
290
291 // wslservice doesn't exist, this is expected
292 return;
293 }
294
295 // Sanity check: Validate that this is indeed an MSIX service
296 wil::unique_hkey key = wsl::windows::common::registry::OpenKey(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services", KEY_READ);
297 THROW_LAST_ERROR_IF(!key);
298
299 const auto AppUserModelId = wsl::windows::common::registry::ReadString(key.get(), L"WSLService", L"AppUserModelId", L"");
300 key.reset();
301
302 DWORD DeleteStatus = ERROR_NOT_SUPPORTED;
303
304 if (!AppUserModelId.empty())
305 {
306 GrantDeletePermissionToSystem(wslservice.get());
307 wslservice.reset(OpenService(manager.get(), L"wslservice", DELETE));
308
309 if (DeleteService(wslservice.get()))
310 {
311 DeleteStatus = NO_ERROR;
312 }
313 else
314 {
315 DeleteStatus = GetLastError();
316 }
317 }
318
319 WSL_LOG(
320 "MsixServiceRegistrationFound",
321 TraceLoggingValue(AppUserModelId.c_str(), "AppModelUserId"),
322 TraceLoggingValue(DeleteStatus, "DeleteStatus"));
323 }
324 CATCH_LOG();
325
326 bool RemoveRegistryKeyProtectionImpl(LPCWSTR Path)
327 {
328 if (!SfcIsKeyProtected(HKEY_LOCAL_MACHINE, Path, KEY_WOW64_64KEY))
329 {
330 return false; // The key doesn't exist or isn't protected, nothing to do.
331 }
332
333 // Open the registry key.
334 auto key = wsl::windows::common::registry::OpenKey(HKEY_LOCAL_MACHINE, Path, KEY_READ | KEY_WRITE, REG_OPTION_BACKUP_RESTORE);
335
336 // Get its security descriptor.
337 DWORD bufferSize = 0;
338 auto result = RegGetKeySecurity(key.get(), OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, nullptr, &bufferSize);
339 THROW_WIN32_IF(result, result != ERROR_INSUFFICIENT_BUFFER);
340
341 std::vector<char> buffer(bufferSize);
342
343 result = RegGetKeySecurity(key.get(), OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, buffer.data(), &bufferSize);
344 THROW_IF_WIN32_ERROR(result);
345
346 // Get the ACL from the security descriptor
347 // N.B. 'acl' is stored inside the security descriptor buffer, and so doesn't need to be individually deleted.
348 PACL acl{};
349 BOOL present{};
350 BOOL defaulted{};
351 THROW_IF_WIN32_BOOL_FALSE(GetSecurityDescriptorDacl(reinterpret_cast<PSECURITY_DESCRIPTOR>(buffer.data()), &present, &acl, &defaulted));
352
353 // Grant write access to local administrator group.
354 // N.B. A registry key is considered protected if:
355 // - TrustedInstaller has GENERIC_ALL or KEY_FULL_ACCESS granted
356 // - No other ACL grants write access to anyone else
357 // - No deny ACL is set for TrustedInstaller
358 auto [localAdministratorsSid, sidBuffer] =
359 wsl::windows::common::security::CreateSid(SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS);
360
361 EXPLICIT_ACCESS newAce{};
362 newAce.grfAccessMode = GRANT_ACCESS;
363 newAce.grfAccessPermissions = KEY_WRITE;
364 newAce.grfInheritance = NO_INHERITANCE;
365 BuildTrusteeWithSid(&newAce.Trustee, localAdministratorsSid);
366
367 // Create an updated ACL.
368 wsl::windows::common::security::unique_acl newAcl{};
369 THROW_IF_WIN32_ERROR(SetEntriesInAcl(1, &newAce, acl, &newAcl));
370
371 // Create a new security descriptor with the updated ACL.
372 SECURITY_DESCRIPTOR newDescriptor{};
373 THROW_IF_WIN32_BOOL_FALSE(InitializeSecurityDescriptor(&newDescriptor, SECURITY_DESCRIPTOR_REVISION));
374 THROW_IF_WIN32_BOOL_FALSE(SetSecurityDescriptorDacl(&newDescriptor, true, newAcl.get(), false));
375
376 // Update the key security descriptor.
377 THROW_IF_WIN32_ERROR_MSG(
378 RegSetKeySecurity(key.get(), DACL_SECURITY_INFORMATION, &newDescriptor), "Failed to update key security for key: %ls", Path);
379
380 key.reset();
381
382 if constexpr (wsl::shared::Debug)
383 {
384 THROW_HR_IF_MSG(
385 E_FAIL, SfcIsKeyProtected(HKEY_LOCAL_MACHINE, Path, KEY_WOW64_64KEY), "Failed to remove protection for key: %ls", Path);
386 }
387
388 return true;
389 }
390
391 extern "C" UINT __stdcall RemoveRegistryKeyProtections(MSIHANDLE install)
392 {
393 try
394 {
395 auto restore = wsl::windows::common::security::AcquirePrivileges({SE_BACKUP_NAME, SE_RESTORE_NAME});
396
397 for (const auto* key : {
398 LR"(SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\IdListAliasTranslations\WSL)",
399 LR"(SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\IdListAliasTranslations\WSLLegacy)",
400 LR"(SOFTWARE\Classes\Directory\Background\shell\WSL)",
401 LR"(SOFTWARE\Classes\Directory\Background\shell\WSL\command)",
402 LR"(SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\{B2B4A4D1-2754-4140-A2EB-9A76D9D7CDC6})",
403 LR"(SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel)",
404 })
405 {
406 bool updated = false;
407 auto result = wil::ResultFromException([&updated, key]() { updated = RemoveRegistryKeyProtectionImpl(key); });
408
409 if (updated || FAILED(result))
410 {
411 WSL_LOG(
412 "RemoveKeyProtection",
413 TraceLoggingValue(key, "key"),
414 TraceLoggingValue(result, "error"),
415 TraceLoggingValue(updated, "updated"));
416 }
417 }
418 }
419 CATCH_LOG();
420
421 return NOERROR;
422 }
423
424 bool CleanExplorerShortcutFlags(LPCWSTR Sid)
425 {
426 constexpr auto valueName = L"Attributes";
427
428 const auto keyPath = std::format(
429 LR"({}\Software\Microsoft\Windows\CurrentVersion\Explorer\CLSID\{{B2B4A4D1-2754-4140-A2EB-9A76D9D7CDC6}}\ShellFolder)", Sid);
430
431 auto [key, result] = wsl::windows::common::registry::OpenKeyNoThrow(HKEY_USERS, keyPath.c_str(), KEY_READ | KEY_WRITE);
432
433 if (!SUCCEEDED(result))
434 {
435 // Either the key doesn't exist, or the user isn't logged in.
436 THROW_HR_IF(result, result != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) && result != HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND));
437 return false;
438 }
439
440 auto flags = wsl::windows::common::registry::ReadDword(key.get(), nullptr, valueName, 0);
441 if (WI_IsFlagClear(flags, SFGAO_NONENUMERATED))
442 {
443 // The problematic flag is not set, nothing to do.
444 return false;
445 }
446
447 WI_ClearFlag(flags, SFGAO_NONENUMERATED);
448
449 wsl::windows::common::registry::WriteDword(key.get(), nullptr, valueName, flags);
450 return true;
451 }
452
453 extern "C" UINT __stdcall CleanExplorerState(MSIHANDLE install)
454 {
455 // N.B. This method is imperfect because it can only access the registry hives of logged in users.
456
457 try
458 {
459 const auto profiles = wsl::windows::common::registry::OpenKey(
460 HKEY_LOCAL_MACHINE, LR"(SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList)", KEY_READ);
461
462 // List all available profiles on the machine.
463 for (const auto& [name, key] : wsl::windows::common::registry::EnumKeys(profiles.get(), KEY_READ))
464 {
465 // Look for full profiles.
466 if (wsl::windows::common::registry::ReadDword(key.get(), nullptr, L"FullProfile", 0))
467 {
468 bool changed = false;
469 auto result = wil::ResultFromException([&]() { changed = CleanExplorerShortcutFlags(name.c_str()); });
470
471 if (changed || FAILED(result))
472 {
473 WSL_LOG(
474 "ClearExplorerFlag",
475 TraceLoggingValue(name.c_str(), "sid"),
476 TraceLoggingValue(result, "error"),
477 TraceLoggingValue(changed, "changed"));
478 }
479 }
480 }
481 }
482 CATCH_LOG();
483
484 return NOERROR;
485 }
486
487 extern "C" UINT __stdcall CleanMsixState(MSIHANDLE install)
488 {
489 try
490 {
491 WSL_LOG("CleanMsixState");
492
493 const std::map<LPCWSTR, LPCWSTR> keys{
494 {L"SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application", L"WSL"},
495 {L"SOFTWARE\\Classes\\CLSID", L"{7e6ad219-d1b3-42d5-b8ee-d96324e64ff6}"},
496 {L"SOFTWARE\\Classes\\AppID", L"{17696EAC-9568-4CF5-BB8C-82515AAD6C09}"},
497 {L"SOFTWARE\\Microsoft\\Terminal Server Client", L"Default"},
498 {L"SOFTWARE\\Microsoft\\Terminal Server Client\\Default", L"OptionalAddIns"},
499 {L"SOFTWARE\\Microsoft\\Terminal Server Client\\Default\\OptionalAddIns", L"WSLDVC_PACKAGE"}};
500
501 for (const auto& e : keys)
502 {
503 try
504 {
505 DeleteRegistryKeyIfVolatile(e.first, e.second);
506 }
507 catch (...)
508 {
509 LOG_CAUGHT_EXCEPTION_MSG("Failed to clear registry key: %ls/%ls", e.first, e.second);
510 }
511 }
512
513 /*
514 * Because of a probable bug in MSIX / Packaged COM, it's possible that an old registration is still present on the machine,
515 * which will break instantiations of LxssUserSessions.
516 * Because this method executes after all MSIX packages have been removed, we know that this registration shouldn't be there,
517 * so delete it if it still happens to be there.
518 * See: https://github.com/microsoft/WSL/issues/10782
519 */
520
521 try
522 {
523
524 const auto packagedComClassIndex{
525 wsl::windows::common::registry::OpenKey(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Classes\\PackagedCom\\ClassIndex", KEY_WRITE)};
526
527 if (wsl::windows::common::registry::DeleteKey(packagedComClassIndex.get(), L"{A9B7A1B9-0671-405C-95F1-E0612CB4CE7E}"))
528 {
529 WSL_LOG("OldComRegistrationCleared");
530 }
531 }
532 CATCH_LOG();
533
534 /*
535 * Because of another probable MSIX bug, wslservice can sometimes be left even after an WSL < 2.0 package is removed, which causes the installation to fail.
536 * If found, we delete the service registration.
537 * See: https://github.com/microsoft/WSL/issues/10831
538 */
539
540 RemoveMsixService();
541 }
542 catch (...)
543 {
544 LOG_CAUGHT_EXCEPTION();
545 }
546
547 // Always succeed here since failure in this method aren't fatal.
548 return NOERROR;
549 }
550
551 extern "C" UINT __stdcall DeprovisionMsix(MSIHANDLE install)
552 try
553 {
554 WSL_INSTALL_LOG("DeprovisionMsix");
555
556 const winrt::Windows::Management::Deployment::PackageManager packageManager;
557 const auto result = WaitForDeploymentOperation(
558 packageManager.DeprovisionPackageForAllUsersAsync(wsl::windows::common::wslutil::c_msixPackageFamilyName));
559 LOG_IF_FAILED_MSG(result.ExtendedErrorCode(), "%ls", result.ErrorText().c_str());
560
561 return NOERROR;
562 }
563 catch (...)
564 {
565 LOG_CAUGHT_EXCEPTION();
566
567 IGNORE_MSIX_ERROR_IF_DIRECT_MSI_EXECUTION_SUPPORTED();
568
569 const auto error = wsl::windows::common::wslutil::GetErrorString(wil::ResultFromCaughtException());
570 DisplayError(install, wsl::shared::Localization::MessagedFailedToRemoveMsix(error).c_str());
571
572 return ERROR_INSTALL_FAILURE;
573 }
574
575 extern "C" UINT __stdcall RemoveMsixAsSystem(MSIHANDLE install)
576 try
577 {
578 WSL_INSTALL_LOG("RemoveMsixAsSystem");
579
580 const winrt::Windows::Management::Deployment::PackageManager packageManager;
581
582 for (const auto& e : packageManager.FindPackages(wsl::windows::common::wslutil::c_msixPackageFamilyName))
583 {
584 WSL_LOG("RemovePackage", TraceLoggingValue(e.Id().FullName().c_str(), "FullName"));
585
586 ThrowIfOperationError(packageManager.RemovePackageAsync(
587 e.Id().FullName(), winrt::Windows::Management::Deployment::RemovalOptions::RemoveForAllUsers));
588 }
589
590 return NOERROR;
591 }
592 catch (...)
593 {
594 LOG_CAUGHT_EXCEPTION();
595
596 IGNORE_MSIX_ERROR_IF_DIRECT_MSI_EXECUTION_SUPPORTED();
597
598 const auto error = wsl::windows::common::wslutil::GetErrorString(wil::ResultFromCaughtException());
599 DisplayError(install, wsl::shared::Localization::MessagedFailedToRemoveMsix(error).c_str());
600
601 return ERROR_INSTALL_FAILURE;
602 }
603
604 extern "C" UINT __stdcall RemoveMsixAsUser(MSIHANDLE install)
605 try
606 {
607 WSL_INSTALL_LOG("RemoveMsixAsUser");
608
609 const winrt::Windows::Management::Deployment::PackageManager packageManager;
610
611 for (const auto& e : packageManager.FindPackagesForUser(L"", wsl::windows::common::wslutil::c_msixPackageFamilyName))
612 {
613 WSL_LOG("RemovePackage", TraceLoggingValue(e.Id().FullName().c_str(), "FullName"));
614
615 ThrowIfOperationError(packageManager.RemovePackageAsync(e.Id().FullName()));
616 }
617
618 return NOERROR;
619 }
620 catch (...)
621 {
622 LOG_CAUGHT_EXCEPTION();
623
624 IGNORE_MSIX_ERROR_IF_DIRECT_MSI_EXECUTION_SUPPORTED();
625
626 const auto error = wsl::windows::common::wslutil::GetErrorString(wil::ResultFromCaughtException());
627 DisplayError(install, wsl::shared::Localization::MessagedFailedToRemoveMsix(error).c_str());
628
629 return ERROR_INSTALL_FAILURE;
630 }
631
632 wsl::windows::common::filesystem::TempFile ExtractMsix(MSIHANDLE install)
633 {
634 // N.B. We need to open the database this way instead of calling MsiGetActiveDatabase() because
635 // this is deferred action so we don't have access to the MSI context here.
636 // The MSIX needs to be extracted like this because in the case of an upgrade this action runs before 'MoveFiles' so the WSL directory isn't available yet.
637
638 const auto installTarget = GetInstallTarget(install);
639
640 unique_msi_handle database;
641 THROW_IF_WIN32_ERROR_MSG(
642 MsiOpenDatabase(installTarget.c_str(), MSIDBOPEN_READONLY, &database), "Failed to open database: %ls", installTarget.c_str());
643
644 THROW_LAST_ERROR_IF(!database);
645
646 unique_msi_handle view;
647 THROW_IF_WIN32_ERROR(MsiDatabaseOpenView(database.get(), L"SELECT Data,Name FROM Binary WHERE Name='msixpackage'", &view));
648
649 THROW_IF_WIN32_ERROR(MsiViewExecute(view.get(), NULL));
650
651 unique_msi_handle record;
652 THROW_IF_WIN32_ERROR(MsiViewFetch(view.get(), &record));
653
654 auto file = wsl::windows::common::filesystem::TempFile(
655 GENERIC_WRITE, 0, CREATE_ALWAYS, wsl::windows::common::filesystem::TempFileFlags::None, L"msix");
656
657 std::vector<char> buffer(1024 * 1024);
658 while (true)
659 {
660 DWORD size = static_cast<DWORD>(buffer.size());
661 THROW_IF_WIN32_ERROR(MsiRecordReadStream(record.get(), 1, buffer.data(), &size));
662 THROW_IF_WIN32_BOOL_FALSE(WriteFile(file.Handle.get(), buffer.data(), size, nullptr, nullptr));
663
664 if (size < buffer.size())
665 {
666 break;
667 }
668 }
669
670 return file;
671 }
672
673 extern "C" UINT __stdcall InstallMsixAsUser(MSIHANDLE install)
674 try
675 {
676 WSL_INSTALL_LOG("InstallMsixAsUser");
677
678 // RegisterPackageByFamilyNameAsync() cannot be run as SYSTEM.
679 // If this thread runs as SYSTEM, simply skip this step.
680 if (wsl::windows::common::security::IsTokenLocalSystem(nullptr))
681 {
682 WSL_LOG("InstallMsixAsUserSkipped");
683 return NOERROR;
684 }
685
686 const winrt::Windows::Management::Deployment::PackageManager packageManager;
687 ThrowIfOperationError(packageManager.RegisterPackageByFamilyNameAsync(
688 wsl::windows::common::wslutil::c_msixPackageFamilyName,
689 nullptr,
690 winrt::Windows::Management::Deployment::DeploymentOptions::ForceTargetApplicationShutdown |
691 winrt::Windows::Management::Deployment::DeploymentOptions::ForceApplicationShutdown,
692 nullptr,
693 nullptr));
694
695 return NOERROR;
696 }
697 catch (...)
698 {
699 LOG_CAUGHT_EXCEPTION();
700
701 IGNORE_MSIX_ERROR_IF_DIRECT_MSI_EXECUTION_SUPPORTED();
702
703 const auto errorCode = wil::ResultFromCaughtException();
704
705 const auto error = wsl::windows::common::wslutil::GetErrorString(errorCode);
706 DisplayError(install, wsl::shared::Localization::MessagedFailedToInstallMsix(error).c_str());
707
708 return ERROR_INSTALL_FAILURE;
709 }
710
711 extern "C" UINT __stdcall InstallMsix(MSIHANDLE install)
712 try
713 {
714 auto msixFile = ExtractMsix(install);
715
716 // Release a file handle to the MSIX file so that it can be installed.
717 msixFile.Handle.reset();
718
719 WSL_INSTALL_LOG("InstallMsix", TraceLoggingValue(msixFile.Path.c_str(), "Path"));
720
721 winrt::Windows::Management::Deployment::PackageManager packageManager;
722
723 winrt::Windows::Foundation::Uri uri(msixFile.Path.c_str());
724 winrt::Windows::Management::Deployment::StagePackageOptions options;
725 options.ForceUpdateFromAnyVersion(true);
726
727 try
728 {
729 try
730 {
731 ThrowIfOperationError(packageManager.StagePackageByUriAsync(uri, options));
732 }
733 catch (...)
734 {
735 // For convenience, automatically trust the MSIX's certificate if this is NOT an official build and
736 // the package installation failed because of an untrusted certificate.
737 #ifndef WSL_OFFICIAL_BUILD
738 auto error = wil::ResultFromCaughtException();
739 if (error == CERT_E_UNTRUSTEDROOT)
740 {
741 TrustPackageCertificate(msixFile.Path.c_str());
742 ThrowIfOperationError(packageManager.StagePackageByUriAsync(uri, options));
743 }
744 #else
745 throw;
746 #endif
747 }
748
749 ThrowIfOperationError(packageManager.ProvisionPackageForAllUsersAsync(wsl::windows::common::wslutil::c_msixPackageFamilyName));
750 }
751 catch (...)
752 {
753 // On Windows Server, ProvisionPackageForAllUsersAsync() fails with ERROR_NOT_SUPPORTED or ERROR_INSTALL_FAILED.
754 // Using powershell as a fallback in case we hit this issue.
755 auto error = wil::ResultFromCaughtException();
756 if ((error == REGDB_E_CLASSNOTREG || error == HRESULT_FROM_WIN32(ERROR_INSTALL_REGISTRATION_FAILURE) ||
757 error == HRESULT_FROM_WIN32(ERROR_INSTALL_PACKAGE_NOT_FOUND)) &&
758 IsWindowsServerCoreWithMsiSupport())
759 {
760 // MSIX applications are not supported on ServerCore SKU's so as long as this build has direct MSI support
761 // the installation can continue.
762 return NOERROR;
763 }
764 else if ((error == HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED) || error == HRESULT_FROM_WIN32(ERROR_INSTALL_FAILED)) && IsWindowsServer())
765 {
766 std::wstring commandLine;
767 wil::GetSystemDirectoryW(commandLine);
768
769 // N.B. powershell is always installed under 'v1.0' so this path is constant.
770 commandLine +=
771 L"\\WindowsPowerShell\\v1.0\\powershell.exe -ExecutionPolicy Bypass -NoProfile -NonInteractive -Command "
772 L"Add-AppxProvisionedPackage "
773 L"-Online -PackagePath \"" +
774 msixFile.Path.wstring() + L"\" -SkipLicense";
775
776 WSL_LOG("CallPS", TraceLoggingValue(commandLine.c_str(), "CommandLine"));
777
778 wsl::windows::common::SubProcess process(nullptr, commandLine.c_str());
779 process.SetFlags(CREATE_NO_WINDOW);
780 process.SetShowWindow(SW_HIDE);
781
782 auto output = process.RunAndCaptureOutput();
783 if (output.ExitCode != 0)
784 {
785 if (output.Stderr.size() > 250) // Limit how big the error message can get
786 {
787 output.Stderr.resize(250);
788 }
789
790 DisplayError(install, wsl::shared::Localization::MessagedFailedToInstallMsix(output.Stderr).c_str());
791
792 return ERROR_INSTALL_FAILURE;
793 }
794 }
795 else
796 {
797 THROW_IF_FAILED(error);
798 }
799 }
800
801 WSL_LOG("InstallMsixComplete");
802
803 return NOERROR;
804 }
805 catch (...)
806 {
807 LOG_CAUGHT_EXCEPTION();
808
809 IGNORE_MSIX_ERROR_IF_DIRECT_MSI_EXECUTION_SUPPORTED();
810
811 auto error = wsl::windows::common::wslutil::GetErrorString(wil::ResultFromCaughtException());
812 DisplayError(install, wsl::shared::Localization::MessagedFailedToInstallMsix(error).c_str());
813
814 return ERROR_INSTALL_FAILURE;
815 }
816
817 extern "C" UINT __stdcall WslFinalizeInstallation(MSIHANDLE install)
818 {
819 try
820 {
821 WSL_INSTALL_LOG("WslFinalizeInstallation");
822 }
823 CATCH_LOG();
824
825 return NOERROR;
826 }
827
828 extern "C" UINT __stdcall WslValidateInstallation(MSIHANDLE install)
829 try
830 {
831 WSL_INSTALL_LOG("WslValidateInstallation");
832
833 // TODO: Use a more precise version check so we don't install if the Windows build doesn't support lifted.
834
835 if (wsl::windows::common::helpers::GetWindowsVersion().BuildNumber < wsl::windows::common::helpers::Vibranium)
836 {
837 DisplayError(install, wsl::windows::common::wslutil::GetErrorString(WSL_E_OS_NOT_SUPPORTED).c_str());
838 return ERROR_INSTALL_FAILURE;
839 }
840
841 return NOERROR;
842 }
843 catch (...)
844 {
845 LOG_CAUGHT_EXCEPTION();
846
847 return ERROR_INSTALL_FAILURE;
848 }
849
850 void RegisterLspCategoriesImpl(DWORD flags)
851 {
852 const auto installRoot = wsl::windows::common::wslutil::GetMsiPackagePath();
853 THROW_HR_IF(E_INVALIDARG, !installRoot.has_value());
854
855 for (const auto& e : {L"wsl.exe", L"wslhost.exe", L"wslrelay.exe", L"wslg.exe", L"wslservice.exe"})
856 {
857 auto executable = installRoot.value() + e;
858 INT error{};
859
860 DWORD previous{};
861 LOG_HR_IF_MSG(
862 E_UNEXPECTED,
863 WSCSetApplicationCategory(executable.c_str(), static_cast<DWORD>(executable.size()), nullptr, 0, flags, &previous, &error) == SOCKET_ERROR,
864 "Failed to register LSP category for : %ls, flags: %lu, error: %i",
865 executable.c_str(),
866 flags,
867 error);
868 }
869 }
870
871 extern "C" UINT __stdcall RegisterLspCategories(MSIHANDLE install)
872 {
873 /*
874 * This logic is required because some VPN providers register LSP components that break WSL.
875 * See: https://github.com/microsoft/WSL/issues/4177/
876 */
877
878 try
879 {
880 WSL_LOG("RegisterLspCategories");
881 RegisterLspCategoriesImpl(LSP_SYSTEM);
882 }
883 CATCH_LOG();
884
885 // Failures in this method aren't fatal.
886 return NOERROR;
887 }
888
889 extern "C" UINT __stdcall UnregisterLspCategories(MSIHANDLE install)
890 {
891 try
892 {
893 WSL_LOG("UnregisterLspCategories");
894 RegisterLspCategoriesImpl(0); // '0' means removing the entry.
895 }
896 CATCH_LOG();
897
898 // Failures in this method aren't fatal.
899 return NOERROR;
900 }
901
902 std::wstring GetWslSettingsInstalledExePath(MSIHANDLE install)
903 {
904 const auto wslSettingsInstallFolder = GetMsiProperty(install, c_wslSettingsInstalledDirectoryPropertyName);
905 THROW_HR_IF_MSG(E_UNEXPECTED, wslSettingsInstallFolder.empty(), "GetMsiProperty for '%s' resulted in unexpected empty string", c_wslSettingsInstalledDirectoryPropertyName);
906
907 auto wslSettingsInstalledExePath = std::filesystem::path(wslSettingsInstallFolder) / L"wslsettings.exe";
908 return wslSettingsInstalledExePath.make_preferred().wstring();
909 }
910
911 // The following function is borrowed directly from the Windows App SDK
912 std::wstring ComputeAppId(const std::wstring& seed)
913 {
914 // Prefix = App -- Simple human readable piece to help organize these together.
915 // AppId = Prefix + Hash(seed)
916
917 const std::hash<std::wstring> hasher;
918 const auto hash = hasher(seed);
919 uint64_t hash64 = static_cast<uint64_t>(hash);
920
921 // Simulate a larger hash on 32bit platforms to keep the id length consistent.
922 if constexpr (sizeof(size_t) < sizeof(uint64_t))
923 {
924 hash64 = (static_cast<uint64_t>(hash) << 32) | static_cast<uint64_t>(hash);
925 }
926
927 wchar_t hashString[17]{}; // 16 + 1 characters for 64bit value represented as a string with a null terminator.
928 THROW_IF_FAILED(StringCchPrintf(hashString, _countof(hashString), L"%I64x", hash64));
929
930 std::wstring result{c_progIdPrefix};
931 result += hashString;
932 return result;
933 }
934
935 std::wstring ComputeProgId(const std::wstring& appId)
936 {
937 return std::wstring(appId + c_protocolProgIdSuffix);
938 }
939
940 extern "C" UINT __stdcall CalculateWslSettingsProtocolIds(MSIHANDLE install)
941 {
942 try
943 {
944 WSL_LOG("CalculateWslSettingsProtocolIds");
945
946 const auto wslSettingsInstalledExePath = GetWslSettingsInstalledExePath(install);
947 THROW_HR_IF_MSG(
948 E_UNEXPECTED,
949 wslSettingsInstalledExePath.empty(),
950 "Fetching WSL Settings installed exe path resulted in unexpected empty string");
951
952 const auto appId = ComputeAppId(wslSettingsInstalledExePath);
953 const auto progId = ComputeProgId(appId);
954
955 UINT result = MsiSetProperty(install, c_wslSettingsAppIDPropertyName, appId.c_str());
956 THROW_HR_IF_MSG(E_UNEXPECTED, result != ERROR_SUCCESS, "MsiSetProperty for '%s' failed with %u", c_wslSettingsAppIDPropertyName, result);
957
958 result = MsiSetProperty(install, c_wslSettingsProgIDPropertyName, progId.c_str());
959 THROW_HR_IF_MSG(E_UNEXPECTED, result != ERROR_SUCCESS, "MsiSetProperty for '%s' failed with %u", c_wslSettingsProgIDPropertyName, result);
960 }
961 CATCH_LOG();
962
963 // Failures in this method aren't fatal.
964
965 return NOERROR;
966 }
967
968 static void SetWslServiceStartType(DWORD StartType)
969 {
970 const wil::unique_schandle manager{OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT)};
971 THROW_LAST_ERROR_IF(!manager);
972
973 const wil::unique_schandle service{OpenServiceW(manager.get(), L"WSLService", SERVICE_CHANGE_CONFIG)};
974 if (!service)
975 {
976 const auto error = GetLastError();
977 if (error == ERROR_SERVICE_DOES_NOT_EXIST)
978 {
979 return;
980 }
981 THROW_WIN32(error);
982 }
983
984 THROW_IF_WIN32_BOOL_FALSE(ChangeServiceConfigW(
985 service.get(), SERVICE_NO_CHANGE, StartType, SERVICE_NO_CHANGE, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr));
986 }
987
988 extern "C" UINT __stdcall DisableWslService(MSIHANDLE install)
989 {
990 try
991 {
992 WSL_INSTALL_LOG("DisableWslService");
993 SetWslServiceStartType(SERVICE_DISABLED);
994 }
995 CATCH_LOG();
996
997 return NOERROR;
998 }
999
1000 extern "C" UINT __stdcall EnableWslService(MSIHANDLE install)
1001 {
1002 try
1003 {
1004 WSL_INSTALL_LOG("EnableWslService");
1005 SetWslServiceStartType(SERVICE_AUTO_START);
1006 }
1007 CATCH_LOG();
1008
1009 return NOERROR;
1010 }
1011
1012 #ifndef WSL_OFFICIAL_BUILD
1013 extern "C" __declspec(dllexport) UINT __stdcall WslTestForceInstallFailure(MSIHANDLE install)
1014 {
1015 try
1016 {
1017 WSL_INSTALL_LOG("WslTestForceInstallFailure", TraceLoggingValue("Forcing install failure for rollback testing", "Reason"));
1018 }
1019 CATCH_LOG();
1020
1021 return ERROR_INSTALL_FAILURE;
1022 }
1023 #endif
1024
1025 EXTERN_C BOOL STDAPICALLTYPE DllMain(_In_ HINSTANCE Instance, _In_ DWORD Reason, _In_opt_ LPVOID Reserved)
1026 {
1027 wil::DLLMain(Instance, Reason, Reserved);
1028
1029 switch (Reason)
1030 {
1031 case DLL_PROCESS_ATTACH:
1032 WslTraceLoggingInitialize(LxssTelemetryProvider, false);
1033 break;
1034
1035 case DLL_PROCESS_DETACH:
1036 WslTraceLoggingUninitialize();
1037 break;
1038 }
1039
1040 return TRUE;
1041 }