Wednesday, May 21, 2008

RemoteService Function

Slacker.  Go ahead, say it. I know your thinking it.  Work has been busy but that doesn't mean I'm slacking off on coding.  Oh no, I'm hard at work writing new functions and subs.  I finished one recently when I needed to stop and start a service on a remote machine.  And as usual, I couldn't just make a sub that only stopped and started a service.  I had to add in other abilities like Pause, Resume, and Restart.  Oh yeah, now we're talking.

But that's not all.  I started to think, what else would I want to do with a service?  How about changing it's startup type to Automatic, Manual, or Disabled?  That's something we network administrators need to do from time to time.  So I added that in too.

I wrapped all the code up in a nifty little sub (sorry, no shiny paper and bow).  To use it, simply pass the computer name, service name, and the action you want to complete and presto!  The service will be changed.

chrip chrip chrip..  What's that?  The little birdy on my shoulder said "what about errors?".  Good point.  What if we want to capture the return status of the action?  For example, what if I try to start a service that is already started?  It turns out that we can capture the return code.  The code is a user un-friendly integer so run it through a select case and set a boolean variable as true/false as well as setting some status text. So now that we have that, how do we pass two variables back?  You can't pass it back in a single function.  So here's what I did.

To get the return code and status, declare these two variables in the main part of the script: blnServiceStatus and strServiceStatus.

When the action on the service completes, the return code is filtered and these two variables are set.  All you need to do is declare them globally and now you've got the status!  One special note on the blnServiceStatus results, I tried my best to interpret the return code as a true or false as representative of the success of the action.  You may need to tweak these for your desired results.

All this talk and no action.  Let's see some code!

Sub remoteService(sstrComputer,sstrService,sstrAction)
'This accepts a computer name, service name, and action to complete on the service.
'Input: sstrComputer = Machine to perform action on.  Use "." for local
'Input: sstrService = Name of the service
'Input: sstrAction = See options below
'        "Start" = Start the service
'        "Stop" = Stop the service
'        "Restart" = Restart the service
'        "Pause" = Pause the service
'        "Resume" = Resume the service
'        "Automatic" = Set the startup type to Automatic
'        "Manual" = Set the startup type to Manual
'        "Disabled" = Set the startup type to Disabled
 
'To get the status codes:
'1.  Declare strServiceStatus and blnServiceStatus globally (Main part of the script)
'2.  Call this sub as usual.
'3.  blnServiceStatus will be set to True if successfull.  
'4.  strServiceStatus will be set to the status of the action
 
 
 
On Error Resume Next 
    Set sobjWMIService = GetObject("winmgmts:" _
        & "{impersonationLevel=impersonate}!\\" & sstrComputer & "\root\cimv2")
    
    Set scolServiceList = sobjWMIService.ExecQuery("Select * from Win32_Service where Name='" & sstrService &"'")
    'Set colServiceList = objWMIService.ExecQuery("Select * from Win32_Service")
 
    For Each sobjService in scolServiceList
        'WScript.Echo sobjService.Name
        
        sstrAction = UCase(sstrAction)
        Select Case sstrAction
        
            Case "START"
                serrReturn = sobjService.StartService()
           
            Case "STOP"
                serrReturn = sobjService.StopService()
                
            Case "RESTART"
                serrReturn = sobjService.StopService()
                serrReturn = sobjService.StartService()
                
            Case "PAUSE"
                serrReturn = sobjService.PauseService()
                
            Case "RESUME"
                serrReturn = sobjService.ResumeService()
                
            Case "AUTOMATIC"
                serrReturn = sobjService.ChangeStartMode("Automatic")
            
            Case "MANUAL"
                serrReturn = sobjService.ChangeStartMode("Manual")
            
            Case "DISABLED"
                serrReturn = sobjService.ChangeStartMode("Disabled")
        
        End Select
        
        'WScript.Echo serrReturn
        
        Select Case serrReturn
        
        
            Case 0  'Success
                blnServiceStatus = True
                strServiceStatus = "Success"
                
            Case 1    'Not Supported
                blnServiceStatus = False
                strServiceStatus = "Not Supported"
            
            Case 2    'Access Denied
                blnServiceStatus = False
                strServiceStatus = "Access Denied"
            
            Case 3    'Dependent Services Running
                blnServiceStatus = True
                strServiceStatus = "Dependent Services Running"
 
            Case 4    'Invalid Service Control
                blnServiceStatus = False
                strServiceStatus = "Invalid Service Control"
 
            Case 5    'Service Cannot Accept Control
                blnServiceStatus = False
                strServiceStatus = "Service Cannot Accept Control"
 
            Case 6    'Service Not Active
                blnServiceStatus = False
                strServiceStatus = "Service Not Active"
 
            Case 7    'Service Request Timeout
                blnServiceStatus = False
                strServiceStatus = "Service Request Timeout"
 
            Case 8    'Unknown Failure
                blnServiceStatus = False
                strServiceStatus = "Unknown Failure"
 
            Case 9    'Path Not Found
                blnServiceStatus = False
                strServiceStatus = "Path Not Found"
 
            Case 10    'Service Already Running
                blnServiceStatus = True
                strServiceStatus = "Service Already Running"
 
            Case 11    'Service Database Locked
                blnServiceStatus = False
                strServiceStatus = "Service Database Locked"
 
            Case 12    'Service Dependency Deleted
                blnServiceStatus = True
                strServiceStatus = "Service Dependency Deleted"
 
            Case 13    'Service Dependency Failure
                blnServiceStatus = False
                strServiceStatus = "Service Dependency Failure"
 
            Case 14    'Service Disabled
                blnServiceStatus = True
                strServiceStatus = "Service Disabled"
 
            Case 15    'Service Logon Failure
                blnServiceStatus = False
                strServiceStatus = "Service Logon Failure"
 
            Case 16    'Service Marked For Deletion
                blnServiceStatus = True
                strServiceStatus = "Service Marked For Deletion"
 
            Case 17    'Service No Thread
                blnServiceStatus = False
                strServiceStatus = "Service No Thread"
 
            Case 18    'Status Circular Dependency
                blnServiceStatus = False
                strServiceStatus = "Circular Dependency"
 
            Case 19    'Status Duplicate Name
                blnServiceStatus = False
                strServiceStatus = "Duplicate Name"
 
            Case 20    'Status Invalid Name
                blnServiceStatus = False
                strServiceStatus = "Invalid Name"
 
            Case 21    'Status Invalid Parameter
                blnServiceStatus = False
                strServiceStatus = "Invalid Paramenter"
 
            Case 22    'Status Invalid Service Account
                blnServiceStatus = False
                strServiceStatus = "Invalid Service Account"
 
            Case 23    'Status Service Exists
                blnServiceStatus = True
                strServiceStatus = "Service Exists"
 
            Case 24    'Service Already Paused  
                blnServiceStatus = True
                strServiceStatus = "Already Paused"
        
        
        End Select
        
    Next
    
 
