Chalain - Computers Fear Me
|Dec. 19th, 2007 07:24 pm Computers Fear Me|
So a few months ago I decided I wanted to work full-time in Ruby on Rails. I spent a month unemployed and had to take a pay cut, but finally landed a sweet job at my current employer. I get to work with some real Rails wizards. They're teaching me good stuff and in exchange I bring a broad programming background to the table.
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!
Current Mood: naughty56 comments - Leave a comment
Current Music: Bad Habit - The Offspring
|Date:||December 20th, 2007 04:17 am (UTC)|| |
When you mentioned InnoSetup, I immediately wondered if it was from Innosoft, and when you brought up Pascal, I was sure of it. The only real (as opposed to classroom) use I have ever made of Pascal was writing/customizing mail transport drivers for PMDF (the Pascal Memo Distribution Facility) which, for its time, was the best MTA around for VMS systems. I haven't even considered programming anything in Pascal in years. I suspect it would come back to me, but I'd just as soon not find out. :)
The whole thing sounds like some of the things I have had to do, but I'm not sure I've ever gone through *quite* that many languages to accomplish one goal.
Hee! That's so absurd, it's fabulous. Bonus points for you!
Pascal gives me warm and fuzzy memories of high school!
What's truly sad is that the two languages I learned _well_ were Pascal and Fortran. (specifically, Turbo Pascal)
|Date:||December 20th, 2007 01:25 pm (UTC)|| |
Mine were APL and a proprietary assembler language (that was my job to invent as part of the design team making the CPU).
|Date:||December 20th, 2007 08:17 am (UTC)|| |
ShellExecute() on my file associations?
What about on machines like mine, where .js and .vbs both open in Visual Studio? Does ShellExecute() follow Windows file associations?
Good luck :P
|Date:||December 20th, 2007 12:36 pm (UTC)|| |
Re: ShellExecute() on my file associations?
I would imagine that anyone whose system does this would know what to do with the script when it opened. ;-)
**bubbles over in geeky giggles**
Wow. Brownie points to you, sir.
You should get together with my husband. He says he can make computers behave just by looking at them.
|Date:||December 20th, 2007 03:12 pm (UTC)|| |
Me too, only they don't behave for me so much as break down. :-)
|Date:||December 20th, 2007 02:32 pm (UTC)|| |
InnoSetup's pascal bindings support COM natively
There's no need to shell out to WSH->JScript to get to COM. InnoSetup's pascal dialect has a CreateOleObject('prog.id') routine that lets you access ActiveX objects; it only works for objects that support IDispatch, but Windows Script Host has that same requirement.
|Date:||December 20th, 2007 03:14 pm (UTC)|| |
Re: InnoSetup's pascal bindings support COM natively
Mmm, good point. I saw the CreateOleObject in there. The trick is that the winmgmts object and it's usages are WAY nonstandard. How do you iterate over a COM collection, in Pascal, for instance?
I spent about 20 minutes researching that, and finding no answers at all, nor even the beginnings of places to start researching, I finally went with wsh/js because I knew how do to that. :-)
|Date:||December 20th, 2007 02:39 pm (UTC)|| |
I spent years doing installation development on a massive system - where that was what I was supposed to do on the side.
I feel your pain.
|Date:||December 21st, 2007 12:20 am (UTC)|| |
Re: Installation development
That's not my pain....!
SO! How 'bout them Bears?
|Date:||December 20th, 2007 03:28 pm (UTC)|| |
What happens if they have another ruby process running? :-)
|Date:||December 20th, 2007 04:31 pm (UTC)|| |
"Why does my Rails dev environment keep crashing? It can't possibly have anything to do with this obviously native Windows app."
Fix the original problem.
Did you try to fix the original problem...
"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."
...before resorting to this hack?
|Date:||December 20th, 2007 04:28 pm (UTC)|| |
Re: Fix the original problem.
I tried upmodding you, but the arrow was missing :(
This is so obscene, yet it doesn't routinely break?
You are a satyr-legged monster code monkey, and I want to have your babies.
|Date:||December 20th, 2007 11:36 pm (UTC)|| |
It doesn't. For all of it being a 3-language hack, it's a total of ten lines of code in two files. It's easy to read and easy to understand, if a bit hard to appreciate why it must exist.
One commenter pointed out that there IS a way to do it all in Pascal. I would revisit this later and try to discover that solution except for the part where I really want to get right back to not knowing Pascal anymore. The hack is also pretty clearly labeled; maintenance programmers can clearly see where to chisel out my gnastiness and replace it with something better.
Also, that is the BEST compliment I think I have EVER been paid, and it is going to get quoted.
|Date:||December 20th, 2007 04:51 pm (UTC)|| |
Dude, if Pascal gives you ShellExecute() why not just write a ruby script and require 'Win32API' and use that to find and terminate the ruby process?
|Date:||December 20th, 2007 04:59 pm (UTC)|| |
"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."
If your installer installs Ruby, then it should make sure the executable is in the path (or registry). If ruby is already installed, your installer should be able to figure it out and make sure to put it in the path or registry!
The only way this wouldn't work is if some dummy installed ruby but didn't put it in the path, in which case screw em.
That is a truly impressive kludge. Bravo.
Installation stuff does seem to involve polyglot weirdness an awful lot. The single task I used the largest number of languages/environments for was also an installation thing. I don't remember details too well, as this was back in the Ancient Times (1993).
I'm very soon to be back in the job market, having officially finished school (in all likelihood, barring bureaucratic nonsense or surprises with my grades) last Friday. Stay tuned.
|Date:||December 20th, 2007 05:38 pm (UTC)|| |
cant you simply do this:
if (!unlink_or_remove("holy_file")) return BADD_ASSS_ERRORRR;
since windows locks files by default, this should throw an error if you try to delete a file presumably owned/opened by ruby.
u thought of the most retarded solution - congrats!
|Date:||December 20th, 2007 11:20 pm (UTC)|| |
"Every complex problem has a solution that is simple, neat and wrong."
Explain to me how you are going to find holy_file, when the user has placed it in an arbitrary location not yet known to setup?
Also, if the file is NOT locked, you just deleted it. If the user cancels setup, how do they run the application anymore?
I'm not saying my hack wasn't grade-A nasty, it was. But don't sprain your arm patting yourself on the back for spotting the obvious solution there, skipper.
|Date:||December 20th, 2007 05:41 pm (UTC)|| |
Oh them Windows boxen
...`ps aux | grep rails`...
But hei, GREAT hack!
|Date:||December 20th, 2007 09:50 pm (UTC)|| |
Re: Oh them Windows boxen
Better, yet... my favorte:
kill -0 rails; echo $?
This is a fun story from a geeky point-of-view, but really it seems that the wrong tool (InnoSetup) is being used in the first place. The days spent hacking a fragile solution cost the company far more than buying a solid, commercial installer. As a rule of thumb, when a solution starts looking really complicated it's often a sign to take a few steps back, analyze the problem again, and investigate alternative solutions.
|Date:||December 20th, 2007 11:28 pm (UTC)|| |
I agree. It's a good idea and I considered it. In this case, however, one of our programmers knew InnoSetup and none of us know any other installer. (Sad but true.) It's the devil you know versus the devil you don't; changing installers for one we know would work is a good idea, but stopping to see IF an installer will work... unacceptably risky.
I committed to my manager that I would have a fix for this by the end of the week. One of my plan B's was to research Wise Installer and InstallShield to see if they would solve the problem AND replace all the other functionality of our installer without introducing a whole new set of bugs.
|Date:||December 20th, 2007 09:57 pm (UTC)|| |
Pascal was one of the reasons why I have chosen NSIS over InnoSetup. And guess what? It has FindProc and KillPorc plugins waiting for you just to grab and use them.
|Date:||December 20th, 2007 11:31 pm (UTC)|| |
Mmm, tasty. One of my takeaways from this project will be: Never use InnoSoft again unless there's no better solution.
NSIS looks interesting, I'll definitely give it a look. Thanks!
P.S. Does "KillPorc" take a parameter like MAKE_BACON or CURE_HAM?
|Date:||December 21st, 2007 03:10 am (UTC)|| |
Script in installers
I'm not sure what the impact of doing so in InnoSetup installers is, but installers using the Windows Installer system shouldn't use scripts - amongst other problems, some antivirus software just kills the scripts when they try to run. http://blogs.msdn.com/robmen/archive/2004/05/20/136530.aspx