WinSCPのファイル移動をタスクスケジューラーで自動化する場合は、サーバー内の移動とPCへのダウンロードを区別します。以下はSFTPサーバーの受信フォルダーから完了済みCSVを別フォルダーへ移し、失敗を終了コード1で通知する例です。
接続先・フォルダー・実行ユーザーを決めてから、検証用ファイルで手動実行を確認します。WinSCPが起動しただけでは移動成功と判断せず、ログと移動後のファイルを確認してください。
移動・ダウンロード・同期の違い
| 目的 | WinSCPの処理 | 対象 |
|---|---|---|
| 同じサーバー内の移動・名前変更 | MoveFile/CLIのmv | リモート→リモート |
| PCへ取得 | GetFiles/CLIのget | リモート→ローカル |
| PCから送信 | PutFiles/CLIのput | ローカル→リモート |
| 同期 | synchronize local/remote/both | localはリモートの変更をPCへ、remoteはPCの変更をリモートへ、bothは双方 |
synchronize remoteは双方向同期ではありません。また、同期は別世代を保存するバックアップとは異なります。ダウンロード後に元ファイルを消す運用なら、取得結果を確認してから削除する別設計が必要です。この記事の例はサーバー内の移動で、PCへコピーを作りません。
実行前に用意するもの
- Windows PowerShell 5.1と、同じバージョンのWinSCPnet.dll・winscp.exe。Automationパッケージのルートにある.NET Framework版DLLを使う。
- 例のC:\SftpJobとC:\SftpJob\logsを事前に作り、タスク実行ユーザーへ必要な読み取り・書き込み権限を付ける。
- SFTP管理者へホスト名・利用者・接続先のSSHホスト鍵フィンガープリントを確認する。鍵を「*」等に置き換えて検証を省かない。
- サーバー側に/inboxと/processedを用意し、対象アカウントで一覧取得・移動できることを確認する。
- 送信側が完成時だけCSV名にする運用を決める。書き込み中は.tmp等を使い、拡張子だけで完了を判定しない。
- この例では対象を直下の通常ファイル*.csvに限定し、下位フォルダーやシンボリックリンクを扱わない。
PowerShell 7を使う場合は.NET Standard版DLL等の構成が異なります。以下をそのまま別ランタイムへ流用せず、公式のDLL選択と実行ファイルの組み合わせを確認してください。
1.同じWindowsユーザーで資格情報を保存する
タスクを実行するWindowsユーザーで、同じPC上のWindows PowerShellを開いて次を実行します。入力するのはSFTPのユーザー名とパスワードです。WindowsではExport-Clixmlが資格情報をDPAPIで保護しますが、別のユーザーや別PCでそのまま復号できません。フォルダーのアクセス権も制限します。
Get-Credential | Export-Clixml -Path 'C:\SftpJob\sftp-credential.xml' -NoClobber
既にファイルがある場合は上書きせず止まります。更新時は実行を止め、承認された手順で入れ替えます。CLIXMLはWindows以外では同じ暗号化にならないため、この手順をmacOS・Linuxへ当てはめません。鍵認証が必要な環境では、別途SshPrivateKeyPath等を設定し、鍵とパスフレーズの運用を管理者と決めてください。
2.サーバー内のCSVを移動するPowerShell例
次をC:\SftpJob\move-csv.ps1へ保存します。ホスト名とフィンガープリントは仮の値です。DLL・実行ファイルは例ではC:\SftpJob\WinSCPへ置き、実際の配置に合わせます。移動先に同名があれば上書きせず失敗として止めます。
$ErrorActionPreference = 'Stop'
$exitStatus = 0
$session = $null
$jobFolder = 'C:\SftpJob'
$sourceDirectory = '/inbox'
$targetDirectory = '/processed'
try {
if ($sourceDirectory -eq $targetDirectory) {
throw '移動元と移動先は別のフォルダーを指定してください。'
}
$dllPath = Join-Path $jobFolder 'WinSCP\WinSCPnet.dll'
$exePath = Join-Path $jobFolder 'WinSCP\winscp.exe'
$logDirectory = Join-Path $jobFolder 'logs'
if (-not (Test-Path -LiteralPath $logDirectory -PathType Container)) {
throw 'logsフォルダーを事前に作成してください。'
}
foreach ($requiredFile in @($dllPath, $exePath)) {
if (-not (Test-Path -LiteralPath $requiredFile -PathType Leaf)) {
throw "必要なファイルがありません: $requiredFile"
}
}
Add-Type -Path $dllPath
$credential = Import-Clixml -LiteralPath (Join-Path $jobFolder 'sftp-credential.xml')
if ($credential -isnot [System.Management.Automation.PSCredential]) {
throw 'SFTPのPSCredentialを保存したファイルが必要です。'
}
$options = New-Object WinSCP.SessionOptions -Property @{
Protocol = [WinSCP.Protocol]::Sftp
HostName = 'sftp.example.com'
UserName = $credential.UserName
SecurePassword = $credential.Password
SshHostKeyFingerprint = 'ssh-ed25519 255 SHA256:REPLACE_WITH_VERIFIED_KEY'
}
$session = New-Object WinSCP.Session
$session.ExecutablePath = $exePath
$runId = [guid]::NewGuid().ToString('N')
$session.SessionLogPath = Join-Path $logDirectory ("winscp-$runId.log")
$session.Open($options)
if (-not $session.GetFileInfo($targetDirectory).IsDirectory) {
throw '移動先は存在するディレクトリを指定してください。'
}
$movedCount = 0
foreach ($entry in $session.ListDirectory($sourceDirectory).Files) {
if ($entry.FileType -ne '-' -or $entry.Name -notlike '*.csv') { continue }
$destination = $targetDirectory.TrimEnd('/') + '/' + $entry.Name
if ($session.FileExists($destination)) {
throw "同名の移動先が存在します: $destination"
}
$session.MoveFile($entry.FullName, $destination)
$movedCount++
}
Write-Output "Moved: $movedCount"
}
catch {
[Console]::Error.WriteLine($_.Exception.Message)
$exitStatus = 1
}
finally {
if ($null -ne $session) { $session.Dispose() }
}
exit $exitStatus
移動対象が0件ならこの例は正常終了0、接続・一覧・移動・同名衝突等の失敗は1で終了します。途中まで移して失敗した場合、済んだ移動は元に戻りません。ログと両フォルダーを確認してから再実行します。移動先の事前確認と実際の移動は一つの原子操作ではないため、別の処理が同時に同名ファイルを作らない運用も必要です。
サーバーの権限・chroot・ファイルシステム構成によって移動できる範囲は異なります。ルート権限を付けたり、フォルダーの権限を一律に広げたりして解決せず、必要な移動先を管理者へ確認します。接続前の失敗ではWinSCPログがまだ作られない場合もあります。
3.タスクスケジューラーへ登録する
| 項目 | この例の設定 |
|---|---|
| 操作 | プログラムの開始 |
| プログラム | C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe(Windowsの場所に合わせる) |
| 引数 | -NoProfile -NonInteractive -File “C:\SftpJob\move-csv.ps1” |
| 開始(オプション) | C:\SftpJob |
| 実行ユーザー | CLIXMLを作成した同じWindowsユーザー |
| トリガー | 開始時刻を決め、繰り返し間隔15分・必要な継続時間を設定 |
| 既にタスクが実行中の場合 | 新しいインスタンスを開始しない |
「ログオンしているかどうかにかかわらず実行」を使う場合は、非対話で接続できる条件を確認します。「パスワードを保存しない」はローカルリソースのみの条件に注意し、GUIで使うPageantやネットワークドライブがタスクでも利用できると考えないでください。管理者権限は必要な場合に限定し、実行ポリシーは組織の方針と署名等を確認します。
手動では動くのにタスクで失敗するとき
| 症状 | 確認する点 |
|---|---|
| DLLや実行ファイルが見つからない | 絶対パス、DLLの種類・バージョン、実行ユーザーの読み取り権限 |
| 資格情報を読み込めない | CLIXML作成者とタスク実行ユーザー・PCが同じか |
| 接続先の鍵の確認で止まる | 管理者から確認したフィンガープリントと接続先が合うか |
| 移動が失敗する | 対象の完了状態、移動先の存在・同名衝突・サーバー権限 |
| 終了0だが対象がない | 空のフォルダー・対象拡張子・処理済みかを確認する |
| ログが増え続ける | 保存期間・容量・権限を決め、必要な証跡を残して整理する |
CLIを使う場合、/commandへは各コマンドを別々の引数として渡します。「/command:」へコマンドを&で連結する形式ではありません。また、PowerShellの$Hostは組み込み変数なので接続先名の保存に使いません。今回の.NET例はそのコマンド文字列の組み立てを使わず、接続と移動をメソッドで呼び出します。
登録後に確認すること
- まず検証用の完成済みCSVと同名衝突を用意し、期待する移動と失敗を確認する。
- 手動実行後、同じ実行ユーザーのタスクから実行し、最終実行結果とWinSCPログを確認する。
- ログオフ時と繰り返し時も確認し、対象0件を正常とする運用でよいか決める。
- 移動をバックアップと扱わず、必要な保管・復元方法を別に用意する。
公式資料
- WinSCP:PowerShellと.NETライブラリ
- WinSCP:SessionOptionsとホスト鍵の確認
- WinSCP:リモートファイルを移動するMoveFile
- WinSCP:リモート一覧の取得
- WinSCP:RemoteFileInfoのファイル種別
- WinSCP:移動先の存在確認
- WinSCP:実行ファイルとログを指定するSession
- Microsoft:WindowsでのCLIXML資格情報の保護
- WinSCP:タスクスケジューラーの設定
- WinSCP:手動では動くがタスクで失敗する場合
- WinSCP:同期方向の違い
- WinSCP:CLI構文と終了コード
- Microsoft:タスクのログオン方式とS4Uの制約
- Microsoft:PowerShellの自動変数と終了コード

コメント