Client Drive Mappings in a View Desktop

A customer recently came to me with a very specific request.  They wanted all of the drive mappings that each user had created on their local workstation (their View Client) to be created on their VDI Desktop.  Without roaming their profile, I created a simple logon script for the VDI Desktop to execute (VMware’s Profile Migration does not capture Drive Mappings, at least not when converting from XP to Win 7).  As you’ll see below, I really just found 3 other smarter people’s scripts and mashed them together  (with some minor adjustments to work in the VDI scenario) in order to get them to do what I needed.  Since the results are highly specific (and a bit interesting), I figured that I’d go ahead and post the script.  Be aware that it abuses the fact that, in this environment, everyone is a local administrator on their physical desktop, so I expect that some of the client registry queries might fail under other circumstances.

Just so that everyone knows, the customer is considering a few solutions to accomplish their needs.  A logon script is not the most desirable solution, and we’d certainly want to find a real scripting guy to at least clean this up before going live with it.  That said, it seems to do what it’s supposed to do in the test scenarios that I’ve subjected it to so far (which is not nearly as in depth as I’d like).  This script was an interesting challenge because it had to accomplish a few distinct tasks:
1)      It had to identify the Client machine
2)      It had to check for mapped network drives on the Client machine
3)      It had to ensure that it wasn’t conflicting with existing drives in the VDI Desktop
4)      It had to map any drives that weren’t already in use

The dictionary procedure that I used for goal #3 could be adapted to find the next available drive letter (and in fact, that’s what the source had originally written it to accomplish), but I elected to just skip that drive if it already exists (otherwise you’d have to start checking mapping destinations as well as the existence of drive letters, which would get onerous). 

