This is the first in (hopefully) a series of posts about vbScript. Please see my post vbScript - The Basics for more details on vbScript.
My wife and I take a lot of pictures. Naturally, we end up sending pictures to friends through email. I find it is unnecessary, and often inconsiderate to send full size images via email. When most of our friends end up viewing these images on a hand-held device, it is pointless to send an image wider than 600 pixels. As such, I have to repeatedly
- Copy the full size image to a temporary folder
- Run FastStone (my viewer of choice)
- Locate the image
- Resize it
- Save it
This gets tedious after a while. Fortunately, I found a free package, ImageMagick which allows me to do the resize from the command line. What I would like is to set up a special folder such that when I drop an image file into it, it gets automatically resized. I could easily do this by running a script in a loop and repeatedly polling a folder but this is wasteful in terms of resources. I would prefer to have Windows notify me when a new file appears in a folder. Fortunately Windows has the ability to create a FolderWatch that will sit idly by, consuming next to nothing in the way of resources, then do something when there is something to be done.
A couple of notes:
'#region Header '#endregion
are recognized by PrimalScript to define code folding (collapsible) regions and are treaded by the interpreter as comments.
This includes the code
Function iif ( ByVal condition , ByVal truevalue , ByVal falsevalue ) If condition Then iif = truevalue: Else: iif = falsevalue: End If End Function
to provide a ternary function that should have been in vbScript to begin with.
WatchFolder uses a Windows component, WMI (for Windows Management Instrumentation). WMI is complex enough that when I need to use it I just Google what I want then use it. I could spend months learning it but it's not worth my time to do that for the little I use it. Simply put, the query to create a FolderWatch is
query = "SELECT * FROM __InstanceOperationEvent WITHIN 2" & vbCrLf _ & " WHERE Targetinstance ISA 'CIM_DataFile'" & VbCrLf _ & " AND TargetInstance.Drive='" & drive & "'" & VbCrLf _ & " AND TargetInstance.Path='" & folder & "'"
It is not necessary to add
vbCrLf (carriage return/line feed) except that it makes the query easier to read when displayed to the console (code is for humans to read). Once we execute the query we enter a loop which does nothing but wait for events which are then farmed out to external handlers. vbScript does not do callbacks so we simulate that functionality with the
Execute command. If you have provided a handler to handle the
Create event - for this example, let's assume you have created a function
and you have copied
myfile.jpg to the watched folder - what will result is the execution of the string (assuming you are watching the folder D:\temp\resize)
If you have not provided a handler for that event (passed a null string) then what results is
which does nothing. So now that we have a WatchFolder sub, how is it used? Here is the front end.
''#region Header ' '' ' '' Name: ' '' ' '' AutoResize.vbs ' '' ' '' Description: ' '' ' '' This script uses a Windows folder watch process to monitor a given folder for ' '' File Create events. When a file is created, the Resize function is called by ' '' the folder watch process. If the created file was of type jpg, the file is ' '' read, resized acdording to the command line arguments, then saved back into ' '' the same file. ' '' ' '' Notes: ' '' ' '' Requires the installation of ImageMagick (https://www.imagemagick.org/) ' '' ' '' I recommend running this with wscript.exe instead of cscript.exe ' '' ' '' Audit: ' '' ' '' 2018-06-16 rj Original code ' '' ' ''#endregion ' Include "%INCLUDE%\WatchFolder.vbs" Set wso = CreateObject("Wscript.Shell") Set fso = CreateObject("Scripting.FileSystemObject") ''Get folder name If WScript.Arguments.Unnamed.Count = 0 Then WScript.Echo "You must give a folder name on the command line" WScript.Quit End If folder = WScript.Arguments.Unnamed(0) If Not fso.FolderExists(folder) Then fso.CreateFolder(folder) End If ''Get command line options ' width = 600 For Each arg In WScript.Arguments.Named Select Case Lcase(arg) Case "w","width" : width = Wscript.Arguments.Named(arg) Case "?","help" : Help("") : Wscript.Quit Case Else : Help(arg): Wscript.Quit End Select Next 'check if width is numeric If not IsNumeric(width) Then WScript.Echo "Width must be a numeric value" WScript.Quit End If width = CInt(width) 'chbeck if width is in an acceptable range If width < 100 Or width > 2000 Then WScript.Echo "Please enter a width value from 100-2000" WScript.Quit End If 'start the folder watch with a callback to Resize on 'Create' event WatchFolder folder, "ReSize", "", "" Function ReSize (file) If LCase(fso.GetExtensionName(file)) = "jpg" Then cmd = "magick " & """" & file & """" & " -resize " & width & " """ & file & """" wso.Run cmd, 7, True '7 = run minimized wso.Popup file & " resized", 5, "AutoResize", 4096 End If End Function ''Display help info ' Function Help (str) If Len(str) > 0 Then WScript.Echo VbCrLf & ">>>Unknown option: /" & str Wscript.Echo "" Wscript.Echo "AutoResize folder" WScript.Echo "" WScript.Echo " Watch a folder for the creation of jpg files. If triggered, resize" WScript.Echo " the file to a given width in the range 100-2000)." Wscript.Echo "" Wscript.Echo " Description" Wscript.Echo "" Wscript.Echo " Options:" Wscript.Echo "" WScript.Echo " /width:### (/w) width (default is 600 pixels)" Wscript.Echo " /help (/?) show this help" End Function ''Include the given file in the global namespace ' Function Include ( ByVal file ) Dim wso: Set wso = CreateObject("Wscript.Shell") Dim fso: Set fso = CreateObject("Scripting.FileSystemObject") ExecuteGlobal(fso.OpenTextFile(wso.ExpandEnvironmentStrings(file)).ReadAll) End Function
You'll see that almost all of the front end is housekeeping (checking arguments, providing help text). The interesting parts are
WatchFolder folder, "ReSize", "", ""
This line calls WatchFolder and passes it four parameters
- The folder to monitor
- The name of the handler for
Createevents (as a string)
- A null string for
Deleteevents (ignore these events)
- A null string for
Modufyevents (ignore these events)
The other code of interest is the handler
Function ReSize (file) If LCase(fso.GetExtensionName(file)) = "jpg" Then cmd = "magick " & """" & file & """" & " -resize " & width & " """ & file & """" wso.Run cmd, 7, True '7 = run minimized wso.Popup file & " resized", 5, "AutoResize", 4096 End If End Function
The actual command line that we will execute will look something like
magick "d:\temp\resize\myimage.jpg" -resize 600 "d:\temp\resize\myimage.jpg"
and to execute it we use the
Run method of the Wscript.Shell object,
Run method is passed three parameters
- The name of the program to run
- The Window type of the new process (7=run minimized)
- A boolean indicating whether to wait for completion (True) or not (False)
The next line calls the PopUp method which (in this case) will display a popup dialog that will display on top (4096). If the user doesn't close it within 5 seconds it will auto close.
shell:startup folder I have created a shortcut
so that this process will always sit in the background ready to work when needed.