简体   繁体   English

在Windows 2008上以编程方式创建漫游用户配置文件

[英]Creating roaming user profiles programmatically on Windows 2008

Problem: 问题:

Logon and load profile via LoadUserProfile API for a roaming user is not creating proper profile. 通过LoadUserProfile API为漫游用户登录和加载配置文件没有创建正确的配置文件。 This only happens under Windows 2008 (UAC off and on). 这仅在Windows 2008(关闭和打开UAC)下发生。 Login using standard windows logon works correctly and same code works correctly on Windows 2003. 使用标准Windows登录登录可以正常工作,并且相同的代码在Windows 2003上也可以正常工作。

Logs: 日志:

  1. http://drop.io/6t2b3f3 ETL of user profile service created via command: logman -start profile -p {eb7428f5-ab1f-4322-a4cc-1f1a9b2c5e98} 255 3 –ets The file needs to be analyzed by someone with access to the source and hopefully that will shed some light on why the profile is always loading as temporary. 通过以下命令创建的用户配置文件服务的http://drop.io/6t2b3f3 ETL:logman -start profile -p {eb7428f5-ab1f-4322-a4cc-1f1a9b2c5e98} 255 3 –ets该文件需要由有权访问以下文件的人分析资料来源,并希望可以阐明为什么配置文件始终总是临时加载。

Environment: 环境:

  1. Windows 2008 mode domain/forest Windows 2008模式域/林
  2. Server fs001 - Windows 2008 Standard SP2 x86 box 服务器fs001-Windows 2008 Standard SP2 x86盒
  3. File share on fs001 "Share" with Everyone+SYSTEM having full control on share and ntfs. 与所有人对共享和ntfs完全控制的fs001“共享”上的文件共享。

To reproduce: 复制:

  1. Create domain user tuser1 with roaming profile set to \\fs001\\Share\\tuser1\\profile 创建域用户tuser1,并将漫游配置文件设置为\\ fs001 \\ Share \\ tuser1 \\ profile
  2. Run this program as any domain admin and local admin account on fs001 (if UAC is on run as administrator), a temporary user profile is loaded in c:\\users\\temp* rather than c:\\users\\tuser1 以fs001上的任何域管理员和本地管理员帐户运行该程序(如果UAC以管理员身份运行),则将临时用户配置文件加载到c:\\ users \\ temp *中,而不是c:\\ users \\ tuser1中。

The ETL is probably the best way to go here and will be provide the quickest diagnosis. ETL可能是到达此处的最佳方法,并且将提供最快的诊断。 Procmon of user profile service svchost instance and tracing logon on the system level did not reveal too much about what's going wrong (I can provide more info if necessary but it's a dead end). 用户配置文件服务svchost实例和在系统级别上的跟踪登录的说明并没有太多揭示出问题所在(如有必要,我可以提供更多信息,但这是死胡同)。 userenv.log on Windows 2003 would have helped but ETL can only be analyzed by someone at MSFT. Windows 2003上的userenv.log会有所帮助,但ETL只能由MSFT的人员进行分析。

Any ideas? 有任何想法吗?

