Shared User Profiles – Staging Scripts
by dwarfsoft on Mar.15, 2010, under Novell, Scripting, Tweet, Work
As promised, here are the scripts required for the Pre-staging of Domain User Profiles on the local machine. The first thing we need to do is Enumerate all the Local User Accounts.
Function StageAllUsers(DomainFQDN, strDomain) ' Enumerate all users that are Local and not built in accounts. strComputer = "." Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") 'Enumerate users where the User Domain is the Local Machine Set colItems = objWMIService.ExecQuery _ ("Select * from Win32_UserAccount " & _ "Where Domain = '" & GetComputerName & "' " & _ "And Disabled = FALSE And Name <> 'Administrator'") ' Stage each user For Each objItem In colItems ' Ensure the account actually has a profile (otherwise we can ignore it) If GetLocalUserProfile(objItem.Name) <> "" Then ret = StageUser(objItem.Name, DomainFQDN, strDomain) End If Next End Function
The functions called here are GetComputerName, which returns the name of the local machine, and the other important ones are GetLocalUserProfile and Stage User. The first we can check is GetLocalUserProfile.
'Gets the Profile Path for the User passed in UserName. ' This requires the profile to exist on the local machine ' Returns an empty string on error Function GetLocalUserProfile(UserName) On Error Resume Next GetLocalUserProfile = "" SDDL = GetLocalUserSDDL(UserName) If SDDL = "" Then Exit Function End If Set objShell = CreateObject("WScript.Shell") GetLocalUserProfile = objShell.ExpandEnvironmentStrings( _ objShell.RegRead( _ "HKLM\Software\Microsoft\Windows NT\CurrentVersion\ProfileList\" & _ SDDL & _ "\ProfileImagePath")) On Error Goto 0 End Function
The registry path (including the SDDL, covered next) returns the profile path for the user. This relies on a function called GetLocalUserSDDL which goes as follows:
Function GetLocalUserSDDL(UserName) strComputer = "." Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") 'Enumerate users where the User Domain is the Local Machine 'and the username matches the Supplied Name Set colItems = objWMIService.ExecQuery _ ("Select * from Win32_UserAccount " & _ "Where Domain = '" & GetComputerName & "' " & _ "And Name = '" & UserName & "'") GetLocalUserSDDL = "" For Each objItem In colItems 'Get the first returned SID/SDDL then exit GetLocalUserSDDL = objItem.SID Exit Function Next End Function
This reads the SDDL (eg S-1-5-21-791012361-4073638415-1907800938-1006 or otherwise known as the String SID) for the User on the local machine. The next major function is Stage User:
Function StageUser(UserName, DomainFQDN, strDomain) Set objShell = CreateObject("WScript.Shell") ' Not Assuming "C:\Documents and Settings\" & UserName: ' Get SDDL of user via WMI interrogation of UserAccount ' Get Profile Location of user (from HKLM\Software\Microsoft\Windows NT\CurrentVersion\ProfileList\" & SDDL & "ProfileImagePath" strProfile = GetLocalUserProfile(UserName) ' Set ACLs on ProfileLocation ret = objShell.Run("subinacl /subdirectories """ & strProfile & """ /grant=""" & UserName & "@" & DomainFQDN & """=F",0,True) ' Set ACLs on D:\UserData\%username% ret = objShell.Run("subinacl /subdirectories ""D:\UserData\" & UserName & """ /grant=""" & UserName & "@" & DomainFQDN & """=F",0,True) ' Reg load ProfileLocation\NTUSER.DAT into HKU\%username% ret = objShell.Run("reg load ""HKU\" & UserName & """ """ & strProfile & "\NTUSER.DAT""" ,0,True) ' Reg load ProfileLocation\Local Settings\Application Data\Microsoft\Windows\UsrClass.DAT into HKU\%username%_Classes ret = objShell.Run("reg load ""HKU\" & UserName & "_Classes"" """ & strProfile & "\Local Settings\Application Data\Microsoft\Windows\UsrClass.DAT""" ,0,True) 'Try both of the following. One should fail, the other should pass. ' Set ACLs on HKU\%username% ret = objShell.Run("subinacl /subkeyreg ""HKEY_USERS\" & UserName & """ /grant=""" & UserName & "@" & DomainFQDN & """=F",0,True) ' If User is already logged in then the registry will be open at HKU\SDDL ret = objShell.Run("subinacl /subkeyreg ""HKEY_USERS\" & GetLocalUserSDDL(UserName) & """ /grant=""" & UserName & "@" & DomainFQDN & """=F",0,True) 'Try both of the following. One should fail, the other should pass. ' Set ACLs on HKU\%username% ret = objShell.Run("subinacl /subkeyreg ""HKEY_USERS\" & UserName & "_Classes"" /grant=""" & UserName & "@" & DomainFQDN & """=F",0,True) ' If User is already logged in then the registry will be open at HKU\SDDL ret = objShell.Run("subinacl /subkeyreg ""HKEY_USERS\" & GetLocalUserSDDL(UserName) & "_Classes"" /grant=""" & UserName & "@" & DomainFQDN & """=F",0,True) ' Reg unload HKU\%username% ret = objShell.Run("reg unload ""HKU\" & UserName & """",0,True) ' Reg unload HKU\%username%_Classes ret = objShell.Run("reg unload ""HKU\" & UserName & "_Classes""",0,True) ' Get Domain User SID from ProfileLocation ACL arrSID = GetDomainUserSidFromFolderACL(strProfile, UserName, strDomain) If UBound(arrSID) > 0 Then ' Convert Domain User SID to SDDL WScript.Echo "Getting Sid for " & UserName SDDL = ArraySidToStrSid(arrSID) ' Stage Domain User Profile into Registry under HKLM\Software\Microsoft\Windows NT\CurrentVersion\ProfileList\%DomainUserSDDL% ret = CreateAndLinkProfile(SDDL, arrSID, strProfile) Else 'Account didn't exist in the domain or ACL failed to set on Folder End If End Function
For the most part this function behaves by running external commands to mount registry hives, and set ACLs on folders and registry, all up until we call GetDomainUserSidFromFolderACL. This is where we retrieve the SID of the Domain User that we are pre-staging via the ACL we set earlier on the Local User Profile folder. This subverts the need to query Active Directory for the SID through other means.
Function GetDomainUserSidFromFolderACL(strFolder, strUserName, strUserDomain) Dim arrSID(0) GetDomainUserSidFromFolderACL = arrSID Set objFSO = CreateObject("Scripting.FileSystemObject") If Not objFSO.FolderExists(strFolder) Then Set objFSO = Nothing Exit Function End If ' Ensure that strFolder is of the form C:\\Documents and Settings\\UserName strFolderName = Replace(strFolder, "\\","\") strFolderName = Replace(strFolderName, "\","\\") Set wmiFileSecSetting = GetObject( _ "winmgmts:Win32_LogicalFileSecuritySetting.path='" & strFolderName & "'") RetVal = wmiFileSecSetting. _ GetSecurityDescriptor(wmiSecurityDescriptor) If Err <> 0 Then Exit Function End If ' Retrieve the DACL array of Win32_ACE objects. DACL = wmiSecurityDescriptor.DACL For each wmiAce in DACL ' Get Win32_Trustee object from ACE Set Trustee = wmiAce.Trustee If (StrComp(Trustee.Name, strUserName, vbTextCompare) = 0) And _ (StrComp(Trustee.Domain, strUserDomain, vbTextCompare) = 0) Then GetDomainUserSidFromFolderACL = Trustee.SID Exit Function End If Next End Function
Although this subverts the need to Query Active Directory, this does also mean that we receive the SID in a format that is unexpected. Normally, via querying Active Directory we would have received a SID in an Octet String format. Querying the SID from WMI returned it in SDDL or String SID format. The format that we receive the SID from the ACL is in an Array of Integers. In order to Pre-stage an account we need the SDDL/String SID, and the only way to get this in VBScript is to manually convert it using some functions developed by Richard Mueller and Wilfred Wong in this newsgroup posting.
Instead of using only the code listed, we need to alter it for dealing with the SID array we received back from the ACL.
Function ArraySidToStrSid(arrSid) ' Function to convert OctetString (byte array) to Decimal string (SDDL) Sid. Dim strHex, strDec strHex = ArraySidToHexStr(arrSid) strDec = HexStrToDecStr(strHex) ArraySidToStrSid = strDec End Function Function ArraySidToHexStr(arrSid) ArraySidToHexStr = "" For Each x In arrSid ArraySidToHexStr = ArraySidToHexStr & _ Right("0" & Hex(x), 2) Next End Function
The HexStrToDecStr function is the same as it was from the Richard Mueller posting listed above.
Finally we just need to run through the actual staging of the account:
Function CreateAndLinkProfile(strNewSDDL, strNewSID, strProfilePath) WScript.Echo "Creating Profile for " & strNewSDDL & ": " & strProfilePath 'Write Sid to registry ret = WriteRegBinaryToRegistry(HKEY_LOCAL_MACHINE,"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\" & strNewSDDL,"Sid",strNewSID) ret = WriteRegStringToRegistry(HKEY_LOCAL_MACHINE,"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\" & strNewSDDL,"ProfileImagePath",strProfilePath) ret = WriteRegStringToRegistry(HKEY_LOCAL_MACHINE,"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\" & strNewSDDL,"CentralProfile","") ret = WriteRegDwordToRegistry(HKEY_LOCAL_MACHINE,"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\" & strNewSDDL,"Flags",1) ret = WriteRegDwordToRegistry(HKEY_LOCAL_MACHINE,"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\" & strNewSDDL,"State",0) End Function
At this point if the Domain User logs in they get redirected to the existing local users profile. Then if rollback is initiated they log on with the Local User account with the same profile. This is seamless to end users, both forward and backward.
On a side note the functions for writing to the registry are through WMI due to needing to write Binary values to the Registry
Function WriteRegBinaryToRegistry(Hive, strKeyPath, strValueName, ArrValues) strComputer = "." Set oReg = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _ strComputer & "\root\default:StdRegProv") oReg.CreateKey Hive,strKeyPath oReg.SetBinaryValue Hive, strKeyPath, strValueName,ArrValues End Function Function WriteRegDwordToRegistry(Hive, strKeyPath, strValueName, Value) strComputer = "." Set oReg = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _ strComputer & "\root\default:StdRegProv") oReg.CreateKey Hive,strKeyPath oReg.SetDwordValue Hive, strKeyPath, strValueName,Value End Function Function WriteRegStringToRegistry(Hive, strKeyPath, strValueName, Value) strComputer = "." Set oReg = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _ strComputer & "\root\default:StdRegProv") oReg.CreateKey Hive,strKeyPath oReg.SetStringValue Hive, strKeyPath, strValueName,Value End Function
Cheers, Chris.
Edit: DomainAccountProfileStaging.vbs has been uploaded as a complete file.
2 Comments for this entry
1 Trackback or Pingback for this entry
-
Last Login Time - Dwarfsoft [GPA]
April 22nd, 2010 on 11:54 am[...] the issue manually. As I have had a fair bit of experience with Windows Profiles recently (see the last blog post) I was familiar with the ProfileLoadTimeHigh and ProfileLoadTimeLow registry keys in the [...]

May 1st, 2010 on 4:30 am
I am interested in using this script, as I work for a school district who is migrating from Novell to AD but I’m not sure how to put it together and how to run it. Could you provide some more info?
Thanks,
Dustin Roberts
May 3rd, 2010 on 11:28 am
Hi,
Well, to put the script together you simply create a VBScript file that includes all the functions, then call StageAllUsers passing the domain FQDN and the Netbios Domain.
From there you simply package it so it installs on the Workstations, then get it to run after every logon. This way the domain accounts and the local accounts are synchronized.
I have uploaded the VBS script into the original Article now.
If, however, you do not need to worry about rollback then you can simply use the User State Migration Tools that Microsoft has provided to convert the user accounts to Domain accounts (which is much simpler).
Cheers, Chris.