フォルダーのバックアップを考える 1

概要
オブジェクトの選択なしの場合
$items = Get-ChildItem -Directory -Recurse -path 'C:\ghc'
$i = 0
while ($i -lt 5){
$items[$i]
$i++
}

オブジェクトの選択をする場合
$items = Get-ChildItem -Directory -Recurse -path 'C:\ghc' | Select-Object Mode, Name, FullName, Length, LastWriteTime
$i = 0
while ($i -lt 5){
$items[$i]
$i++
}

ディレクトリ・ファイルのデータを配列にセット
$items = @()
Get-ChildItem -Directory -Recurse -path 'C:\ghc' | ForEach-Object {
$item = @()
$item += $_.Mode
$item += $_.Name
$item += $_.FullName
$item += $_.Length
$item += $_.LastWriteTime
$items += ,$item
}
$i = 0
while ($i -lt 5){
$items[$i][0]
$items[$i][1]
$items[$i][2]
$items[$i][3]
$items[$i][4]
$i++
}

確認用のデータの作成
$number = "10"
$folder = "C:\F1\F" + $number
$thead = "\T"
$fhead = "\F"
$extent = ".txt"
for ($i=0; $i -lt 5; $i++){
#ファイル名
$fn1 = "$thead$number$i$extent"
#フォルダー名
$dn1 = "$folder$fhead$number$i"
#フォルダー作成
New-Item $dn1 -ItemType Directory
for ($j=0; $j -lt 5; $j++){
#フォルダー名
$dn2 = "$dn1$fhead$number$i$j"
#フォルダー作成
New-Item $dn2 -ItemType Directory
for ($k=0; $k -lt 5; $k++){
#フォルダー名
$dn3 = "$dn2$fhead$number$i$j$k"
#フォルダー作成
New-Item $dn3 -ItemType Directory
#ファイル作成
for ($l=0; $l -lt 5; $l++){
#ファイル名
$fn3 = "$thead$number$i$j$k$l$extent"
"あいうえお" | Out-File "$dn3$fn3"
}
}
#ファイル作成
for ($l=0; $l -lt 5; $l++){
#ファイル名
$fn2 = "$thead$number$i$j$l$extent"
"かきくけこ" | Out-File "$dn2$fn2"
}
}
}
#メッセージを表示して終わらないようにします。
$shell=New-Object -ComObject wscript.shell
$shell.Popup("表示",0,"キャプチャ",1)

