Dwarfsoft [GPA]

Shared User Profiles – Staging Scripts

by on Mar.15, 2010, under Novell, Scripting, Tweet, Work


Print This Post Print This Post

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.

:, , , , , , , , , ,

Looking for something?

Use the form below to search the site:

Still not finding what you're looking for? Drop a comment on a post or contact us so we can take care of it!