1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

「AIにコードを書かせれば自動化できる」と思っていた

1
Last updated at Posted at 2026-08-02

2025年、新社会人だった私は、生成AIにやりたいことを伝えれば、業務で使える自動化コードが簡単に完成すると思っていました。

実際、AIは数秒でPowerShellでもExcel VBAでもコードを書いてくれます。
簡単な処理なら、そのまま動くこともあります。

しかし、私が最初に作ったPowerShellのコードは、
コピー先フォルダーが存在しないだけでエラーになりました。

原因はAIの性能ではありませんでした。私が「コピー先が存在しない場合にどうするか」を決めておらず、AIにも伝えていなかったことでした。

この記事では、その失敗をきっかけにAIとの向き合い方が変わった過程と、現在使っている具体的な手順・プロンプトを紹介します。

この記事で伝えたいこと

先に結論だけ書いておきます。

  1. AIへ指示する前に、自分が正常時と異常時の動作を決める
  2. 一度動いても完成とは判断せず、異なる条件でテストする
  3. すべてを自動化せず、重要な判断には人の確認を残す

AIにコードを書いてもらうことは、自動化のゴールではありませんでした。私にとっては、そこがスタートでした。

私について

  • 社会人2年目のエンジニア(24歳)
  • 大学ではC系とPythonが中心。PowerShellとExcel VBAは業務効率化のため勉強中
  • 設計は、まさに現在進行形で学んでいる段階

なぜ自動化を始めたのか

業務をしていると、同じような操作を繰り返す場面があります。

  • 決められたフォルダーへファイルをコピーする
  • ファイル名を所定の形式へ変更する
  • Excelの決められたセルへ情報を入力する
  • 複数のファイルから必要な情報を探す

一つひとつは難しい作業ではありません。しかし何度も繰り返すと時間がかかり、手作業によるミスも起こり得ます。

同じ操作を繰り返しているなら、PowerShellやExcel VBAで自動化できるのではないか。
そう考えたものの、当時はやりたい処理を最初からコードにできるほど詳しくありませんでした。

そこで使い始めたのが生成AIです。
日本語で伝えるだけで、自分ではすぐに書けないコードが数秒で出てくる。初めてコードが動いたときは、素直に感動しました。

同時に、こんな思い込みも生まれました。

AIに作業内容を伝えれば、あとは生成されたコードを実行するだけでよい。

今振り返ると、コードを「作ること」だけに注目し、その後の確認を軽く考えていました。

AIに最初に出した指示

例として、指定したフォルダー内のExcelファイルを、バックアップ用フォルダーへコピーする処理を考えます。

当時の私は、こう指示していました。

フォルダー内のExcelファイルを
別のフォルダーへコピーするPowerShellを作成してください。

かなり曖昧な指示です。それでもAIは、次のコードを生成しました。

$sourceFolder = "C:\Work\Source"
$destinationFolder = "C:\Work\Backup"

Get-ChildItem -Path $sourceFolder -Filter "*.xlsx" |
    Copy-Item -Destination $destinationFolder

短く、見た目も分かりやすいコードです。私はコピー元のフォルダーにExcelファイルを置き、そのまま実行しました。

実際に起きた失敗

実行すると、ファイルはコピーされませんでした。コピー先が存在しない、というエラーが表示されます。

確認すると、コピー元の C:\Work\Source は作成していましたが、コピー先の C:\Work\Backup を作っていませんでした。

私はAIに「Excelファイルを別のフォルダーへコピーしてほしい」としか伝えていません。そのため、次の条件は何も決まっていませんでした。

  • コピー先が存在しない場合は自動作成するのか
  • エラーを表示して終了するのか
  • 親フォルダーも存在しない場合はどうするのか

補足として、Copy-Item は存在しないパスを渡されたとき、必ずエラーになるとは限りません。対象ファイルが1件だけの場合、コピー先を「フォルダー」ではなく「コピー先のファイル名」と解釈し、C:\Work\Backup という拡張子のないファイルが作られることがあります。エラーで止まってくれた方が、まだ気づけるだけ親切だったことになります。

コピー先フォルダーを手動で作成して再実行すると、今度はコピーできました。しかし、そこで新たな疑問が出てきます。

今回はフォルダーを作れば動いたが、ほかにも決めていない条件があるのではないか。

確認すると、次の条件も抜けていました。

  • コピー先に同名ファイルがある場合
  • 対象ファイルが1件もない場合
  • Excelの一時ファイルが含まれる場合
  • ファイルへのアクセス権限がない場合
  • 途中でコピーに失敗した場合
  • 何件コピーできたか確認したい場合

