|
# ============================================================================= |
|
# Oracle ストアドプロシージャ実行スクリプト |
|
# ============================================================================= |
|
# 使用方法: |
|
# .\run-stored-procedure.ps1 |
|
# .\run-stored-procedure.ps1 -SqlFile "db/package/PKG_COMMON.sql" |
|
# .\run-stored-procedure.ps1 -SqlFile "db/package/PKG_COMMON.sql" -NoConfirm |
|
# ============================================================================= |
|
|
|
param( |
|
[string]$SqlFile = "", |
|
[switch]$NoConfirm = $false, |
|
[switch]$TestConnection = $false |
|
) |
|
|
|
# ============================================================================= |
|
# 【接続情報設定】※ここを環境に合わせて変更してください |
|
# ============================================================================= |
|
$OracleConfig = @{ |
|
# データベース接続情報 |
|
Host = "10.000.000.000" # Oracleサーバーのホスト名またはIP |
|
Port = "1521" # ポート番号(通常は1521) |
|
SID = "orcl" # SID名またはサービス名 |
|
Username = "USERNAME" # ユーザー名 |
|
Password = "PASSWORD" # パスワード |
|
|
|
# 接続文字列のタイプ(SID または SERVICE_NAME) |
|
ConnectionType = "SERVICE_NAME" # "SID" または "SERVICE_NAME" |
|
} |
|
|
|
# ============================================================================= |
|
# 関数定義 |
|
# ============================================================================= |
|
|
|
function Get-UserDataPath { |
|
param([hashtable]$Config) |
|
|
|
# 接続情報からフォルダ名を生成(特殊文字をサニタイズ) |
|
$hostPart = $Config.Host |
|
$portPart = $Config.Port |
|
$sidPart = $Config.SID -replace '[\\/:*?"<>|]', '-' |
|
|
|
$folderName = "oracle-${hostPart}-${portPart}-${sidPart}" |
|
$userDataDir = "$env:USERPROFILE\.myscript\$folderName" |
|
|
|
return $userDataDir |
|
} |
|
|
|
# ユーザーデータディレクトリの取得 |
|
$UserDataDir = Get-UserDataPath -Config $OracleConfig |
|
|
|
# ロックファイルの設定 |
|
$LockFile = "$UserDataDir\run-stored-procedure.lock" |
|
|
|
# クリーンアップ状態管理 |
|
$script:CleanupExecuted = $false |
|
$script:CancelHandler = $null |
|
$script:ExitHandler = $null |
|
|
|
# 多重起動チェック関数 |
|
function Test-ScriptLock { |
|
param([string]$LockFilePath) |
|
|
|
if (Test-Path $LockFilePath) { |
|
try { |
|
$lockInfo = Get-Content $LockFilePath | ConvertFrom-Json |
|
$lockPid = $lockInfo.ProcessId |
|
$lockTime = [DateTime]::Parse($lockInfo.StartTime) |
|
|
|
# プロセスが実行中か確認 |
|
$process = Get-Process -Id $lockPid -ErrorAction SilentlyContinue |
|
if ($process) { |
|
$timeDiff = (Get-Date) - $lockTime |
|
Write-Host "`n警告: スクリプトは既に実行中です" -ForegroundColor Yellow |
|
Write-Host " プロセスID: $lockPid" -ForegroundColor Yellow |
|
Write-Host " 開始時刻: $($lockTime.ToString('yyyy-MM-dd HH:mm:ss'))" -ForegroundColor Yellow |
|
Write-Host " 経過時間: $($timeDiff.TotalMinutes.ToString('0.0'))分" -ForegroundColor Yellow |
|
Write-Host "" |
|
Write-Host "選択してください:" -ForegroundColor Cyan |
|
Write-Host " [1] 終了する(デフォルト)" -ForegroundColor White |
|
Write-Host " [2] ロックを強制解除して続行する" -ForegroundColor White |
|
|
|
$choice = Read-Host "選択 (1-2)" |
|
|
|
switch ($choice) { |
|
"2" { |
|
Write-Host "ロックを強制解除します..." -ForegroundColor Yellow |
|
return "locked_force" |
|
} |
|
default { |
|
Write-Host "終了します。" -ForegroundColor Yellow |
|
return "locked_exit" |
|
} |
|
} |
|
} |
|
else { |
|
# プロセスが存在しない場合、古いロックファイルを削除 |
|
Remove-Item $LockFilePath -Force |
|
return "unlocked" |
|
} |
|
} |
|
catch { |
|
# ロックファイルが壊れている場合は削除 |
|
Remove-Item $LockFilePath -Force -ErrorAction SilentlyContinue |
|
return "unlocked" |
|
} |
|
} |
|
return "unlocked" |
|
} |
|
|
|
# ロックファイル作成関数 |
|
function New-ScriptLock { |
|
param([string]$LockFilePath) |
|
|
|
$lockInfo = @{ |
|
ProcessId = $PID |
|
StartTime = (Get-Date).ToString("yyyy-MM-ddTHH:mm:ss") |
|
ScriptPath = $PSCommandPath |
|
} |
|
|
|
$lockInfo | ConvertTo-Json | Out-File -FilePath $LockFilePath -Encoding UTF8 -Force |
|
} |
|
|
|
# ロックファイル削除関数 |
|
function Remove-ScriptLock { |
|
param([string]$LockFilePath) |
|
|
|
if (Test-Path $LockFilePath) { |
|
Remove-Item $LockFilePath -Force -ErrorAction SilentlyContinue |
|
} |
|
} |
|
|
|
# 包括的なクリーンアップ関数 |
|
function Invoke-ScriptCleanup { |
|
param([string]$Reason = "不明") |
|
|
|
# 重複実行防止 |
|
if ($script:CleanupExecuted) { |
|
return |
|
} |
|
$script:CleanupExecuted = $true |
|
|
|
Write-Host "`nクリーンアップ実行中... (理由: $Reason)" -ForegroundColor Yellow |
|
|
|
# ログバッファをフラッシュ |
|
try { |
|
Flush-LogBuffer |
|
} |
|
catch { |
|
# エラーは無視 |
|
} |
|
|
|
# ロックファイル削除 |
|
try { |
|
Remove-ScriptLock $LockFile |
|
Write-Host "ロックファイルを削除しました" -ForegroundColor Green |
|
} |
|
catch { |
|
Write-Host "ロックファイル削除に失敗: $($_.Exception.Message)" -ForegroundColor Red |
|
} |
|
|
|
# イベントハンドラーの解除 |
|
try { |
|
if ($script:CancelHandler) { |
|
[Console]::CancelKeyPress -= $script:CancelHandler |
|
} |
|
if ($script:ExitHandler) { |
|
Unregister-Event -SourceIdentifier "ScriptExit" -ErrorAction SilentlyContinue |
|
} |
|
} |
|
catch { |
|
# エラーは無視 |
|
} |
|
} |
|
|
|
# Ctrl+C および終了イベントハンドラーの登録 |
|
function Register-ExitHandlers { |
|
try { |
|
# Ctrl+C ハンドラー |
|
$script:CancelHandler = { |
|
param($sender, $e) |
|
Invoke-ScriptCleanup -Reason "Ctrl+C" |
|
$e.Cancel = $false # プロセス終了を許可 |
|
} |
|
[Console]::CancelKeyPress += $script:CancelHandler |
|
|
|
# PowerShell終了ハンドラー |
|
$script:ExitHandler = Register-EngineEvent -SourceIdentifier "PowerShell.Exiting" -Action { |
|
Invoke-ScriptCleanup -Reason "PowerShell終了" |
|
} |
|
|
|
Write-Log "終了イベントハンドラーを登録しました" |
|
} |
|
catch { |
|
Write-Log "終了イベントハンドラーの登録に失敗しました: $($_.Exception.Message)" "WARN" |
|
} |
|
} |
|
|
|
# ログ設定 |
|
$LogDir = "$UserDataDir\logs" |
|
$ProcessId = [System.Diagnostics.Process]::GetCurrentProcess().Id |
|
$Timestamp = Get-Date -Format 'yyyyMMdd_HHmmss_fff' |
|
$LogFile = "$LogDir\run-stored-procedure_${Timestamp}_${ProcessId}.log" |
|
|
|
# 履歴設定 |
|
$HistoryFile = "$UserDataDir\sql-history.json" |
|
|
|
# ログバッファ設定 |
|
$script:LogBuffer = New-Object System.Collections.ArrayList |
|
$script:LogBufferSize = 50 # バッファサイズ(行数) |
|
$script:LastFlushTime = Get-Date |
|
|
|
function Write-Log { |
|
param( |
|
[string]$Message, |
|
[string]$Level = "INFO", |
|
[switch]$NoBuffer = $false |
|
) |
|
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" |
|
$logMessage = "[$timestamp] [$Level] $Message" |
|
Write-Host $logMessage |
|
|
|
# ログメッセージをバッファに追加 |
|
[void]$script:LogBuffer.Add($logMessage) |
|
|
|
# バッファフラッシュ条件をチェック |
|
$shouldFlush = $false |
|
|
|
# 条件1: バッファサイズを超えた |
|
if ($script:LogBuffer.Count -ge $script:LogBufferSize) { |
|
$shouldFlush = $true |
|
} |
|
|
|
# 条件2: 重要なログ(ERROR, WARN)または明示的なフラッシュ要求 |
|
if ($Level -eq "ERROR" -or $Level -eq "WARN" -or $NoBuffer) { |
|
$shouldFlush = $true |
|
} |
|
|
|
# 条件3: 前回のフラッシュから5秒以上経過 |
|
if (((Get-Date) - $script:LastFlushTime).TotalSeconds -ge 5) { |
|
$shouldFlush = $true |
|
} |
|
|
|
# バッファをフラッシュ |
|
if ($shouldFlush -and $script:LogBuffer.Count -gt 0) { |
|
Flush-LogBuffer |
|
} |
|
} |
|
|
|
function Flush-LogBuffer { |
|
if ($script:LogBuffer.Count -eq 0) { |
|
return |
|
} |
|
|
|
# ログファイルへの書き込み(排他制御付き) |
|
if (Test-Path $LogDir) { |
|
$retryCount = 0 |
|
$maxRetries = 3 |
|
do { |
|
try { |
|
# バッファの内容を一度に書き込む |
|
$script:LogBuffer | Out-File -FilePath $LogFile -Append -Encoding UTF8 |
|
$script:LogBuffer.Clear() |
|
$script:LastFlushTime = Get-Date |
|
break |
|
} |
|
catch { |
|
$retryCount++ |
|
if ($retryCount -ge $maxRetries) { |
|
Write-Host "警告: ログファイルへの書き込みに失敗しました: $($_.Exception.Message)" -ForegroundColor Yellow |
|
$script:LogBuffer.Clear() # 失敗してもバッファをクリア |
|
break |
|
} |
|
Start-Sleep -Milliseconds (100 * $retryCount) |
|
} |
|
} while ($retryCount -lt $maxRetries) |
|
} |
|
} |
|
|
|
function Test-SqlPlus { |
|
try { |
|
$sqlplusVersion = & sqlplus -v 2>&1 |
|
if ($LASTEXITCODE -eq 0) { |
|
Write-Log "SQL*Plus が見つかりました" |
|
return $true |
|
} |
|
} |
|
catch { |
|
Write-Log "SQL*Plus が見つかりません。Oracleクライアントがインストールされていることを確認してください。" "ERROR" |
|
return $false |
|
} |
|
return $false |
|
} |
|
|
|
function Get-ConnectionString { |
|
if ($OracleConfig.ConnectionType -eq "SERVICE_NAME") { |
|
return "$($OracleConfig.Username)/$($OracleConfig.Password)@$($OracleConfig.Host):$($OracleConfig.Port)/$($OracleConfig.SID)" |
|
} |
|
else { |
|
return "$($OracleConfig.Username)/$($OracleConfig.Password)@$($OracleConfig.Host):$($OracleConfig.Port):$($OracleConfig.SID)" |
|
} |
|
} |
|
|
|
function Test-OracleConnection { |
|
Write-Log "データベース接続をテストしています..." |
|
$connectionString = Get-ConnectionString |
|
|
|
$testSql = @" |
|
whenever sqlerror exit sql.sqlcode |
|
set pagesize 0 |
|
set feedback off |
|
set heading off |
|
select 'CONNECTION_OK' from dual; |
|
exit; |
|
"@ |
|
|
|
$tempFile = [System.IO.Path]::GetTempFileName() |
|
$testSql | Out-File -FilePath $tempFile -Encoding ASCII |
|
|
|
try { |
|
$result = & sqlplus -s $connectionString "@$tempFile" 2>&1 |
|
if ($result -like "*CONNECTION_OK*" -and $LASTEXITCODE -eq 0) { |
|
Write-Log "データベース接続テスト成功" "SUCCESS" |
|
return $true |
|
} |
|
else { |
|
Write-Log "データベース接続テスト失敗: $result" "ERROR" |
|
return $false |
|
} |
|
} |
|
catch { |
|
Write-Log "データベース接続テスト中にエラーが発生しました: $($_.Exception.Message)" "ERROR" |
|
return $false |
|
} |
|
finally { |
|
if (Test-Path $tempFile) { |
|
Remove-Item $tempFile -Force |
|
} |
|
} |
|
} |
|
|
|
function Get-SqlHistory { |
|
if (!(Test-Path $HistoryFile)) { |
|
return @() |
|
} |
|
|
|
try { |
|
$fileContent = Get-Content $HistoryFile -Raw |
|
|
|
if ([string]::IsNullOrWhiteSpace($fileContent)) { |
|
return @() |
|
} |
|
|
|
$historyData = $fileContent | ConvertFrom-Json |
|
$rawHistory = $historyData.recentFiles |
|
|
|
# 履歴を正規化(絶対パスに変換し、重複を削除) |
|
$normalizedHistory = @() |
|
$seenPaths = @{} |
|
|
|
foreach ($item in $rawHistory) { |
|
try { |
|
$absolutePath = [System.IO.Path]::GetFullPath($item.path) |
|
|
|
# 重複チェック(絶対パスで) |
|
if (-not $seenPaths.ContainsKey($absolutePath)) { |
|
$seenPaths[$absolutePath] = $true |
|
$normalizedHistory += @{ |
|
path = $absolutePath |
|
lastUsed = $item.lastUsed |
|
} |
|
} |
|
} |
|
catch { |
|
# パス変換に失敗した場合は元のパスをそのまま使用 |
|
if (-not $seenPaths.ContainsKey($item.path)) { |
|
$seenPaths[$item.path] = $true |
|
$normalizedHistory += $item |
|
} |
|
} |
|
} |
|
|
|
# 正規化した履歴が元の履歴と異なる場合は保存 |
|
if ($normalizedHistory.Count -ne $rawHistory.Count -or |
|
($normalizedHistory | ConvertTo-Json -Depth 2) -ne ($rawHistory | ConvertTo-Json -Depth 2)) { |
|
|
|
$normalizedHistoryData = @{ recentFiles = $normalizedHistory } |
|
try { |
|
$normalizedHistoryData | ConvertTo-Json -Depth 2 | Out-File -FilePath $HistoryFile -Encoding UTF8 -Force |
|
Write-Log "履歴を正規化しました(重複削除・絶対パス変換)" |
|
} |
|
catch { |
|
Write-Log "履歴の正規化保存に失敗しました: $($_.Exception.Message)" "WARN" |
|
} |
|
} |
|
|
|
return $normalizedHistory |
|
} |
|
catch { |
|
Write-Log "履歴ファイルの読み込みに失敗しました: $($_.Exception.Message)" "WARN" |
|
|
|
try { |
|
Remove-Item $HistoryFile -Force |
|
} |
|
catch { |
|
# ファイル削除失敗は無視 |
|
} |
|
|
|
return @() |
|
} |
|
} |
|
|
|
function Add-SqlHistory { |
|
param([string]$FilePath) |
|
|
|
# ユーザーデータディレクトリの作成 |
|
if (!(Test-Path $UserDataDir)) { |
|
New-Item -ItemType Directory -Path $UserDataDir -Force | Out-Null |
|
} |
|
|
|
# 現在の履歴を取得 |
|
$history = Get-SqlHistory |
|
|
|
# パスを絶対パスに変換 |
|
try { |
|
$absolutePath = [System.IO.Path]::GetFullPath($FilePath) |
|
} |
|
catch { |
|
Write-Log "パスの絶対パス変換に失敗しました: $FilePath - $($_.Exception.Message)" "WARN" |
|
$absolutePath = $FilePath |
|
} |
|
|
|
# 新しいエントリを作成 |
|
$newEntry = @{ |
|
path = $absolutePath |
|
lastUsed = (Get-Date).ToString("yyyy-MM-ddTHH:mm:ss") |
|
} |
|
|
|
# 既存のエントリを削除(重複回避)- 絶対パスで比較 |
|
$history = $history | Where-Object { |
|
try { |
|
$existingAbsolutePath = [System.IO.Path]::GetFullPath($_.path) |
|
$existingAbsolutePath -ne $absolutePath |
|
} |
|
catch { |
|
# パス変換に失敗した場合は文字列比較 |
|
$_.path -ne $absolutePath |
|
} |
|
} |
|
|
|
# 新しいエントリを先頭に追加 |
|
$history = @($newEntry) + $history |
|
|
|
# 最大10件まで保持 |
|
if ($history.Count -gt 10) { |
|
$history = $history[0..9] |
|
} |
|
|
|
# 履歴を保存 |
|
$historyData = @{ recentFiles = $history } |
|
try { |
|
$historyData | ConvertTo-Json -Depth 2 | Out-File -FilePath $HistoryFile -Encoding UTF8 -Force |
|
Write-Log "履歴を更新しました: $absolutePath" |
|
} |
|
catch { |
|
Write-Log "履歴の保存に失敗しました: $($_.Exception.Message)" "WARN" |
|
} |
|
} |
|
|
|
function Select-SqlFile { |
|
Write-Host "`nSQLファイルの選択:" -ForegroundColor Yellow |
|
Write-Host "===========================================" -ForegroundColor Yellow |
|
|
|
# 履歴を取得 |
|
$history = Get-SqlHistory |
|
|
|
# 履歴が配列でない場合の対処 |
|
if ($history -and $history -isnot [array]) { |
|
$history = @($history) |
|
} |
|
|
|
if ($history -and $history.Count -gt 0) { |
|
Write-Host "最近使用したSQLファイル:" -ForegroundColor Green |
|
for ($i = 0; $i -lt $history.Count; $i++) { |
|
$lastUsed = [DateTime]::Parse($history[$i].lastUsed).ToString("MM-dd HH:mm") |
|
Write-Host " [$($i + 1)] $($history[$i].path) ($lastUsed)" -ForegroundColor White |
|
} |
|
Write-Host "" |
|
} |
|
else { |
|
Write-Host "履歴がありません。SQLファイルのパスを直接入力してください。" -ForegroundColor Cyan |
|
Write-Host "" |
|
} |
|
|
|
# ユーザー入力 |
|
do { |
|
if ($history -and $history.Count -gt 0) { |
|
$maxCount = [Math]::Min($history.Count, 10) |
|
$input = Read-Host "番号(1-$maxCount)またはファイルパスを入力" |
|
} |
|
else { |
|
$input = Read-Host "SQLファイルのパスを入力" |
|
} |
|
|
|
if ([string]::IsNullOrWhiteSpace($input)) { |
|
Write-Host "入力が空です。再度入力してください。" -ForegroundColor Red |
|
continue |
|
} |
|
|
|
# 数字かどうか判定 |
|
if ($input -match '^\d+$' -and $history -and $history.Count -gt 0) { |
|
$index = [int]$input - 1 |
|
$maxCount = [Math]::Min($history.Count, 10) |
|
if ($index -ge 0 -and $index -lt $maxCount) { |
|
$selectedPath = $history[$index].path |
|
# 番号選択時も履歴を更新(最新順維持のため) |
|
Add-SqlHistory -FilePath $selectedPath |
|
return $selectedPath |
|
} |
|
else { |
|
Write-Host "無効な番号です。1-$maxCount の範囲で入力してください。" -ForegroundColor Red |
|
continue |
|
} |
|
} |
|
else { |
|
# パスとして扱う |
|
return $input.Trim() |
|
} |
|
} while ($true) |
|
} |
|
|
|
function Get-OracleErrors { |
|
param( |
|
[string]$ConnectionString, |
|
[string]$SqlFilePath |
|
) |
|
|
|
Write-Log "実行したSQLファイルのエラー詳細を取得しています..." |
|
|
|
# SQLファイルからオブジェクト名を抽出 |
|
$objectNames = @() |
|
if (Test-Path $SqlFilePath) { |
|
try { |
|
$sqlContent = Get-Content $SqlFilePath -Raw |
|
|
|
# パッケージ、プロシージャ、ファンクション名を抽出 |
|
$patterns = @( |
|
'CREATE\s+OR\s+REPLACE\s+PACKAGE\s+BODY\s+([A-Za-z_][A-Za-z0-9_]*)', |
|
'CREATE\s+OR\s+REPLACE\s+PACKAGE\s+([A-Za-z_][A-Za-z0-9_]*)', |
|
'CREATE\s+OR\s+REPLACE\s+PROCEDURE\s+([A-Za-z_][A-Za-z0-9_]*)', |
|
'CREATE\s+OR\s+REPLACE\s+FUNCTION\s+([A-Za-z_][A-Za-z0-9_]*)' |
|
) |
|
|
|
foreach ($pattern in $patterns) { |
|
$matches = [regex]::Matches($sqlContent, $pattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) |
|
foreach ($match in $matches) { |
|
$objectName = $match.Groups[1].Value.ToUpper() |
|
if ($objectNames -notcontains $objectName) { |
|
$objectNames += $objectName |
|
} |
|
} |
|
} |
|
} |
|
catch { |
|
Write-Log "SQLファイルの解析に失敗: $($_.Exception.Message)" "WARN" |
|
} |
|
} |
|
|
|
if ($objectNames.Count -eq 0) { |
|
Write-Log "実行したSQLファイルからオブジェクト名を特定できませんでした" "WARN" |
|
return |
|
} |
|
|
|
# 特定されたオブジェクトのエラーのみを取得 |
|
$objectNamesList = "'" + ($objectNames -join "','") + "'" |
|
|
|
$errorSql = @" |
|
whenever sqlerror continue |
|
set pagesize 0 |
|
set feedback off |
|
set heading off |
|
set linesize 1000 |
|
select 'COMPILATION_ERRORS_FOR_EXECUTED_OBJECTS:' from dual; |
|
select name||' ('||type||') Line:'||line||' Position:'||position||' - '||text as error_detail |
|
from user_errors |
|
where name in ($objectNamesList) |
|
order by name, sequence; |
|
exit; |
|
"@ |
|
|
|
$tempFile = [System.IO.Path]::GetTempFileName() |
|
$errorSql | Out-File -FilePath $tempFile -Encoding ASCII |
|
|
|
try { |
|
$errorResult = & sqlplus -s $ConnectionString "@$tempFile" 2>&1 |
|
if ($errorResult) { |
|
$hasErrors = $false |
|
Write-Log "=== 実行したオブジェクトのコンパイルエラー ===" "ERROR" |
|
|
|
$errorResult | ForEach-Object { |
|
$line = $_.ToString().Trim() |
|
if ($line -ne "" -and $line -ne "COMPILATION_ERRORS_FOR_EXECUTED_OBJECTS:") { |
|
$hasErrors = $true |
|
Write-Log $line "ERROR" |
|
} |
|
} |
|
|
|
if (-not $hasErrors) { |
|
Write-Log "実行したオブジェクト ($($objectNames -join ', ')) にコンパイルエラーはありません" |
|
} |
|
|
|
Write-Log "=============================================" "ERROR" |
|
} |
|
} |
|
catch { |
|
Write-Log "エラー詳細取得中に例外が発生しました: $($_.Exception.Message)" "ERROR" |
|
} |
|
finally { |
|
if (Test-Path $tempFile) { |
|
Remove-Item $tempFile -Force |
|
} |
|
} |
|
} |
|
|
|
function Execute-SqlFile { |
|
param([string]$FilePath) |
|
|
|
Write-Log "SQLファイルを実行しています: $FilePath" |
|
$connectionString = Get-ConnectionString |
|
|
|
# オプション: SQL実行ログを別ディレクトリに保存する場合 |
|
# $sqlLogDir = "$LogDir\sql-execution" |
|
# if (!(Test-Path $sqlLogDir)) { |
|
# New-Item -ItemType Directory -Path $sqlLogDir -Force | Out-Null |
|
# } |
|
# $sqlLogFile = "$sqlLogDir\sql-execution_${Timestamp}_${ProcessId}.log" |
|
|
|
# sqlplusのスクリプトを作成(spoolを削除してメインログに統合) |
|
$sqlScript = @" |
|
whenever sqlerror continue |
|
set serveroutput on size unlimited |
|
set feedback on |
|
set echo on |
|
set linesize 1000 |
|
set pagesize 0 |
|
@$FilePath |
|
show errors; |
|
exit; |
|
"@ |
|
|
|
# オプション: SQL実行ログを別ファイルに出力する場合は以下を有効化 |
|
# $sqlScript = @" |
|
# whenever sqlerror continue |
|
# set serveroutput on size unlimited |
|
# set feedback on |
|
# set echo on |
|
# set linesize 1000 |
|
# set pagesize 0 |
|
# spool $sqlLogFile |
|
# @$FilePath |
|
# spool off |
|
# show errors; |
|
# "@ |
|
|
|
$tempFile = [System.IO.Path]::GetTempFileName() |
|
$sqlScript | Out-File -FilePath $tempFile -Encoding ASCII |
|
|
|
try { |
|
Write-Log "sqlplusを実行中..." |
|
$result = & sqlplus $connectionString "@$tempFile" 2>&1 |
|
|
|
# 実行結果をログに記録 |
|
Write-Log "=== SQL実行結果 ===" |
|
$result | ForEach-Object { |
|
if ($_ -and $_.ToString().Trim() -ne "") { |
|
Write-Log $_ |
|
} |
|
} |
|
Write-Log "==================" |
|
|
|
# エラーが発生した場合の詳細取得 |
|
if ($LASTEXITCODE -ne 0 -or ($result -join "`n") -match "(ORA-|PLS-|SP2-)") { |
|
Write-Log "エラーが検出されました。詳細を取得中..." "ERROR" |
|
Get-OracleErrors -ConnectionString $connectionString -SqlFilePath $FilePath |
|
|
|
Write-Log "SQLファイルの実行中にエラーが発生しました (終了コード: $LASTEXITCODE)" "ERROR" |
|
return $false |
|
} |
|
else { |
|
Write-Log "SQLファイルの実行が完了しました" "SUCCESS" |
|
return $true |
|
} |
|
} |
|
catch { |
|
Write-Log "SQLファイル実行中に例外が発生しました: $($_.Exception.Message)" "ERROR" |
|
return $false |
|
} |
|
finally { |
|
if (Test-Path $tempFile) { |
|
Remove-Item $tempFile -Force |
|
} |
|
} |
|
} |
|
|
|
# ============================================================================= |
|
# メイン処理 |
|
# ============================================================================= |
|
|
|
Write-Host "==============================================================================" -ForegroundColor Cyan |
|
Write-Host "Oracle ストアドプロシージャ実行スクリプト" -ForegroundColor Cyan |
|
Write-Host "==============================================================================" -ForegroundColor Cyan |
|
|
|
# ユーザーデータディレクトリとログディレクトリ作成 |
|
if (!(Test-Path $UserDataDir)) { |
|
New-Item -ItemType Directory -Path $UserDataDir -Force | Out-Null |
|
} |
|
if (!(Test-Path $LogDir)) { |
|
New-Item -ItemType Directory -Path $LogDir -Force | Out-Null |
|
} |
|
|
|
Write-Log "スクリプト開始" |
|
|
|
# スクリプト終了時の処理を確実に行うためのtrap設定 |
|
trap { |
|
Write-Log "スクリプトが異常終了しました: $($_.Exception.Message)" "ERROR" |
|
Invoke-ScriptCleanup -Reason "異常終了" |
|
exit 1 |
|
} |
|
|
|
# SQLファイルの決定 |
|
if ([string]::IsNullOrEmpty($SqlFile)) { |
|
Write-Log "SQLファイルが指定されていません。ファイルを選択してください。" |
|
$SqlFile = Select-SqlFile |
|
if ([string]::IsNullOrEmpty($SqlFile)) { |
|
Write-Log "SQLファイルが選択されませんでした。" "ERROR" |
|
exit 1 |
|
} |
|
} |
|
|
|
# SQLファイルの存在確認 |
|
if (!(Test-Path $SqlFile)) { |
|
Write-Log "SQLファイルが見つかりません: $SqlFile" "ERROR" |
|
exit 1 |
|
} |
|
|
|
Write-Log "使用するSQLファイル: $SqlFile" |
|
|
|
# 履歴に追加(SQL実行結果に関係なくパス確定時点で保存) |
|
Add-SqlHistory -FilePath $SqlFile |
|
|
|
# 多重起動チェック(SQLファイル確定後に実行) |
|
$lockResult = Test-ScriptLock $LockFile |
|
switch ($lockResult) { |
|
"unlocked" { |
|
# ロックファイル作成 |
|
try { |
|
New-ScriptLock $LockFile |
|
Write-Log "ロックファイルを作成しました: $LockFile" |
|
# 終了イベントハンドラーを登録 |
|
Register-ExitHandlers |
|
} |
|
catch { |
|
Write-Log "ロックファイルの作成に失敗しました: $($_.Exception.Message)" "ERROR" |
|
exit 1 |
|
} |
|
} |
|
"locked_exit" { |
|
Write-Log "ユーザーによりキャンセルされました" "WARN" |
|
exit 0 |
|
} |
|
"locked_force" { |
|
Write-Log "ロックを強制解除してスクリプトを続行します" "WARN" |
|
Remove-ScriptLock $LockFile |
|
try { |
|
New-ScriptLock $LockFile |
|
Write-Log "新しいロックファイルを作成しました: $LockFile" |
|
# 終了イベントハンドラーを登録 |
|
Register-ExitHandlers |
|
} |
|
catch { |
|
Write-Log "ロックファイルの作成に失敗しました: $($_.Exception.Message)" "ERROR" |
|
exit 1 |
|
} |
|
} |
|
|
|
} |
|
|
|
# 設定情報の表示 |
|
Write-Host "`n接続情報:" -ForegroundColor Yellow |
|
Write-Host " ホスト: $($OracleConfig.Host):$($OracleConfig.Port)" -ForegroundColor White |
|
Write-Host " $($OracleConfig.ConnectionType): $($OracleConfig.SID)" -ForegroundColor White |
|
Write-Host " ユーザー: $($OracleConfig.Username)" -ForegroundColor White |
|
Write-Host " SQLファイル: $SqlFile" -ForegroundColor White |
|
|
|
# SQL*Plusの確認 |
|
if (!(Test-SqlPlus)) { |
|
exit 1 |
|
} |
|
|
|
# 接続テスト(オプション) |
|
if ($TestConnection) { |
|
if (!(Test-OracleConnection)) { |
|
Write-Log "接続テストに失敗しました。設定を確認してください。" "ERROR" |
|
exit 1 |
|
} |
|
} |
|
|
|
# 実行確認 |
|
if (!$NoConfirm) { |
|
Write-Host "`n実行確認:" -ForegroundColor Yellow |
|
$confirm = Read-Host "上記の設定でSQLファイルを実行しますか? (y/N)" |
|
if ($confirm -ne "y" -and $confirm -ne "Y") { |
|
Write-Log "ユーザーによりキャンセルされました" |
|
Invoke-ScriptCleanup -Reason "ユーザーキャンセル" |
|
exit 0 |
|
} |
|
} |
|
|
|
# SQLファイル実行 |
|
Write-Host "`nSQLファイルを実行中..." -ForegroundColor Green |
|
$success = Execute-SqlFile -FilePath $SqlFile |
|
|
|
if ($success) { |
|
Write-Host "`n✓ SQLファイルの実行が完了しました!" -ForegroundColor Green |
|
Write-Log "スクリプト正常終了" |
|
|
|
# 正常終了時のクリーンアップ |
|
Invoke-ScriptCleanup -Reason "正常終了" |
|
} |
|
else { |
|
Write-Host "`n✗ SQLファイルの実行中にエラーが発生しました" -ForegroundColor Red |
|
Write-Log "スクリプト異常終了" "ERROR" |
|
|
|
# 異常終了時のクリーンアップ |
|
Invoke-ScriptCleanup -Reason "SQL実行エラー" |
|
exit 1 |
|
} |
|
|
|
Write-Host "`nログファイル: $LogFile" -ForegroundColor Cyan |
|
Write-Host "==============================================================================" -ForegroundColor Cyan |