Thursday, August 21, 2008

VBScript: How to Lock a Workstation

I'm not a huge fan of VBScript but occasionally I find it a useful tool to employ. I like the fact that it gives me the ability to make a quick, double-click-and-take-action solution, all while using the simplest of code compliers out there; Notepad!

As a quick example, this code can be used to lock a workstation. To create the script just open a new text document in Notepad (not word, if you please) and enter the following:

"""""""Start Code""""""""""""""

On Error Resume Next
Set objShell = CreateObject("Wscript.Shell")
objShell.Run "%windir%\System32\rundll32.exe user32.dll,LockWorkStation"

"""""""End Code"""""""""""""""


Save the file and then change the file extension from .txt to .vbs and there you have it.

(Note, some system admins lock out the .vbs file extension for safety and security reasons so this may or may not work where you work, as it were.)

I typically place a shortcut to this little script in my quick launch bar and use it as a "one click and walk away" convenience at work. Once you launch the script there's pretty much nothing short of a full OS failure that's going to stop the workstation from locking. Comes in handy when you're constantly dashing away from your desk to put out fires.

Wednesday, August 20, 2008

Visual Basic 6: How to run an event at a given time (Without using the Timer Control)

Without a doubt one of the best tools that Visual Basic 6 brings to the table is the Timer Control, in my opinion. This gives you the ability to make your application perform events at given intervals without using looping delays and burning up resources, and under most circumstances it works wonders. There are some obvious limitations however, such as the Timer Interval maxing out around 64 seconds, so if you want to trigger an event at intervals of say an hour apart, you're left with a challenge.

There are a couple of ways that you can get around this limitation but I've found the following to be the "best" for the most number of situations and circumstances that I commonly encounter. It's relatively straight forward (at least for VB) but explanations are seldom as effective as a simple demonstration so, here you go.

As an example you can create a new .exe project with two buttons, one text box and a standard module. Enter the code below and then compile the project as a standard .exe.

Put the following code in the standard module:

'Start Module Code
Declare Function SetTimer Lib "user32" _
(ByVal hwnd As Long, _
ByVal nIDEvent As Long, _
ByVal uElapse As Long, _
ByVal lpTimerFunc As Long) As Long

Declare Function KillTimer Lib "user32" _
(ByVal hwnd As Long, _
ByVal nIDEvent As Long) As Long


‘This is the code that you want the timer to execute at each interval

Public Sub VB_Action(ByVal hwnd As Long, _
ByVal uMsg As Long, _
ByVal idEvent As Long, _
ByVal dwTime As Long)

Form1.Text1.Text = Now

End Sub


Place the following code within Form1.

Dim Timer_ID As Long

Private Sub Command1_Click()
'Starts the timer.
Timer_ID = SetTimer(0, 0, 1000, AddressOf VB_Action)
End Sub

Private Sub Command2_Click()
'stops the timer.
Timer_ID = KillTimer(0, Timer_ID)
End Sub

Now compile the project as a .exe.

NOTE: This example WILL NOT work correctly if you try to run it from within VB6 as an uncompiled program. The Timer will start but the KillTimer function will not stop it correctly and when you try to end the run it's likely that VB6 will crash with an error. If you compile the code to a .exe and run it, it works fine.

This simple example will basically make a VB Clock. When you click Command Button 1 the program will start updating text1 with the date / time at 1 second intervals until you either click command button 2 to stop it, or exit the program.

To use this procedure in a more practical manner you would replace VB_Action with the sub of your choice and would set the interval to whatever time frame you needed. When setting the interval you'll need to convert the interval to milliseconds - I know, it's painful - and you have a maximum interval of 2,147,483,647 milliseconds or approximately 24.8 days.

Another limitation with this method is that for the event to occur the application must remain open and running between intervals. If the application is closed or stops responding at any point then no further action will occur from that point on. I'm sure there's probably a way to cause the application to launch itself with a callback but it's not something I've needed to know how to do at this point so I can't offer any advice on that one.

I don't see any reason why this method wouldn't work for VBA as well, however it's typically easier to the use the Application.OnTime method when you're dealing with VBA. As a bonus, when using the Application.OnTime method, you can close the file between intervals and the system will automatically launch the file at the next interval.

Tuesday, August 19, 2008

Visual Basic 6: How to Simulate a Mouse Click

Sometimes, despite everything else you've tried you just need to simulate a mouse click somewhere. Again, not that difficult but not intuitive either.


'Start Module Code

Public Declare Sub mouse_event Lib "user32" (ByVal dwFlags As Long, ByVal dX As Long, ByVal dY As Long, ByVal cButtons As Long, ByVal dwExtraInfo As Long)

Public Enum vButtons
vRightClick = 2
vDoubleRight = 4
vLeftClick = 8
vDoubleLeft = 16
End Enum

Public Const LEFTDOWN = &H2, LEFTUP = &H4, MIDDLEDOWN = &H20, MIDDLEUP = &H40, RIGHTDOWN = &H8, RIGHTUP = &H10

Public Sub Mouse_Click(Check_Button As vButtons = vNothing)

Select Case Check_Button
Case vRightClick
mouse_event RIGHTDOWN, 0&, 0&, 0&, 0&
mouse_event RIGHTUP, 0&, 0&, 0&, 0&

