質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.61%

Powershell: Copy-Itemで元のファイルと同じ場所に別名でファイルをコピーできない

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 6,097

minhouse10

score 37

実現したいこと:
ある名前のファイルを検索し、そのファイルの中の特定のキーワードを変更するためのPowershellスクリプトの中で、変更前ファイルのバックアップ機能として、元のファイルがある同じディレクトリに別名として(同じファイル名に日時情報を加える)コピーを作成機能が実現できずにおり、Webで検索したり本をみたりしていますが、Powershellのスクリプトを書いたのが今回初めてのため解決策が見つけられずにいます。アドバイス頂ければ幸いです。どうぞ、よろしくお願い致します。

エラー:
Copy-Item : The given path's format is not supported.
At C:\Users\Desktop\PS_Folder\change_connstr.ps1:38 char:17
+ ...             Copy-Item -Path $fPath'\'$targetFile -Destination $fPath' ...
+                 ~~~~~~~~~~~~~
+ CategoryInfo          : NotSpecified: (:) [Copy-Item], NotSupportedException
+ FullyQualifiedErrorId : System.NotSupportedException,Microsoft.PowerShell.Commands.CopyItemCommand 

バックアップをとる部分の修正前スクリプトは以下になります。

#複数のサブディレクトリに同じ名前のファイルがあるので再帰的に取得
$web_configfiles = Get-ChildItem $homeDir -recurse -include $targetFile

#バックアップ取得するかどうかは実行者が決定する仕様
    $backUp = Read-Host "Do You Want to Take Backups of $targetFile ? Please enter Y or N"
    if ($backUp -eq "Y"){
        echo "--Start Backuping $targetFile--`r`n"
        #以下でファイルがみつかったディレクトリパスのみを取得
        $dir_paths = ($web_configfiles).DirectoryName
    #ループにて対象ファイルを一つずつ取得
        foreach ($bkFile in $web_configfiles) {
       #ループにて対象ディレクトリパスを一つずつ取得
            foreach ($fPath in $dir_paths) {
         #コピー対象のパスとファイルをそれぞれうまく渡せず上記エラーになる
                Copy-Item -Path $fPath'\'$bkFile -Destination $fPath'\'$bkFile'_'$date$fPath'\web.config_'$date
                echo "Backup was success to make in : $fPath"
            }
        }
    }
    elseif ($backUp -eq "N") {
        echo "--User didn't choose to take backups--`r`n"
    }
    else {
        echo "--Please enter Y or N--"
        exit

satocha様からご指摘の箇所に関して:

web_configfilesをechoした結果は以下のようにヒットした複数ファイル数分出力されます。今回はweb.configというファイルが9つ検索にヒットします。

Directory: C:\Users\Desktop\PS_Folder\A

Mode                LastWriteTime         Length Name                                                                                                                                  
----                -------------         ------ ----                                                                                                                                  
-a----         9/1/2018   8:54 PM           5308 web.config


        $web_configfiles | foreach-object{
            $filePath = $_.fullname  #ファイルのフルパスを文字列として取得
            #echo $filePath
            $parent = split-path $filePath #親ディレクトリ
            #echo $parent
            $fileBase = [io.path]::getFileNameWithoutExtension( $filePath ) #拡張子を除いたファイル名
            #echo $fileBase
            $ext = [io.path]::getExtension( $filePath ) #拡張子(.を含む)
            #echo $fileBase$ext
            #あとは新パスを適当に組み立ててコピー
            $sourcePath = Join-Path $parent $fileBase$ext
            $destPath = Join-Path $parent $fileBase$ext$date
            echo $sourcePath
            echo $destPath
            Copy-Item -Path $sourcePath -Destination $destPath
        }


Copy元とコピー先のそれぞれ対象ディレクトリのパスとファイル名は意図したものとなっているがエラーとなってしまう。

echo $sourcePath
C:\Users\Desktop\PS_Folder\CC\A\web.config
echo $destPath
C:\Users\Desktop\PS_Folder\CC\A\web.config-20180902-11:18:47

Copy-Item : The given path's format is not supported.
At C:\Users\Desktop\PS_Folder\change_connstr.ps1:51 char:13
+             Copy-Item -Path $sourcePath -Destination $destPath
+             ~~~~~~~~~~
+ CategoryInfo          : NotSpecified: (:) [Copy-Item], NotSupportedException
+ FullyQualifiedErrorId : System.NotSupportedException,Microsoft.PowerShell.Commands.CopyItemCommand

satocha様からご指摘の箇所に関して その2:

satocha様にご指摘頂きました通り、dateの変数のフォーマットが問題でした。
以下のように変更したところスクリプトが意図した通りの結果を出しました!

$date = Get-Date -Format "-yyyyMMdd-HH-mm-ss"

ただしコロンを含んでいてもTrueが返りました。
PS C:\Users\Desktop\PS_Folder> test-path -isvalid "C:\Users\Desktop\PS_Folder\CC\A\web.config-20180902-11:18:47 "
True

全体的にまだまだ改善の余地の多い汚いコードかと思いますが、修正版フルコードは以下の通りとなります。

$date = Get-Date -Format "-yyyyMMdd-HH-mm-ss"
$homeDir = 'C:\Users\Desktop\PS_Folder'
$conStrFrom = 'Server1'
$conStrTo = 'Server2'
$targetFile = 'web.config'

Set-Location $homeDir

function Get-ScriptPath {
    Split-Path $MyInvocation.ScriptName
}

$CntReplaced = 0
if ((Get-ScriptPath) -eq $homeDir) {
    echo "The Path is correct: $homeDir"
    echo "`r`n"
    }
else {
    echo "--Script is not in the correct path!!--"
    exit
}
    $web_configfiles = Get-ChildItem $homeDir -recurse -include $targetFile
    $filesCnt = ($web_configfiles).Count

    echo ("There are $filesCnt `"$targetFile`" Files`r`n")

    $backUp = Read-Host "Do You Want to Take Backups of $targetFile ? Please enter Y or N"
    if ($backUp -eq "Y"){
        echo "--Start Backuping $targetFile--`r`n"
        $web_configfiles | foreach-object{
            $filePath = $_.fullname
            $parent = split-path $filePath
            $fileBase = [io.path]::getFileNameWithoutExtension( $filePath )
            $ext = [io.path]::getExtension( $filePath )
            $sourcePath = Join-Path $parent $fileBase$ext
            $destPath = Join-Path $parent $fileBase$ext$date
            Copy-Item -Path $sourcePath -Destination $destPath
            echo "Backup was successful to create in : $parent"
        }
    }
    elseif ($backUp -eq "N") {
        echo "--User didn't choose to take backups--`r`n"
    }
    else {
        echo "--Please enter Y or N--"
        exit
    }

    if ($web_configfiles) {
        foreach ($file in $web_configfiles) {
            $cnt = (Get-Content $file |Select-String $conStrFrom).Count
            $fname = Split-Path $file -Leaf
            echo ("$fname contains Target text in `"$cnt`" location")

            if ($cnt -gt 0) {
                (Get-Content $file) | Foreach-Object { $_ -creplace $conStrFrom, $conStrTo } | Set-Content $file
                $CntReplaced = $CntReplaced + 1
            }
            else {
                echo ("--The File doesn't contain Target Text--")
            }

        }
    }

