April 27, 2015

BMP to JPG the PowerShell way!

Grumpy Admin Here – When I am writing documentation or blog posts. My tool of choice for screen captures is Microsoft OneNote. It a great little tool, I have all my engineering notes, and screenshots inside it. I think it actually very useful, searchable and I can store, lots of different content types, from files, emails to screenshots. Due to the “cloud” I can then access all these on my phone and home PC due to the syncing to my professional OneDrive Account.

This is great, however, there is a problem with-it and it makes me grumpy! I can’t take screenshots of menu items! If I am in a menu Item such as the file menu and I click out of the focus to activate OneNotes screen clipping feature and I lose the menu being displayed.

So I have to resort back to the old fashion “prt scr” button and then edit the resulting file in paint. Wow welcome to the year 2015 Grumpy Admin. Being lazy sometime this resulting file is in a few BMP format files littering my screenshot directory. Not very good. And annoying to have to edit lots of files and resaving as a different format.

Now, Mr Kermit the Frog (muppet) here, went through a whole process of such screenshots and forgot to save them as png/jpeg, resulting in 20/30 bitmap files littering my work in progress folder on my customer’s admin station.  They were all BMP taking more space than they should – This kind of mistake is no good for uploading to a blog or for embedding as it happens into a work proposal/ Implementation plan.

So to get these images to the right size and format before I embedded them in to the Word document, I had to open each of them up in paint again, convert and resave… Well after the fourth time I opened it up a file… I slapped myself in the head! And thought! There must be a better way!

My mind went right towards some form of freeware application. However, this is the on the customers network and well, can’t have the IT guy installing what he wants now can we. I was about to hit the google machine, when I remembered something I saw online months ago about image handling. I read it in relation to some saving graphs from excel using PowerShell script (oh might do that as my next blog J )

I could get PowerShell, to do the heavy work using .net to convert the files for me!  Why do something manual when you can automate it! After all that is what PowerShell is for!

So this should be a simple one really!

  • I need to list all the BMP files I want to convert
  • then one by one convert and resave
  • Have Coffee

In order to handle images, we need the base Windows Form .Net class, from there we can create a new System.Drawing.Bitmap object which will load our file, then there are methods such as SAVE which we can use. The rest is just supplying the correct parameters to the functions.

Lets start simple and get it working, then we can come back and make it abit more reusable code in the form of a function.  The simple, get it working code, is what I did on my customers system to convert my screenshots and then for this blog I thought I would go a bit more at it when there no time pressure. IE Lunch Time!!!!

 

So let’s import the System.windows.Forms namespace, so we can access all them wonder functions.  I could have added the assembly directly but you have declare versions and other stuff a bit messy. It is much quicker to use the relection.assembly loadwithpartialname method. You will perhaps see most people doing it this way. Kinda neat trick – So our first like is like this

[Reflection.Assembly]::LoadWithPartialName(“System.Windows.Forms”);

2

 

Next we need to Get-ChildItem (GCI) the directory where we are going to be storing our BMP images for conversion.

As this is just an initial get it working stuff, I am going to hardcode it to my test directory. This will change later!

1GCI “c:\test\grumpy\imgcon\”

3

Now we want to apply some filtering really don’t we, as we only really need to return the objects that have the correct file extension? I can’t see windows being able to convert a pdf to jpg, there bound to be away to do it but I shall not bother exploring that today!

So let’s use our old friend the Where-Object so we can build a statement like this

GCI “c:\test\grumpy\imgcon\” | where-object {$_.Extension -eq “.bmp”}

4

Now if I had thought about it I could have put other files in the source directory to prove to you that the filtering on the file extension works! I guess you will just have to trust me I am not cheating! The $_ on the pipeline has lots of useful properties I am just using the extension property.

Now all we need to do is iterate through all the objects on the pipeline and do our image conversion.

So as I will be working with filenames hardcode them is just dumb.  Dumb == Grumpy.  So I will use the object properties again to great effect.

So I can do the following 3 line to achieve our aim! Inside a foreach (%).

%{
$file = new-object System.Drawing.Bitmap($_.FullName);
$file.Save($_.Directory.FullName +”\”+ $_.BaseName+”.jpg”,”jpeg”);
$file.Dispose()
}

So I create the object, then perform a save operation on that object with the newfile name and the ImageFormat supplied by me J I have to adjust the new file name with the correct extension, so the right program opens the file! Typically the same program Windows Image Viewer!

So the whole string of code looks like this

[Reflection.Assembly]::LoadWithPartialName(“System.Windows.Forms”);
GCI “c:\test\grumpy\imgcon\” | where-object {$_.Extension -eq “.bmp”}| %{
$file = new-object System.Drawing.Bitmap($_.FullName);
$file.Save($_.Directory.FullName +”\”+ $_.BaseName+”.jpg”,”jpeg”);
$file.Dispose()
}

6

5

7

 

I have to dispose/delete/destroy (cross off which ever object orientation wording you use!!) of the $file object, otherwise the last image saved is technically still open as long as the object is in memory.  I put it inside the foreach loop just to make sure everything gets destroyed in each iteration.

8

As you can see from the screenshots and directory listing, this worked a treat. All the BMP images were converted to JPG format.  You can see there is a reduction in file size and if you were to open them up in a hex editor you will see the file header and store data is different.

So let’s quickly, now make this in to re-usable PowerShell code – but in order to do that we need to know what file formats we can save as…

