[wue] add automatic local account creation and regional settings duplication

* Local account is created with the same name as the current user along with an *empty* password
  (which we force the user to change on next logon). This is done to assuage users who might be
  weary of entering a password in a third party application, and has the benefit of enabling
  autologon when the install is complete.
* Note that the creation of a local account through an answer file prevents Windows 11 22H2
  from bugging users about MSA *even with an active network connection*.
* For convenience reasons, only duplication of the current username is enabled. We *may* add a
  dialog to enter any random username in a future version, but for 3.20, this is all you get.
* Likewise, the locale duplication is only carried out during OOBE and *not* WinPE (which means
  that you still get the initial "Windows setup language and user preferences" prompt). This is
  intentional as otherwise the default screen and "Repair Windows" options are not presented.
* It's not my fault that the Windows password change screen is super ill conceived, whereas it
  doesn't hide the current password field as it should when the current password is blank, and
  one needs to click on a very small arrow to get the changes applied, instead of a PROMINENT
  button that should intuitively have been positioned right next to "Cancel".
* If you want to complain that we should just "present the user with XYZ and be done with it",
  please bear in mind that we can't add new dialogs to Rufus as willy-nilly as you believe we
  can. *ANY* new UI interface requires major planning, which is the reason why, for the time
  being, we are limited to reusing a simple dissociated list of checkboxes for all WUE options.
This commit is contained in:
Pete Batard 2022-07-19 19:06:42 +01:00
parent 5117a3b4a8
commit 9073962faf
No known key found for this signature in database
GPG key ID: 38E0CF5E69EDD671
10 changed files with 204 additions and 127 deletions

View file

