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:

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:  

13 Comments

  • Bryan said

    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>

  • kazanjig said

    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

    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

    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

    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

    @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

  • Dmg said

    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:)

Add a Comment