Files
ROMFASTSQL/proxmox/lxc108-oracle/roa-windows-setup/scripts/lib/oracle-functions.ps1
Marius f50bfcf8d8 Fix DMPDIR handling and datafile auto-detection for ROA Windows setup
- New-OracleDirectory: Improved verification with direct SQL check, preserves
  existing DMPDIR path instead of blindly recreating
- Get-DatafilePath: Better fallback logic using ORACLE_HOME to derive path,
  no longer hardcodes C:\app\oracle
- grants-public.sql: Fixed DMPDIR creation - now preserves existing path
  instead of overriding with wrong D:\Oracle\admin\ORCL\dpdump
- config.example.ps1: Added DATAFILE_DIR parameter with documentation

These fixes ensure scripts work without manual intervention on fresh Oracle XE
installations where default DMPDIR points to non-existent paths.

Tested on VM 302 - full installation (01-08) now completes successfully.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 02:38:54 +02:00

1138 lines
31 KiB
PowerShell

#Requires -Version 5.1
<#
.SYNOPSIS
Oracle connectivity and operations functions for ROA setup scripts.
.DESCRIPTION
Provides functions for Oracle database connectivity, SQL*Plus execution,
version detection, and Data Pump operations.
.NOTES
File Name : oracle-functions.ps1
Prerequisite : PowerShell 5.1 or higher, Oracle Client installed
Copyright 2024 : ROMFAST
#>
# Source logging functions
. "$PSScriptRoot\logging-functions.ps1"
<#
.SYNOPSIS
Get the listener host address.
.DESCRIPTION
Auto-detects the host address from listener configuration.
Returns the IP address the listener is bound to.
.PARAMETER OracleHome
Oracle Home directory.
.OUTPUTS
String. The host address (IP or hostname).
.EXAMPLE
$host = Get-ListenerHost -OracleHome "C:\app\oracle\product\21c\dbhomeXE"
#>
function Get-ListenerHost {
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string]$OracleHome
)
$oraHome = Get-OracleHome -OracleHome $OracleHome
$lsnrctl = Join-Path $oraHome "bin\lsnrctl.exe"
if (-not (Test-Path $lsnrctl)) {
return "localhost"
}
try {
# Set Oracle environment
$env:ORACLE_HOME = $oraHome
$env:PATH = "$oraHome\bin;$env:PATH"
$output = & $lsnrctl status 2>&1 | Out-String
# Parse HOST from listener output - look for TCP endpoint (not TCPS)
# Format: (PROTOCOL=tcp)(HOST=10.0.20.130)(PORT=1521)
# Use simple IP pattern to avoid regex escaping issues
$tcpMatch = [regex]::Match($output, "PROTOCOL=tcp\).*?HOST=([0-9\.]+)")
if ($tcpMatch.Success) {
$listenerHost = $tcpMatch.Groups[1].Value
# If listener is bound to 0.0.0.0, try to get actual IP
if ($listenerHost -eq "0.0.0.0") {
$ip = (Get-NetIPAddress -AddressFamily IPv4 |
Where-Object { $_.IPAddress -notmatch "^127\." -and $_.PrefixOrigin -ne "WellKnown" } |
Select-Object -First 1).IPAddress
if ($ip) { return $ip }
return "localhost"
}
# If it's a specific IP (not localhost), return it
if ($listenerHost -notmatch "^127\." -and $listenerHost -ne "localhost") {
return $listenerHost
}
}
# Fallback: try any HOST= pattern
$anyHostMatch = [regex]::Match($output, "HOST=([0-9\.]+)")
if ($anyHostMatch.Success) {
$listenerHost = $anyHostMatch.Groups[1].Value
if ($listenerHost -notmatch "^127\." -and $listenerHost -ne "0.0.0.0") {
return $listenerHost
}
}
return "localhost"
}
catch {
return "localhost"
}
}
<#
.SYNOPSIS
Find Oracle Home directory.
.DESCRIPTION
Auto-detects Oracle Home from registry, environment variable, or common paths.
.PARAMETER OracleHome
Optional explicit Oracle Home path. If not specified, auto-detects.
.OUTPUTS
String. The Oracle Home path.
.EXAMPLE
$oracleHome = Get-OracleHome
#>
function Get-OracleHome {
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string]$OracleHome
)
# If explicitly provided, validate and return
if ($OracleHome) {
if (Test-Path -Path "$OracleHome\bin\sqlplus.exe") {
return $OracleHome
}
throw "Invalid Oracle Home: sqlplus.exe not found at $OracleHome\bin\sqlplus.exe"
}
# Try ORACLE_HOME environment variable
if ($env:ORACLE_HOME -and (Test-Path -Path "$env:ORACLE_HOME\bin\sqlplus.exe")) {
return $env:ORACLE_HOME
}
# Try registry for Oracle XE
$regPaths = @(
'HKLM:\SOFTWARE\Oracle\KEY_OraDB21Home1',
'HKLM:\SOFTWARE\Oracle\KEY_OraDB18Home1',
'HKLM:\SOFTWARE\Oracle\KEY_XE',
'HKLM:\SOFTWARE\Wow6432Node\Oracle\KEY_OraDB21Home1',
'HKLM:\SOFTWARE\Wow6432Node\Oracle\KEY_OraDB18Home1'
)
foreach ($regPath in $regPaths) {
if (Test-Path -Path $regPath) {
$oraHome = (Get-ItemProperty -Path $regPath -ErrorAction SilentlyContinue).ORACLE_HOME
if ($oraHome -and (Test-Path -Path "$oraHome\bin\sqlplus.exe")) {
return $oraHome
}
}
}
# Try common installation paths (including user-specific paths)
$currentUser = $env:USERNAME
$commonPaths = @(
"C:\app\$currentUser\product\21c\dbhomeXE",
"C:\app\$currentUser\product\21c\dbhome_1",
"C:\app\$currentUser\product\18c\dbhomeXE",
'C:\app\oracle\product\21c\dbhomeXE',
'C:\app\oracle\product\21c\dbhome_1',
'C:\app\oracle\product\18c\dbhomeXE',
'C:\app\romfast\product\21c\dbhomeXE',
'C:\app\romfast\product\21c\dbhome_1',
'C:\oraclexe\app\oracle\product\11.2.0\server',
"D:\app\$currentUser\product\21c\dbhomeXE",
'D:\app\oracle\product\21c\dbhomeXE',
'D:\app\oracle\product\18c\dbhomeXE'
)
foreach ($path in $commonPaths) {
if (Test-Path -Path "$path\bin\sqlplus.exe") {
return $path
}
}
throw "Oracle Home not found. Please specify -OracleHome parameter or set ORACLE_HOME environment variable."
}
<#
.SYNOPSIS
Get Oracle version information.
.DESCRIPTION
Detects Oracle version (XE vs SE), edition, and whether it's a Container Database (CDB).
.PARAMETER OracleHome
Oracle Home directory.
.PARAMETER ServiceName
Database service name.
.PARAMETER Username
Username for connection (default: SYSTEM).
.PARAMETER Password
Password for connection.
.OUTPUTS
PSCustomObject with Version, Edition, IsCDB, IsXE properties.
.EXAMPLE
$version = Get-OracleVersion -ServiceName "XE" -Password "oracle"
#>
function Get-OracleVersion {
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string]$OracleHome,
[Parameter(Mandatory = $true)]
[string]$ServiceName,
[Parameter(Mandatory = $false)]
[string]$DbHost,
[Parameter(Mandatory = $false)]
[int]$Port = 1521,
[Parameter(Mandatory = $false)]
[string]$Username = "SYSTEM",
[Parameter(Mandatory = $true)]
[string]$Password
)
$oraHome = Get-OracleHome -OracleHome $OracleHome
$sql = @"
SET PAGESIZE 0 FEEDBACK OFF VERIFY OFF HEADING OFF ECHO OFF
SELECT 'VERSION:' || version_full FROM v`$instance;
SELECT 'EDITION:' || edition FROM v`$instance;
SELECT 'CDB:' || CDB FROM v`$database;
EXIT;
"@
$result = Invoke-SqlPlus -OracleHome $oraHome -ServiceName $ServiceName `
-DbHost $DbHost -Port $Port `
-Username $Username -Password $Password -SqlCommand $sql -Silent
$versionInfo = [PSCustomObject]@{
Version = ""
Edition = ""
IsCDB = $false
IsXE = $false
FullInfo = $result
}
foreach ($line in $result -split "`n") {
if ($line -match "^VERSION:(.+)$") {
$versionInfo.Version = $Matches[1].Trim()
}
elseif ($line -match "^EDITION:(.+)$") {
$versionInfo.Edition = $Matches[1].Trim()
$versionInfo.IsXE = $versionInfo.Edition -match "XE|Express"
}
elseif ($line -match "^CDB:(.+)$") {
$versionInfo.IsCDB = $Matches[1].Trim() -eq "YES"
}
}
return $versionInfo
}
<#
.SYNOPSIS
Get the default service name based on Oracle configuration.
.DESCRIPTION
Auto-detects the appropriate service name. Returns XEPDB1 for Oracle XE CDB,
or ROA for traditional non-CDB installations.
.PARAMETER OracleHome
Oracle Home directory.
.PARAMETER SystemPassword
SYSTEM user password.
.OUTPUTS
String. The service name.
.EXAMPLE
$serviceName = Get-ServiceName -SystemPassword "oracle"
#>
function Get-ServiceName {
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string]$OracleHome,
[Parameter(Mandatory = $false)]
[string]$DbHost,
[Parameter(Mandatory = $false)]
[int]$Port = 1521,
[Parameter(Mandatory = $true)]
[string]$SystemPassword
)
$oraHome = Get-OracleHome -OracleHome $OracleHome
# First try to connect to XE and check if it's CDB
try {
$version = Get-OracleVersion -OracleHome $oraHome -ServiceName "XE" `
-DbHost $DbHost -Port $Port `
-Password $SystemPassword -ErrorAction Stop
if ($version.IsCDB) {
# For CDB, use XEPDB1 (the default pluggable database)
return "XEPDB1"
}
else {
return "XE"
}
}
catch {
# Try ROA as service name
try {
$null = Test-OracleConnection -OracleHome $oraHome -ServiceName "ROA" `
-DbHost $DbHost -Port $Port `
-Username "SYSTEM" -Password $SystemPassword -ErrorAction Stop
return "ROA"
}
catch {
# Default to XEPDB1 for modern XE installations
return "XEPDB1"
}
}
}
<#
.SYNOPSIS
Test Oracle database connection.
.DESCRIPTION
Attempts to connect to Oracle and verifies the connection is successful.
.PARAMETER OracleHome
Oracle Home directory.
.PARAMETER ServiceName
Database service name.
.PARAMETER Username
Username for connection.
.PARAMETER Password
Password for connection.
.OUTPUTS
Boolean. True if connection successful.
.EXAMPLE
if (Test-OracleConnection -ServiceName "XEPDB1" -Username "SYSTEM" -Password "oracle") {
Write-Host "Connected!"
}
#>
function Test-OracleConnection {
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string]$OracleHome,
[Parameter(Mandatory = $true)]
[string]$ServiceName,
[Parameter(Mandatory = $false)]
[string]$DbHost,
[Parameter(Mandatory = $false)]
[int]$Port = 1521,
[Parameter(Mandatory = $false)]
[string]$Username = "SYSTEM",
[Parameter(Mandatory = $true)]
[string]$Password,
[Parameter(Mandatory = $false)]
[switch]$AsSysdba
)
$oraHome = Get-OracleHome -OracleHome $OracleHome
$sql = "SELECT 'CONNECTED' FROM dual; EXIT;"
try {
$result = Invoke-SqlPlus -OracleHome $oraHome -ServiceName $ServiceName `
-DbHost $DbHost -Port $Port `
-Username $Username -Password $Password -SqlCommand $sql -AsSysdba:$AsSysdba -Silent
return $result -match "CONNECTED"
}
catch {
return $false
}
}
<#
.SYNOPSIS
Test if connected to a PDB or non-CDB.
.DESCRIPTION
Checks the current container type to determine if connected to a PDB,
the CDB root, or a non-CDB database.
.PARAMETER OracleHome
Oracle Home directory.
.PARAMETER ServiceName
Database service name.
.PARAMETER Username
Username for connection.
.PARAMETER Password
Password for connection.
.OUTPUTS
PSCustomObject with ContainerName, IsPDB, IsCDBRoot, IsNonCDB properties.
.EXAMPLE
$containerInfo = Test-PDB -ServiceName "XEPDB1" -Password "oracle"
#>
function Test-PDB {
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string]$OracleHome,
[Parameter(Mandatory = $true)]
[string]$ServiceName,
[Parameter(Mandatory = $false)]
[string]$DbHost,
[Parameter(Mandatory = $false)]
[int]$Port = 1521,
[Parameter(Mandatory = $false)]
[string]$Username = "SYSTEM",
[Parameter(Mandatory = $true)]
[string]$Password
)
$oraHome = Get-OracleHome -OracleHome $OracleHome
$sql = @"
SET PAGESIZE 0 FEEDBACK OFF VERIFY OFF HEADING OFF ECHO OFF
SELECT 'CONTAINER:' || SYS_CONTEXT('USERENV', 'CON_NAME') FROM dual;
SELECT 'CON_ID:' || SYS_CONTEXT('USERENV', 'CON_ID') FROM dual;
EXIT;
"@
$result = Invoke-SqlPlus -OracleHome $oraHome -ServiceName $ServiceName `
-DbHost $DbHost -Port $Port `
-Username $Username -Password $Password -SqlCommand $sql -Silent
$containerInfo = [PSCustomObject]@{
ContainerName = ""
ConId = 0
IsPDB = $false
IsCDBRoot = $false
IsNonCDB = $false
}
foreach ($line in $result -split "`n") {
if ($line -match "^CONTAINER:(.+)$") {
$containerInfo.ContainerName = $Matches[1].Trim()
}
elseif ($line -match "^CON_ID:(.+)$") {
$containerInfo.ConId = [int]$Matches[1].Trim()
}
}
if ($containerInfo.ConId -eq 0) {
$containerInfo.IsNonCDB = $true
}
elseif ($containerInfo.ConId -eq 1) {
$containerInfo.IsCDBRoot = $true
}
else {
$containerInfo.IsPDB = $true
}
return $containerInfo
}
<#
.SYNOPSIS
Execute SQL via SQL*Plus.
.DESCRIPTION
Runs a SQL command or file using SQL*Plus and returns the output.
.PARAMETER OracleHome
Oracle Home directory.
.PARAMETER ServiceName
Database service name.
.PARAMETER Username
Username for connection.
.PARAMETER Password
Password for connection.
.PARAMETER SqlCommand
SQL command(s) to execute.
.PARAMETER SqlFile
Path to SQL file to execute.
.PARAMETER AsSysdba
Connect as SYSDBA.
.PARAMETER Silent
Suppress console output.
.OUTPUTS
String. The SQL*Plus output.
.EXAMPLE
Invoke-SqlPlus -ServiceName "XEPDB1" -Username "SYSTEM" -Password "oracle" -SqlCommand "SELECT * FROM dual;"
.EXAMPLE
Invoke-SqlPlus -ServiceName "XEPDB1" -Username "SYS" -Password "oracle" -SqlFile "C:\scripts\setup.sql" -AsSysdba
#>
function Invoke-SqlPlus {
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string]$OracleHome,
[Parameter(Mandatory = $true)]
[string]$ServiceName,
[Parameter(Mandatory = $false)]
[string]$DbHost,
[Parameter(Mandatory = $false)]
[int]$Port = 1521,
[Parameter(Mandatory = $false)]
[string]$Username = "SYSTEM",
[Parameter(Mandatory = $true)]
[string]$Password,
[Parameter(Mandatory = $false)]
[string]$SqlCommand,
[Parameter(Mandatory = $false)]
[string]$SqlFile,
[Parameter(Mandatory = $false)]
[switch]$AsSysdba,
[Parameter(Mandatory = $false)]
[switch]$Silent
)
$oraHome = Get-OracleHome -OracleHome $OracleHome
$sqlplus = Join-Path $oraHome "bin\sqlplus.exe"
if (-not (Test-Path -Path $sqlplus)) {
throw "SQL*Plus not found at: $sqlplus"
}
# Auto-detect host from listener if not provided
if (-not $DbHost) {
$DbHost = Get-ListenerHost -OracleHome $oraHome
}
# Build connection string (always use EZConnect format for reliability)
$sysdba = if ($AsSysdba) { " as sysdba" } else { "" }
$connString = "$Username/`"$Password`"@${DbHost}:${Port}/${ServiceName}$sysdba"
$tempFile = $null
try {
if ($SqlFile) {
# Execute SQL file
if (-not (Test-Path -Path $SqlFile)) {
throw "SQL file not found: $SqlFile"
}
$arguments = "-S `"$connString`" @`"$SqlFile`""
}
else {
# Create temp file for SQL command
$tempFile = [System.IO.Path]::GetTempFileName()
$tempFile = [System.IO.Path]::ChangeExtension($tempFile, ".sql")
Set-Content -Path $tempFile -Value $SqlCommand -Encoding ASCII
$arguments = "-S `"$connString`" @`"$tempFile`""
}
# Set Oracle environment
$env:ORACLE_HOME = $oraHome
$env:PATH = "$oraHome\bin;$env:PATH"
$env:NLS_LANG = "AMERICAN_AMERICA.AL32UTF8"
# Execute SQL*Plus
$processInfo = New-Object System.Diagnostics.ProcessStartInfo
$processInfo.FileName = $sqlplus
$processInfo.Arguments = "-S `"$connString`""
$processInfo.RedirectStandardInput = $true
$processInfo.RedirectStandardOutput = $true
$processInfo.RedirectStandardError = $true
$processInfo.UseShellExecute = $false
$processInfo.CreateNoWindow = $true
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $processInfo
$process.Start() | Out-Null
# Send SQL
if ($SqlFile) {
$process.StandardInput.WriteLine("@`"$SqlFile`"")
}
else {
$process.StandardInput.WriteLine($SqlCommand)
}
$process.StandardInput.Close()
$output = $process.StandardOutput.ReadToEnd()
$errorOutput = $process.StandardError.ReadToEnd()
$process.WaitForExit()
if ($errorOutput) {
$output += "`n$errorOutput"
}
# Check for Oracle errors
if ($output -match "ORA-\d{5}:|SP2-\d{4}:") {
if (-not $Silent) {
Write-LogWarning "SQL*Plus returned errors:"
Write-Host $output -ForegroundColor Yellow
}
}
return $output
}
finally {
if ($tempFile -and (Test-Path -Path $tempFile)) {
Remove-Item -Path $tempFile -Force -ErrorAction SilentlyContinue
}
}
}
<#
.SYNOPSIS
Create Oracle directory object for Data Pump.
.DESCRIPTION
Creates a directory object in Oracle for Data Pump operations if it doesn't exist.
.PARAMETER OracleHome
Oracle Home directory.
.PARAMETER ServiceName
Database service name.
.PARAMETER Password
SYS or SYSTEM password.
.PARAMETER DirectoryName
Oracle directory object name (default: DMPDIR).
.PARAMETER DirectoryPath
File system path for the directory (default: C:\DMPDIR).
.EXAMPLE
New-OracleDirectory -ServiceName "XEPDB1" -Password "oracle" -DirectoryPath "D:\Dumps"
#>
function New-OracleDirectory {
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string]$OracleHome,
[Parameter(Mandatory = $true)]
[string]$ServiceName,
[Parameter(Mandatory = $true)]
[string]$Password,
[Parameter(Mandatory = $false)]
[string]$DirectoryName = "DMPDIR",
[Parameter(Mandatory = $false)]
[string]$DirectoryPath = "C:\DMPDIR"
)
# Create physical directory if it doesn't exist
if (-not (Test-Path -Path $DirectoryPath)) {
New-Item -ItemType Directory -Path $DirectoryPath -Force | Out-Null
Write-Log "Created directory: $DirectoryPath"
}
# Always drop and recreate directory to ensure correct path
# This fixes Oracle XE issue where DMPDIR may point to wrong default location
# Using DROP + CREATE instead of CREATE OR REPLACE for reliability
$sql = @"
SET PAGESIZE 0 FEEDBACK OFF VERIFY OFF HEADING OFF ECHO OFF LINESIZE 500
-- Check current directory path
VARIABLE v_path VARCHAR2(500);
VARIABLE v_exists NUMBER;
BEGIN
SELECT COUNT(*), MAX(directory_path) INTO :v_exists, :v_path
FROM dba_directories WHERE directory_name = '$DirectoryName';
END;
/
PRINT v_path
-- Drop if exists with different path
BEGIN
IF :v_exists > 0 AND UPPER(TRIM(:v_path)) != UPPER('$DirectoryPath') THEN
EXECUTE IMMEDIATE 'DROP DIRECTORY $DirectoryName';
DBMS_OUTPUT.PUT_LINE('DROPPED_DIFFERENT_PATH');
ELSIF :v_exists > 0 THEN
DBMS_OUTPUT.PUT_LINE('PATH_ALREADY_CORRECT');
END IF;
EXCEPTION
WHEN OTHERS THEN NULL;
END;
/
-- Create or replace directory
CREATE OR REPLACE DIRECTORY $DirectoryName AS '$DirectoryPath';
GRANT READ, WRITE ON DIRECTORY $DirectoryName TO PUBLIC;
-- Verify
SELECT 'VERIFIED:' || directory_path FROM dba_directories WHERE directory_name = '$DirectoryName';
EXIT;
"@
$result = Invoke-SqlPlus -OracleHome $OracleHome -ServiceName $ServiceName `
-Username "SYS" -Password $Password -SqlCommand $sql -AsSysdba -Silent
# Check verification result
if ($result -match "VERIFIED:(.+)") {
$verifiedPath = $Matches[1].Trim()
if ($verifiedPath.ToUpper() -eq $DirectoryPath.ToUpper()) {
Write-Log "Oracle directory $DirectoryName created pointing to $DirectoryPath"
return $true
}
else {
Write-LogWarning "Directory path mismatch! Expected: $DirectoryPath, Got: $verifiedPath"
return $false
}
}
else {
Write-LogWarning "Could not verify directory creation"
Write-LogWarning "SQL output: $result"
return $false
}
}
<#
.SYNOPSIS
Get the default datafile path based on Oracle installation.
.DESCRIPTION
Determines the appropriate datafile location based on Oracle Home and version.
.PARAMETER OracleHome
Oracle Home directory.
.PARAMETER ServiceName
Database service name.
.PARAMETER Password
SYSTEM password.
.OUTPUTS
String. The datafile directory path.
.EXAMPLE
$datafilePath = Get-DatafilePath -ServiceName "XEPDB1" -Password "oracle"
#>
function Get-DatafilePath {
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string]$OracleHome,
[Parameter(Mandatory = $true)]
[string]$ServiceName,
[Parameter(Mandatory = $true)]
[string]$Password
)
$oraHome = Get-OracleHome -OracleHome $OracleHome
# Query Oracle for default datafile location
$sql = @"
SET PAGESIZE 0 FEEDBACK OFF VERIFY OFF HEADING OFF ECHO OFF LINESIZE 500
SELECT 'DATAFILE_PATH:' || SUBSTR(file_name, 1, INSTR(file_name, '\', -1))
FROM dba_data_files
WHERE tablespace_name = 'SYSTEM'
AND ROWNUM = 1;
EXIT;
"@
$result = Invoke-SqlPlus -OracleHome $oraHome -ServiceName $ServiceName `
-Username "SYSTEM" -Password $Password -SqlCommand $sql -Silent
foreach ($line in $result -split "`n") {
if ($line -match "^DATAFILE_PATH:(.+)$") {
$path = $Matches[1].Trim()
# Remove trailing backslash if present
return $path.TrimEnd('\')
}
}
# Fallback: derive path from ORACLE_HOME
# Example: C:\app\romfast\product\21c\dbhomeXE -> C:\app\romfast\product\21c\oradata\XE\XEPDB1
Write-LogWarning "Could not query datafile path from database, using fallback based on ORACLE_HOME"
$version = Get-OracleVersion -OracleHome $oraHome -ServiceName $ServiceName -Password $Password
# Try to derive oradata path from ORACLE_HOME
# Pattern: <base>\product\<version>\dbhomeXE -> <base>\product\<version>\oradata\XE\<PDB>
if ($oraHome -match "^(.+\\product\\[^\\]+)\\dbhome") {
$baseOradata = "$($Matches[1])\oradata"
if ($version.IsXE) {
if ($version.Version -match "^21") {
return "$baseOradata\XE\XEPDB1"
}
elseif ($version.Version -match "^18") {
return "$baseOradata\XE"
}
}
return $baseOradata
}
# Legacy fallback for non-standard installations
# Extract base from ORACLE_HOME (e.g., C:\app\romfast from C:\app\romfast\product\21c\dbhomeXE)
if ($oraHome -match "^([A-Z]:\\[^\\]+\\[^\\]+)\\") {
$appBase = $Matches[1]
if ($version.IsXE) {
if ($version.Version -match "^21") {
return "$appBase\oradata\XE\XEPDB1"
}
elseif ($version.Version -match "^18") {
return "$appBase\oradata\XE"
}
}
return "$appBase\oradata"
}
# Ultimate fallback - this should rarely be reached
Write-LogWarning "Could not derive datafile path from ORACLE_HOME, using hardcoded default"
return "C:\app\oracle\oradata"
}
<#
.SYNOPSIS
Execute Data Pump import.
.DESCRIPTION
Runs impdp with specified parameters.
.PARAMETER OracleHome
Oracle Home directory.
.PARAMETER ServiceName
Database service name.
.PARAMETER Username
Username for connection (default: SYSTEM).
.PARAMETER Password
Password for connection.
.PARAMETER DumpFile
Name of the dump file (in DMPDIR).
.PARAMETER LogFile
Name of the log file.
.PARAMETER Schemas
Schema(s) to import.
.PARAMETER RemapSchema
Remap schema mapping (e.g., "OLD_SCHEMA:NEW_SCHEMA").
.PARAMETER DirectoryName
Oracle directory object name (default: DMPDIR).
.PARAMETER TableExists
Table exists action: SKIP, APPEND, TRUNCATE, REPLACE (default: REPLACE).
.PARAMETER AdditionalParams
Additional impdp parameters.
.OUTPUTS
PSCustomObject with Success, LogContent, ErrorMessage properties.
.EXAMPLE
Invoke-DataPumpImport -ServiceName "XEPDB1" -Password "oracle" -DumpFile "CONTAFIN_ORACLE.dmp" -Schemas "CONTAFIN_ORACLE"
#>
function Invoke-DataPumpImport {
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string]$OracleHome,
[Parameter(Mandatory = $true)]
[string]$ServiceName,
[Parameter(Mandatory = $false)]
[string]$Username = "SYSTEM",
[Parameter(Mandatory = $true)]
[string]$Password,
[Parameter(Mandatory = $true)]
[string]$DumpFile,
[Parameter(Mandatory = $false)]
[string]$LogFile,
[Parameter(Mandatory = $false)]
[string[]]$Schemas,
[Parameter(Mandatory = $false)]
[string]$RemapSchema,
[Parameter(Mandatory = $false)]
[string]$DirectoryName = "DMPDIR",
[Parameter(Mandatory = $false)]
[ValidateSet('SKIP', 'APPEND', 'TRUNCATE', 'REPLACE')]
[string]$TableExists = "REPLACE",
[Parameter(Mandatory = $false)]
[string]$AdditionalParams
)
$oraHome = Get-OracleHome -OracleHome $OracleHome
$impdp = Join-Path $oraHome "bin\impdp.exe"
if (-not (Test-Path -Path $impdp)) {
throw "impdp not found at: $impdp"
}
# Build impdp command
if (-not $LogFile) {
$LogFile = [System.IO.Path]::GetFileNameWithoutExtension($DumpFile) + "_import.log"
}
$connString = "$Username/`"$Password`"@$ServiceName"
$params = @(
"`"$connString`"",
"directory=$DirectoryName",
"dumpfile=$DumpFile",
"logfile=$LogFile",
"table_exists_action=$TableExists"
)
if ($Schemas) {
$params += "schemas=$($Schemas -join ',')"
}
if ($RemapSchema) {
$params += "remap_schema=$RemapSchema"
}
if ($AdditionalParams) {
$params += $AdditionalParams
}
$arguments = $params -join " "
Write-Log "Executing: impdp $($params -join ' ' -replace $Password, '****')"
# Set Oracle environment
$env:ORACLE_HOME = $oraHome
$env:PATH = "$oraHome\bin;$env:PATH"
$env:NLS_LANG = "AMERICAN_AMERICA.AL32UTF8"
# Execute impdp
$process = Start-Process -FilePath $impdp -ArgumentList $arguments -Wait -NoNewWindow -PassThru `
-RedirectStandardOutput "$env:TEMP\impdp_out.txt" -RedirectStandardError "$env:TEMP\impdp_err.txt"
$stdout = Get-Content -Path "$env:TEMP\impdp_out.txt" -Raw -ErrorAction SilentlyContinue
$stderr = Get-Content -Path "$env:TEMP\impdp_err.txt" -Raw -ErrorAction SilentlyContinue
$result = [PSCustomObject]@{
Success = $process.ExitCode -eq 0
ExitCode = $process.ExitCode
Output = $stdout
ErrorOutput = $stderr
LogFile = $LogFile
}
# Clean up temp files
Remove-Item -Path "$env:TEMP\impdp_out.txt" -Force -ErrorAction SilentlyContinue
Remove-Item -Path "$env:TEMP\impdp_err.txt" -Force -ErrorAction SilentlyContinue
return $result
}
<#
.SYNOPSIS
Check if a user/schema exists in the database.
.DESCRIPTION
Queries DBA_USERS to check if a user exists.
.PARAMETER OracleHome
Oracle Home directory.
.PARAMETER ServiceName
Database service name.
.PARAMETER Password
SYSTEM password.
.PARAMETER SchemaName
Name of the schema/user to check.
.OUTPUTS
Boolean. True if user exists.
.EXAMPLE
if (Test-OracleUser -ServiceName "XEPDB1" -Password "oracle" -SchemaName "CONTAFIN_ORACLE") {
Write-Host "User exists"
}
#>
function Test-OracleUser {
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string]$OracleHome,
[Parameter(Mandatory = $true)]
[string]$ServiceName,
[Parameter(Mandatory = $true)]
[string]$Password,
[Parameter(Mandatory = $true)]
[string]$SchemaName
)
$sql = @"
SET PAGESIZE 0 FEEDBACK OFF VERIFY OFF HEADING OFF ECHO OFF
SELECT 'USER_EXISTS' FROM dba_users WHERE username = UPPER('$SchemaName');
EXIT;
"@
$result = Invoke-SqlPlus -OracleHome $OracleHome -ServiceName $ServiceName `
-Username "SYSTEM" -Password $Password -SqlCommand $sql -Silent
return $result -match "USER_EXISTS"
}
<#
.SYNOPSIS
Get object count for a schema.
.DESCRIPTION
Counts objects in a schema grouped by type.
.PARAMETER OracleHome
Oracle Home directory.
.PARAMETER ServiceName
Database service name.
.PARAMETER Password
SYSTEM password.
.PARAMETER SchemaName
Name of the schema.
.OUTPUTS
Hashtable with object types and counts.
.EXAMPLE
$counts = Get-SchemaObjectCount -ServiceName "XEPDB1" -Password "oracle" -SchemaName "CONTAFIN_ORACLE"
#>
function Get-SchemaObjectCount {
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string]$OracleHome,
[Parameter(Mandatory = $true)]
[string]$ServiceName,
[Parameter(Mandatory = $true)]
[string]$Password,
[Parameter(Mandatory = $true)]
[string]$SchemaName
)
$sql = @"
SET PAGESIZE 1000 FEEDBACK OFF VERIFY OFF HEADING OFF ECHO OFF LINESIZE 200
SELECT object_type || ':' || COUNT(*)
FROM dba_objects
WHERE owner = UPPER('$SchemaName')
GROUP BY object_type
ORDER BY object_type;
SELECT 'TOTAL:' || COUNT(*) FROM dba_objects WHERE owner = UPPER('$SchemaName');
SELECT 'INVALID:' || COUNT(*) FROM dba_objects WHERE owner = UPPER('$SchemaName') AND status = 'INVALID';
EXIT;
"@
$oraHome = Get-OracleHome -OracleHome $OracleHome
$result = Invoke-SqlPlus -OracleHome $oraHome -ServiceName $ServiceName `
-Username "SYSTEM" -Password $Password -SqlCommand $sql -Silent
$counts = @{}
# Parse result lines for object type counts
$lines = $result -split "`r?`n"
foreach ($line in $lines) {
$trimmed = $line.Trim()
if ($trimmed.Length -gt 0 -and $trimmed -match "^([A-Z][A-Z_ ]*):(\d+)$") {
$key = $Matches[1].Trim()
$value = [int]$Matches[2]
$counts[$key] = $value
}
}
return $counts
}
# Note: Functions are available when dot-sourced (. .\oracle-functions.ps1)
# Do NOT use Export-ModuleMember - it only works inside .psm1 modules