正在编辑新建用户的注册表值

本文关键字:注册表 用户 新建 编辑 | 更新日期: 2023-09-27 18:26:48

我有一个.NET应用程序,它可以创建一个新的本地用户,如下所示:

var principalContext = new PrincipalContext(ContextType.Machine);
                var userPrincipal = new UserPrincipal(principalContext);
                userPrincipal.Name = StandardUserName.Text;
                userPrincipal.Description = "New local user";
                userPrincipal.UserCannotChangePassword = true;
                userPrincipal.PasswordNeverExpires = true;
                userPrincipal.Save();
                // Add user to the users group
                var usersGroupPrincipal = GroupPrincipal.FindByIdentity(principalContext, UserGroupName.Text);
                usersGroupPrincipal.Members.Add(userPrincipal);
                usersGroupPrincipal.Save();

接下来,我想为该用户设置一些注册表值。为此,我需要用户的SID:

private string GetSidForStandardUser()
{
    var principalContext = new PrincipalContext(ContextType.Machine);
    var standardUser = UserPrincipal.FindByIdentity(principalContext, StandardUserName.Text);
    return standardUser.Sid.ToString();
}

并创建一个新的子密钥:

var key = string.Format("{0}{1}", GetSidForStandardUser(), keyString);
var subKey = Registry.Users.CreateSubKey(key, RegistryKeyPermissionCheck.ReadWriteSubTree);

然而,在对CreateSubKey的调用中,我得到了一个IOException,它告诉参数无效。发生这种情况是因为在用户第一次登录之前,该用户的子密钥还不存在。如果我在以新用户身份登录之前检查regedit(在管理员权限下),我可以看到HKEY_Users下不存在SID。如果我以新用户身份登录,然后以原始用户身份注销并重新登录并刷新regedit,则新SID存在。

我的问题是:有没有办法为尚未登录的用户添加子密钥?我希望避免以新用户身份登录,然后在过程中中途退出。

正在编辑新建用户的注册表值

我已经找到了这个问题的解决方案,但它并不漂亮,它会引发你必须处理的各种新问题。尽管如此,它仍然有效。我在这里发布这个解决方案,供我自己参考,也供将来可能需要它的其他人参考。

问题是,用户的注册表配置单元位于名为NTUSER.DAT的文件中的用户配置文件文件夹(例如c:''users''username)中。但是,用户的用户配置文件夹在登录之前不会创建,因此当您创建新用户时,还没有用户配置文件,也没有包含其注册表配置单元的NTUSER.DAT文件,因此您无法编辑其任何注册表设置。

不过,有一个技巧:当您在用户凭据下运行某个东西时,确实会创建用户配置文件。有一个名为runas.exe的可执行文件,可以让您在指定用户的凭据下运行第二个可执行文件。如果你创建了一个新用户并让它运行,比如cmd.exe,就像这样:

runas /user:newuser cmd.exe

它将打开一个Cmd实例,但更重要的是,在''users文件夹中创建新用户的配置文件,包括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,你可以为此PInvoke一些Win32函数,但对于这个特定的场景,我发现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);

我发现从C#加载注册表配置单元中的大部分代码都失败了。

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());