Case vDoubleRight
mouse_event RIGHTDOWN, 0&, 0&, 0&, 0&
mouse_event RIGHTUP, 0&, 0&, 0&, 0&
DoEvents
mouse_event RIGHTDOWN, 0&, 0&, 0&, 0&
mouse_event RIGHTUP, 0&, 0&, 0&, 0&

Case vLeftClick
mouse_event LEFTDOWN, 0&, 0&, 0&, 0&
mouse_event LEFTUP, 0&, 0&, 0&, 0&

Case vDoubleLeft
mouse_event LEFTDOWN, 0&, 0&, 0&, 0&
mouse_event LEFTUP, 0&, 0&, 0&, 0&
DoEvents
mouse_event LEFTDOWN, 0&, 0&, 0&, 0&
mouse_event LEFTUP, 0&, 0&, 0&, 0&

End Select
End Sub

'End Module Code


Using the code is simple: just find a spot where you need to simulate a mouse click and call the Mouse_Click() sub with one of the built in options (Right Click, Double Right Click, Left Click, Double Left Click). I typically use this in conjunction with SetCursorPos to move the pointer to a specific location and then call whichever click action I need at the time.

Monday, August 18, 2008

Visual Basic 6: How to Move the Mouse

Moving the Mouse isn't exactly difficult, but it's not exactly straight forward either. To make it happen in VB6 you have to use a couple of API calls.

Place the following code in a standard module:

'Start Module Code

Public Declare Function GetCursorPos Lib "user32" (lpPoint As _ POINTAPI) As Long

Public Declare Function SetCursorPos Lib "user32" (ByVal X As Long, _ ByVal Y As Long) As Long

Public Type POINTAPI
X As Long
Y As Long
End Type

Public a As POINTAPI
Public b As Long
Public c As Long

'End Module Code

You can then use the following code to perform some useful tasks:

GetCursorPos a
'This code will take the current cursor coordinates and will assign it to a.x and a.y.

SetCursorPos X, Y
'Where X and Y are the coordinates you want the mouse to end up at.

How do I use this code? Typically I make a button with a delay of a few seconds (using the Sleep API) followed by the GetCursorPos a code above. I then output the values of a.x and a.y to either a text field or a message box. This lets me map out the coordinates of where I want to put the cursor.

Once I know where I want the cursor to be I use the SetCursorPos X, Y code to move it.

Visual Basic 6: How to create a delay using the Sleep API Call

Note: The following applies to both Visual Basic 6 and VBA.

Sometimes you just need to put your application to sleep for a little while such as when you need to build in a delay while other applications do their thing. There are a couple of ways you can accomplish this but the easiest is to call on the Sleep API.

Enter the following in the General Declarations section of a form or module.

Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)

To use: just add the line Sleep X to your code where X is the number of milliseconds of delay that you want to build in. Works like a charm.

Visual Basic 6: How to save a record to a Sequential File

This code will save a record to a sequential file; basically a clear text file where each record is separated by a return carriage (vbCrLf). This is useful for quick input / output scenarios where you don't want or need to use anything other than a text editor to review output or modify input. Note - using the Open For Append As statement means that if the file exists the code will open it and add the record to the end of the file. Otherwise the code will create the file and will add the record as the first entry.

Public Sub Save_To_Sequential()
'Set the path for the file
Save_Path = "C:\Temp\Test.txt"
'Assign the data you want to save to a variable
Data_To_Save = "This is a test line."
Open Save_Path for Append as #1
Print #1, Data_To_Save
Close #1
End Sub

Now to reverse the process, that is to read from a file a sequential file, you would use code like the following:

Public Sub Read_From_Sequential()
'Set the path for the file
Save_Path = "C:\Temp\Test.txt"
Open Save_Path for Input as #1
Line Input #1, Holder
'Holder is the variable to hold the record
Close #1
End Sub

Mind you these are simple examples that deal with single records only. To handle multiple records you just need to repeat the Print #1, Data_To_Save code while changing Data_To_Save to a new value to save each time (such as by using an array) when saving. When reading a file, each time you repeat the Line Input #1, Holder code, Holder will take on the value of the next line within the file.

For example, if you save a series of strings to a string array called Save_Items() and want to output them all to a file the following code would do just that.

Public Sub Multiple_Save_To_Sequential()
'Set the path for the file
Save_Path = "C:\Temp\Test.txt"
For I = 1 to ubound (Save_Items())
Open Save_Path for Input as #1
Print #1, Save_Items(I)
Close #1
Next I
End Sub

To read the file back into the array you would use the following:

Public Sub Multiple_Read_From_Sequential()
'Set the path for the file
Save_Path = "C:\Temp\Test.txt"
Open Save_Path for Input as #1
Do
Count = Count + 1
Redim Preserve Save_Items(Count)
Line Input #1, Holder
Save_Items(Count) = Holder
Loop While Seek(1) < LOF(1)
Close #1
End Sub

Keep in mind that the files created by this kind of code can be read and modified by anyone with a text editor such as note pad. This makes them great when you want to be able to make quick, easy changes to values on the fly, but less than desirable when you're dealing with data that confidential or sensitive.