Thanks, Alex 谢谢,亚历克斯

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace consoleLogon {
  class Program {
    #region Helpers for setting privilegies functionality
    [StructLayout(LayoutKind.Sequential, Pack = 4)]
    public struct LUID_AND_ATTRIBUTES {
        public long Luid;
        public int Attributes;
    }

    [StructLayout(LayoutKind.Sequential, Pack = 4)]
    public struct TOKEN_PRIVILEGES {
        public int PrivilegeCount;
        public LUID_AND_ATTRIBUTES Privileges;
    }

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool AdjustTokenPrivileges(
        IntPtr TokenHandle,
        bool DisableAllPrivileges,
        ref TOKEN_PRIVILEGES NewState,
        int BufferLength,
        IntPtr PreviousState,
        IntPtr ReturnLength
        );

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool LookupPrivilegeValue(
        string lpSystemName,
        string lpName,
        ref long lpLuid
        );

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool OpenProcessToken(
        IntPtr ProcessHandle,
        int DesiredAccess,
        ref IntPtr TokenHandle
        );

    public const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
    public const int TOKEN_QUERY = 0x00000008;
    public const int TOKEN_DUPLICATE = 0x00000002;
    public const int TOKEN_IMPERSONATE = 0x00000004;
    public const int SE_PRIVILEGE_ENABLED = 0x00000002;
    public const string SE_RESTORE_NAME = "SeRestorePrivilege";
    public const string SE_BACKUP_NAME = "SeBackupPrivilege";

    [DllImport("advapi32.dll", SetLastError = true)]
    static public extern bool LogonUser(String lpszUsername, String lpszDomain,
        String lpszPassword,
        int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

    [DllImport("Userenv.dll", CallingConvention = CallingConvention.Winapi, SetLastError = true, CharSet = CharSet.Auto)]
    public static extern bool LoadUserProfile(
        IntPtr hToken,               // user token
        ref PROFILEINFO lpProfileInfo  // profile
        );

    [DllImport("Userenv.dll", CallingConvention = CallingConvention.Winapi, SetLastError = true, CharSet = CharSet.Auto)]
    public static extern bool UnloadUserProfile(
        IntPtr hToken,   // user token
        IntPtr hProfile  // handle to registry key
        );

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private extern static bool DuplicateToken(
        IntPtr ExistingTokenHandle,
        int SECURITY_IMPERSONATION_LEVEL,
        ref IntPtr DuplicateTokenHandle
        );


    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    public struct PROFILEINFO {
        public static readonly int SizeOf = Marshal.SizeOf(typeof(PROFILEINFO));

        public int dwSize;                 // Set to sizeof(PROFILEINFO) before calling
        public int dwFlags;                // See PI_ flags defined in userenv.h
        public string lpUserName;          // User name (required)
        public string lpProfilePath;       // Roaming profile path (optional, can be NULL)
        public string lpDefaultPath;       // Default user profile path (optional, can be NULL)
        public string lpServerName;        // Validating domain controller name in netbios format (optional, can be NULL but group NT4 style policy won't be applied)
        public string lpPolicyPath;        // Path to the NT4 style policy file (optional, can be NULL)
        public IntPtr hProfile;            // Filled in by the function.  Registry key handle open to the root.
    }
    #endregion

    static void Main(string[] args) {
        string domain = "dev1";
        string userName = "tuser1";
        string password = "asd!234";
        string profilePath = @"\\fs001\TestProfiles\tuser9\profile";

        bool retVal = false;
        IntPtr primaryToken = IntPtr.Zero;
        IntPtr dupeToken = IntPtr.Zero;
        PROFILEINFO profileInfo = new PROFILEINFO();
        try {
            // Add RESTORE AND BACUP privileges to process primary token, this is needed for LoadUserProfile function
            IntPtr processToken = IntPtr.Zero;

            OpenProcessToken(System.Diagnostics.Process.GetCurrentProcess().Handle, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref processToken);

            long luidRestore = 0;
            long luidBackup = 0;
            retVal = LookupPrivilegeValue(null, SE_RESTORE_NAME, ref luidRestore);
            retVal = LookupPrivilegeValue(null, SE_BACKUP_NAME, ref luidBackup);

            TOKEN_PRIVILEGES tpRestore = new TOKEN_PRIVILEGES();
            TOKEN_PRIVILEGES tpBackup = new TOKEN_PRIVILEGES();

            tpRestore.PrivilegeCount = 1;
            tpRestore.Privileges = new LUID_AND_ATTRIBUTES();
            tpRestore.Privileges.Attributes = SE_PRIVILEGE_ENABLED;
            tpRestore.Privileges.Luid = luidRestore;

            tpBackup.PrivilegeCount = 1;
            tpBackup.Privileges = new LUID_AND_ATTRIBUTES();
            tpBackup.Privileges.Attributes = SE_PRIVILEGE_ENABLED;
            tpBackup.Privileges.Luid = luidBackup;

            retVal = AdjustTokenPrivileges(processToken, false, ref tpRestore, 0, IntPtr.Zero, IntPtr.Zero);
            if (false == retVal) {
                throw new Win32Exception();
            }
            retVal = AdjustTokenPrivileges(processToken, false, ref tpBackup, 0, IntPtr.Zero, IntPtr.Zero);
            if (false == retVal) {
                throw new Win32Exception();
            }

            // Logon as user + password in clear text for sake of simple sample (protocol transitioning is better).
            retVal = LogonUser(userName, domain, password, 3 /* LOGON32_LOGON_NETWORK */, 0 /*LOGON32_PROVIDER_DEFAULT */, ref primaryToken);
            if (false == retVal) {
                throw new Win32Exception();
            }

            // Duplicate primary token.
            // LoadUserProfile needs a token with TOKEN_IMPERSONATE and TOKEN_DUPLICATE access flags.
            retVal = DuplicateToken(primaryToken, 2 /* securityimpersonation */, ref dupeToken);
            if (false == retVal) {
                throw new Win32Exception();
            }

            // Load user profile for roaming profile
            profileInfo.dwSize = PROFILEINFO.SizeOf;
            profileInfo.lpUserName = domain + @"\" + userName;
            profileInfo.lpProfilePath = profilePath;

            Console.WriteLine("UserName: {0}", profileInfo.lpUserName);
            Console.WriteLine("ProfilePath: {0}", profileInfo.lpProfilePath);

            retVal = LoadUserProfile(dupeToken, ref profileInfo);
            if (false == retVal) {
                throw new Win32Exception();
            }

            // What should happen
            // 1.  Local new profile in c:\users\tuser1.dev1 folder with copy from default.
            // 2.  Valid user registry hive ntuser.dat
            // 3.  Loaded profile session entry in the registry entry
            //     HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\SID of tuser1\
            //     State bit mask should have new local profile (x004), new central profile (x008), 
            //     update the central profile (0010).
            //
            //       "State"=dword:0000021c
            //       "CentralProfile"="\\fs001\Share\tuser1\profile.V2"
            //       "ProfileImagePath"="C:\Users\tuser1"
            //
            //     See http://technet.microsoft.com/en-us/library/cc775560(WS.10).aspx fpr more info
            //     Roaming Profile - New User section.
            // 
            // What actually happens:
            // 1.  Temp profile is loaded in c:\users\temp
            // 2.  Registry entry ProfieList/SID is showing temporary profile
            //       "State"=dword:00000a04
            //       "CentralProfile"="\\fs001\Share\tuser1\profile.V2"
            //       "ProfileImagePath"="C:\Users\TEMP"

            Console.WriteLine("Profile loaded, hit enter to unload profile");
            Console.ReadLine();
        }
        catch (Exception e) {
            Console.WriteLine(e.ToString());
            Console.ReadLine();
        }
        finally {
            // Unload profile properly.
            if (IntPtr.Zero != dupeToken) {
                retVal = UnloadUserProfile(dupeToken, profileInfo.hProfile);
                if (false == retVal) {
                    throw new Win32Exception();
                }
            }
        }
    }
}

} }