Identifying the client’s mapped network drives (goal #2) proved to be more challenging than expected.  Finding the network mapped drives from the local system is a trivial procedure (accomplished with the WMI Query: “Select * from Win32_LogicalDisk Where DriveType = 4”), however a remote system is querying the computer’s WMI, whereas mapped network drives are associated with a user.  The long and short of it is that that method simply doesn’t work from a remote system (in this case, the virtual desktop).

The procedure that I found for discovering a remotecomputer’s network drive mappings involved going through that computer’s registry and looking at the current user’s hive.  The original code that I found to do this just looked at the owner of the Explorer.exe process in order to determine who is currently logged in, however our desktops are not starting the Explorer shell (and are instead booting directly to the View Client), so that option was out (and I want something flexible enough that it will run regardless of the shell that Windows starts).  Fortunately, Guy Thomas posted a very easy to use bit of code that used WMI’s Win32_ComputerSystem class to fetch that same information.

So, with elements of those three scripts combined with a little bit (a very little bit) of my own VDI Registry knowledge, I put together the following script.  Please feel free to use it (or bits of it), and hopefully you’ll be able to improve on my mess… but always make sure that those aforementioned smart people get credit where it’s due.  As always, please be wary of unintended line breaks due to blog width and this is provided as-is, use at your own risk, etc.

'===========================================

' Dynamic Drive Mappings Based on Client Computer Mappings
' Author(s): Ginolard / ScriptingGuy1 / Guy Thomas / Jason Coleman
' Source(s): a lot of duct tape.
' This script is designed to be run on a VMware View Virtual Desktop that is being accessed from a Windows Client.
' It looks at all of the drives that the current user has mapped on their Windows Client, then creates those drive mappings on the Virtual Desktop.
' It does not attempt to map a network drive if that drive letter is already in use on the system.
' It also maps the client's "CD" share in order to support local CD-Rom access.
'===========================================
on error resume next

'Define variables, constants and objects
Dim objShell
Dim sUser, strRemComputer, strRemotePath
Set objShell = CreateObject("WScript.Shell")
Set objNetwork = CreateObject("Wscript.Network")
Set objDictionary = CreateObject("Scripting.Dictionary")
strComputer = "."
'Defines the remote computer by looking in the local computer's registry for the View Client info
strRemComputer = objShell.RegRead("HKEY_CURRENT_USER\Volatile Environment\ViewClient_Machine_Name")

Const HKEY_USERS = &H80000003
Set objWbem = GetObject("winmgmts:")
Set objRegistry = GetObject("winmgmts://" & strRemComputer & "/root/default:StdRegProv")
Set objRemWMIService = GetObject("winmgmts:"  & "{impersonationLevel=impersonate}!\\" & strRemComputer & "\root\cimv2")
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colDisks = objWMIService.ExecQuery("Select * from Win32_LogicalDisk")

'Create a Dictionary of all local disks, to easily check against later when mapping
For Each objDisk in colDisks
    objDictionary.Add objDisk.DeviceID, objDisk.DeviceID
Next

'Uses Guy Thomas' code to discover the currently logged on user of the Client system
Set colComputer = objRemWMIService.ExecQuery ("Select * from Win32_ComputerSystem")
For Each objComputer in colComputer
    'Note: if objComputer.UserName is null, this generates a script error.  objComputer.UserName is null when no one is logged in.
    sUser = Split(objComputer.UserName,"\")
    sUser(1) = LCase(sUser(1))
Next

WScript.sleep 3000
WriteFile sUser(1) & " is logged on at " & strRemComputer, false
objNetwork.RemoveNetworkDrive "E:"

'Quit out of the script if the Client is not responding to WMI and is therefore assumed to not be Windows
If sUser(1) = Chr(0) Then
                WriteFile "Unable to discover logged on client user, client does not appear to be Windows", true
                wscript.quit
End If

'Loop through the HKEY_USERS hive until (ignoring the .DEFAULT and _CLASSES trees) we find the tree that corresponds to the currently logged on user.
lngRtn = objRegistry.EnumKey(HKEY_USERS, "", arrRegKeys)    
     
For Each strKey In arrRegKeys
    If UCase(strKey) = ".DEFAULT" Or UCase(Right(strKey, 8)) = "_CLASSES" Then
    Else
                                'Gets the user name that corresponds with the SID that owns the registry key
        Set objSID = objWbem.Get("Win32_SID.SID='" & strKey & "'")
                                'If the account name of the current sid we're checking matches the username that is logged into the Client, then enumerate the Network subtree
        If objSID.accountname = sUser(1) Then 
            regpath2enumerate = strkey & "\Network" 'strkey is the SID
            objRegistry.enumkey hkey_users, regpath2enumerate, arrkeynames
                                                'If the array has elements, go and get the drive mapping info from the registry
            If Not (IsEmpty(arrkeynames)) Then
                For Each subkey In arrkeynames
                    regpath = strkey & "\Network\" & subkey
                    regentry = "RemotePath"
                    objRegistry.getstringvalue hkey_users, regpath, regentry, dapath
                    WriteFile subkey & ":" & vbTab & dapath, true
                                                                                If objDictionary.Exists(subkey & ":") Then
                                                                                                WriteFile subkey & ": drive already exists", true
                                                                                Else
                                                                                                WriteFile "Mapping " & subkey & ": to " & dapath, true
                                                                                                objNetwork.MapNetworkDrive subkey & ":", dapath, true
                                                                                End If
                Next
            End If
        End If
    End If
Next

'Map the Client's CD Share to the E Drive
'creates the path to the client's CD Share based on the registry
WriteFile "Mapping E: to \\" & strRemComputer & "\CD", true
objNetwork.MapNetworkDrive "E:", "\\" & strRemComputer & "\CD", true

Function WriteFile(sText, bAppend)
                strFile = "C:\Temp\Drives-Log.txt"
                Const ForAppending = 8
                set objFSO = CreateObject("Scripting.FileSystemObject")
                if bAppend then
                                set objFile = objFSO.OpenTextFile(strFile, ForAppending, true)
                else
                                Set objFile = objFSO.CreateTextFile(strFile, True)
                end if
                objFile.WriteLine(Now & ": " & sText)
                objFile.Close
End Function


Comments

Popular posts from this blog

PowerShell Sorting by Multiple Columns

Clone a Standard vSwitch from one ESXi Host to Another

Deleting Orphaned (AKA Zombie) VMDK Files