Expert Solution for 2011 Scripting Games Advanced Event 8: Use PowerShell to Resize Images and to Remove EXIF Data

Expert Solution for 2011 Scripting Games Advanced Event 8: Use PowerShell to Resize Images and to Remove EXIF Data

  • Comments 3
  • Likes

Summary: Microsoft PowerShell MVP, Marc van Orsouw, resizes images and removes EXIF metadata while solving 2011 Scripting Games Advanced Event 8.

Microsoft Scripting Guy, Ed Wilson, here. Marc van Orsouw (MoW) is our expert commentator for Advanced Event 8.

Image of Marc van Orsouw

Marc has been a Windows PowerShell MVP for the last six years. He is best known for his blog, thePowerShellGuy.com and for his utilities for Windows PowerShell, such as PowerTab and the PowerShell WMI explorer. He was among the very early adapters of Windows PowerShell, has  lots of hands-on experience , has spoken about Windows PowerShell at diverse events, and he is running the PowerShell Script Club in Zurich for the Swiss IT user group. He is from the Netherlands, but he has been living in Switzerland for two years, and he is working as a technical consultant at WinWorkers Schweiz GmbH, mostly in the Microsoft System Center area…and of course, he is using automation and Windows PowerShell wherever feasible.

Worked solution

First, a disclaimer. This is not a production ready script, it just does enough to fulfill the requirements, but with some work, I think it would make a cool and handy tool and a good addition to the Imaging library.

When I started looking at Advanced Event 8, my first thought was to use no external dependencies. I knew it was possible to get the EXIF data by using .NET, and I have done that before. But whilst searching for info, I was reminded of the fact that a lot of refactoring has to be done to extract the interesting data, because a lot of properties need their own custom conversion (a good example is the GPS information that I really wanted to have in). Also see this post from James O’ Neil.

I knew that James’s library was included in the PowerShellPack module, and I decided to use that module in my solution after all. I was making a dependency on the PowerShellPack anyway, so I figured I could use WPK as well for the interface part, knowing that I could “steal” the basic interface from an article by Doug Finke.

However, when I got started, I missed some of the functionality that I needed and expected in the library from James. The Get-ImageProperty function was a bit too basic, and it did not do the job for me. The problem is not getting the values, but rather because they are byte arrays, converting the values to usable data.

And the Get-ImagePropery function only got me part of the way. The byte arrays are converted, but a lot of data is still not in a readable format, and it needs more conversion to be usable. This is shown in the following image.

Image of code

With further research aimed at a more complete solution, I found the information I was looking for—again on James’ blog, and there seemed to be a newer version of the image module available on MSDN.

After downloading and installing the module, I found Get-Exifdata, which did what I needed. It gives a much nicer list of properties, and it also did the GPS coordinates conversion—the work I did not want to do again myself. The use of this function is illustrated here. 

PS C:\powershell\SG2011> Get-Image 'C:\Users\morsouw\Pictures\ww-xmasparty2010\P1000501.JPG' | Get-Exif

 

LightSource      : Flash

FocalLength      : 4.1

CaptureMode      : Standard

FileSource       : Digital still camera

ExposureBias     : 0

DateTaken        : 15.12.2010 19:14:09

Orientation      : 0

Title            :

Sharpness        : Normal

Height           : 480

Keywords         :

Exposuretime     : 0.0666666666666667

FocalLength35mm  : 25

Model            : DMC-TZ10

Contrast         : Normal

Comment          :

ISO              : 800

ExposureMode     : Auto

Manufacturer     : Panasonic

Subject          :

Artist           :

Copyright        :

WhiteBalance     : Auto

Author           :

Flash            : Flash Auto, Flash fired

GPS              : 47°27'13.95"N  8°34'35.62"E

Software         : Ver.1.0

Path             : C:\Users\morsouw\Pictures\ww-xmasparty2010\P1000501.JPG

Width            : 640

MaxApperture     : 3.44

SubjectRange     :

ColorSpace       : sRGB

DigitalZoomRatio :

FNumber          : 3.3

MeteringMode     : Multi-Segment

ExposureProgram  : Program: Normal

Saturation       : Normal

After I got this output, I spent a lot of time searching, but it still saved me a lot of work to get decent information for the properties window, and I was ready to work on the interface part.

I tweaked the interface a bit to add the controls I needed. There were no big problems in doing that. I added some grid rows for the extra directory pickers, and an extra vertical grid to add the RTF box for the properties. It is not a fancy interface but, but I wanted to show that by using StackPanels and grids, you can easily build a basic interface, even in code, as long it you do not need it to be fancy. In a couple of minutes, I came up with the interface shown in the following image.

