Dwarfsoft [GPA]

Shared User Profiles – Staging Scripts

by dwarfsoft 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.

:, , , , , , , , , ,

2 Comments for this entry

  • droberts

    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

  • dwarfsoft

    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.

    Const HKEY_LOCAL_MACHINE = &amp;H80000002
    ADFQDN = GetDomainFQDN()
    ADDomain = GetDomainShortName()
     
    If ADFQDN  "" Then
        ret = StageAllUsers(ADFQDN,ADDomain)
    End If
     
    '**************************************************************************************************
    ' Get the Workstations Domain FQDN (if it has one)
    ' Returns "" if the machine is a not a member of a domain
    Function GetDomainFQDN
        GetDomainFQDN = ""
        Set objWMISvc = GetObject( "winmgmts:\\.\root\cimv2" )
        Set colItems = objWMISvc.ExecQuery( "Select * from Win32_ComputerSystem", , 48 )
        For Each objItem in colItems
            strComputerDomain = objItem.Domain
            If objItem.PartOfDomain Then
                GetDomainFQDN = strComputerDomain
            Else
                ' Workgroup Computer, Ignore it
            End If
        Next
    End Function
     
     
    '**************************************************************************************************
    ' Get the Workstations Domain Short Name (if it has one)
    ' Returns "" if the machine is a not a member of a domain
    Function GetDomainShortName
        GetDomainShortName = ""
        Set objWMISvc = GetObject( "winmgmts:\\.\root\cimv2" )
        Set colItems = objWMISvc.ExecQuery( "Select * from Win32_ComputerSystem", , 48 )
        For Each objItem in colItems
     
            If objItem.PartOfDomain Then
                Set objSystemInfo = CreateObject("ADSystemInfo") 
                GetDomainShortName = objSystemInfo.DomainShortName
            Else
                ' Workgroup Computer, Ignore it
            End If
        Next
    End Function

    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.

1 Trackback or Pingback for this entry

Leave a Reply

You must be logged in to post a comment.

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!