はじめに
SOAP API login() 廃止(Summer '27予定)に備えたDataLoader無人バッチ検証シリーズの第3弾です。
第2弾では、Salesforce CLI(sf)+ JWT Bearerフローを使えば、ブラウザを介さずに認証〜エクスポートまで完全無人で回せることを実証しました。ただしあの構成は、エクスポートの実行そのものをSalesforce CLI(sf data query)に載せ替えるやり方でした。
そもそもの疑問:データ処理までCLIに任せていいのか?
第2弾を終えて、こんな疑問が残りました。
DataLoaderのように大量データを安定して処理する能力が、Salesforce CLIのクエリ(sf data query)に本当にあるのか?
DataLoaderでは大量レコードのエクスポート・アップサートが可能で、バッチサイズの制御、リトライ、成功/エラーファイルの分離といった機能も揃っています。一方でSalesforce CLIのクエリは手軽ですが、業務で扱う規模のデータ処理を任せる相手として実績が十分かは、また別の話です。
そこで、新しい方式への対応が求められる認証部分だけSalesforce CLIに任せ、データ操作はこれまで通りDataLoaderに残す——という形で移行のリスクを小さくする目的で今回の検証にいたりました。
着想:process-conf.xml には sfdc.oauth.accesstoken がある
DataLoaderの process-conf.xml には、認証情報としてアクセストークンを直接渡す sfdc.oauth.accesstoken というパラメータが存在します。これは公式ドキュメントで大きく扱われているものではありませんが、forcedotcom/dataloader の Issue #385 で実運用例が議論されています。
このIssueから読み取れる事が2つあります。
1.DataLoaderのバッチ認証は、長年 username+暗号化パスワードが基本。
近年のバージョンではOAuthログインも公式サポートに入りました(OAuth利用時は暗号化パスワード手順を省略可)。ただし無人化しづらい Device Flow / User-Agent Flow は廃止され、残るのはブラウザを伴う PKCE(Web Server Flow)です。一方、本記事のように取得済みアクセストークンを sfdc.oauth.accesstoken に直接注入する方法は、今も非公開(undocumented)プロパティで、v64の設定項目一覧にも載っていません。DataLoaderが sfdc.password の存在をチェックするため、バージョンによっては「password が無い」というエラーになる点も同様です。
2.アクセストークンは短命のため「一度設定して終わり」ではなく、実行のたびにトークンを取り直して設定する必要がある。
つまり、この方式は「動けばDataLoaderをそのまま無人化できる」魅力がある一方で、非公式・短命トークンという2つの制約を抱えています。本記事はそれを踏まえて「実際どこまでいけるか」を検証する記録です。
検証の全体像
第2弾で構築したJWT用の外部クライアントアプリ(ECA)と、sf のJWTログインをそのまま流用します。手順はこの流れです。
- Salesforce CLI でアクセストークンとインスタンスURLを取得する
- 取得したトークンを暗号化し、DataLoaderの process-conf.xml をアクセストークン方式に差し替える
- DataLoaderを実行し、ハマりどころを潰しながらSOAP
login()を介さずに動くかを確認する - SOAP
login()を回避できているかを裏取りする - 短命トークン問題を解決し、無人・定期実行に仕上げる
手順①:Salesforce CLI でアクセストークンを取得する
第2弾でJWTログイン済みの状態から、トークンとインスタンスURLを取り出します。自動化でパースしやすいよう --json を付けます。
# 通常出力ではトークンがマスクされるため、一時的に表示を許可する環境変数を立てる
$env:SF_TEMP_SHOW_SECRETS = "true"
sf org display --target-org <ユーザー名> --json
出力JSONの result.accessToken(アクセストークン)と result.instanceUrl(https://xxx.my.salesforce.com)を使います。
⚠️ 2026年5月27日以降、Salesforce CLIの仕様変更で sf org display の通常出力からアクセストークン等が削除される予定です(forcedotcom/cli Issue #3560)。今後は sf org auth 系の新コマンドでの取得に切り替わる見込みで、その時点で本手順は更新が必要になります。暫定的に表示させたい場合が、上記の環境変数 SF_TEMP_SHOW_SECRETS=true です。
手順②:トークンを暗号化して process-conf.xml を差し替える
2-1. トークンを暗号化する
ここが最初のハマりどころです。sfdc.oauth.accesstoken は暗号化プロパティで、DataLoaderは実行時にこの値を「暗号化済み=16進エンコードされたバイト列」とみなして復号しようとします。平文のアクセストークンは16進数として不正なので、復号処理が途中で失敗します。アクセストークンはOrg ID(00D…)で始まり I や ! など16進数にない文字を含むため、For input string: "DI" under radix 16 のようなエラーになります(先頭付近の2文字ペアで最初に引っかかる。表示される文字はトークンによって変わります)。
For input string: "DI" under radix 16
そこで、DataLoader付属の encrypt.bat と暗号キーで、取得したトークンを暗号化しておきます。
# encrypt.bat -e "<平文トークン>" "<暗号キーファイル>"
cd "<DataLoaderフォルダ>\bin"
.\encrypt.bat -e "<CLIで取得したアクセストークン>" "<暗号キーファイル>"
出力される16進文字列(暗号化済みトークン)を、次のxmlに使います。
2-2. process-conf.xml を書き換える
第2弾では、process-conf.xml にOAuthの**コンシューマ鍵(clientid)とシークレット(clientsecret)**を設定していました。まずはその状態(変更前)がこちらです。
<!-- 変更前:OAuth(コンシューマ鍵/シークレット)方式 -->
<entry key="sfdc.endpoint" value="https://login.salesforce.com"/>
<entry key="sfdc.username" value="<ユーザーID>"/>
<entry key="sfdc.oauth.Production.eca.clientid" value="<コンシューマ鍵>"/>
<entry key="sfdc.oauth.Production.eca.clientsecret" value="<コンシューマシークレット>"/>
<entry key="sfdc.entity" value="Account"/>
<entry key="process.operation" value="extract"/>
<entry key="sfdc.extractionSOQL" value="SELECT Id, Name FROM Account"/>
<entry key="dataAccess.type" value="csvWrite"/>
<entry key="dataAccess.name" value="C:\...\output\account_export.csv"/>
これを、アクセストークン方式に差し替えます。ポイントは、単に accesstoken を1行足すだけでは動かず、4つの要素をセットで入れる必要がある点です。
-
sfdc.oauth.accesstoken:手順②-1で暗号化したトークン -
sfdc.password:同じ暗号化トークンを併記(後述。無いとOAuthフローに戻ってしまう) -
salesforce.oauth.instanceURL:インスタンスURL(後述。無いと接続先が壊れる) -
process.encryptionKeyFile:復号に使う暗号キーファイル - あわせて
sfdc.endpointをインスタンスURLに、sfdc.resetUrlOnLoginをfalseに
変更後がこちらです。
<!-- 変更後:アクセストークン方式 -->
<entry key="sfdc.endpoint" value="https://<your-instance>.my.salesforce.com"/>
<entry key="sfdc.username" value="<ユーザーID>"/>
<entry key="sfdc.oauth.accesstoken" value="<暗号化トークン>"/>
<entry key="sfdc.password" value="<暗号化トークン(accesstokenと同じ値)>"/>
<entry key="salesforce.oauth.instanceURL" value="https://<your-instance>.my.salesforce.com"/>
<entry key="sfdc.resetUrlOnLogin" value="false"/>
<entry key="process.encryptionKeyFile" value="<暗号キーファイル>"/>
<entry key="sfdc.entity" value="Account"/>
<entry key="process.operation" value="extract"/>
<entry key="sfdc.extractionSOQL" value="SELECT Id, Name FROM Account"/>
<entry key="dataAccess.type" value="csvWrite"/>
<entry key="dataAccess.name" value="C:\...\output\account_export.csv"/>
手順③:実行して、ハマりどころを潰す
このアクセストークン方式は、一発では通りませんでした。実際にぶつかった順に潰していきます。
ハマり1:sfdc.password が無いとOAuthフローに戻る
accesstoken だけにして password を消すと、DataLoaderはトークンを無視してOAuth(PKCE→ブラウザ→デバイスフロー)をやり直そうとし、device flow is not enabled for the app で失敗しました。これがIssue #385で言われている「password の存在チェック」です。回避策として sfdc.password を併記します。
ここで注意。Issue #385には「password はダミー文字列でよい」という趣旨のコメントもありますが、暗号キーファイルを使う本構成では password も復号対象になるため、dummy のような平文では通りません。実測では、accesstoken と同じ暗号化トークンを password にも入れることで前進しました。
ハマり2:接続先URLが false に化ける
password を併記すると、ログは Using session id from OAuth to establish Salesforce session まで到達しますが、今度は次で止まりました。
Failed to send request to false/services/Soap/u/64.0/
接続先が false になっています。原因は、注入セッションを使うとき DataLoader が接続先ホストとして参照する内部プロパティ salesforce.oauth.instanceURL の既定値が boolean の false で、通常のOAuthフローを通らないと誰もこの値を埋めないためです。sfdc.endpoint を直すだけでは不十分で、salesforce.oauth.instanceURL にインスタンスURLを明示指定して解消しました(あわせて sfdc.resetUrlOnLogin=false)。
結果:エクスポート成功
上記2点を潰したところ、SOAP login() を介さずにエクスポートが完了しました!
手順④:SOAP login() を回避できているかの確認
エクスポートが成功したら、本当にSOAP login() を使っていないかを裏取りします。第1弾では、従来のパスワード方式のバッチはログイン履歴上「SOAP Partner」として記録されていました。
Salesforceの「設定」→「ログイン履歴」を確認し、今回のアクセスが**「SOAP Partner」ではない**形で記録されていれば、SOAP login() を回避できている証拠になります。さらに確実を期すなら、第1弾で使った「SOAP API login() Retirement」の Test Run を有効化したまま、このアクセストークン方式が通るかを確認します。
↑無事SOAP API login()を使わずにログインできているようです。
手順⑤:短命トークン問題を解決する(無人化の仕上げ)
アクセストークンは短命なので、process-conf.xml にトークンを固定で書いてもいずれ失効します。無人・定期実行にするには、実行のたびに「CLIでトークン取得 → 暗号化 → process-conf.xml に反映 → DataLoader実行」を自動化する必要があります。
この一連の流れをPowerShellスクリプト1本にまとめました。ポイントは、トークンだけでなくインスタンスURLも毎回 sf から取得して書き込むことで、環境差やURL化けを構造的に防いでいる点です。
[CmdletBinding()]
param(
[string]$Org = "<ユーザー名>",
[string]$AppDir = "C:\DataLoader",
[string]$ConfigDir = "C:\DataLoader\configs-export",
[string]$KeyFile = "<暗号キーファイル>",
[string]$Bean = "accountExport"
)
$ErrorActionPreference = 'Stop'
$ProcessConf = Join-Path $ConfigDir 'process-conf.xml'
$EncryptBat = Join-Path $AppDir 'bin\encrypt.bat'
$ProcessBat = Join-Path $AppDir 'bin\process.bat'
$LogFile = Join-Path $ConfigDir 'schedule_log.txt'
function Write-Log {
param([string]$Message, [string]$Level = 'INFO')
$line = "{0} [{1}] {2}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Level, $Message
Add-Content -Path $LogFile -Value $line -Encoding UTF8
Write-Host $line
}
$token = $null
try {
# 1. sf からトークンと instance URL を取得(短命なので毎回)
$env:SF_TEMP_SHOW_SECRETS = 'true'
$display = (sf org display --target-org $Org --json | ConvertFrom-Json)
$token = $display.result.accessToken
$instanceUrl = $display.result.instanceUrl
if ([string]::IsNullOrWhiteSpace($token)) {
throw "accessToken が空です。sf の JWT ログイン状態を確認してください。"
}
Write-Log ("トークン取得 OK (len={0})" -f $token.Length)
# 2. トークンを暗号化(出力から最長の16進文字列=暗号化値を抽出)
$encOut = & $EncryptBat '-e' $token $KeyFile 2>&1 | Out-String
$encToken = ([regex]::Matches($encOut, '[0-9a-fA-F]{32,}') |
Sort-Object { $_.Value.Length } | Select-Object -Last 1).Value
if ([string]::IsNullOrWhiteSpace($encToken)) { throw "暗号化トークンの抽出に失敗" }
# 3. process-conf.xml の accesstoken / password / instanceURL を書き換え
$xml = Get-Content -LiteralPath $ProcessConf -Raw -Encoding UTF8
$xml = [regex]::Replace($xml, '(?<=key="sfdc\.oauth\.accesstoken"\s+value=")[^"]*', { param($m) $encToken })
$xml = [regex]::Replace($xml, '(?<=key="sfdc\.password"\s+value=")[^"]*', { param($m) $encToken })
$trimmedUrl = $instanceUrl.TrimEnd('/')
$xml = [regex]::Replace($xml, '(?<=key="salesforce\.oauth\.instanceURL"\s+value=")[^"]*', { param($m) $trimmedUrl })
[System.IO.File]::WriteAllText($ProcessConf, $xml, (New-Object System.Text.UTF8Encoding($false)))
# 4. バッチ実行
& $ProcessBat $ConfigDir $Bean
if ($LASTEXITCODE -eq 0) { Write-Log "=== 実行成功 ===" } else { Write-Log "=== 実行失敗 (exit=$LASTEXITCODE) ===" 'ERROR' }
exit $LASTEXITCODE
}
catch {
Write-Log $_.Exception.Message 'ERROR'
exit 1
}
finally {
if ($token) { $token = $null } # 平文トークンをメモリから消す
Remove-Item Env:\SF_TEMP_SHOW_SECRETS -ErrorAction SilentlyContinue
}
あとはこのスクリプトをWindowsタスクスケジューラに登録すれば、無人・定期のエクスポートが完成します。
schtasks /Create /TN "DataLoader_AccountExport" /SC DAILY /ST 06:00 ^
/TR "powershell -NoProfile -ExecutionPolicy Bypass -File \"C:\DataLoader\configs-export\run_accountExport.ps1\""
おわりに
そもそもの動機は、「変更を迫られている認証だけを新方式にし、実績あるDataLoaderのデータ処理は残すせないか」という問いでした。
結果として、Salesforce CLIのアクセストークンを引き継ぐことで、DataLoaderの資産(大量データ処理の実績、既存のバッチ・マッピング)を活かしたまま無人バッチを実現できました。第2弾(CLIに載せ替える)に対して、第3弾は「DataLoaderを残す」という選択肢を発見できたことになります!
⚠️ セキュリティ注記:アクセストークンは、暗号化していても・短時間とはいえ有効な認証情報です。
process-conf.xmlや暗号キーの取り扱い(ファイルのアクセス権限、スクショへの写り込み、リポジトリへのコミット禁止)に十分注意してください。
