从那以后,我找到了解决问题的方法,但它并不漂亮,它会引发你必须处理的各种新问题。尽管如此,它仍然有效。我在这里发布解决方案供我自己参考,也供将来可能需要它的其他人参考。
问题是用户的注册表配置单元位于他们的用户配置文件夹(例如 c:\users\username)中的一个名为 NTUSER.DAT 的文件中。但是,在用户登录之前不会创建用户的用户配置文件文件夹,因此当您创建新用户时,还没有用户配置文件,也没有包含其注册表配置单元的 NTUSER.DAT 文件,因此您无法编辑他们的任何注册表设置.
但是有一个技巧:当您在该用户的凭据下运行某些东西时,确实会创建用户配置文件。有一个名为runas.exe的可执行文件可让您在指定用户的凭据下运行第二个可执行文件。如果您创建一个新用户并使其运行,例如 cmd.exe,如下所示:
runas /user:newuser cmd.exe
...它将打开一个 Cmd 实例,但更重要的是,在 \users 文件夹中创建 newuser 的配置文件,包括 NTUSER.DAT。
现在,Cmd.exe 会打开一个命令窗口,您可以手动关闭它,但它有点笨拙。https://superuser.com/a/389288将我指向 rundll32.exe,它在没有任何参数的情况下运行时,什么都不做并立即退出。此外,它在每个 Windows 安装中都可用。
因此,通过调用 runas 并告诉它以新用户身份运行 rundll32.exe,我们可以创建用户的配置文件,而无需任何进一步的交互:
Process.Start("runas", string.Format("/user:{0} rundll32", "newuser"));
嗯...几乎没有互动。Runas 会打开一个控制台窗口,要求您输入用户密码,即使没有设置密码(它希望您只需按 Enter 键)。这很烦人,但可以通过巧妙地使用 Pinvoke 和可选的 System.Windows.Forms 将窗口置于前台并向其发送一些按键来解决:
var createProfileProcess = Process.Start("runas",
string.Format("/user:{0} rundll32",
"newuser"));
IntPtr hWnd;
do
{
createProfileProcess.Refresh();
hWnd = createProfileProcess.MainWindowHandle;
} while (hWnd.ToInt32() == 0);
SetForegroundWindow(hWnd);
System.Windows.Forms.SendKeys.SendWait("{ENTER}");
这将创建配置文件,等待窗口具有句柄,然后调用 Win32 函数 SetForegroundWindow() 将其置于前台。然后,它使用 SendKeys.SendWait 将输入键发送到该窗口。如果您不想使用 WinForms DLL,您可以使用 Win32 函数为此 PInvoke,但对于这种特殊情况,我发现 winforms 方式更快更容易。
这行得通,但揭示了另一个问题:runas 不会让您在没有密码的帐户下运行东西。超级用户再次救援;https://superuser.com/a/470539指出有一个本地策略,称为限制本地帐户使用空白密码只能进行控制台登录,可以禁用它以允许这种确切的情况。我不希望用户必须手动禁用此策略,因此我在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa中使用了相应的注册表值LimitBlankPasswordUse。
我现在通过将注册表值设置为 0 来禁用该策略,运行 runas 命令创建配置文件,然后通过将值设置为 1 重新启用该策略。(首先检查该值可能会更清晰,并且只重新- 如果一开始就设置它,则启用它,但出于演示目的,这将执行以下操作:
const string keyName = "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Lsa";
Registry.SetValue(keyName, "LimitBlankPasswordUse", 0);
var createProfileProcess = Process.Start("runas",
string.Format("/user:{0} rundll32",
"newuser"));
IntPtr hWnd;
do
{
createProfileProcess.Refresh();
hWnd = createProfileProcess.MainWindowHandle;
} while (hWnd.ToInt32() == 0);
SetForegroundWindow(hWnd);
System.Windows.Forms.SendKeys.SendWait("{ENTER}");
Registry.SetValue(keyName, "LimitBlankPasswordUse ", "1");
这行得通!但是,用户的注册表配置单元尚未加载,因此您仍然无法读取或写入它。为此,该过程需要一些特权,您可以使用一些 Win32 方法再次提供这些特权:
OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
out _myToken);
LookupPrivilegeValue(null, SE_RESTORE_NAME, out _restoreLuid);
LookupPrivilegeValue(null, SE_BACKUP_NAME, out _backupLuid);
_tokenPrivileges.Attr = SE_PRIVILEGE_ENABLED;
_tokenPrivileges.Luid = _restoreLuid;
_tokenPrivileges.Count = 1;
_tokenPrivileges2.Attr = SE_PRIVILEGE_ENABLED;
_tokenPrivileges2.Luid = _backupLuid;
_tokenPrivileges2.Count = 1;
AdjustTokenPrivileges(_myToken,
false,
ref _tokenPrivileges,
0,
IntPtr.Zero,
IntPtr.Zero);
AdjustTokenPrivileges(_myToken,
false,
ref _tokenPrivileges2,
0,
IntPtr.Zero,
IntPtr.Zero);
最后使用新用户的 SID 加载配置单元:
// Load the hive
var principalContext = new PrincipalContext(ContextType.Machine);
var standardUser = UserPrincipal.FindByIdentity(principalContext, "newuser");
var sid = standardUser.Sid.ToString();
StringBuilder path = new StringBuilder(4 * 1024);
int size = path.Capacity;
GetProfilesDirectory(path, ref size);
var filename = Path.Combine(path.ToString(), "newuser", "NTUSER.DAT");
Thread.Sleep(2000);
int retVal = RegLoadKey(HKEY_USERS, sid, filename);
我在Load registry hive from C# failed中发现了大部分代码。
RegLoadKey 应该在成功时返回 0。我注意到偶尔,它会无缘无故地加载配置单元。考虑到用户配置文件中的必要文件可能尚未创建,我在加载配置单元之前添加了一个 Thread.Sleep(2000) 以给 Windows 时间来创建所有必要的文件。可能有一种更简洁的方法可以做到这一点,但现在这会起作用。
现在,您可以使用 newuser 的 SID 为 newuser 加载和设置注册表值,例如:
var subKeyString = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced";
var keyString = string.Format("{0}{1}", sid, subKeyString);
var subKey = Registry.Users.CreateSubKey(keyString,
RegistryKeyPermissionCheck.ReadWriteSubTree);
subKey.SetValue("EnableBalloonTips", 0, RegistryValueKind.DWord);
可以肯定的是,完成后我还卸载了注册表配置单元。我不确定它是否需要,但这似乎是一件好事:
var retVal = RegUnLoadKey(HKEY_USERS, GetSidForStandardUser());