Again the internet and our Google machine comes to the rescue.

https://msdn.microsoft.com/en-us/library/system.drawing.imaging.imageformat%28v=vs.110%29.aspx

As you can see there is a wide range of format type we can use.

9

Now I am going to call my function convertto-imageformat – sounds logical to me, but seems a bit long in the tooth perhaps I will shorten it at some point!

Now the basics

I want the user to specify the directory, I don’t want this command at the moment to accept pipeline objects. This is a choice, just to be awkward! I am grumpy after all – I would have to add some extra stuff in anyway and I only have lunchtime to do this really!

So we need a param block in our code

function ConvertTo-ImageFormat {
[CmdletBinding()]
param
(    [Parameter(Mandatory=$True,
ValueFromPipeline=$false)]
[string[]]$path,
[string[]]$oldext,
[validateset(“bitmap”,”emf”,”exif”,”gif”,”icon”,”jpeg”,”png”,”tiff”,”wmf”)]
[string[]]$format
)

As we know what formats there are, we can limit the parameter input by using the validateset options, this gives us the tab abilities in the command line, that will make it easier on the end users of the function. Will also save me checking the input as it will make sure $format is one of them values.

Now, I am going to break and do something slight different from my normal style – this is based on some feedback and use the life cycle coding method (well only part skipping an end). See I listen to people, sometimes….

I am going to put a begin and process block in there!

So my begin code will look like this :-

begin {
if((Test-Path -Path $path))
{
}
Else
{
Write-Warning “Path doesn’t exist – error”
Return
}
$files=Get-ChildItem $path |where-object {$_.Extension -eq $oldext}
$newext =””

switch($format)
{
“bitmap”{$newext =”.bmp”;}
“emf”{$newext =”.emf”;}
“exif”{$newext =”.exif”}
“gif”{$newext =”.gif”}
“icon”{$newext =”.ico”;}
“jpeg”{$newext =”.jpg”}
“png”{$newext =”.png”}
“tiff”{$newext =”.tif”}
“wmf”{$newext =”.wmf”}
}

}

Let me walk you through this simple code – I am doing some basic user input validation at the tope – I am using test-path to check if the path the user submitted is valid if not I RETURN out and back to the shell or calling function.

Next I generate the list of files, using our filter method. There is no validation done here yet as I read though this – perhaps that is  an oversight – I might change that in the future… but as I have already cut and paste and saved the code and done some screenshots… meh Grumpy and Lazy Admin will ignore!

Next I generate the new file ext. from the selected format, Now for the heart of this little function the process block

 

 process {
foreach ($file in $files) {
Write-Verbose “Processing $file to convert to $format”
$newimage = new-object System.Drawing.Bitmap($file.FullName);
$newfile = Join-Path -Path $file.directory -ChildPath ($file.BaseName + $newext)

      if(!(Test-Path -Path $newfile))
{
switch($format)
{
“bitmap”{$newimage.Save($newfile,”bmp”);}
“emf”{$newimage.Save($newfile,”emf”);}
“exif”{$newimage.Save($newfile,”exif”);}
“gif”{$newimage.Save($newfile,”gif”);}
“icon”{$newimage.Save($newfile,”icon”);}
“jpeg”{$newimage.Save($newfile,”jpeg”);}
“png”{$newimage.Save($newfile,”png”);}
“tiff”{$newimage.Save($newfile,”tiff”);}
“wmf”{$newimage.Save($newfile,”wmf”);}
}
}
else
{
write-warning “$newfile already exists”
}

$newimage.Dispose()

}
}

So here we iterate through each of the files in the generated list of files ($files). I have a debug/verbose message – as we bound the cmdletbindings at the top so we have access to the -verbose options so we might as well make use of it right! Otherwise we should just drop that line of code!

Next we create our system.drawing.bitmap object using the first file from the $files list!

Now we generate the new filename by using the join-path method and various properties of the $file such as the directory and the basename + out new extenstion

Next a bit of logic using test-path again to check and see if the file already exists. If it does we can skip over saving it and just write a warning to the screen. Again we then depending on the format we supplied, we can save the file down…. now I hear you asking why don’t I just stick $format inside the $newimage.save() function call.

Well that because of the data type  ImageFormat. Is a lookup to a GUID format.  I would have to build and map all the guids and then call them.  This is too much effort and harder than a switch statement as there are only choices.

This also means I can break out and do other options depending on choice of format, for example the JPEG option actually has quality options. So breaking this problem down like this actually is smarter in the long run if I want to do certain things depending on the format before I save it! I have the control blocks in place already for future expansion of this module/function/cmdlet.

As we have already done our data validation in the format parameter on the top, I didn’t bother putting in a default answer!  Meh! Might be a mistake but meh! Grumpy Admin doesn’t care!

Finally at the end we delete our $newimage object and work on the next file!

10

 

This is just a start, there quite a bit of work to do on this function to get it to a stage where I would ship it out or use it in production. As you can see I was short of time, and all my bad coding habits show up here! But it works! We can improve it later right? So I went from one line using the pipeline to achieve a hardcoded goal to turning that code into something reusable with a few options. The usage case might be limited to when we mess up like I did but to prove the point all my screenshots were BITMAPS prior to converting to png for posting on this blog! All converted using this tool! Neat!

So that was my lunchtime efforts today! I love it when I get side tracked allowing me to share stuff with people, better get back to work! Meh!

Hazzy