最初のコードは、必要なフォルダーが存在し、対象ファイルにも問題がない状態でのみ期待どおりに動くコードでした。

原因は「AIの間違い」だけではなかった

当初は、AIが不完全なコードを生成したと考えました。

しかし、そもそも私は「コピー先が存在しない場合にどうするか」をAIへ伝えていません。それどころか、自分自身でも決めていませんでした。

AIへの指示が曖昧だったというより、自動化する処理の仕様そのものが曖昧だったのです。

そして、コピーが成功した時点でも完成ではありませんでした。私は次の質問に答えられなかったからです。

  • Get-ChildItem は具体的に何を取得しているのか
  • サブフォルダーのファイルも対象になるのか
  • 同じスクリプトを2回実行するとどうなるのか

コードが一度動いたことと、安全に繰り返し使えることは別です。確認できたのは、特定の条件でコピーできたことだけでした。

AIへの指示を具体化した

不足していた条件を整理し、指示を次のように変更しました。

PowerShellで、指定したフォルダー内の.xlsxファイルを
バックアップフォルダーへコピーするスクリプトを作成してください。

条件は以下のとおりです。

・サブフォルダー内のファイルは対象外
・ファイル名が「~$」から始まる一時ファイルは対象外
・コピー元フォルダーが存在しない場合はエラー終了
・コピー先フォルダーが存在しない場合は作成
・同名ファイルが存在する場合は上書きせずスキップ
・実行前に対象ファイルを一覧表示
・成功、失敗、スキップの件数を最後に表示
・コード内に日本語のコメントを付ける
・ファイルを削除する処理は含めない

生成されたコードのうち、要点は次の3箇所です。

コピー先が存在しない場合は作成します。New-Item -ItemType Directory は、途中の親フォルダーが存在しない場合もまとめて作成してくれます。

if (-not (Test-Path -LiteralPath $DestinationFolder -PathType Container)) {
    New-Item -ItemType Directory -Path $DestinationFolder -Force | Out-Null
}

対象ファイルの取得時に、Excelの一時ファイルを除外します。

$files = @(
    Get-ChildItem -LiteralPath $SourceFolder -File -Force |
        Where-Object { $_.Extension -eq '.xlsx' -and $_.Name -notlike '~$*' } |
        Sort-Object Name
)

コピー先に同名ファイルがある場合は、上書きせずスキップします。

if (Test-Path -LiteralPath $destinationPath) {
    Write-Host "スキップ  : $($file.Name)(コピー先に同名ファイルあり)"
    $skipCount++
    continue
}

最初のコードより長くなりましたが、何を正常とし、どの条件では処理しないかが明確になりました。極端に言えば、自分が望んでいない処理をさせないためのコードです。

完成版スクリプト

条件をすべて反映したものが以下です。Backup-ExcelFiles.ps1 として保存すれば動きます。

Windows PowerShell 5.1 は、BOMなしのUTF-8で保存された .ps1 をANSIとして読み込みます。日本語のコメントや Write-Host の文字列が化けるため、「UTF-8 (BOM付き)」で保存してください。VS Codeなら右下のエンコード表示から Save with EncodingUTF-8 with BOM です。

#Requires -Version 5.1

<#
.SYNOPSIS
    指定したフォルダー直下の .xlsx ファイルを、バックアップフォルダーへコピーします。

.DESCRIPTION
    - サブフォルダーは対象外です
    - Excel の一時ファイル(~$ で始まるもの)は対象外です
    - コピー先に同名ファイルがある場合は、上書きせずスキップします
    - ファイルの削除・移動は一切行いません

.EXAMPLE
    # まず何が起きるかだけ確認する(実際にはコピーされません)
    .\Backup-ExcelFiles.ps1 -SourceFolder 'C:\Work\Source' -DestinationFolder 'C:\Work\Backup' -WhatIf

.EXAMPLE
    # 実行する
    .\Backup-ExcelFiles.ps1 -SourceFolder 'C:\Work\Source' -DestinationFolder 'C:\Work\Backup'
#>

[CmdletBinding(SupportsShouldProcess = $true)]
param(
    [Parameter(Mandatory = $true)]
    [string]$SourceFolder,

    [Parameter(Mandatory = $true)]
    [string]$DestinationFolder
)

