背景
PowerShellスクリプトでファイル一覧をExcelに出力させること自体はできていたのですが、出力内容をメンテナンスしたい事情(出力項目の位置調整、空白列の挿入など)があり、リファクタリングしてみたので投稿してみました。
PowerShellで実装したこと
ざっくりいえば、コマンドプロンプトでいうところのtreeコマンドっぽい出力結果をExcelに転記します。(実用するのはスクリプトの成果物(ファイル一覧Excel)の方です。)
ファイル一覧をExcelに出力しておけば、Windows10標準ファイルマネージャーの検索機能よりも一覧性に優れるし、柔軟かつ高速な検索(ファイル発掘)ができるようになります。
もちろん、Excelなので検索結果の行数が多いと重たいし、リアルタイム性がないといった欠点もありますが、ユースケース次第では結構実用性があるかと思います。
※前提1:Windowsのファイルシステムでみえるディレクトリが対象です。Web(ファイルストレージ等)のディレクトリには非対応です(-_-;)
※前提2:バージョンはPowerShell 5.1
使い方
1.コードをスクリプトファイル(.ps1)として保存する
2.ショートカットをつくって、プロパティをごにょごにょする
3.ショートカット経由でスクリプトを起動する
4.コンソール画面で検索対象"ディレクトリ"のパスを入力しEnterする("ファイル"のパスだとエラーになる仕様です)
検索したいディレクトリのパスを入力(コピペ)&Enter後、しばし待てば・・・
ファイル一覧Excelが完成!(検索対象ディレクトリ自体は出力されない仕様です)
※参考情報:自宅環境(Wifi経由でNASストレージを参照)にて、ディレクトリエントリが多いディレクトリに対して実行したところ...
約5.17万件のエントリ → 約15分で完了(= エントリ取得 約4分 + Excel出力 約11分、ファイルサイズは3733KB)しました。
-----
21:52:00 ディレクトリからエントリを取得中...
21:56:09 51738件のエントリをExcelに書き込み中...
22:06:54 以下にファイル出力済み!
-----
が、リードタイムは環境依存要素が強かったハズなことをお含めおきください。(ボトルネックはストレージ?)
職場環境(Wifi経由で共有ストレージ)だと、2~3万件のエントリ取得だけで1時間位かかるので、半年に1回位の頻度で休憩中に実行!~みたいな使い方をしています。(リアルタイム性がないのは妥協)
コード
# ディレクトリエントリを取得する
function StoreEntrysInHashArray
{
param(
$root_directory
)
$entrys = @()
Get-ChildItem $root_directory -Recurse |
%{
# 項目名の順序は保持される。取得不要なら#でコメントアウト (カスタマイズ可)
$tmp =
[ordered]@{
Name = $_.Name
IsDirectory = $_.PSiscontainer
Size_KB = [Math]::Round(($_.Length / 1KB),2)
Size_MB = [Math]::Round(($_.Length / 1MB),2)
#Dummy = $null
LastWriteTime = (Get-Date $_.LastWriteTime).ToString("yyyy/MM/dd HH:mm:ss")
DirectoryName = $_.DirectoryName
FullName = $_.FullName
FullName_lnk = $_.FullName
}
$entrys += $tmp
}
Write-Output $entrys
Return
}
# ファイル一覧をExcelで作る
function MakeFileListToExcel
{
param(
$entrys
,$dn_output = $PSScriptRoot
)
$fp_output = Join-Path $dn_output ("filelist_{0}.xlsx" -f (Get-Date).ToString("yyyyMMddhhmmss"))
$xl = New-Object -ComObject Excel.application
$wb = $xl.Workbooks.add()
$ws = $wb.WorkSheets.item(1)
$ktn = $ws.cells(1,1)
# 連想配列の項目名を書き込む。ヘッダ行として
$row_offset = $col_offset = 0
[Array]$header_row = $entrys[0].keys
$header_row |
%{
$ktn.offset($row_offset, $col_offset) = $_
$col_offset += 1
}
$row_offset = 1
# エントリを書き込む。項目名の末尾が"_lnk" かつ 実在パスの場合はハイパーリンク化
$entrys |
%{
$entry = $_
$col_offset = 0
foreach($col_name in $header_row)
{
$ktn.offset($row_offset,$col_offset) = $entry.$col_name
if(($col_name -match ".*_lnk$") -and ($entry.$col_name -notin @("",$null)) -and (Test-Path($entry.$col_name)))
{
$ktn.offset($row_offset,$col_offset) = '=Hyperlink("{0}")' -f $entry.$col_name
}
$col_offset += 1
}
$row_offset += 1
}
[void]$ktn.AutoFilter()
$wb.saveas($fp_output)
$xl.quit()
$ws = $wb = $xl = $null
[GC]::Collect()
Write-Output $fp_output
Return
}
# メイン処理はココから
$root_directory = Read-Host "検索対象のディレクトリを入力したらEnter [中断したい場合は Ctrl+C] `n"
if(-not ((Test-Path $root_directory) -and ((Get-Item $root_directory) -is [System.IO.DirectoryInfo])))
{
Write-Output "実在するディレクトリを入力してください(>_<)"
Exit
}
Write-Output ("`n{0} ディレクトリからエントリを取得中..." -f (Get-Date).ToString("HH:mm:ss"))
$entrys = @()
$entrys = StoreEntrysInHashArray -root_directory $root_directory
if($entrys.count -eq 0)
{
Write-Output "エントリがありませんでした...┐(-ω-;)┌"
Exit
}
Write-Output ("{0} {1}件のエントリをExcelに書き込み中..." -f (Get-Date).ToString("HH:mm:ss"),$entrys.count)
#$fp_filelist = MakeFileListToExcel -entrys $entrys -dn_output ("C:\Users\hoge\Desktop")
$fp_filelist = MakeFileListToExcel -entrys $entrys
Write-Output ("{0} 以下にファイル出力済み!`n{1}`n" -f (Get-Date).ToString("HH:mm:ss"),$fp_filelist)
Read-Host "出力ファイルを開きますか?[y/n]" |
%{
if($_ -eq "y"){Start-Process $fp_filelist}
}
Write-Output "オシマイ!! (/・ω・)/ =3"
参考にしたWebサイト
PowerShell で与えられたパスがフォルダかどうか判定する方法
PowerShell: orderedによる順序付けされた連想配列
PowerShellのHashtableの並び替えを制御したい
PowerShellにおける"戻り値"と"Return"について
参考図書
・PowerShell 5.1のお勉強にオススメです ※細かい誤字脱字を気にしない度量のある人向けですが...(・人-)
PowerShell実践ガイドブック クロスプラットフォーム対応の次世代シェルを徹底解説
以上