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.
Include "%INCLUDE%\iif.vbs"
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
Function Resize(file)
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)
Resize("D:\temp\resize\myfile.jpg")
If you have not provided a handler for that event (passed a null string) then what results is
Execute ""
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
Create
events (as a string) - A null string for
Delete
events (ignore these events) - A null string for
Modufy
events (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, wso
. The 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.
In my shell:startup
folder I have created a shortcut
wscript.exe d:\temp\resize
so that this process will always sit in the background ready to work when needed.