重複を拾いたいので、フォルダー「F1000」を重複しました。
バックアップ処理
$shell = New-Object -ComObject wscript.shell
$gDrive = 'C:\'
$gFolder = 'F1\F10'
$pDrive = 'C:\'
$pFolder = 'tmp'
$basecount = "$gDrive$gFolder".split("\").Count
#目的のディレクト名は $basecount + 2 番目にあります
$ind = $basecount + 2 - 1
$preind = $ind - 1
$backuplist = @{}
$unilist = @{}
Get-ChildItem -Directory -Recurse -path "$gDrive$gFolder" | ForEach-Object {
$_.FullName
#区切り \ で分割
$buf = $_.FullName.Split('\')
if ($buf.count -gt $ind) {
#目的のディレクト名をキーにして目的のディレクトまでのパスを値にして連想配列を作成します。
#重複に対応するため配列を使います。
$dpath = "$gDrive$gFolder" + "\" + $($buf[$preind]) + "\" + $buf[$ind]
if ($backuplist.ContainsKey($buf[$ind]) -eq $True) {
if ($unilist.ContainsKey($dpath) -eq $True) {
$unilist[$dpath] += 1
}
else {
#配列に追加
$backuplist[$buf[$ind]] += $dpath
$unilist.add($dpath, 1)
}
}
else {
#空の配列に追加します。
$fcount = @()
$fcount += $dpath
#新規のキーの連想配列を追加します。
$backuplist.add($buf[$ind], $fcount)
$unilist.add($dpath, 1)
}
}
}
#キーを表示します。
foreach ($key in $backuplist.Keys) {
Write-Host ('連想配列のキー:' + $key + ' 値:' + $backuplist[$key])
}
#メッセージを表示して終わらないようにします。
$shell.Popup($items.Count, 0, "キャプチャ", 1)

重複もうまく拾うことが出来ています。
複写の実行
データの複写を実行してみます。
$shell = New-Object -ComObject wscript.shell
$gDrive = 'C:\'
$gFolder = 'F1\F10'
$pDrive = 'C:\'
$pFolder = 'tmp'
$basecount = "$gDrive$gFolder".split("\").Count
#目的のディレクト名は $basecount + 2 番目にあります
$ind = $basecount + 2 - 1
$preind = $ind - 1
$backuplist = @{}
$unilist = @{}
Get-ChildItem -Directory -Recurse -path "$gDrive$gFolder" | ForEach-Object {
$_.FullName
#区切り \ で分割
$buf = $_.FullName.Split('\')
if ($buf.count -gt $ind) {
#目的のディレクト名をキーにして目的のディレクトまでのパスを値にして連想配列を作成します。
#重複に対応するため配列を使います。
$dpath = "$gDrive$gFolder" + "\" + $($buf[$preind]) + "\" + $buf[$ind]
if ($backuplist.ContainsKey($buf[$ind]) -eq $True) {
if ($unilist.ContainsKey($dpath) -eq $True) {
$unilist[$dpath] += 1
}
else {
#配列に追加
$backuplist[$buf[$ind]] += $dpath
$unilist.add($dpath, 1)
}
}
else {
#空の配列に追加します。
$fcount = @()
$fcount += $dpath
#新規のキーの連想配列を追加します。
$backuplist.add($buf[$ind], $fcount)
$unilist.add($dpath, 1)
}
}
}
#キーを表示します。
foreach ($key in $backuplist.Keys) {
Write-Host ('連想配列のキー:' + $key + ' 値:' + $backuplist[$key])
}
#指定の場所にフォルダーを作ります。
foreach ($key in $backuplist.Keys) {
$cf ="$pDrive$pFolder" + "\" + $key
#フォルダーの重複の場合分けます。
if ($backuplist[$key].count -eq 1){
#重複無し
if (Test-Path $cf){
# Remove-Item $cf -Force -Recurse
}
Copy-Item $backuplist[$key] $cf -Force -Recurse
} else {
#重複あり
for ($i=0; $i -lt $backuplist[$key].count; $i++) {
#枝番を付けます。
$cf2 = $cf + "\" + $key + "(" + [string]$i + ")"
$eda = $backuplist[$key]
if (Test-Path $cf2){
# Remove-Item $cf2 -Force -Recurse
}
Copy-Item $eda[$i] $cf2 -Force -Recurse
}
}
}
$shell.Popup($items.Count, 0, "キャプチャ", 1)
重複の処理もうまくいきました。

再度実行してみる
次の画像のようにフォルダーが入れ子になって実行されました。
これはちょっと違うので、別の方法を考えてみます。

再実行時の現象をどうするか
いろいろ試してみましたが、現在のところ私の力量ではファイル毎に複写するのが良さそうです。
そのためには、コピー先に必要なフォルダーを作成して、ファイルを一つずつ複写します。
以下がそのコードです。
$shell = New-Object -ComObject wscript.shell
$gDrive = 'C:\'
$gFolder = 'F1\F10'
$pDrive = 'C:\'
$pFolder = 'tmp'
$baseind = "$gDrive$gFolder".split("\").Count - 1
# コピー先のフォルダー名は基準フォルダーの下位2番目のフォルダーとし、これを対象フォルダーと呼びます。
$ind = $baseind + 2
$maxind = 0
# 存在しなければならないフォルダーと優先順位を格納する連想配列
$makelist = @{}
# ファイルパスの対象フォルダー前半部分と後半部分を格納する連想配列
$filelist = @{}
# ファイルパスの対象フォルダー前半部分とコピー先のフォルダー名を格納する連想配列
$folderlist = @{}
# ----------$makelist を作成します。
# 1オブジェクト毎に処理をします。
Get-ChildItem -Directory -Recurse -path "$gDrive$gFolder" | ForEach-Object {
# フォルダー区切り \ で分割
$buf = $_.FullName.Split('\')
if ($maxind -lt $buf.count - $ind - 1){
$maxind = $buf.count - $ind - 1
}
# 一時的配列
$tmp = @()
# 処理は対象フォルダーの階層以下があるものとするので $ind+1 とします。
if ($buf.count -ge $ind + 1) {
for ($i = $ind; $i -lt $buf.count; $i++){
$tmp += $buf[$i]
# 配列を結合してフォルダーの階層を取得します。
$pDirectory = $tmp -join "\"
# フォルダーのリストに加えます。
if ($makelist.ContainsKey($pDirectory) -eq $False){
# $i - $ind は作成する優先順位(0から始まる)
$makelist.add($pDirectory, $i - $ind)
}
}
}
}
# ----------$filelist を作成します。
# ----------$folderlist を作成します。
# 1オブジェクト毎に処理をします。
Get-ChildItem -File -Recurse -path "$gDrive$gFolder" | ForEach-Object {
# フォルダー区切り \ で分割
$buf = $_.FullName.Split('\')
# ファイルパスが対象フォルダー階層以上
# 最後にファイル名が含まれるので $ind+2 とします。
if ($buf.count -ge $ind + 2) {
# 対象フォルダー階層含む以前の前半部分
$head = @()
# 対象フォルダー階層含まない以降の後半部分
$tail = @()
# 一旦配列に格納
for ($i=0; $i -lt $buf.count; $i++){
if ($i -le $ind){
$head += $buf[$i]
} else {
$tail += $buf[$i]
}
}
# 結合します。
$hval = $head -join "\"
$tval = $tail -join "\"
# 前半部分をキーにし、値を配列にして後半部分を配列にセットします。
# 一時的配列
$tmp = @()
if ($filelist.ContainsKey($hval) -eq $False) {
# 新規登録
# 後半部分を配列にセット
$tmp += $tval
# 前半部分をキーにして、値を配列にした連想配列の作成
$filelist.add($hval,$tmp)
} else {
# キーが既にある場合、値の配列にセットします。
$filelist[$hval] += $tval
}
# 前半部分をキーにして、値をコピー先のフォルダー名にした連想配列にセット
if ($folderlist.ContainsKey($hval) -eq $False){
$folderlist.add($hval,$buf[$ind])
}
}
}
# ----------キーを表示します。
foreach ($key in $filelist.Keys) {
Write-Host ('連想配列のキー:' + $key + ' 値:' + $filelist[$key])
}
# ----------キーを表示します。
foreach ($key in $folderlist.Keys) {
Write-Host ('連想配列のキー:' + $key + ' 値:' + $folderlist[$key])
}
# ----------コピー先のフォルダーを確認して存在しないなら作成します。
for ($i = 0; $i -le $maxind; $i++){
foreach ($key in $makelist.Keys) {
# 0 から順番にチェック・作成します。
if ($i -eq $makelist[$key]){
# チェックするフォルダーのフルパスを作成します。
$makefolder = "$pDrive$pFolder" + "\" + $key
if (Test-Path $makefolder){
# 何もしません。
} else {
# フォルダーが存在しない場合
New-Item $makefolder -ItemType Directory
}
}
}
}
# ----------ファイルリストのファイルをバックアップします。
foreach ($key in $filelist.Keys) {
# コピー元のディレクトリをキーにしたファイルリストを使います。
for ($i = 0; $i -lt $filelist[$key].count; $i++){
# コピー元のファイルのフルパスを作成します。
$copyfile = $key + "\" + $filelist[$key][$i]
# コピー先のファイルのフルパスを作成します。
$pastefile = "$pDrive$pFolder" + "\" + $folderlist[$key] + "\" + $filelist[$key][$i]
# コピーを実行します。
Copy-Item $copyfile $pastefile -Force
}
}
$shell.Popup($items.Count, 0, "キャプチャ", 1)
重複フォルダーに対応
上のコードでは、ホルダーが重複してると最後のファイルで上書きされるのでちょっと不都合です。
下記のコードに修正してみました。
実行すると親フォルダー名の中にファイルが保存されますので上書きされることはありません。

$shell = New-Object -ComObject wscript.shell
$gDrive = 'C:\'
$gFolder = 'F1\F10'
$pDrive = 'C:\'
$pFolder = 'tmp'
$baseind = "$gDrive$gFolder".split("\").Count - 1
# コピー先のフォルダー名は基準フォルダーの下位2番目のフォルダーとし、これを対象フォルダーと呼びます。
$ind = $baseind + 2
$maxind = 0
# 存在しなければならないフォルダーと優先順位を格納する連想配列
$makelist = @{}
# ファイルパスの対象フォルダー前半部分と後半部分を格納する連想配列
$filelist = @{}
# ファイルパスの対象フォルダー前半部分とコピー先のフォルダー名を格納する連想配列
$folderlist = @{}
# ----------$makelist を作成します。
# 1オブジェクト毎に処理をします。
Get-ChildItem -Directory -Recurse -path "$gDrive$gFolder" | ForEach-Object {
# フォルダー区切り \ で分割
$buf = $_.FullName.Split('\')
if ($maxind -lt $buf.count - $ind - 1){
$maxind = $buf.count - $ind - 1
}
# 一時的配列
$tmp = @()
# 処理は対象フォルダーの階層以下があるものとするので $ind+1 とします。
if ($buf.count -ge $ind + 1) {
$a = $buf[$ind - 1]
$buf[$ind - 1] = $buf[$ind]
$buf[$ind] = $a
for ($i = $ind - 1; $i -lt $buf.count; $i++){
$tmp += $buf[$i]
# 配列を結合してフォルダーの階層を取得します。
$pDirectory = $tmp -join "\"
# フォルダーのリストに加えます。
if ($makelist.ContainsKey($pDirectory) -eq $False){
# $i - $ind は作成する優先順位(0から始まる)
$makelist.add($pDirectory, $i - $ind)
}
}
}
}
# ----------$filelist を作成します。
# ----------$folderlist を作成します。
# 1オブジェクト毎に処理をします。
Get-ChildItem -File -Recurse -path "$gDrive$gFolder" | ForEach-Object {
# フォルダー区切り \ で分割
$buf = $_.FullName.Split('\')
# ファイルパスが対象フォルダー階層以上
# 最後にファイル名が含まれるので $ind+2 とします。
if ($buf.count -ge $ind + 2) {
# 対象フォルダー階層含む以前の前半部分
$head = @()
# 対象フォルダー階層含まない以降の後半部分
$tail = @()
# 一旦配列に格納
for ($i=0; $i -lt $buf.count; $i++){
if ($i -le $ind){
$head += $buf[$i]
} else {
$tail += $buf[$i]
}
}
# 結合します。
$hval = $head -join "\"
$tval = $tail -join "\"
# 前半部分をキーにし、値を配列にして後半部分を配列にセットします。
# 一時的配列
$tmp = @()
if ($filelist.ContainsKey($hval) -eq $False) {
# 新規登録
# 後半部分を配列にセット
$tmp += $tval
# 前半部分をキーにして、値を配列にした連想配列の作成
$filelist.add($hval,$tmp)
} else {
# キーが既にある場合、値の配列にセットします。
$filelist[$hval] += $tval
}
# 前半部分をキーにして、値をコピー先のフォルダー名にした連想配列にセット
if ($folderlist.ContainsKey($hval) -eq $False){
$folderlist.add($hval,$buf[$ind] + "\" + $buf[$ind - 1])
}
}
}
# ----------キーを表示します。
foreach ($key in $filelist.Keys) {
Write-Host ('連想配列のキー:' + $key + ' 値:' + $filelist[$key])
}
# ----------キーを表示します。
foreach ($key in $folderlist.Keys) {
Write-Host ('連想配列のキー:' + $key + ' 値:' + $folderlist[$key])
}
# ----------コピー先のフォルダーを確認して存在しないなら作成します。
for ($i = 0; $i -le $maxind; $i++){
foreach ($key in $makelist.Keys) {
# 0 から順番にチェック・作成します。
if ($i -eq $makelist[$key]){
# チェックするフォルダーのフルパスを作成します。
$makefolder = "$pDrive$pFolder" + "\" + $key
if (Test-Path $makefolder){
# 何もしません。
} else {
# フォルダーが存在しない場合
New-Item $makefolder -ItemType Directory
}
}
}
}
# ----------ファイルリストのファイルをバックアップします。
foreach ($key in $filelist.Keys) {
# コピー元のディレクトリをキーにしたファイルリストを使います。
for ($i = 0; $i -lt $filelist[$key].count; $i++){
# コピー元のファイルのフルパスを作成します。
$copyfile = $key + "\" + $filelist[$key][$i]
# コピー先のファイルのフルパスを作成します。
$pastefile = "$pDrive$pFolder" + "\" + $folderlist[$key] + "\" + $filelist[$key][$i]
# コピーを実行します。
Copy-Item $copyfile $pastefile -Force
}
}
$shell.Popup($items.Count, 0, "キャプチャ", 1)
今回はここまでとします。