Monday, March 24, 2008

Better Drive Mapping

As a network administrator, we often use vbscripts to map network shares.  Our end users are constantly wanting new shares with specific drive letters in many different locations.  So as administrators, it is our job to script a drive mapping solution for those users.

In this article, I'm going to talk about drive mapping for end users.  First, we have to specify a drive letter (i.e. T: ) when mapping.  This is typically supplied by the user(s) requesting the drive mapping.   Next we need the server and share (i.e. \\myserver\sharename).  Finally, we write the code to map the share to that drive letter and pop it into their script.  We also use other logic to determine who maps what drive, but we'll save those techniques for another article.

First, let's look at the basics of drive mapping.  In order to map drives, we use the Wscript.Network object.  From here, we can access all kinds of things like enumerating all the current network drive mappings, disconnecting drives, and mapping drives.  There are several other methods available using the link above.

Set WshNet = WScript.CreateObject("WScript.Network")

Now that we have our object, we can disconnect drives using the RemoveNetworkDrive method and we can map drives using the MapNetworkDrive method:

WshNet.MapNetworkDrive "X:", "\\myserver\sharename" 'Connect drive
WshNet.RemoveNetworkDrive "X:", "\\myserver\sharename" 'Disconnect drive

Easy huh?  But what if a user already has a different share mapped to drive X:?

As administrators, we have the power to force users to map certain shares to certain drive mappings.  Often times, we enforce this by disconnecting anything on the requested drive mapping before we map it.  This ensures we have the correct mapping on the drive letter.  This seems like overkill in my opinion.  Why waste time disconnecting and reconnecting a drive if we don't need to? 

To get around this, we can use the EnumNetworkDrives method.  This returns an array of drive letters and server shares.  We can then iterate through the list and compare to see if they exist.   Let's add this capability and create a nice function we can reuse:

 

Function MapDrive(strDrive,strPath)
'Input: strDrive = Drive letter - ex. "x:"
'Input: strPath = Path to server/share - ex. "\\server\share"
'Output: bln = True or False
 
    Err.clear
    MapDrive = False
    
    Set WshNet = WScript.CreateObject("WScript.Network")
    
    'Step 1: Get the current drives
    Set oDrives = WshNet.EnumNetworkDrives
    If Err.Number <> 0 Then
        'Code here for error logging
        Err.Clear
        MapDrive = False
        Exit Function 
    End If
    
    'Step 2: Compare drive letters to the one requested
    blnFound = False
    For i = 0 To oDrives.Count - 1 Step 2
        If UCase(strDrive) = UCase(oDrives.Item(i)) Then
            WScript.Echo "Drive letter " & strDrive & " mapped, checking connection"
            blnFound = True
            'Drive letter was found.  Now see if the network share on it is the same as requested
            If UCase(strPath) = UCase(oDrives.Item(i+1)) Then
                'Correct mapping on the drive
                MapDrive = True
            Else
                'Wrong mapping on drive.  Disconnect and remap
                WshNet.RemoveNetworkDrive strDrive, true, True 'Disconnect drive
                If Err.Number <> 0 Then
                    'Code here for error logging
                    Err.clear
                    MapDrive = False
                    Exit Function 
                End If
                
                WshNet.MapNetworkDrive strDrive, strPath 'Connect drive
                If Err.Number <> 0 Then
                    'Code here for error logging
                    Err.clear
                    MapDrive = False
                    Exit Function 
                End If
                
                MapDrive = True
                
            End If
        End If
        
    Next'Drive in the list
    
    'Ok.  If blnFound is still false, the drive letter isn't being used.  So let's map it.
    If Not blnFound Then
        WshNet.MapNetworkDrive strDrive, strPath
        If Err.Number <> 0 Then
            'Code here for error logging
            Err.clear
            MapDrive = False
            Exit Function 
        End If
        
        MapDrive = True
    End If
    
    Set WshNet = Nothing
    Set oDrives = Nothing
 
End Function

 

