misc.log

日常茶飯事とお仕事と

バッチファイルからOracle DBのexpdp.exeを実行する

バッチファイルから、Oracle DBのデータエクスポートコマンド「expdp.exe」を、非管理者ユーザーが管理者権限で動かせるようにする必要があったので、少し模索してみた際のメモを残しておきます。

処理の概要

単にコマンドを発行するだけであれば簡単だったのですが、要件として下記の処理を夜間処理でスケジュール実行する流れの中でexpdpを実行する必要がありました。

  1. 前回のダンプファイルやログファイルを退避する。
  2. expdp.exeを実行してスキーマの内容をダンプする。
  3. ダンプファイルが存在することを確認する。

問題は2番目の部分。ここを管理者権限で実行する必要があったのですが、ざっとネットで調べたところ方法は2つ。

  • RunAsコマンドでAdministrator権限を使ってコマンドを実行する。
  • PowerShellの「-verb runas」でコマンドを実行する。

実際に試してはいないのですが、ブログなどでの情報では、どうやらPowerShellを使った場合ユーザーアカウント制御の問合せメッセージが表示されるとのこと。サーバーで動く夜間処理でそれは困るので、まずは消去法でRunAsを使う方式を調べることに。

RunAsの問題1:コマンドを起動したら自分は終わってしまう

RunAsはWindowsに備わっているコマンド(runas.exe)なので、実行方法も何通りか考えられます。パッと出てきた方法それぞれについて問題点等を記載しておきます。

  • バッチファイルから直接RunAsとして動かす …… 下記のように、RunAs /user:Administrator コマンド名、でコマンドを実行できますが、RunAsは指定されたコマンドを実行すると、その終了を監視せずに「終わったよ」とバッチ処理に戻ってしまうので、上記の要件に書いた「ダンプファイルの存在確認」がダンプ中に実行されてしまい、エラーになります。

runas /user:Administrator "expdp (引数)"

  • バッチファイルからstart /wait コマンドでRunAsを指定する …… バッチファイルで使えるコマンド「start」は、引数 /wait でその結果を待つことができます。しかしstartが呼ぶのはRunAsで、RunAsは自分が動かすプログラムにGoという指示を出した時点で、startコマンドに「終わったよ」と返事してしまうため、これも使えませんでした。

start /wait runas /user:Administrator "expdp (引数)"

  • VBScriptのRunコマンドでRunAsを動かす …… RunAsの部分をVBScript(拡張子.vbs)のスクリプトで記述し、WScript.shwllのRunメソッドで「終わるまで待つ」指定でRunAsとexpdpを呼び出し。これをバッチファイルから呼べば……とも思いましたが、やはりRunAsがexpdpを呼んだ時点で終了となってしまいダメでした。

set wsh = WScript.CreateObject("WScript.Shell")
wsh.Run "runas /user:Administrator ""expdp (引数)"" ", 10, True

最終的に、下記teratailのQAにあった方法でrunasが呼び出すexpdpのダンプ処理完了を検知して、バッチを先に進めるという処置を組むことが出来ました。
teratail.com

方法は下記の通りです。

  • 処理を主処理と副処理に分割し、副処理バッチの中でexpdp.exeを実行する。
  • 主処理側では、一応 start /wait でRunAsを実行する。
  • RunAsの実行後、waitfor xxx(xxxはあらかじめ決めた文字列)で副処理終了シグナルを待つようにする。
  • 副処理の中ではexpdpによるダンプ処理を記述し、その後に waitfor /si xxx で待っている主処理に終了シグナルを送るようにする。

これで、主処理のRunAsはすぐに終了するもののwaitforでバッチ処理は待ち状態に。一方、副処理は時間が掛かるダンプ処理をじっくりと実行した後、終わったら主処理に waitfor /si で終了の通知を送って、主処理が先に進み始めます。

実際の処理記述は下記のようになります。まずは主処理バッチ。

start /wait runas /user:Administrator 副処理バッチ
waitfor xxx
(ファイル存在確認処理)

次に副処理のバッチ。

expdp (引数)
waitfor /si xxx

RunAsの問題2:パスワードを聞いてくる

これが致命的でした。RunAsではユーザー名は指定できますが、パスワードは指定できません。流石に平文でスクリプトに埋め込まれてはたまらないからでしょう。で、どうなるかというと、パスワードを入力してください、という問合せメッセージがコマンドプロンプト上に表示されます。

これについては対応方法は大きく2通りでした。

  • runasのオプション /savecred を使い、一度パスワードをWindowsの資格情報マネージャーに記録させてしまう(次回からはパスワードを聞かれることは無くなる)。
  • WScript.ShellのSendkeyを使い、パスワードとEnterを強制入力する。

後者については、さらに2通りの方法がありました。

  • RunAsの実行自体をVBScriptに埋め込んでしまい、VBScript上で自分のプロンプトにSendKeyでパスワードを打ち込む。
  • SendKeyを使うVBSCriptを別途作り、バッチでRunAsを実行する前にVBScriptを先行実行。AppActivateでrunas.exeのプロセスにSendKeyでパスワードを打ち込む。

前者については、前述のとおり、RunAsのあとにバッチ処理が続く部分の対応ができなくなるため、残念ながら却下。後者については下記のサイトに詳しく実例が書いてありましたので、そちらを参考にさせてもらって動作を試してみました。

hrkworks.com

結論は……「ほぼ上手くいった」。です。ほぼ、である理由は下記。

  • パスワード入力に失敗することがあった …… Sleepの長さなどを調整しないといけないのかもしれませんが、明確な理由に行き着くことができませんでいた。再度やりなおすと上手くいくことが多かったのですが、これだとデータベースの大事なダンプデータ取得には信頼性が足りません(せめて理由は把握しておきたい)。
  • 上記の対応で、RunAs実行後に「%errorlevel%がゼロでなければエラーログを出して終了」という処理を追加したところ、ログイン失敗後、自動的になんどもリトライするようになってしまいました(笑)。

後者、どういうこと??エラー処理はexitで処理をぶった切っているので、リトライなんて事が起きる可能性は無いと思われるのですが、実際、前述のログインできないエラーが起きたあと5回繰り返してログインできて、さらにきちんとダンプも取得出来てしまったことがありました。実際にシステムの背後で動く処理において、「なんだか判らないけれど上手くいくから大丈夫」なんてのは許されません。

魅力的な手でしたが、ちょっと不安定さが目立ったのと、ここを突き詰めている時間が無いので、資格情報マネージャーに登録する方法と併用して、初回さえ乗り切ればあとは保存したパスワードで……という路線を考えていたところ、取引先から連絡が。

処理実行ユーザーにOracleの権限を付けてみる

取引先から、過去にUNDO領域の操作でWindowsユーザーの権限が足りず、権限を追加したことがあった、との連絡がありました。結論から言うと、この権限を追加しても駄目でした(2021/10/28時点)。

ちなみにその権限グループは「ora_dba」。下記は最新版のOracle DB 19ですが、「データベース管理者用の標準Oracle Databaseグループ」として説明が書かれているので良かったら参考にしてください。

docs.oracle.com

参考情報

この件について調べる過程で参考にさせてもらったサイトのURLを記載しておきます。