End Sub 

And there you have it.  A nice and robust sub for handling services.  Remember, you can use this in a local script too.  Just use "." as the computer name.  :)

 

Happy coding!

-Corey

Thursday, April 17, 2008

Create a new link in Favorites

Well, it's been a while since I've been able to post a new script.  Guess I've been too busy using my skillz to pay the billz as they say...  So today I'm posting a new script or rather, a snippet you can use in your own scripts.  This comes directly from my library of functions and is used frequently in enterprise logon scripts.

 

As a system administrator, you may find it necessary to create a script to add a link to a user's favorites.  There are several ways to do this including Group Policy, copying the .url or .lnk to the local computer, or vbscript.  I could put my smartz hat on and show you how it's done via group policies but this is a scripting blog.  I could also show you how to copy a shortcut file via copy, xcopy, or robocopy but that would be no fun.

 

So let's look at a script all ready, begeezus!  Fine.  There are really two things you can do.  1.  Copy a premade link using the filesystemobject.  Or, and better yet.. 2. Create a shortcut file on the fly using the Wscript.Shell object.

 

Code:

Sub CreateFavorite(sstrURL, sstrFileName, sstrFolder)
'Notes: Creates a favorite for the current logged on user.
'Input: strURL = URL to website
'Input: strFilename = file name to save (ex. technet.url)  
'Input: strFolder = folder to put the favorite in.  (pass null and it will default to the root)
 
    sstrURL = Trim(sstrURL)
    sstrFileName = Trim(sstrFileName)
    Set sobjFSO = CreateObject("Scripting.FileSystemObject")
    
    If IsNull(sstrFolder) Then
        sstrFolder = ""
    End If 
    
    If Not UCase(Right(sstrFileName,3)) = "URL" Then
        sstrFileName = sstrFileName & ".url"
    End If 
    
    Set sWshShell = CreateObject("WScript.Shell")
    sstrPath = sWshShell.SpecialFolders("Favorites")
    
    If Not sobjFSO.FolderExists(sstrPath & "\" & sstrFolder) Then
        sobjFSO.CreateFolder(sstrPath & "\" & sstrFolder)
    End If 
        
    Set sobjShortcutUrl = sWshShell.CreateShortcut(sstrPath & "\" & sstrFolder & "\" & sstrFileName)
    sobjShortcutUrl.TargetPath = sstrURL
    sobjShortcutUrl.Save 
 
End Sub 

 

To use:  Simply call CreateFavorite and pass the information.

Example:  CreateFavorite "http://vbscripter.blogspot.com", "VBScript Blog", "VBScript Stuff"

 

This will create a link called "VBScript Blog" to this website in a folder called VBScript Stuff inside Favorites.  Nifty. 

Take note that this is ran locally so you'll need to execute it via logon script or some other method like remote scripting, etc.  Enjoy!

 

Until next time (whenever that may be at this rate)!

-Corey

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.  :)