C# 编辑新创建用户的注册表值
我有一个.NET应用程序,它创建了一个新的本地用户,如下所示:C# 编辑新创建用户的注册表值,c#,.net,registry,sid,userprincipal,C#,.net,Registry,Sid,Userprincipal,我有一个.NET应用程序,它创建了一个新的本地用户,如下所示: var principalContext = new PrincipalContext(ContextType.Machine); var userPrincipal = new UserPrincipal(principalContext); userPrincipal.Name = StandardUserName.Text; use
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();
}
// 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);
并创建一个新的子项:
var key = string.Format("{0}{1}", GetSidForStandardUser(), keyString);
var subKey = Registry.Users.CreateSubKey(key, RegistryKeyPermissionCheck.ReadWriteSubTree);
但是,我在调用CreateSubKey时得到一个IOException,它告诉我参数无效。这是因为在用户首次登录之前,该用户的子密钥还不存在。如果我在以新用户身份登录之前选中regedit(以管理员权限),我可以看到HKEY_用户下不存在SID。如果我以新用户身份登录,然后以原始用户身份注销并重新登录并刷新regedit,则新SID存在
我的问题是:有没有办法为尚未登录的用户添加子键?我希望避免以新用户身份登录,然后中途退出。我已经找到了解决问题的方法,但这并不漂亮,它会引发各种新问题,你必须处理。尽管如此,它仍然有效。我在这里发布解决方案,供我自己参考,也供将来可能需要的其他人参考 问题是,用户的注册表配置单元位于名为NTUSER.DAT的文件中的用户配置文件文件夹(例如c:\users\username)中。但是,用户的用户配置文件文件夹在他们登录之前不会创建,因此当您创建新用户时,还没有用户配置文件,也没有包含其注册表配置单元的NTUSER.DAT文件,因此您无法编辑他们的任何注册表设置 不过,有一个技巧:当您在该用户的凭据下运行某个操作时,确实会创建用户配置文件。有一个名为的可执行文件,允许您在指定用户的凭据下运行第二个可执行文件。如果创建新用户并使其运行,例如cmd.exe,如下所示:
runas /user:newuser cmd.exe
…它将打开一个Cmd实例,但更重要的是,在\users文件夹中创建新用户的配置文件,包括NTUSER.DAT
现在,Cmd.exe打开了一个命令窗口,您可以手动关闭它,但它有点笨重。将我指向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函数setForeGroundIndow()将其置于前台。然后,它使用SendKeys.SendWait将enter键发送到该窗口。如果你不想使用WinForms DLL,有一些Win32函数你可以用PInvoke来实现,但是对于这个特殊的场景,我发现WinForms的方式更快更简单
这是可行的,但揭示了另一个问题:runas不允许您在没有密码的帐户下运行东西。超级用户再次施救;指出有一个名为的本地策略,限制本地帐户使用空白密码,只允许控制台登录,可以禁用该策略,以允许出现这种情况。我不希望用户必须手动禁用此策略,因此我在HKEY\U LOCAL\U 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加载配置单元:
private string GetSidForStandardUser()
{
var principalContext = new PrincipalContext(ContextType.Machine);
var standardUser = UserPrincipal.FindByIdentity(principalContext, StandardUserName.Text);
return standardUser.Sid.ToString();
}
// 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);
我在中找到了大部分代码
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());