Looks long huh?  In summary, the function enumerates all the network drive mappings first.  Then it looks through the list for the requested drive letter to see if it's in use.  If so, it checks the share mapping on that drive letter.  If it's not correct, it disconnects and connects it proper.  If the drive letter is not in use, it then maps the share to the requested drive letter.

To use:

MapDrive("X:",\\myserver\sharename)

And because it is a function that returns True or False, we can use the results as well:

If MapDrive("X:","\\myserver\sharename") Then
    Wscript.Echo "Drive mapped successfully"
Else
    Wscript.Echo "Drive not mapped successfully"
End If 

There you have it.  A better drive mapping solution.  It's not perfect by no means, but it's better than just disconnecting wildly.  I plan on adding more logic to search for shares mapped on other letters to ensure we don't get double mappings and to migrate people to the correct ones in times were we need to change the drive letter (and they will, end users are fickle).

 

Till next time!

-Corey

Have a burning VBScript question to be answered?  Send it in and you may just be featured in our next article... and get your question answered.  :)

Thursday, March 20, 2008

Q: How do I sort data in an Array? (Part 2)

In my previous post, I walked you through a standard bubble sort method of sorting data in an array.  This method works and works fairly well but as I said earlier, there is another way to sort data using disconnected recordsets.

Disk on naked record sex?  What?  Disconnected recordsets are like ghostly databases.  Think of it as a database that lives solely in memory.  Once your program terminates (or the connection terminates), the information vanishes faster than plot lines on the new Knight Rider tv show.

Just like bubble sorting, a disconnected recordset can sort data in ascending and descending order.  But you may ask, why would I chose this method over good ol' reliable bubble sort?  Well, disconnected recordsets offer more than just sorting capability.  Remember, these are mini-databases so we can add and remove data on the fly (which is MUCH easier than dealing with resizing arrays). There are lots of other good reasons for using disconnected recordsets but I won't go into those now because we're talking about sorting arrays right?

 

Right.  So why are you holding back code like some trampy schoolgirl playing hard to get?  Fine, you wanna get right to it?  Here ya go:

 

Function DataListSort(arrData,strSort)
'Input: arrData = Array of data items, strings or integers
'Input: strSort = Sort method.  
'        Options: ASC (ascending) or DESC (descending)
'Output: Sorted Array
'Notes: This is currently only storing the first 10 characters.
'        Change MaxCharacters to increase this limit to allow more precise sorting.
'        This will also increase memory usage.
    
    DataListSort = ""
    strList = ""
    strSort = Trim(UCase(strSort))
    If Not strSort = "ASC" And Not strSort = "DESC" Then
        strSort = "ASC"
    End If 
    
    Const adVarChar = 200       ' Set the data type to variant.
    Const MaxCharacters = 10    ' Set the max num of characters to store
    
    'Setup Data object and connection
    Set DataList = CreateObject("ADOR.Recordset")
    DataList.Fields.Append "Items", adVarChar, MaxCharacters
    DataList.Open
    
    'Iterate through to add the array items to the record set
    For Each item In arrData
      DataList.AddNew
      DataList("Items") = item
      DataList.Update
    Next
    
    'Sort the record set
    DataList.Sort = "Items" & " " & strSort
    DataList.MoveFirst
    
    DataListSort = DataList.GetRows()
    DataList.Close
 
End Function 

 

 

This is a good reusable function that you can use and abuse.  First, you pass an array of data to it and the direction for sorting (ASC or DESC).  The function first creates a new recordset object, saves each element of the array to the database.  Then we do a very clean sort method and then read the sorted results back into a string with a comma between each element.   Since we're asking for an array back, we split the string by commas into an array and pass that as the return results of the function.  Thanks to a nifty suggestion from Paul Randall, we can completely bypass the old way of re-iterating through the rows by using the GetRows() method of the recordset.  This pulls all of the rows into an array.  Nice!

Sweet huh?  This is good example of using disconnected recordsets for sorting but you can use them for all kinds of cool stuff. For more information on disconnected recordsets, click here.

 

So there you have it.  You can now sort array data using old school bubble sort and disconnected recordsets.  Another adventure in vbscripting complete!  Next up, a little something for the network administrators out there: intelligent drive mapping!

 