# ---------------------------------------------------------------
# 1. コピー元フォルダーの存在確認
#    -PathType Container を付けることで、同名の「ファイル」を
#    フォルダーと誤認することを防ぐ
# ---------------------------------------------------------------
if (-not (Test-Path -LiteralPath $SourceFolder -PathType Container)) {
    throw "コピー元フォルダーが見つかりません: $SourceFolder"
}

# ---------------------------------------------------------------
# 2. 対象ファイルの取得
#    -File      : ファイルのみ取得(フォルダーを除外)
#    -Force     : 隠し属性が付いたファイルも「いったん」取得する
#                 ※Excel の ~$ 一時ファイルは隠しファイルなので、
#                   -Force を付けないとそもそも一覧に出てこない
#    Extension  : -Filter は 8.3 形式の短い名前にもマッチして
#                 意図しないファイルを拾うことがあるため、
#                 拡張子は Extension プロパティで厳密に比較する
#    Name       : Excel でファイルを開いている間だけ作られる
#                 ~$xxxx.xlsx を除外する
#    @( ) で囲む : 対象が 0 件・1 件でも配列として扱えるようにする
# ---------------------------------------------------------------
$files = @(
    Get-ChildItem -LiteralPath $SourceFolder -File -Force |
        Where-Object { $_.Extension -eq '.xlsx' -and $_.Name -notlike '~$*' } |
        Sort-Object Name
)

if ($files.Count -eq 0) {
    Write-Host '対象ファイルが 1 件もありません。処理を終了します。'
    return
}

# ---------------------------------------------------------------
# 3. 実行前に対象ファイルを一覧表示する
# ---------------------------------------------------------------
Write-Host ''
Write-Host "コピー元    : $SourceFolder"
Write-Host "コピー先    : $DestinationFolder"
Write-Host "対象ファイル: $($files.Count) 件"
foreach ($file in $files) {
    Write-Host "  - $($file.Name)"
}
Write-Host ''

# ---------------------------------------------------------------
# 4. コピー先フォルダーの作成
#    New-Item -ItemType Directory は、途中の親フォルダーが
#    存在しない場合もまとめて作成してくれる
#    -Force は「すでに存在していてもエラーにしない」ためのもので、
#    既存フォルダーの中身を消すものではない
# ---------------------------------------------------------------
if (-not (Test-Path -LiteralPath $DestinationFolder -PathType Container)) {
    if ($PSCmdlet.ShouldProcess($DestinationFolder, 'コピー先フォルダーを作成')) {
        New-Item -ItemType Directory -Path $DestinationFolder -Force | Out-Null
        Write-Host "コピー先フォルダーを作成しました: $DestinationFolder"
    }
}

# ---------------------------------------------------------------
# 5. コピー処理
# ---------------------------------------------------------------
$successCount = 0
$skipCount    = 0
$failureCount = 0

foreach ($file in $files) {

    $destinationPath = Join-Path -Path $DestinationFolder -ChildPath $file.Name

    # 同名ファイルがある場合は上書きせずスキップする
    # ※名前だけで判定しており、中身や更新日時までは比較していない
    if (Test-Path -LiteralPath $destinationPath) {
        Write-Host "スキップ  : $($file.Name)(コピー先に同名ファイルあり)"
        $skipCount++
        continue
    }

    try {
        # Copy-Item のエラーは既定では「非終了エラー」で catch できないため、
        # -ErrorAction Stop を明示して try/catch で拾えるようにする
        Copy-Item -LiteralPath $file.FullName -Destination $destinationPath -ErrorAction Stop

        # -WhatIf のときは実際にはコピーされていないので、成功件数に数えない
        if (-not $WhatIfPreference) {
            Write-Host "コピー成功: $($file.Name)"
            $successCount++
        }
    }
    catch {
        # 1 件失敗しても、残りのファイルの処理は続ける
        Write-Warning "コピー失敗: $($file.Name) / $($_.Exception.Message)"
        $failureCount++
    }
}

# ---------------------------------------------------------------
# 6. 処理結果の表示
# ---------------------------------------------------------------
Write-Host ''
Write-Host '---------------- 実行結果 ----------------'
Write-Host "成功    : $successCount 件"
Write-Host "スキップ: $skipCount 件"
Write-Host "失敗    : $failureCount 件"
Write-Host '------------------------------------------'

# 失敗があったことを呼び出し元(タスクスケジューラーなど)へ伝える
if ($failureCount -gt 0) {
    exit 1
}

