2012年3月12日 星期一

使用 Windows PowerShell 將照片依照拍攝日期更名,並分置於不同日期資料夾歸檔

我在整理數位相片的習慣,一向是依拍照日期分置各自資料夾整理。
數位相機使用頻繁以後,要處理的檔案大增,雖然市面上有許多軟體可以方便做到分資料夾,但是不能完全自訂總是不夠滿意,幸好有PowerShell這個方便的工具,讓我省下許多時間。

以往都是將數位相片在記憶卡中就分好資料夾,並沒有特別變更檔名,但是近來相機數量逐漸增加,而且各大廠四位數序號命名早就拍超過並重新歸零數次,於是前陣子開始再次去閱讀PowerShell的資料,把更名跟分資料夾一次完成。


純粹分置資料夾的指令我愛用了好幾年,一開始是指定位置,我這個"PhotoArrange.ps1"是這樣寫的:
$dir = 'S:\DCIM\102CANON'
cd $dir
$files = dir -path $dir | ?{ !$_.PSIsContainer }
$files | %{ [void] (New-Item -ItemType Directory `
    $_.LastWriteTime.ToString("yyyyMMdd") -ErrorAction SilentlyContinue); $_ }`
    | %{ Move-Item $_.PSPath -dest $_.LastWriteTime.ToString("yyyyMMdd") }
因為都是在記憶卡就整理,所以單純使用最後修改日期,不去調用EXIF拍照資訊,省得麻煩,缺點是有時候要去改路徑。

後來拍到相機自動新建資料夾,而且多了其他台相機,所以就把目標資料夾路徑去掉,改在桌面捷徑中修改,比較方便。
這個"PhotoArrangeWithoutPath.ps1"是這樣:
$files = dir | ?{ !$_.PSIsContainer }
$files | %{ [void] (New-Item -ItemType Directory `
    $_.LastWriteTime.ToString("yyyyMMdd") -ErrorAction SilentlyContinue); $_ }`
    | %{ Move-Item $_.PSPath -dest $_.LastWriteTime.ToString("yyyyMMdd") }
然後在桌面的捷徑中,
設定目標為「%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe -noexit D:\路徑\PhotoArrangeWithoutPath.ps1」、
設定開始位置為「S:\DCIM\102CANON」(待整理檔案的路徑位置)。

以上指令所有檔案類型一視同仁都處理,若是讀不到路徑(記憶卡沒插入),會把桌面的所有東西依日期分資料夾擺喔!

題外話,在網路上看到有人把「PsIsContainer」設別名(Alias):
$IsDir = {$_.PsIsContainer}
$IsFile = {!$_.PsIsContainer}
dir | Where $IsDir
dir | Where $IsFile
以避免重複輸入落落長。
重點是聽說PowerShell 第3版有內建:
dir -directory
dir -ad
dir -file
dir -af
這樣真的有比較方便。


回到主題,為了要依照日期變更檔名,想說要做就弄好一點,JPEG檔就去抓EXIF資訊來改,其它檔案再以老方法取修改時間。
本來想用jHead這個以指令惡搞JPEG檔頭資訊的強大好物,搭配PowerShell來整理。
沒想網路上撈到有前人已經以PowerShell寫好了,功能跟我要的幾乎一樣!還加了執行說明畫面,真是棒!
但是小修改還是必須的,我把它的新資料夾路徑從年月改成年月日、加入保留舊檔名及是否使用序號的選項,並把注解及執行畫面都改成中文。
它這個指令調用JamesBrundage的PowerShellPack模組的PSImageTools以讀取EXIF,因為用不到相片EXIF外其他功能,所以另外找了James O'Neill修改自PSImageTools的增強版PowerShell Image模組。
試了一下跟我說有些東西找不到,看來是要先裝PowerShellPack,所以還是乖乖用回PowerShellPack。
這個模組說明要V2以上才能用,WIN7內建的就是囉,雖然執行檔路徑是在1.0,但是在PS畫面下輸入"get-host",可以確認已經是2.0了。

模組服用方式:
安裝前先在PowerShell畫面輸入「$Env:PsModulePath」,看看模組捷徑指向何方,待會要把模組檔放在裡面。
一般放在「%SystemRoot%\system32\WindowsPowerShell\v1.0\Modules\」,則該電腦所有使用者都能使用;
如果沒權限,就去打開「我的文件夾」,放在裡面的「WindowsPowerShell」的「Modules」資料夾裡。
要用的時候,輸入指令「Import-Module 模組名稱」來呼叫模組即可。
上述的PowerShellPack模組是自動安裝檔,會裝在安裝者的"我的文件夾"裡,別的使用者要調用必須自行安裝,或複製一份到system32去大家共用。

安裝好模組後,接下來我的這個"PhotoRenameAndFiling.ps1"指令是這樣寫的:

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#  原作:Rikard Ronnkvist / snowland.se || 修改/中文註解:Tony Huang / tonysnote.blogspot.com
#
#  以下將使用EXIF資訊中的拍照日期,或使用最後修改日期,將JPG及其它檔案更名,並分置於各自子資料夾內。
#  檔案將被更名並放置於:
#    \some path\YYYYMMDD\YYYYMMDDHHMMSS_00.JPG
#               ^^^^^^^^ - 可選擇是否歸檔,若要則將"createSubdir"設為"True"。
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#  可輸入[$basePath = convert-path ~]選定目前(捷徑設定)路徑,或自設路徑例如:[$basePath = "F:\Pictures\Import dir"]。
$basePath = [Environment]::CurrentDirectory
#  $basePath = "F:\Pictures\Import dir"

#  若僅更名不歸檔,可將"$createSubdir"設為"$False"。
$createSubdir = $True
#  $createSubdir = $False

#  測試模式,若要測試可將"$testMode"設為"$True"。
$testMode = $False
#  $testMode = $True

#  日期時間後保留舊檔名與否。
$keepOldName = $True
#  $keepOldName = $False

#  自動加序號與否。
#  $addString = $True
$addString = $False
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# 呼叫PowerShellPack模組[http://archive.msdn.microsoft.com/PSImage],為自動安裝檔。
# 若要讓電腦中所有使用者使用,將"我的文件夾\WindowsPowerShell\Modules\"內所有內容,複製到"%SystemRoot%\system32\WindowsPowerShell\v1.0\Modules\"即可。
Import-Module PowerShellPack
 
# 加上 \* 以搜尋路徑下所有檔案。
$searchPath = $basePath + "\*"
 
# 搜尋待處理檔案。
Write-Host "             搜尋: " -ForegroundColor DarkGray -NoNewline
Write-Host $basePath -ForegroundColor Yellow
 
$allFiles  = Get-ChildItem -Path $searchPath -Include *.JPG,*.MOV,*.THM,*.RAW,*.AVI -Exclude folder.jpg,*.ini
 
Write-Host "             找到: " -ForegroundColor DarkGray -NoNewline
Write-Host $allFiles.Count -ForegroundColor Yellow -NoNewline
Write-Host " 個檔案" -ForegroundColor DarkGray
 
$fNum = 0
# 依序處理所有檔案。
foreach ($file in $allFiles )
{
    $fNum++
    # 若是jpg檔,使用exif資訊,否則使用最後修改日期。
    if ($file.Extension -eq ".JPG") {
        $imgInfo = $file | Get-Image | Get-ImageProperty
        $fileDate = $imgInfo.dt
    } else {
        $fileDate = $file.LastWriteTime
    }
 
    if ($createSubdir -eq $True) {
        # 設定新資料夾路徑。
        $fileDir = $basePath + "\" + $fileDate.ToString("yyyyMMdd")
 
        # 檢查新資料夾路徑是否已存在,若無則新建。
        if (!(Test-Path($fileDir))) {
            # 新建子資料夾路徑。
            if ($testMode -ne $True) {
                $newDir = New-Item -Type directory -Path $fileDir
                Write-Host "             建立: " -ForegroundColor DarkGray -NoNewline
                Write-Host $fileDir -ForegroundColor Red
            }
        }
    } else {
        # 若檔頭選擇僅更名不歸檔,則將檔案留在目前(捷徑設定)路徑或檔頭自設之路徑,不依日期歸檔。
        $fileDir = $basePath
    }
 
    # 設定好新的檔名路徑,並於while迴圈開頭時檢查。
    $newPath = $file.Fullname

   # 若檔頭選擇保留舊檔名,則將其添加於日期時間之後。
    if ($keepOldName -eq $True) {
        $oldName = "-" + $file.Name.Remove($file.Name.Length - $file.Extension.Length)
    } else {
        $oldName = ""
    }   

   # 若檔頭選擇添加序號,則添加於主檔名最後。
    if ($addString -eq $True) {
        $i = 0
        while (Test-Path $newPath) {
            # 設定有序號之新檔名。
            $newPath = $fileDir + "\" + $fileDate.ToString("yyyyMMddHHmmss") + $oldName + "_" + $i.ToString("00") + $file.Extension
            $i++
        }
    } else {
            # 設定無序號之新檔名
            $newPath = $fileDir + "\" + $fileDate.ToString("yyyyMMddHHmmss") + $oldName + $file.Extension
    }

 
    # 顯示執行資訊。
    Write-Host $fNum.ToString().PadLeft(4) -ForegroundColor DarkYellow -NoNewline
    Write-Host " / " -ForegroundColor DarkGray -NoNewline
    Write-Host $allFiles.Count.ToString().PadRight(4) -ForegroundColor Yellow -NoNewline
    Write-Host "  移動: " -ForegroundColor DarkGray -NoNewline
    Write-Host $file.Name -ForegroundColor Cyan -NoNewline
    Write-Host " 到 " -ForegroundColor DarkGray -NoNewline
    Write-Host $newPath -ForegroundColor Green
 
    # 更名並移動檔案。
    if ($testMode -ne $True) {
        Move-Item $file.Fullname $newPath -ErrorAction SilentlyContinue
    }
}

使用時一樣在桌面設定好捷徑即可使用,可針對不同相機記憶卡設不同捷徑。

至此大功告成!!

2012年3月10日 星期六

以前的 Windows PowerShell 筆記

Windows PowerShell 是方便的指令工具,Windows7有內建,較舊的版本也可安裝執行順利,我主要拿來整理照片以及其他檔案。
今天整理資料看到三年前的筆記,貼上來分享一下不專業心得,順便貼一些連結。

================================
下載並安裝PowerShell,PowerShell Help最好也一併下載參考。
在WinXP SP3環境下,安裝路徑在:「%SystemRoot%\system32\WindowsPowerShell\v1.0\」
使用時執行PowerShell「%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe」環境。

輸入「Set-ExecutionPolicy RemoteSigned」以解除近端執行限制,但同時限定遠端腳本須簽署過才能執行。
或者輸入「Set-ExecutionPolicy Unrestricted」以解除所有執行限制。

執行「.ps1」腳本文件時,需提供完整路徑。除非在腳本文件同路徑時,方可以執行「.\example.ps1」代替完整路徑。
若路徑中有空白,須前後加雙引號並以「呼叫運算式」→「&」引用。例如:「& "C:\My Scripts\example.ps1"」。

若在系統「PATH」設定過路徑,則不輸入檔案路徑時,腳本亦可被找到並執行。
若採行此法,為免混淆最好統一集中在同一個資料夾(路徑)中。
可輸入「$a = $env:path; $a.Split(";")」以查詢已設定過之路徑(通常為系統安裝時預設)。
更可輸入「$env:path = $env:path + ";d:\scripts"」,直接把「d:\scripts」這個路徑加進PATH設定最後一行。

"dot sourcing"可將選定腳本的變數及值,存在記憶中並對之後的腳本開放。
例如:假設「. c:\scripts\example.ps1」的內容為:
####[example.ps1] Begin####
$A = 5
$B = 10
$C = $A + $B
####[example.ps1] End####
執行後,再輸入「$C」,可得運算結果「15」。或再執行內容只有「$C」的腳本時,可得到相同結果。
若要清除記憶中的C變數值,可輸入「Remove-Variable C」即可。

不先開啟PowerShell環境,直接執行「powershell.exe –noexit 」後加腳本文件路徑、或腳本內容,可直接獲得運算結果視窗。
若想以此用於登錄腳本,可以以下VBScript呼叫:
####[VBScript that calls PowerShell script] Begin####
Set objShell = CreateObject("Wscript.Shell")
objShell.Run("powershell.exe –noexit c:\scripts\test.ps1")
####[VBScript that calls PowerShell script] End####
================================

PowerShell 在網路上有許多教學資源,其實並不難,尤其是對視窗時代前就開始用電腦的朋友,有時候打指令甚至跑巨集可以解決的東西,比翻來移去,按許多滑鼠按鍵更加方便。
好人賴榮樞大師在微軟TechNet有一系列主筆的專欄,是使用PS必讀的文件:
  1. Windows PowerShell 講座 (1)—指令、重導、別名
  2. Windows PowerShell 講座 (2)—自訂工作環境
  3. Windows PowerShell 講座 (3)— PS 磁碟機
  4. Windows PowerShell 講座 (4)— 變數
  5. Windows PowerShell 講座 (5)—儲存資料的其他方式及編寫指令碼的前置準備
  6. Windows PowerShell 講座 (6)— 運算子(上)
  7. Windows PowerShell 講座 (7)— 運算子(下)
  8. Windows PowerShell 講座 (8)— 迴圈與流程控制
  9. Windows PowerShell 講座 (9)— 模組化
  10. 易學易用的 Windows PowerShell
  11. 賴大師擔任板主的微軟技術社群討論區Windows PowerShell板
賴大師的部落格裡也有許多相關文章,非常值得參考學習。

另外,微軟官方也有許多其他資源:
  1. 指令碼中心 / 學習園地 的 Windows PowerShell 專頁(中文)
  2. 線上使用手冊(英文版)
  3. 依照功能需求分類介紹的教學(簡單的英文):Windows PowerShell Cmdlet 實務指南
  4. 許多(六千多筆)高手寫好的指令等著你去找來用:Windows PowerShell 範例指令碼存放庫
記得之前參考過的其他資源:
  1. 網友TYPE提供的PowerShell 語法說明/範例教學
  2. 高手 James O'Neill 在MSDN提供的影像EXIF資料存取模組
  3. Jamesone部落格PS分類,以及關於上述模組的說明及運用:
  4. 其他一時手邊找沒有,還有很多熱心人士提供過實用教學及範例
這篇到此結束。