Until next time, happy scripting!

Corey Thomas

 

Edit Notes: Updated the code to use the GetRows() method instead of iterating through the records.  Much faster.  Thanks for the suggestion Paul.  :)

Wednesday, March 19, 2008

Q: How do I sort data in an Array?

Sorting data is a task all programmers run into.  Most of the time, we are connecting to a database like SQL, MySQL, and Access and those database providers give us nice and neat sort methods for Ascending and Descending order.  We just ask for the data, tell it how we want it sorted, and BAM!  Formatted results.  Brilliant!

But you say, "gee Corey, that's great and all, but I want to sort data that is NOT in a database like an array".  That's where the adventure begins!

First, there are two well established methods of sorting arrays.  There is the Bubble Sort and the Disconnected Recordset methods.  Let's start with bubble sort.

First, here's the code:

 
arrData = Array(324,234,33,7,467,34)
 
For i = LBound(arrData) to UBound(arrData)
  For j = LBound(arrData) to UBound(arrData)
    If j <> UBound(arrData) Then
      If arrData(j) > arrData(j + 1) Then
         TempValue = arrData(j + 1)
         arrData(j + 1) = arrData(j)
         arrData(j) = TempValue
      End If
    End If
  Next
Next

 

This is the bubble sort method.  Think of it like this.  An array is just a long line of numbers or letters. The bubble sort looks at the first element of the array and compares it to the second.  If the first is greater than the second, it saves the value to a temporary variable, copies the second to the first, and the sets the second element back to the first.  Confusing?  Just think of it as swapping places.

Now a bubble sort works by going through the entire array and swapping two elements at a time until the final product is a nice sorted array.  Nifty.  But what if we wanted to do descending?  Simple, change the greater than to a less than:

If arrData(j) < arrData(j + 1) Then

See, told you it was simple.  Now, let's take this new bubble sorting knowledge and wrap it up into a nice function we can use, reuse, and abuse.

 

Function BubbleSort(arrData,strSort)
'Input: arrData = Array of data.  Text or numbers.
'Input: strSort = Sort direction (ASC or ascending or DESC for descending)
'Output: Array
'Notes: Text comparison is CASE SENSITIVE
'        strSort is checked for a match to ASC or DESC or else it defaults to Asc
 
 
    strSort = Trim(UCase(strSort))
    If Not strSort = "ASC" And Not strSort = "DESC" Then
        strSort = "ASC"
    End If 
 
    For i = LBound(arrData) to UBound(arrData)
      For j = LBound(arrData) to UBound(arrData)
        If j <> UBound(arrData) Then
            If strSort = "ASC" Then
              If arrData(j) > arrData(j + 1) Then
                 TempValue = arrData(j + 1)
                 arrData(j + 1) = arrData(j)
                 arrData(j) = TempValue
              End If
            End If
            
            If strSort = "DESC" Then
                If arrData(j) < arrData(j + 1) Then
                    TempValue = arrData(j + 1)
                    arrData(j + 1) = arrData(j)
                    arrData(j) = TempValue
                 End If        
            End If 
        End If
      Next
    Next
    
    BubbleSort = arrData
 
End Function

 

Now we're coding!  This function will take in an array and a sort direction.  ASC for ascending (lowest to highest) and DESC for descending (highest to lowest).  It even handles text comparison but is case sensitive.  If you wanted to disregard case sensitivity, you can check to see if the element is a string and if so, convert it to upper (or lower) case before you do the sort.

That's it for now.  Stay tuned for my next installment: Sorting arrays by disconnected recordsets!

Have a burning VBScript question to be answered?  Send it in and you may just be featured on the blog.  :0)

Happy Scripting!

Corey

Welcome aboard!

This blog will be the starting place of my adventures in VBScript.  I'm already a well experienced vbscripter, so I want to take the code I write and publish it to the web for other lucky adventures to use (and abuse).  
So welcome aboard!  Now grab an oar and start rowing.  :0)
-Corey