スクリプトの実行がブロックされる場合は、実行ポリシーの設定が必要です。ただしAIは Set-ExecutionPolicy Bypass を安易に勧めてくることがあります。恒久的に緩めるのではなく、Set-ExecutionPolicy -Scope Process のように影響範囲を限定するか、社内の運用ルールに従ってください。

実行する前に「何が起きるか」だけ確認する

このスクリプトには -WhatIf を付けられるようにしてあります。実際にはコピーせず、何が実行されるかだけを表示するPowerShellの標準機能です。

PS C:\Work> .\Backup-ExcelFiles.ps1 -SourceFolder 'C:\Work\Source' -DestinationFolder 'C:\Work\Backup' -WhatIf

コピー元    : C:\Work\Source
コピー先    : C:\Work\Backup
対象ファイル: 2 件
  - test01.xlsx
  - test02.xlsx

WhatIf: 対象 "C:\Work\Backup" に対して操作 "コピー先フォルダーを作成" を実行しています。
WhatIf: 対象 "アイテム: C:\Work\Source\test01.xlsx 到着先: C:\Work\Backup\test01.xlsx" に対して操作 "ファイルのコピー" を実行しています。
WhatIf: 対象 "アイテム: C:\Work\Source\test02.xlsx 到着先: C:\Work\Backup\test02.xlsx" に対して操作 "ファイルのコピー" を実行しています。

---------------- 実行結果 ----------------
成功    : 0 件
スキップ: 0 件
失敗    : 0 件
------------------------------------------

「実行する前に確認を残す」ことは、自分でロジックを書かなくても標準機能で実現できます。Remove-ItemMove-Item のような取り返しのつかない処理を扱うときほど、まずこれを付けて動かすようにしています。

動作確認

次の構成のフォルダーで確認しました。

C:\Work\Source
├─ test01.xlsx
├─ test02.xlsx
├─ ~$test03.xlsx   ← Excel で test03.xlsx を開いている間だけ生成される
└─ test_memo.txt

1回目の実行

コピー元    : C:\Work\Source
コピー先    : C:\Work\Backup
対象ファイル: 2 件
  - test01.xlsx
  - test02.xlsx

コピー先フォルダーを作成しました: C:\Work\Backup
コピー成功: test01.xlsx
コピー成功: test02.xlsx

---------------- 実行結果 ----------------
成功    : 2 件
スキップ: 0 件
失敗    : 0 件
------------------------------------------

2回目の実行(同名ファイルが存在する状態)

コピー元    : C:\Work\Source
コピー先    : C:\Work\Backup
対象ファイル: 2 件
  - test01.xlsx
  - test02.xlsx

スキップ  : test01.xlsx(コピー先に同名ファイルあり)
スキップ  : test02.xlsx(コピー先に同名ファイルあり)

---------------- 実行結果 ----------------
成功    : 0 件
スキップ: 2 件
失敗    : 0 件
------------------------------------------

一時ファイル ~$test03.xlsx とテキストファイル test_memo.txt は、どちらも対象外として扱われています。初期コードでは、そもそもコピー先フォルダーが存在しない時点で止まっていました。

検証中に気づいたこと

~$ 一時ファイルは、そもそも一覧に出てこない

除外条件を書いたあと、念のため Get-ChildItem の結果だけを表示してみたところ、~$test03.xlsx は最初から一覧に含まれていませんでした。

Excelが作る ~$xxxx.xlsx には隠しファイル属性が付いており、Get-ChildItem-Force を付けない限り隠しファイルを返さないためです。

つまり -Force なしのコードでは、「除外条件が正しく効いた」のではなく「そもそも取得されていなかった」だけでした。

さらに厄介なのが、テスト用に自分で作った ~$test03.xlsx には隠し属性が付かないという点です。手作業で用意したテストデータは、本番の条件と一致しているとは限りません。

このスクリプトでは、あえて -Force で隠しファイルも取得したうえで ~$ を明示的に除外しています。そうすることで、何を除外しているのかがコードから読み取れる状態になります。

プロンプトを長くするだけでは解決しなかった

条件を詳しく指定すると、目的に近いコードが生成されやすくなります。一方で、条件を増やすほどコードは長くなります。

私は一時期、思いついた条件をすべてプロンプトへ追加し、最初から完璧なコードを作らせようとしていました。その結果が次の状態です。

  1. 簡単な指示では、条件不足のコードが生成される
  2. 条件を大量に追加する
  3. 長く複雑なコードが生成される
  4. 自分ではコード全体を確認できなくなる

