I'm working on a project that's really quite clever. It looks like an installed Windows application. In reality, it is a Ruby on Rails environment, backed by a SQLite3 database, and accessed by Firefox Portable. Our lead developer created a plugin for Rails called acts_as_replica that lets this application synchronize its database with a master server on the internet. The end result is an installable application with the speed of a local program and the agility of a web system. Since my "broad programming background" includes a lot of years spent programming Windows, I very quickly ended up in charge of maintaining the installer, which is built with a free installer tool called InnoSetup.
I just got my first really hairy problem: sometimes when our application shuts down, the controlling process closes without actually stopping Rails. It leaves rubyw.exe running, and locks several files on disk, which prevents the uninstaller from deleting files and, more importantly, prevents the updater from replacing them.
InnoSetup gives pretty good value for the money if you don't need to do anything crazy in your installer. I can't really complain too much about it because a) it works and b) it's free. It's even extensible by scripting if you need to go off-road. Of course, I wouldn't be a programmer if I didn't complain about the tools I was given, so I'll say this up front: If you need to script the installer, you have to do it in Pascal.
I forgot Pascal 15 years ago. This is not the same as never having learned it. For one thing, you mutter under your breath a lot more.
I took this job so I could program Ruby on Rails, but what the heck, it's just a little Pascal. And so I unstoppered the bottle labeled 'Drink Me', took a giant swig, and down the rabbit hole I went....
I'll spare you most of the madness of the next three days, and try to give you a quick A-Team-style montage: The Pascal scripting has many useful Windows API functions, but none of them were able to suitably identify the rubyw.exe process. I thought about checking for the locked-files side effect, but the only time setup can be aborted is at the very beginning of time before setup is fully initialized, which is before setup is aware of the install directory (not even the directory used by previous installs). Okay, it's time to look at running an external program to detect the process. It's very easy to detect from a C program, but I chose not to pursue that path as it would make it impossible for the other developers on my team to build the project, and including the .exe file would make it impossible for them to modify my code. It's also very easy to detect from a ruby or python script, but again there's no way to find the ruby executables from the previous install.
So it was that I sat and thought and thought and thought, about what scripting and programming options are available on every Windows machine, until it hit me: I could use WSH, the Windows Script Host. Every Windows machine since Windows 98 can run programs written in JScript and VBScript¹. Even better, I didn't want to go digging around for wscript.exe on the user's system, and the Pascal scripting supports ShellExecute(), which lets me try to open "app_status.js" without specifying the program, and Windows finds its own copy of wscript.exe and runs it on the program.
Okay, now we're cooking with gas. All I have to do is use the WSH to establish a connection to Windows Management services. This is packaged as a COM service on Windows, and WSH can create COM objects. Now all I have to do is ask it about running processes. I do this by executing a SQL query—
Stop laughing! I am not making this up for your amusement! I said stop laughing!
—executing a SQL query on the WMI: 'SELECT * FROM Win32_process' and loop through the results looking for rubyw.exe. If I find any, I return an error status, which tells the Pascal script that the ShellExecute failed, which in turn causes InitializeSetup to fail, which displays a friendly message to the user to shut down the application and run setup again.
So yay! I'm a Ruby on Rails programmer! By which I mean
I write Pascal
That shells out to Windows Script Host
So I can write JScript
That calls over COM to the WMI
So I can write SQL
To find out if Rails is running.
I am so horrified by this hack that I can't help but feel just a little bit proud. I put one comment in the Pascal script:
(* Pascal -> WSH -> JScript -> COM -> WMI -> SQL -> Rails.
When I said this was a kludge
I WAS NOT KIDDING AROUND. *)
One of my mottos is "Computers fear me". And for good reason: if they won't give me the data I need, I will rip out their entrails and reroute things until they give me the data I need--often sobbing at me to please just stop.
¹ No lie. Try it at home and amaze your friends: open notepad, type
WScript.echo("Chalain is a big fat dork!");
and save it as hello.js (make sure it does not put .txt at the end). Now double-click the file and see the message box magic!