I have been chasing the same problem for months and finally have an answer. 我几个月来一直在追寻着同样的问题,终于有了答案。 I have not seen this answer anywhere else on the Internet. 我在互联网上其他任何地方都没有看到这个答案。 It removes 5 to 10 seconds from the processing of LoadUserProfile as well. 它也会从LoadUserProfile的处理中删除5到10秒。 These comments only apply for situations where roaming profiles are used. 这些注释仅适用于使用漫游配置文件的情况。

The lpUserName field in PROFILEINFO is used for more than active directory user verification in Windows 2008 R2 at least. 至少在Windows 2008 R2中,PROFILEINFO中的lpUserName字段用于活动目录用户验证之外。 It is used to build the roaming profile path and the local profile path. 它用于构建漫游配置文件路径和本地配置文件路径。 If lpUserName contains the domain name (mydomain\\myusername) then the path used contains this. 如果lpUserName包含域名(mydomain \\ myusername),则使用的路径将包含该域名。 But if you change lpUserName to exclude the domain, the LoadUserProfile call will fail. 但是,如果将lpUserName更改为排除域,则LoadUserProfile调用将失败。

However, if you use just the user name in lpUserName and provide the NetBIOS domain name in lpServerName it works. 但是,如果仅在lpUserName中使用用户名,而在lpServerName中提供NetBIOS域名,则可以使用。 The NetBIOS name is normally the highest level of the domain name (mydomain.parent.com, for example, the name is mydomain), padded or trimmed to 15 characters plus hex 1B (\ in C# strings) in the 16th character. NetBIOS名称通常是域名的最高级别(例如,mydomain.parent.com,名称为mydomain),用第16个字符填充或修剪为15个字符加十六进制1B(在C#字符串中为\\ u001B)。

You can see the problem yourself in the CentralProfile string in HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList{sid}. 您可以在HKLM \\ Software \\ Microsoft \\ Windows NT \\ CurrentVersion \\ ProfileList {sid}中的CentralProfile字符串中自己查看问题。 CentralProfile is only present when you attempt to use LoadUserProfile for roaming profiles. 仅当您尝试使用LoadUserProfile漫游配置文件时,CentralProfile才存在。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM