Friday, September 22, 2017

Calling Image Magick from PowerShell

I am old enough to know what a BAT file is. I used to know MSDOS 3.0 inside and out and created all kinds of tricky BAT scripts for performing lots of useless (and a few useful) tasks. Anyone else that has ever done this knows that you were likely at some point to be tricked by the command processor's funky syntax and semantics. You would get stuck for hours trying to figure out why something that should be obvious was not working as you expected.

Enter Windows NT and CMD. Things were much improved. There was a lot more power and the chance to create even trickier CMD scripts. But, the problem of being out-tricked by the tool was ever present. It seemed I was bound to pull some amount of hair for any non-trivial script I would create (as evidenced by minimal hair remaining on my head).

Fast forward to present day and Microsoft has deprecated all those old scripting technologies in favor of PowerShell. I love it! The days of tricky code are gone. Modern language features, predictable syntax and semantics, powerful tools to perform typical functions, it's all there... until I tried to call an EXE.

The days of me spending hundreds of hours to become an expert in any of this technology are long gone, but it still has its uses and total expertise is not needed (not to mention that Mr. Google is a close friend of mine). I have written scripts to do things I would have never imagined in a BAT or CMD.  I can call remote web services with minimal effort. Two lines of PowerShell code can do what would have taken 200 in a BAT. Then I needed to call Image Magick.

Image Magick is a powerful and free tool for processing images. It does hundreds of things I can never imagine needing and a number I can see using all the time. This time I just needed to combine single pages of scanned TIFF files into a multi-page TIFF file. Magick can do this with a trivial command line statement.
magick file1.tif file2.tif filecombined.tif
Actually I needed a few options to really do what I wanted, but still pretty straight forward.
magick -quiet file1.tif file2.tif -compress JPEG filecombined.tif
I thought it would be simple to add to my PowerShell script just like calling a CmdLet. Not so much.

What I really wanted to do in PowerShell was something like the following.
magick -quiet $ListOfFiles -compress JPEG $Destination
Where my list of input files and my destination file were stored in variables. Well, it was obvious early on that Magick was having a hard time to digest the parameters I was passing. A little playing around with quotes and things seemed to get it sorted out, but I never really understood why it worked or why it did not work as the case may be. However, I was ready to put my script to its one-time use.

I rather like PowerShells Start-Transcript CmdLet. It really simplifies logging for one-time scripts like the ones I often need to create. What I did not realize was that I had accidently developed my script on PowerShell v4, which does not support this handy tool. So, I upgraded to v5 only to find that everything else fell apart.

It turns out that not really knowing why my script was calling Magick correctly or incorrectly meant that a subtle change in EXE processing between v4 and v5 PowerShell was enough to break everything. Well, I went back to my brute force method of trying all sorts of variations only to be stymied at every attempt.

Off to ask Mr. Google about my problem. Turns out that calling an EXE in PowerShell is something special and sometimes it works and sometimes, not so much. If fact, there is a long list of methods of making such calls published by Microsoft. TechNet has an article on the complex topic as do many other blogs and StackExchange questions.

PowerShell: Running Executables

Unfortunately, I could not find one article that would directly solve my issue. The Magick method of using the @ sign to get a list of input files from a text file looked promising, but only resulted in really strange Magick errors like "magick.exe: unable to open image '@z:ÿþz'". What?

What finally did bare fruit was using an array to pass the parameters to the executable along with the call (&) operator. Once I got this setup, it worked perfectly (and logically) every time. Now my code looked a little more like the following.
$MagickParameters = @( '-quiet' )
$MagickParameters += 'file1.tif'
$MagickParameters += 'file2.tif' 
$MagicParameters += @( 'compress', 'JPEG' ) 
 $MagickParameters += 'filecombined.tif'
&'magick' $MagickParameters
Simple. I realize my method of recreating the array to add new parameters is not super efficient, but performance was not a factor here and a more efficient method would have been a waste for a one-time script. Obviously, there was other code intermixed for actually getting the filenames, but I have saved you from the unnecessary complexity for this example.

Unlike my tricky BAT and CMD scripts from the past, the PowerShell solution is pretty clean once you know the trick.

No comments :

Post a Comment