echo `r`n
echo "`"$CntReplaced`" $fname files of text `"$conStrFrom`" were replaced to `"$conStrTo`"" 
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+1

Copy-Itemの行でCopy-Itemでなくechoで各変数を出力すると、何がおかしいか分かるはずです。
たぶん、ファイル名が取れていないのではないでしょうか。

問題は、

$dir_paths = ($web_configfiles).DirectoryName


で、

$dir_pathsの値は検索で複数ヒットしたときはヌルになります。というのも、$web_configfiles(すなわちGet-ChildItemの出力)は検索がひとつだけヒットしたときはFileInfo型(かDirectoryInfo型)、複数の場合はObject[]配列、見つからなければ$nullです。
配列にDirectoryNameというプロパティはないので、複数見つかったときは、$dir_pathsの値が$nullとなります。

全体にかなり苦労してディレクトリ解析をしているようですが、
みつかったファイルのフルパスを文字列として取得し、split-pathコマンドレットやsystem.io.pathのメソッドで分解するのが楽です。

$FoundFiles=Get-ChildItem ほにゃらら
$FoundFiles|foreach-object{
    $filepath=$_.fullname  #ファイルのフルパスを文字列として取得
    $parent=split-path $filepath #親ディレクトリ
    $fileBase=[io.path]::getFileNameWithoutExtension( $filepath ) #拡張子を除いたファイル名
    $ext=[io.path]::getExtension( $filepath ) #拡張子(.を含む)
    #あとは新パスを適当に組み立ててコピー
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/09/02 08:10

    >適当に組み立てて
    と書きましたが、join-pathコマンドレットを使うと楽です。

    キャンセル

  • 2018/09/02 11:21

    satocha様、ご教授頂き本当にありがとうございます!オライリーのPSのクックブックを見ながら試行錯誤しておりましたが、ご提案頂いたコマンドレットも出ておりますね!しっかりと読んで理解ができておりませんでした。早速スクリプトの修正を行い、結果改めてこちらに更新させていただきます。ありがとうございました。取り急ぎお礼まで。

    キャンセル

  • 2018/09/02 12:25

    satocha様、Joint-Pathコマンドレットにて意図したコピー元と先を組み合わせてことができましたが、Copyの段階でエラーになってしまいます。Bashなどで簡単にできることが以外にPSだと難しいのですね。もしよろしければ再度アドバイス頂けますでしょうか?

    キャンセル

  • 2018/09/02 16:19

    satocha様、この度は大変貴重なアドバイス改めてありがとうございました!無事意図した通りにスクリプトが動きました!修正版のコードをアップさせて頂きました。

    キャンセル

+1

エラーメッセージの通りです。
ファイル名として使ってはならない文字が入っています。

test-path -isvalid "C:\Users\Desktop\PS_Folder\CC\A\web.config-20180902-11:18:47 "


でfalseが返るはずです。
使ってはいけない文字の全リストは、

[io.path]::GetInvalidFileNameChars()


で得られます。

今回は、時刻の間のコロンが悪さをしています(コロンはドライブ名などを示す文字ですからね)。
変数$dateをどこかで準備しているのでしょうが、
たとえば$fileにFileInfoが入っている場合

$datestr=$file.lastWriteTime.toString() -replace "/","" -replace ":","_" -replace " ","-"


とすれば、問題のない時刻文字列得られます。念のため、上記test-path -isvalid で確かめてみてください。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/09/02 15:45

    invalidPathCharsにコロンはないですね

    キャンセル

  • 2018/09/02 16:16

    ご指摘ありがとうございます。間違いでした。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.61%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る