3

次のような新しいローカル ユーザーを作成する .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 が存在します。

私の質問は、まだログインしていないユーザーにサブキーを追加する方法はありますか? 新しいユーザーとしてログインし、プロセスの途中で元に戻す必要はありません。

4

1 に答える 1

4

それ以来、私は問題の解決策を見つけましたが、それはきれいではなく、対処しなければならないあらゆる種類の新しい問題を引き起こします. それでも、それは機能します。私自身の参考のために、また将来それが必要になる可能性のある他の人のために、ここにソリューションを投稿しています。

問題は、ユーザーのレジストリ ハイブがユーザー プロファイル フォルダ (c:\users\username など) の NTUSER.DAT というファイルにあることです。ただし、ユーザーのユーザー プロファイル フォルダーはユーザーがログインするまで作成されないため、新しいユーザーを作成すると、ユーザー プロファイルはまだ存在せず、レジストリ ハイブを含む NTUSER.DAT ファイルも存在しないため、レジストリ設定を編集することはできません。 .

ただし、トリックがあります。そのユーザーの資格情報で何かを実行すると、ユーザー プロファイルが作成されます。指定したユーザーの資格情報で 2 番目の実行可能ファイルを実行できる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);

このコードのほとんどはLoad registry hive from C# failsで見つかりました。

成功した場合、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());
于 2015-12-16T11:48:39.477 に答える