Image of interface

Then came the hard part…finding out how to remove ALL the EXIF data. Even in the newer imaging library, I could not find a good way to do this. I spent a lot of time figuring out a way to do this by using the .NET classes. Although I could set the simple text properties, I could not find out how to do what I wanted and remove all properties. I found some examples in C# to remove the binary data, but that looked a bit too “Brute Force.”

I later found out that I could remove the properties as easy as this, but I somehow completely overlooked the Remove method then.

$i = [System.Drawing.Image]::FromFile('C:\Users\morsouw\Pictures\ww-xmasparty2010\P1000501.JPG' )

$i.PropertyItems |% {$i.RemovePropertyItem($_.id)} )

Then it struck me that it might be much easier to copy the image data to a new image and then save it instead of trying to remove the properties. I first came up with something like this:

$ip = [System.Drawing.Image]::FromFile('C:\powershell\SG2011\P1000500.JPG').GetHbitmap() $new = [System.Drawing.Image]::FromHbitmap($ip)

But because the Resize method on the Get-Image function in the Image library came from the COM object, I had problems saving the image again because the SaveFile() method did not allow me to save the Get-Image function that was locked in the file.

Then I looked at the .NET image object again, and I found out that I could actually resize and remove all the EXIF data in one go by using the method shown here.

GetThumbnailImage()

This enabled me to throw away the painfully acquired code to double save the image, first removing properties and then resizing. Therefore, I ended up only using the Get-Exifdata cmdlet from the library, and then the Resize function that I mentioned earlier.

One of the bigger problems I had with the library was that it was using a COM object that kept the file locked. Because I needed to open it in .NET and also remove the attributes, I kept having locked files.

Get-Image 'C:\Users\morsouw\Pictures\ww-xmasparty2010\P1000501.JPG' | gm

 

   TypeName: System.__ComObject#{f4243b65-3f63-4d99-93cd-86b6d62c5eb2}

 

 [System.Drawing.Image]::FromFile('C:\powershell\SG2011\SHARE_P1000500.JPG') | gm

 

   TypeName: System.Drawing.Bitmap

As you can see in the previous code, one time the file is opened by using a COM object and the other time by using .NET. I dispose the .NET objects after use, and therefore unlock the files again.

$oi.dispose()

$ni.dispose()

For the extra points of this event, I added the Folder browsers, checking for the modules, and loading them when available at the start of the script. I also hacked in the Convert-All function (very dirty actually, and I should have done some refactoring for that). Hence, for the script to be really useful, it needs some cleaning up and additions like proportional scaling. But I think it shows how you can relatively easily (with some help from the community) build a small GUI tool in Windows PowerShell.

Enjoy and greetings,
/\/\o\/\/

The complete script will be posted in the Script Repository after this event is complete and Marc can get it uploaded.

Thank you, Marc.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • I used Windows Forms for my entry (Primal Forms CE), and it is just possible to do this without any dependencies.  I discovered the same shortcut you did regarding metadata and just chose one of the Bitmap classes in Forms to resize the image with my own algorithm.  I also found it easier to access the metadata through a Shell.Application COM object than it would have been to incorporate the EXIF data directly.  

    Main limitations in my project were that I had a limited amount of metadata to display due to the form size I designed, and I could not get all of the requested features (like a button to process just one image) due to the lack of time and the learning curve (it was just my second project in Forms--I had to do a smaller project first just to determine if I could do this and I had had to work for several days on the "pilot" project and several days on my entry before I was certain I could deliver.)

    It's very interesting that your version is not production-ready.  I know now first-hand that this was a very difficult project to integrate in a short period of time due to learning several different API's all at once, so I am not at all surprised.  But I interpreted the contest requirements, in general, to mean that every one of our entries in the advanced category had to be production-ready and fully documented.

    I had had to compromise my entry so that it could work;  I could not leave in things that almost worked.  I had tested my application with a ComboBox that I had to remove--I could not learn how to make it work as I would expect, so I used a drop-down instead.  I compromised on the specific metadata I displayed--I was thinking of, and prioritizing for, personally identifiable information that needed to be removed.  I was not even certain, when I posted my entry, that it would be responsive to the guidelines.  What I did enter is production-ready and I could deploy it at my workplace right now if I wanted.  It's just a compromise.

    I learned about Windows Forms and event-driven GUI coding and that is very positive.  I thought at the time this assignment was just too hard for the time allotted.

    Unfortunately, I still do.

  • I do believe that scripting is not a good idea for this task. Because of that I prefer to use specialized utilities such as this metadata remover - www.superutils.com/.../exifcleaner - to get rid of EXIF and some other photographic hidden data.

  • thank you