@ -78,7 +78,7 @@ static BOOL app_changed_label = FALSE;
static BOOL allowed_filesystem[FS_MAX] = { 0 };
static int64_t last_iso_blocking_status;
static int selected_pt = -1, selected_fs = FS_UNKNOWN, preselected_fs = FS_UNKNOWN;
static int image_index = 0, select_index = 0, unattend_xml_mask = UNATTEND_DEFAULT_SELECTION;
static int image_index = 0, select_index = 0, unattend_xml_mask = UNATTEND_DEFAULT_SELECTION_MASK;
static RECT relaunch_rc = { -65536, -65536, 0, 0};
static UINT uMBRChecked = BST_UNCHECKED;
static HANDLE format_thread = NULL;
@ -125,7 +125,7 @@ BOOL write_as_image = FALSE, write_as_esp = FALSE, use_vds = FALSE, ignore_boot_
BOOL appstore_version = FALSE, is_vds_available = TRUE;
float fScale = 1.0f;
int dialog_showing = 0, selection_default = BT_IMAGE, persistence_unit_selection = -1, imop_win_sel = 0;
int default_fs, fs_type, boot_type, partition_type, target_type, unattend_xml_selection = 0;
int default_fs, fs_type, boot_type, partition_type, target_type, unattend_xml_flags = 0;
int force_update = 0, default_thread_priority = THREAD_PRIORITY_ABOVE_NORMAL;
char szFolderPath[MAX_PATH], app_dir[MAX_PATH], system_dir[MAX_PATH], temp_dir[MAX_PATH], sysnative_dir[MAX_PATH];
char app_data_dir[MAX_PATH], user_dir[MAX_PATH];
@ -1250,14 +1250,21 @@ out:
return ret;
}
static char* CreateUnattendXml(int arch, int mask)
/// <summary>
/// Create an installation answer file containing the sections specified by the flags.
/// </summary>
/// <param name="arch">The processor architecture of the Windows image being used.</param>
/// <param name="flags">A bitmask representing the sections to enable.
/// See "Windows User Experience flags and masks" from in rufus.h</param>
/// <returns>The path of a newly created answer file on success or NULL on error.</returns>
static char* CreateUnattendXml(int arch, int flags)
{
static char path[MAX_PATH];
FILE* fd;
int i, order;
const char* xml_arch_names[5] = { "x86", "amd64", "arm", "arm64" };
unattend_xml_selection = mask;
if (arch < ARCH_X86_32 || arch >= ARCH_ARM_64 || mask == 0)
unattend_xml_flags = flags;
if (arch < ARCH_X86_32 || arch >= ARCH_ARM_64 || flags == 0)
return NULL;
arch--;
// coverity[swapped_arguments]
@ -1274,7 +1281,7 @@ static char* CreateUnattendXml(int arch, int mask)
// as alters the layout and options of the initial Windows installer screens, which may scare users.
// So, in format.c, we'll try to insert the registry keys directly and drop this section. However,
// because Microsoft prevents Store apps from editing an offline registry, we do need this fallback.
if (mask & UNATTEND_WINPE_SETUP_MASK) {
if (flags & UNATTEND_WINPE_SETUP_MASK) {
order = 1;
fprintf(fd, " <settings pass=\"windowsPE\">\n");
fprintf(fd, " <component name=\"Microsoft-Windows-Setup\" processorArchitecture=\"%s\" language=\"neutral\" "
@ -1288,7 +1295,7 @@ static char* CreateUnattendXml(int arch, int mask)
fprintf(fd, " </UserData>\n");
fprintf(fd, " <RunSynchronous>\n");
for (i = 0; i < ARRAYSIZE(bypass_name); i++) {
if (!(mask & (1 << (i/2))))
if (!(flags & (1 << (i/2))))
continue;
fprintf(fd, " <RunSynchronousCommand wcm:action=\"add\">\n");
fprintf(fd, " <Order>%d</Order>\n", order++);
@ -1300,7 +1307,7 @@ static char* CreateUnattendXml(int arch, int mask)
fprintf(fd, " </settings>\n");
}
if (mask & UNATTEND_SPECIALIZE_DEPLOYMENT_MASK) {
if (flags & UNATTEND_SPECIALIZE_DEPLOYMENT_MASK) {
order = 1;
fprintf(fd, " <settings pass=\"specialize\">\n");
fprintf(fd, " <component name=\"Microsoft-Windows-Deployment\" processorArchitecture=\"%s\" language=\"neutral\" "
@ -1308,7 +1315,7 @@ static char* CreateUnattendXml(int arch, int mask)
"publicKeyToken=\"31bf3856ad364e35\" versionScope=\"nonSxS\">\n", xml_arch_names[arch]);
fprintf(fd, " <RunSynchronous>\n");
// This part was picked from https://github.com/AveYo/MediaCreationTool.bat/blob/main/bypass11/AutoUnattend.xml
if (mask & UNATTEND_NO_ONLINE_ACCOUNT_MASK) {
if (flags & UNATTEND_NO_ONLINE_ACCOUNT) {
fprintf(fd, " <RunSynchronousCommand wcm:action=\"add\">\n");
fprintf(fd, " <Order>%d</Order>\n", order++);
fprintf(fd, " <Path>reg add HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\OOBE /v BypassNRO /t REG_DWORD /d 1 /f</Path>\n");
@ -1319,27 +1326,82 @@ static char* CreateUnattendXml(int arch, int mask)
fprintf(fd, " </settings>\n");
}
if (mask & UNATTEND_OOBE_SHELL_SETUP) {
if (flags & UNATTEND_OOBE_MASK) {
order = 1;
fprintf(fd, " <settings pass=\"oobeSystem\">\n");
fprintf(fd, " <component name=\"Microsoft-Windows-Shell-Setup\" processorArchitecture=\"%s\" language=\"neutral\" "
"xmlns:wcm=\"http://schemas.microsoft.com/WMIConfig/2002/State\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
"publicKeyToken=\"31bf3856ad364e35\" versionScope=\"nonSxS\">\n", xml_arch_names[arch]);
// https://docs.microsoft.com/en-us/windows-hardware/customize/desktop/unattend/microsoft-windows-shell-setup-oobe-protectyourpc
// It is really super insidous of Microsoft to call this option "ProtectYourPC", when it's really only about
// data collection. But of course, if it was called "AllowDataCollection", everyone would turn it off...
if (mask & UNATTEND_NO_DATA_COLLECTION_MASK) {
fprintf(fd, " <OOBE>\n");
fprintf(fd, " <ProtectYourPC>3</ProtectYourPC>\n");
fprintf(fd, " </OOBE>\n");
if (flags & UNATTEND_OOBE_SHELL_SETUP_MASK) {
fprintf(fd, " <component name=\"Microsoft-Windows-Shell-Setup\" processorArchitecture=\"%s\" language=\"neutral\" "
"xmlns:wcm=\"http://schemas.microsoft.com/WMIConfig/2002/State\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
"publicKeyToken=\"31bf3856ad364e35\" versionScope=\"nonSxS\">\n", xml_arch_names[arch]);
// https://docs.microsoft.com/en-us/windows-hardware/customize/desktop/unattend/microsoft-windows-shell-setup-oobe-protectyourpc
// It is really super insidous of Microsoft to call this option "ProtectYourPC", when it's really only about
// data collection. But of course, if it was called "AllowDataCollection", everyone would turn it off...
if (flags & UNATTEND_NO_DATA_COLLECTION) {
fprintf(fd, " <OOBE>\n");
fprintf(fd, " <ProtectYourPC>3</ProtectYourPC>\n");
fprintf(fd, " </OOBE>\n");
}
if (flags & UNATTEND_DUPLICATE_USER) {
order = 1;
char username[128] = { 0 };
DWORD size = sizeof(username);
if (GetUserNameU(username, &size) && username[0] != 0) {
// If we create a local account in unattend.xml, then we can get Windows 11
// 22H2 to skip MSA even if the network is connected during installation.
fprintf(fd, " <UserAccounts>\n");
fprintf(fd, " <LocalAccounts>\n");
fprintf(fd, " <LocalAccount wcm:action=\"add\">\n");
fprintf(fd, " <Name>%s</Name>\n", username);
fprintf(fd, " <DisplayName>%s</DisplayName>\n", username);
fprintf(fd, " <Group>Administrators;Power Users</Group>\n");
// Sets an empty password for the account (which, in Microsoft's convoluted ways,
// needs to be initialized to the Base64 encoded UTF-16 string "Password").
// The use of an empty password has both the advantage of not having to ask users
// to type in a password in Rufus (which they might be weary of) as well as allowing
// automated logon during setup.
fprintf(fd, " <Password>\n");
fprintf(fd, " <Value>UABhAHMAcwB3AG8AcgBkAA==</Value>\n");
fprintf(fd, " <PlainText>false</PlainText>\n");
fprintf(fd, " </Password>\n");
fprintf(fd, " </LocalAccount>\n");
fprintf(fd, " </LocalAccounts>\n");
fprintf(fd, " </UserAccounts>\n");
// Since we set a blank password, we'll ask the user to change it at next logon.
fprintf(fd, " <FirstLogonCommands>\n");
fprintf(fd, " <SynchronousCommand wcm:action=\"add\">\n");
fprintf(fd, " <Order>%d</Order>\n", order++);
fprintf(fd, " <CommandLine>net user &quot;%s&quot; /logonpasswordchg:yes</CommandLine>\n", username);
fprintf(fd, " </SynchronousCommand>\n");
fprintf(fd, " </FirstLogonCommands>\n");
} else {
uprintf("Warning: Could not retreive current user name. Local Account was not created");
}
}
fprintf(fd, " </component>\n");
}
if (flags & UNATTEND_OOBE_INTERNATIONAL_MASK) {
fprintf(fd, " <component name=\"Microsoft-Windows-International-Core\" processorArchitecture=\"%s\" language=\"neutral\" "
"xmlns:wcm=\"http://schemas.microsoft.com/WMIConfig/2002/State\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
"publicKeyToken=\"31bf3856ad364e35\" versionScope=\"nonSxS\">\n", xml_arch_names[arch]);
// What a frigging mess retreiving and trying to match the various locales
// Microsoft has made. And, *NO*, the new User Language Settings have not
// improved things in the slightest. They made it much worse for developers!
fprintf(fd, " <InputLocale>%s</InputLocale>\n",
ReadRegistryKeyStr(REGKEY_HKCU, "Keyboard Layout\\Preload\\1"));
fprintf(fd, " <SystemLocale>%s</SystemLocale>\n", ToLocaleName(GetSystemDefaultLCID()));
fprintf(fd, " <UserLocale>%s</UserLocale>\n", ToLocaleName(GetUserDefaultLCID()));
fprintf(fd, " <UILanguage>%s</UILanguage>\n", ToLocaleName(GetUserDefaultUILanguage()));
fprintf(fd, " <UILanguageFallback>%s</UILanguageFallback>\n",
// NB: Officially, this is a REG_MULTI_SZ string
ReadRegistryKeyStr(REGKEY_HKLM, "SYSTEM\\CurrentControlSet\\Control\\Nls\\Language\\InstallLanguageFallback"));
fprintf(fd, " </component>\n");
}
fprintf(fd, " </component>\n");
fprintf(fd, " </settings>\n");
}
if (mask & UNATTEND_OFFLINE_SERVICING) {
if (flags & UNATTEND_OFFLINE_SERVICING_MASK) {
fprintf(fd, " <settings pass=\"offlineServicing\">\n");
if (mask & UNATTEND_OFFLINE_INTERNAL_DRIVES) {
if (flags & UNATTEND_OFFLINE_INTERNAL_DRIVES) {
fprintf(fd, " <component name=\"Microsoft-Windows-PartitionManager\" processorArchitecture=\"%s\" language=\"neutral\" "
"xmlns:wcm=\"http://schemas.microsoft.com/WMIConfig/2002/State\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
"publicKeyToken=\"31bf3856ad364e35\" versionScope=\"nonSxS\">\n", xml_arch_names[arch]);
@ -1610,12 +1672,16 @@ static DWORD WINAPI BootCheckThread(LPVOID param)
StrArrayCreate(&options, 2);
if (img_report.win_version.build >= 22500) {
StrArrayAdd(&options, lmprintf(MSG_330), TRUE);
MAP_BIT(UNATTEND_NO_ONLINE_ACCOUNT_MASK);
MAP_BIT(UNATTEND_NO_ONLINE_ACCOUNT);
}
StrArrayAdd(&options, lmprintf(MSG_331), TRUE);
MAP_BIT(UNATTEND_NO_DATA_COLLECTION_MASK);
MAP_BIT(UNATTEND_NO_DATA_COLLECTION);
StrArrayAdd(&options, lmprintf(MSG_332), TRUE);
MAP_BIT(UNATTEND_OFFLINE_INTERNAL_DRIVES);
StrArrayAdd(&options, lmprintf(MSG_333), TRUE);
MAP_BIT(UNATTEND_DUPLICATE_USER);
StrArrayAdd(&options, lmprintf(MSG_334), TRUE);
MAP_BIT(UNATTEND_DUPLICATE_LOCALE);
i = SelectionDialog(BS_AUTOCHECKBOX, lmprintf(MSG_326), lmprintf(MSG_327),
options.String, options.Index, remap8(unattend_xml_mask, map, FALSE));
StrArrayDestroy(&options);
@ -1623,7 +1689,7 @@ static DWORD WINAPI BootCheckThread(LPVOID param)
goto out;
// Remap i to the correct bit positions before calling CreateUnattendXml()
i = remap8(i, map, TRUE);
unattend_xml_path = CreateUnattendXml(arch, i);
unattend_xml_path = CreateUnattendXml(arch, i | UNATTEND_WINDOWS_TO_GO);
// Keep the bits we didn't process
unattend_xml_mask &= ~(remap8(0xff, map, TRUE));
// And add back the bits we did process
@ -1666,15 +1732,19 @@ static DWORD WINAPI BootCheckThread(LPVOID param)
uint8_t map[8] = { 0 }, b = 1;
StrArrayCreate(&options, 4);
StrArrayAdd(&options, lmprintf(MSG_328), TRUE);
MAP_BIT(UNATTEND_SECUREBOOT_TPM_MASK);
MAP_BIT(UNATTEND_SECUREBOOT_TPM);
StrArrayAdd(&options, lmprintf(MSG_329), TRUE);
MAP_BIT(UNATTEND_MINRAM_MINDISK_MASK);
MAP_BIT(UNATTEND_MINRAM_MINDISK);
if (img_report.win_version.build >= 22500) {
StrArrayAdd(&options, lmprintf(MSG_330), TRUE);
MAP_BIT(UNATTEND_NO_ONLINE_ACCOUNT_MASK);
MAP_BIT(UNATTEND_NO_ONLINE_ACCOUNT);
}
StrArrayAdd(&options, lmprintf(MSG_331), TRUE);
MAP_BIT(UNATTEND_NO_DATA_COLLECTION_MASK);
MAP_BIT(UNATTEND_NO_DATA_COLLECTION);
StrArrayAdd(&options, lmprintf(MSG_333), TRUE);
MAP_BIT(UNATTEND_DUPLICATE_USER);
StrArrayAdd(&options, lmprintf(MSG_334), TRUE);
MAP_BIT(UNATTEND_DUPLICATE_LOCALE);
i = SelectionDialog(BS_AUTOCHECKBOX, lmprintf(MSG_326), lmprintf(MSG_327),
options.String, options.Index, remap8(unattend_xml_mask, map, FALSE));
StrArrayDestroy(&options);
@ -1683,7 +1753,6 @@ static DWORD WINAPI BootCheckThread(LPVOID param)
i = remap8(i, map, TRUE);
unattend_xml_path = CreateUnattendXml(arch, i);
// Remember the user preferences for the current session.
// TODO: Do we want to save the current mask as a permanent setting?
unattend_xml_mask &= ~(remap8(0xff, map, TRUE));
unattend_xml_mask |= i;
WriteSetting32(SETTING_WUE_OPTIONS, (UNATTEND_DEFAULT_MASK << 16) | unattend_xml_mask);
@ -2070,7 +2139,7 @@ static void InitDialog(HWND hDlg)
uprintf("Syslinux versions: %s%s, %s%s", embedded_sl_version_str[0], embedded_sl_version_ext[0],
embedded_sl_version_str[1], embedded_sl_version_ext[1]);
uprintf("Grub versions: %s, %s", GRUB4DOS_VERSION, GRUB2_PACKAGE_VERSION);
uprintf("System locale ID: 0x%04X (%s)", GetUserDefaultUILanguage(), GetCurrentMUI());
uprintf("System locale ID: 0x%04X (%s)", GetUserDefaultUILanguage(), ToLocaleName(GetUserDefaultUILanguage()));
ubflush();
if (selected_locale->ctrl_id & LOC_NEEDS_UPDATE) {
uprintf("NOTE: The %s translation requires an update, but the current translator hasn't submitted "
@ -2756,7 +2825,7 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA
fs_type = (int)ComboBox_GetCurItemData(hFileSystem);
write_as_image = FALSE;
write_as_esp = FALSE;
unattend_xml_selection = 0;
unattend_xml_flags = 0;
// Disable all controls except Cancel
EnableControls(FALSE, FALSE);
FormatStatus = 0;