自分の理解を超えるコードを生成できることはAIの長所ですが、理解できないまま実行するとリスクにもなり得ます。そこで現在は、最初から完成版を作らせないようにしています。

現在のAIの使い方

1. コードを書く前に仕様を整理する

最初に、コードではなく必要な条件の洗い出しを依頼します。

指定したフォルダー内のExcelファイルを、
バックアップフォルダーへコピーする作業を
PowerShellで自動化したいです。

まだコードは作成せず、先に以下を整理してください。

・必要な入力情報
・処理の流れ
・事前に決めるべき条件
・想定されるエラー
・実行前に確認すべきこと
・人が判断すべき部分

AIが挙げた条件を自分で確認し、採用するものを決めます。

2. 処理を小さく分けて生成する

今回の例なら、次の単位に分割します。

  1. フォルダーの存在を確認する
  2. 対象ファイルを取得する
  3. 対象ファイルを表示する
  4. ファイルをコピーする
  5. 処理結果を表示する

小さく作ることで、各処理の意味と動作を確認しやすくなりました。

3. 分からない行を確認する

理解できない行は、その部分だけをAIに質問します。

次のPowerShellコードを初心者向けに説明してください。

Get-ChildItem -LiteralPath $sourceFolder -File -Force

以下を分けて説明してください。

・各パラメーターの役割
・取得されるファイル
・取得されないファイル
・サブフォルダーが対象になるか

コード全体ではなく、分からない部分を限定した方が確認しやすくなります。今回の隠しファイルの件も、この質問から気づきました。

4. 問題がある前提でレビューする

「問題ありませんか」だけでは確認観点が曖昧になるため、具体的な条件を指定します。

このコードには問題がある前提で、厳しくレビューしてください。

特に以下を確認してください。

・既存ファイルを上書きする可能性
・指定フォルダー外を処理する可能性
・対象ファイルが0件の場合の動作
・フォルダーが存在しない場合の動作
・途中でコピーに失敗した場合の動作
・2回連続で実行した場合の動作
・削除処理や破壊的な処理の有無

問題がある場合は、
該当箇所、発生条件、影響を分けて説明してください。

AIのレビューも必ず正しいとは限らないため、確認材料の一つとして扱います。

5. テスト用データで実行する

コードを確認した後は、実際の業務ファイルではなくテスト用のフォルダーで試します。特に確認するのは次の条件です。

  • 対象ファイルがない
  • コピー先が存在しない
  • 同名ファイルが存在する
  • 一時ファイルが含まれる
  • 同じ処理を2回実行する

正常に動くことだけでなく、想定外の条件で危険な動作をしないことも確認します。

すべてを自動化しない

以前の私は、手作業が残ると自動化に失敗したように感じていました。

現在は、定型処理だけをスクリプトへ任せ、重要な判断は人が行う半自動化も有効だと考えています。

  • 既存ファイルを上書きする前の確認
  • ファイルを削除する前の確認
  • 大量のファイルを処理する前の確認
  • 業務上の判断が必要な処理

すべてを自動化するより、確認を一つ残した方が、安全性と効率を両立できる場合があります。

残っている制約

今回のスクリプトは、以下については何もしていません。

  • 同名ファイルの中身を比較していない — 名前が同じならスキップするため、コピー元だけ更新されたファイルは反映されません
  • 長いパスに対応していない — 合計260文字を超えるパスは失敗します
  • Excelで開いている最中のファイル — ロック状態によっては失敗します(失敗 として記録されます)
  • 世代管理をしていない — 日付フォルダーへの退避などは含みません

必要になった時点で足す、という方針にしています。最初からすべてを盛り込もうとして、自分で読めないコードになったのが前述の失敗でした。

まとめ

今回の失敗は、コピー先フォルダーが存在しなかったことでした。表面上は単純なエラーですが、原因をたどると、私が異常時の動作を決めていなかったことに行き着きました。

以前の私は、AIにコードを書かせて、そのまま実行していました。

現在は、AIと一緒に仕様を整理し、小さくコードを作り、テストしてから使っています。

特に学んだことは、次の3点です。

  1. AIへ指示する前に、自分が正常時と異常時の動作を決める
  2. 一度動いても完成とは判断せず、異なる条件でテストする
  3. すべてを自動化せず、重要な判断には人の確認を残す

AIにコードを書いてもらうことは、自動化のゴールではありません。
私にとっては、そこが自動化のスタートでした。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?