Copy Pictures To Folders By Date Taken with Powershell
For about the millionth time, I was downloading the photos from my digital camera and organizing them into folders by date... by hand. I've tried a few different tools to do this in the past and have always ended up going back to a manual process for one reason or another. So this time I grew fed up (again) with the inanity of the process and decided I'd use PowerShell to accomplish the task, as it seems made for the job.
Of course I searched first for others who had done this - it's hardly a problem unique to me. I pretty quickly found someone who not only had gotten it to work but had some pretty nice links to help for those new to PowerShell. Hans of course also linked to the original author, Kim, and his post on organizing photos into folders by EXIF date taken. A couple of other useful links:
- Download and Install PowerShell
- Learn How To Run Scripts (no it doesn't just work out of the box, but almost)
- If you grab my file and just type OrgPhotos.ps1 into your PS prompt and it doesn't work, this link will probably tell you why.
Here's a link to my working script:
The only hitch I ran into was that it's been so long since I've done anything with PowerShell, I forgot what the extension for it was. I thought maybe it was psl not ps1 (and the font used on the original post didn't distinguish between the two). Pea-ess-ell sounds more like PowerShell than pea-ess-one but at any rate if you try to run a .psl file from PowerShell, you'll simply get an error like:
Program 'foo.psl' failed to execute: No application is associated with the specified file for this operation.
Easy fix - rename it to foo.ps1.
Here's my final version of the script. I also made the paths use YYYY-MM-DD as these sort properly chronologically when windows sorts the folders by filename.
1: # ==============================================================================================
2: #
3: # Microsoft PowerShell Source File -- Created with SAPIEN Technologies PrimalScript 4.1
4: #
5: # NAME: OrgPhotos.ps1
6: #
7: # UPDATED: Steve Smith
8: # DATE: 18 January 2009
9: # COMMENT: Changed file paths and confirmed it works. Note that file extension must be .psONE not .psELL
10: #
11: # AUTHOR: Kim Oppalfens,
12: # DATE : 12/2/2007
13: #
14: # COMMENT: Helps you organise your digital photos into subdirectory, based on the Exif data
15: # found inside the picture. Based on the date picture taken property the pictures will be organized into
16: # c:\RecentlyUploadedPhotos\YYYY\YYYY-MM-DD
17: # ==============================================================================================
18: 19: [reflection.assembly]::loadfile( "C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Drawing.dll")
20: 21: $Files = Get-ChildItem -recurse -filter *.jpg
22: foreach ($file in $Files)
23: { 24: $foo=New-Object -TypeName system.drawing.bitmap -ArgumentList $file.fullname 25: 26: #each character represents an ascii code number 0-10 is date
27: #10th character is space separator between date and time
28: #48 = 0 49 = 1 50 = 2 51 = 3 52 = 4 53 = 5 54 = 6 55 = 7 56 = 8 57 = 9 58 = :
29: #date is in YYYY/MM/DD format
30: $date = $foo.GetPropertyItem(36867).value[0..9] 31: $arYear = [Char]$date[0],[Char]$date[1],[Char]$date[2],[Char]$date[3] 32: $arMonth = [Char]$date[5],[Char]$date[6] 33: $arDay = [Char]$date[8],[Char]$date[9]34: $strYear = [String]::Join("",$arYear)
35: $strMonth = [String]::Join("",$arMonth)
36: $strDay = [String]::Join("",$arDay)
37: $DateTaken = $strYear + "-" + $strMonth + "-" + $strDay
38: $TargetPath = "c:\RecentlyUploadedPhotos\" + $strYear + "\" + $DateTaken
39: If (Test-Path $TargetPath) 40: { 41: xcopy /Y/Q $file.FullName $TargetPath 42: } 43: Else 44: { 45: New-Item $TargetPath -Type Directory 46: xcopy /Y/Q $file.FullName $TargetPath 47: } 48: } 49:




Comments
Bryan said on 29 Jan 2009 at 6:25 PM
Thank you for the great info! I have been looking for something like this for some time. I have followed your steps above and keep running into an issue. Can anyone see what I am doing wrong below? I have pasted the info from my powershell window. Any help will be GREATLY appreciated! Running Windows XP with SP2
Windows(R) PowerShell
Copyright (C) 2006 Microsoft Corporation. All rights reserved.
PS C:\Documents and Settings\Bryan> cd C:\Scripts
PS C:\Scripts> .\OrgPhotos.ps1
Security Warning
While scripts from the Internet can be useful, this script can potentially harm
your computer. Only run scripts that you trust. Do you want to run
C:\Scripts\OrgPhotos.ps1?
[D] Do not run [R] Run once [S] Suspend [?] Help (default is "D"): r
GAC Version Location
--- ------- --------
True v2.0.50727 C:\WINDOWS\assembly\GAC_MSIL\System.Drawing\2.0.0.0__b...
New-Object : Constructor not found. Cannot find an appropriate constructor for
type system.drawing.bitmap.
At C:\Scripts\OrgPhotos.ps1:24 char:18
+ $foo=New-Object <<<< -TypeName system.drawing.bitmap -ArgumentList $file.f
ullname
You cannot call a method on a null-valued expression.
At C:\Scripts\OrgPhotos.ps1:30 char:31
+ $date = $foo.GetPropertyItem( <<<< 36867).value[0..9]
Cannot index into a null array.
At C:\Scripts\OrgPhotos.ps1:31 char:25
+ $arYear = [Char]$date[0 <<<< ],[Char]$date[1],[Char]$date[2],[Char]$date[3]
Cannot index into a null array.
At C:\Scripts\OrgPhotos.ps1:32 char:26
+ $arMonth = [Char]$date[5 <<<< ],[Char]$date[6]
Cannot index into a null array.
At C:\Scripts\OrgPhotos.ps1:33 char:24
+ $arDay = [Char]$date[8 <<<< ],[Char]$date[9]
Exception calling "Join" with "2" argument(s): "Value cannot be null.
Parameter name: value"
At C:\Scripts\OrgPhotos.ps1:34 char:28
+ $strYear = [String]::Join( <<<< "",$arYear)
Exception calling "Join" with "2" argument(s): "Value cannot be null.
Parameter name: value"
At C:\Scripts\OrgPhotos.ps1:35 char:29
+ $strMonth = [String]::Join( <<<< "",$arMonth)
Exception calling "Join" with "2" argument(s): "Value cannot be null.
Parameter name: value"
At C:\Scripts\OrgPhotos.ps1:36 char:27
+ $strDay = [String]::Join( <<<< "",$arDay)
PSPath : Microsoft.PowerShell.Core\FileSystem::C:\RecentlyUploadedPh
otos\\--
PSParentPath : Microsoft.PowerShell.Core\FileSystem::C:\RecentlyUploadedPh
otos\
PSChildName : --
PSDrive : C
PSProvider : Microsoft.PowerShell.Core\FileSystem
PSIsContainer : True
Mode : d----
Name : --
Parent : RecentlyUploadedPhotos
Exists : True
Root : C:\
FullName : C:\RecentlyUploadedPhotos\--
Extension :
CreationTime : 1/29/2009 3:22:44
CreationTimeUtc : 1/29/2009 11:22:44
LastAccessTime : 1/29/2009 3:22:44
LastAccessTimeUtc : 1/29/2009 11:22:44
LastWriteTime : 1/29/2009 3:22:44
LastWriteTimeUtc : 1/29/2009 11:22:44
Attributes : Directory
0 File(s) copied
PS C:\Scripts>
Bryan said on 31 Jan 2009 at 12:51 PM
Does this not work with XP? Ive been trying for two days and get the same errors each time. Any ideas?
Bryan said on 04 Feb 2009 at 11:20 AM
Thanks for the help anyway.
Bryan said on 18 Feb 2009 at 12:07 PM
Doesn't work.
Edwin said on 25 Mar 2009 at 1:39 PM
Thank you for the script. I was going crazy with my 10,000+ unorganized pictures.
kazanjig said on 05 May 2009 at 9:20 AM
I'm running Vista and have PS running in Administrator mode, but I get the following errors. BTW, I changed the target string.
Get-ChildItem : Access to the path 'C:\Windows\System32\LogFiles\WMI\RtBackup' is denied.
At D:\Users\Jerry\Pictures\orgphotos.ps1:21 char:23
+ $Files = Get-ChildItem <<<< -recurse -filter *.jpg
Exception calling "GetPropertyItem" with "1" argument(s): "Property cannot be found."
At D:\Users\Jerry\Pictures\orgphotos.ps1:30 char:31
+ $date = $foo.GetPropertyItem( <<<< 36867).value[0..9]
Cannot index into a null array.
At D:\Users\Jerry\Pictures\orgphotos.ps1:31 char:25
+ $arYear = [Char]$date[0 <<<< ],[Char]$date[1],[Char]$date[2],[Char]$date[3]
Cannot index into a null array.
At D:\Users\Jerry\Pictures\orgphotos.ps1:32 char:26
+ $arMonth = [Char]$date[5 <<<< ],[Char]$date[6]
Cannot index into a null array.
At D:\Users\Jerry\Pictures\orgphotos.ps1:33 char:24
+ $arDay = [Char]$date[8 <<<< ],[Char]$date[9]
Exception calling "Join" with "2" argument(s): "Value cannot be null.
Parameter name: value"
At D:\Users\Jerry\Pictures\orgphotos.ps1:34 char:28
+ $strYear = [String]::Join( <<<< "",$arYear)
Exception calling "Join" with "2" argument(s): "Value cannot be null.
Parameter name: value"
At D:\Users\Jerry\Pictures\orgphotos.ps1:35 char:29
+ $strMonth = [String]::Join( <<<< "",$arMonth)
Exception calling "Join" with "2" argument(s): "Value cannot be null.
Parameter name: value"
At D:\Users\Jerry\Pictures\orgphotos.ps1:36 char:27
+ $strDay = [String]::Join( <<<< "",$arDay)
Any thoughts or help would be appreciated.
James said on 04 Oct 2009 at 5:50 AM
Thanks Steve, sorted my 12000+ photos into folders when the folder structure was lost after data recovery. Saved me countless hours or even days.
Guillermo said on 28 Nov 2009 at 5:41 PM
Hi!! To specify the folder to be processed, you must change the script like the following:
$Files = Get-ChildItem "E:\photos\100HP927" -recurse -filter *.jpg
where E:\photos\100HP927 is the folder to analyze (recursively)
Danny DeLoach said on 01 Dec 2009 at 7:45 PM
Hi Steve,
This code looks like it could be very helpful for me. However, when I try to run it, I always get the following error:
New-Object : Exception calling ".ctor" with "1" argument(s): "Parameter is not valid."
Thank you for any insight you can offer.
ssmith said on 01 Dec 2009 at 8:25 PM
@Danny,
Sorry you're having trouble. I have not seen that error and I have been using this exact code on several different machines for quite a while now (at least 3 different machines). I'm betting that if you copy and paste the code and the error you're getting into a question at StackOverflow.com (and tag it powershell) that you will get an answer within a couple of hours, though.
Good luck, and let me know if I need to fix anything in the code listed here. Thanks1
JasonBub said on 09 Dec 2009 at 11:41 PM
Great script. Very helpful. Thanks for your work!!!
Anka said on 19 Dec 2009 at 5:56 PM
Thanks loads for sharing your work! An excellent script!
Dmg said on 16 Jan 2010 at 3:02 AM
thank you so much! I was thinking of sitting down and writing this code myself a few months back, but I have been a little lazy to do so. I came across some shareware that would do it for $40 this evening... and became inspired again to write the code finally. thankfully, you have already done so and were awesome enough to post it! thank you very much:)